CustomizeLogoSetting.tsx 6.7 KB

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