CreateTemplateModal.tsx 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import React, { useCallback, useMemo } from 'react';
  2. import { pathUtils } from '@growi/core/dist/utils';
  3. import { useTranslation } from 'next-i18next';
  4. import { Modal, ModalHeader, ModalBody } from 'reactstrap';
  5. import { useCreateTemplatePage } from '~/client/services/create-page';
  6. import { toastError } from '~/client/util/toastr';
  7. import type { TargetType, LabelType } from '~/interfaces/template';
  8. type TemplateCardProps = {
  9. target: TargetType;
  10. label: LabelType;
  11. isPageCreating: boolean;
  12. onClickHandler: () => void;
  13. };
  14. const TemplateCard: React.FC<TemplateCardProps> = ({
  15. target, label, isPageCreating, onClickHandler,
  16. }) => {
  17. const { t } = useTranslation();
  18. return (
  19. <div className="card card-select-template">
  20. <div className="card-header">{t(`template.${target}.label`)}</div>
  21. <div className="card-body">
  22. <p className="text-center"><code>{label}</code></p>
  23. <p className="form-text text-muted text-center"><small>{t(`template.${target}.desc`)}</small></p>
  24. </div>
  25. <div className="card-footer text-center">
  26. <button
  27. disabled={isPageCreating}
  28. data-testid={`template-button-${target}`}
  29. className="btn btn-sm btn-primary"
  30. id={`template-button-${target}`}
  31. onClick={onClickHandler}
  32. type="button"
  33. >
  34. {t('Edit')}
  35. </button>
  36. </div>
  37. </div>
  38. );
  39. };
  40. type CreateTemplateModalProps = {
  41. path: string;
  42. isOpen: boolean;
  43. onClose: () => void;
  44. };
  45. export const CreateTemplateModal: React.FC<CreateTemplateModalProps> = ({
  46. path, isOpen, onClose,
  47. }) => {
  48. const { t } = useTranslation(['translation', 'commons']);
  49. const { createTemplate, isCreating, isCreatable } = useCreateTemplatePage();
  50. const onClickTemplateButtonHandler = useCallback(async(label: LabelType) => {
  51. try {
  52. await createTemplate?.(label);
  53. onClose();
  54. }
  55. catch (err) {
  56. toastError(t('toaster.create_failed', { target: path }));
  57. }
  58. }, [createTemplate, onClose, path, t]);
  59. // Memoize computed path
  60. const parentPath = useMemo(() => pathUtils.addTrailingSlash(path), [path]);
  61. // Memoize template card rendering function
  62. const renderTemplateCard = useCallback((target: TargetType, label: LabelType) => (
  63. <div className="col">
  64. <TemplateCard
  65. target={target}
  66. label={label}
  67. isPageCreating={isCreating}
  68. onClickHandler={() => onClickTemplateButtonHandler(label)}
  69. />
  70. </div>
  71. ), [isCreating, onClickTemplateButtonHandler]);
  72. return (
  73. <Modal isOpen={isOpen} toggle={onClose} data-testid="page-template-modal">
  74. {(isCreatable && isOpen) && (
  75. <>
  76. <ModalHeader tag="h4" toggle={onClose}>
  77. {t('template.modal_label.Create/Edit Template Page')}
  78. </ModalHeader>
  79. <ModalBody>
  80. <div>
  81. <label className="form-label mb-4">
  82. <code>{parentPath}</code><br />
  83. {t('template.modal_label.Create template under')}
  84. </label>
  85. <div className="row row-cols-2">
  86. {renderTemplateCard('children', '_template')}
  87. {renderTemplateCard('descendants', '__template')}
  88. </div>
  89. </div>
  90. </ModalBody>
  91. </>
  92. )}
  93. </Modal>
  94. );
  95. };