CustomizeLogoSetting.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import React, { useCallback, useState, type JSX } from 'react';
  2. import { useTranslation } from 'react-i18next';
  3. import ImageCropModal from '~/client/components/Common/ImageCropModal';
  4. import {
  5. apiv3Delete, apiv3PostForm, apiv3Put,
  6. } from '~/client/util/apiv3-client';
  7. import { toastError, toastSuccess } from '~/client/util/toastr';
  8. import { useIsDefaultLogo } from '~/states/global';
  9. import { useIsCustomizedLogoUploaded } from '~/stores-universal/context';
  10. import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
  11. const DEFAULT_LOGO = '/images/logo.svg';
  12. const CUSTOMIZED_LOGO = '/attachment/brand-logo';
  13. const CustomizeLogoSetting = (): JSX.Element => {
  14. const { t } = useTranslation();
  15. const [isDefaultLogo] = useIsDefaultLogo();
  16. const { data: isCustomizedLogoUploaded, mutate: mutateIsCustomizedLogoUploaded } = useIsCustomizedLogoUploaded();
  17. const [uploadLogoSrc, setUploadLogoSrc] = useState<ArrayBuffer | string | null>(null);
  18. const [isImageCropModalShow, setIsImageCropModalShow] = useState<boolean>(false);
  19. const [isDefaultLogoSelected, setIsDefaultLogoSelected] = useState<boolean>(isDefaultLogo ?? true);
  20. const [retrieveError, setRetrieveError] = useState<any>();
  21. const onSelectFile = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
  22. if (e.target.files != null && e.target.files.length > 0) {
  23. const reader = new FileReader();
  24. reader.addEventListener('load', () => setUploadLogoSrc(reader.result));
  25. reader.readAsDataURL(e.target.files[0]);
  26. setIsImageCropModalShow(true);
  27. }
  28. }, []);
  29. const onClickSubmit = useCallback(async() => {
  30. try {
  31. await apiv3Put('/customize-setting/customize-logo', { isDefaultLogo: isDefaultLogoSelected });
  32. toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_logo'), ns: 'commons' }));
  33. }
  34. catch (err) {
  35. toastError(err);
  36. }
  37. }, [t, isDefaultLogoSelected]);
  38. const onClickDeleteBtn = useCallback(async() => {
  39. try {
  40. await apiv3Delete('/customize-setting/delete-brand-logo');
  41. mutateIsCustomizedLogoUploaded(false);
  42. toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.current_logo'), ns: 'commons' }));
  43. }
  44. catch (err) {
  45. toastError(err);
  46. setRetrieveError(err);
  47. throw new Error('Failed to delete logo');
  48. }
  49. }, [mutateIsCustomizedLogoUploaded, t]);
  50. const processImageCompletedHandler = useCallback(async(croppedImage) => {
  51. try {
  52. const formData = new FormData();
  53. formData.append('file', croppedImage);
  54. await apiv3PostForm('/customize-setting/upload-brand-logo', formData);
  55. mutateIsCustomizedLogoUploaded(true);
  56. toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.current_logo'), ns: 'commons' }));
  57. }
  58. catch (err) {
  59. toastError(err);
  60. setRetrieveError(err);
  61. throw new Error('Failed to upload brand logo');
  62. }
  63. }, [mutateIsCustomizedLogoUploaded, t]);
  64. return (
  65. <React.Fragment>
  66. <div className="row">
  67. <div className="col-12">
  68. <div className="mb-5">
  69. <h2 className="border-bottom my-4 admin-setting-header">{t('admin:customize_settings.custom_logo')}</h2>
  70. <div className="row">
  71. <div className="col-md-6 col-12 mb-3 mb-md-0">
  72. <h4>
  73. <div className="form-check radio-primary">
  74. <input
  75. type="radio"
  76. id="radioDefaultLogo"
  77. className="form-check-input"
  78. form="formImageType"
  79. name="imagetypeForm[isDefaultLogo]"
  80. checked={isDefaultLogoSelected}
  81. onChange={() => { setIsDefaultLogoSelected(true) }}
  82. />
  83. <label className="form-check-label" htmlFor="radioDefaultLogo">
  84. {t('admin:customize_settings.default_logo')}
  85. </label>
  86. </div>
  87. </h4>
  88. <img src={DEFAULT_LOGO} width="64" />
  89. </div>
  90. <div className="col-md-6 col-12">
  91. <h4>
  92. <div className="form-check radio-primary">
  93. <input
  94. type="radio"
  95. id="radioUploadLogo"
  96. className="form-check-input"
  97. form="formImageType"
  98. name="imagetypeForm[isDefaultLogo]"
  99. checked={!isDefaultLogoSelected}
  100. onChange={() => { setIsDefaultLogoSelected(false) }}
  101. />
  102. <label className="form-check-label" htmlFor="radioUploadLogo">
  103. { t('admin:customize_settings.upload_logo') }
  104. </label>
  105. </div>
  106. </h4>
  107. <div className="row mb-3">
  108. <label className="col-sm-4 col-12 col-form-label text-start">
  109. { t('admin:customize_settings.current_logo') }
  110. </label>
  111. <div className="col-sm-8 col-12">
  112. {isCustomizedLogoUploaded && (
  113. <>
  114. <p>
  115. <img src={CUSTOMIZED_LOGO} id="settingBrandLogo" width="64" />
  116. </p>
  117. <button type="button" className="btn btn-danger" onClick={onClickDeleteBtn}>
  118. { t('admin:customize_settings.delete_logo') }
  119. </button>
  120. </>
  121. )}
  122. </div>
  123. </div>
  124. <div className="row">
  125. <label className="col-sm-4 col-12 col-form-label text-start">
  126. { t('admin:customize_settings.upload_new_logo') }
  127. </label>
  128. <div className="col-sm-8 col-12">
  129. <input type="file" onChange={onSelectFile} name="brandLogo" accept="image/*" />
  130. </div>
  131. </div>
  132. </div>
  133. </div>
  134. <AdminUpdateButtonRow onClick={onClickSubmit} disabled={retrieveError != null} />
  135. </div>
  136. </div>
  137. </div>
  138. <ImageCropModal
  139. isShow={isImageCropModalShow}
  140. src={uploadLogoSrc}
  141. onModalClose={() => setIsImageCropModalShow(false)}
  142. onImageProcessCompleted={processImageCompletedHandler}
  143. isCircular={false}
  144. showCropOption={false}
  145. />
  146. </React.Fragment>
  147. );
  148. };
  149. export default CustomizeLogoSetting;