| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- import React, { type JSX, useCallback, useState } from 'react';
- import { isPopulated } from '@growi/core';
- import { useTranslation } from 'next-i18next';
- import ImageCropModal from '~/client/components/Common/ImageCropModal';
- import { apiPost, apiPostForm } from '~/client/util/apiv1-client';
- import { apiv3Put } from '~/client/util/apiv3-client';
- import { toastError, toastSuccess } from '~/client/util/toastr';
- import { useCurrentUser } from '~/states/global';
- import { GRAVATAR_DEFAULT, generateGravatarSrc } from '~/utils/gravatar';
- const DEFAULT_IMAGE = '/images/icons/user.svg';
- const ProfileImageSettings = (): JSX.Element => {
- const { t } = useTranslation();
- const currentUser = useCurrentUser();
- const [isGravatarEnabled, setGravatarEnabled] = useState(
- currentUser?.isGravatarEnabled,
- );
- const [uploadedPictureSrc, setUploadedPictureSrc] = useState(() => {
- if (
- currentUser?.imageAttachment != null &&
- isPopulated(currentUser.imageAttachment)
- ) {
- return currentUser.imageAttachment.filePathProxied ?? currentUser.image;
- }
- return currentUser?.image;
- });
- const [showImageCropModal, setShowImageCropModal] = useState(false);
- const [imageCropSrc, setImageCropSrc] = useState<string | ArrayBuffer | null>(
- null,
- );
- const selectFileHandler = useCallback(
- (e: React.ChangeEvent<HTMLInputElement>) => {
- if (e.target.files == null || e.target.files.length === 0) {
- return;
- }
- const reader = new FileReader();
- reader.addEventListener('load', () => setImageCropSrc(reader.result));
- reader.readAsDataURL(e.target.files[0]);
- setShowImageCropModal(true);
- },
- [],
- );
- const processImageCompletedHandler = useCallback(
- async (croppedImage) => {
- try {
- const formData = new FormData();
- formData.append('file', croppedImage);
- const response = await apiPostForm(
- '/attachments.uploadProfileImage',
- formData,
- );
- toastSuccess(
- t('toaster.update_successed', {
- target: t('Current Image'),
- ns: 'commons',
- }),
- );
- setUploadedPictureSrc((response as any).attachment.filePathProxied);
- } catch (err) {
- toastError(err);
- }
- },
- [t],
- );
- const deleteImageHandler = useCallback(async () => {
- try {
- await apiPost('/attachments.removeProfileImage');
- setUploadedPictureSrc(undefined);
- toastSuccess(
- t('toaster.update_successed', {
- target: t('Current Image'),
- ns: 'commons',
- }),
- );
- } catch (err) {
- toastError(err);
- }
- }, [t]);
- const submit = useCallback(async () => {
- try {
- const response = await apiv3Put('/personal-setting/image-type', {
- isGravatarEnabled,
- });
- const { userData } = response.data;
- setGravatarEnabled(userData.isGravatarEnabled);
- toastSuccess(
- t('toaster.update_successed', {
- target: t('Set Profile Image'),
- ns: 'commons',
- }),
- );
- } catch (err) {
- toastError(err);
- }
- }, [isGravatarEnabled, t]);
- if (currentUser == null) {
- return <></>;
- }
- return (
- <>
- <div className="row justify-content-around mt-5 mt-md-4">
- <div className="col-md-3">
- <h5>
- <div className="form-check radio-primary">
- <input
- type="radio"
- id="radioGravatar"
- className="form-check-input"
- form="formImageType"
- name="imagetypeForm[isGravatarEnabled]"
- checked={isGravatarEnabled}
- onChange={() => setGravatarEnabled(true)}
- />
- <label
- className="form-label form-check-label"
- htmlFor="radioGravatar"
- >
- <img
- src={GRAVATAR_DEFAULT}
- alt="Gravatar"
- className="me-1"
- data-vrt-blackout-profile
- />{' '}
- Gravatar
- </label>
- <a
- href="https://gravatar.com/"
- target="_blank"
- rel="noopener noreferrer"
- >
- <small>
- <span
- className="material-symbols-outlined ms-2 text-secondary"
- aria-hidden="true"
- >
- info
- </span>
- </small>
- </a>
- </div>
- </h5>
- <img
- src={generateGravatarSrc(currentUser.email)}
- alt="Gravatar"
- className="rounded-pill"
- width="64"
- height="64"
- data-vrt-blackout-profile
- />
- </div>
- <div className="col-md-7 mt-5 mt-md-0">
- <h5>
- <div className="form-check radio-primary">
- <input
- type="radio"
- id="radioUploadPicture"
- className="form-check-input"
- form="formImageType"
- name="imagetypeForm[isGravatarEnabled]"
- checked={!isGravatarEnabled}
- onChange={() => setGravatarEnabled(false)}
- />
- <label
- className="form-label form-check-label"
- htmlFor="radioUploadPicture"
- >
- {t('Upload Image')}
- </label>
- </div>
- </h5>
- <div className="row mt-3">
- <span className="col-md-6 col-lg-4 col-form-label text-start">
- {t('Current Image')}
- </span>
- <div className="col-md-6 col-lg-8">
- <p className="mb-0">
- <img
- src={uploadedPictureSrc ?? DEFAULT_IMAGE}
- alt={t('Current Image')}
- width="64"
- height="64"
- className="rounded-circle"
- id="settingUserPicture"
- />
- </p>
- {uploadedPictureSrc && (
- <button
- type="button"
- className="btn btn-danger mt-2"
- onClick={deleteImageHandler}
- >
- {t('Delete Image')}
- </button>
- )}
- </div>
- </div>
- <div className="row align-items-center mt-3 mt-md-5">
- <span className="col-md-6 col-lg-4 col-form-label text-start mt-3 mt-md-0">
- {t('Upload new image')}
- </span>
- <div className="col-md-6 col-lg-8">
- <input
- type="file"
- onChange={selectFileHandler}
- name="profileImage"
- accept="image/png,image/jpeg,image/jpg,image/gif,image/webp,image/avif,image/heic,image/heif,image/tiff,image/svg+xml"
- />
- </div>
- </div>
- </div>
- </div>
- <ImageCropModal
- isShow={showImageCropModal}
- src={imageCropSrc}
- onModalClose={() => setShowImageCropModal(false)}
- onImageProcessCompleted={processImageCompletedHandler}
- isCircular
- showCropOption
- />
- <div className="row mt-4">
- <div className="offset-4 col-5">
- <button type="button" className="btn btn-primary" onClick={submit}>
- {t('Update')}
- </button>
- </div>
- </div>
- </>
- );
- };
- export default ProfileImageSettings;
|