CustomizeLogoSetting.tsx 7.4 KB

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