CreateTemplateModal.tsx 3.4 KB

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