Skip to content

Commit

Permalink
refactor: AccountForm에서 newImageUrl 제외한 로직 전부 하위 컴포넌트로 이동
Browse files Browse the repository at this point in the history
  • Loading branch information
grapefruit13 committed Apr 10, 2024
1 parent 817d45e commit 0b66666
Showing 1 changed file with 23 additions and 294 deletions.
317 changes: 23 additions & 294 deletions src/components/AccountForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,316 +1,45 @@
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import { useState } from 'react';

import { zodResolver } from '@hookform/resolvers/zod';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import classNames from 'classnames/bind';
import { FormProvider, useForm } from 'react-hook-form';

import { QUERY_KEYS } from '@/apis/queryKeys';
import { Users } from '@/apis/users';
import { ImageSchema, PasswordSchema, ProfileSchema } from '@/apis/users/schema';
import { ERROR_MESSAGE, INPUT_NAMES } from '@/constants';

import Avatar from '@/components/commons/Avatar';
import { BaseButton } from '@/components/commons/buttons';
import { InputField } from '@/components/commons/inputs';
import { ConfirmModal, ModalButton } from '@/components/commons/modals';
import useMultiState from '@/hooks/useMultiState';
import PasswordForm from '@/components/account/PasswordForm';
import ProfileForm from '@/components/account/ProfileForm';
import ProfileImageForm from '@/components/account/ProfileImageForm';
import useUserStore from '@/stores/useUserStore';

import styles from './AccountForm.module.scss';

const cx = classNames.bind(styles);

const { profileImageUrl, nickname, password, passwordConfirm, image } = INPUT_NAMES;

const AccountForm = () => {
const { multiState, toggleClick } = useMultiState(['resetConfirmModal', 'saveAlertModal']);
const { userData } = useUserStore();

const userEmail = userData?.email;
const userNickname = userData?.nickname;
const userProfileImageUrl = userData?.profileImageUrl;
const userId = userData?.id;

const [newImageUrl, setNewImageUrl] = useState(userProfileImageUrl || '');
const [isProfileFormDirty, setIsProfileFormDirty] = useState(false);
const fileInputRef = useRef(null);
const buttonRef = useRef<HTMLButtonElement>(null);

const profileImageMethods = useForm({
mode: 'all',
resolver: zodResolver(ImageSchema),
});

const profileMethods = useForm({
mode: 'all',
resolver: zodResolver(ProfileSchema),
defaultValues: {
email: userEmail,
[nickname]: userNickname,
[profileImageUrl]: userProfileImageUrl,
},
});

const passwordMethods = useForm({
mode: 'all',
resolver: zodResolver(PasswordSchema),
});

const { register, setValue, getValues } = profileImageMethods;
const {
setValue: setProfileFormValue,
formState: { dirtyFields: profileFormDirtyFields },
watch: profileWatch,
} = profileMethods;
const { watch: passwordWatch } = passwordMethods;

const watchPassword = passwordWatch(password);

useEffect(() => {
setProfileFormValue(`${nickname}`, userNickname);
setProfileFormValue(`${profileImageUrl}`, userProfileImageUrl);
setNewImageUrl(userProfileImageUrl || '');
}, [userNickname, userProfileImageUrl]);

useEffect(() => {
if (userNickname === profileWatch(nickname) && newImageUrl === userProfileImageUrl) {
setIsProfileFormDirty(false);
} else if (profileFormDirtyFields.nickname || newImageUrl !== userProfileImageUrl) {
setIsProfileFormDirty(true);
}
}, [profileFormDirtyFields.nickname, newImageUrl, userProfileImageUrl, userNickname, profileWatch(nickname)]);

const handleAttach = () => {
if (fileInputRef.current !== null) {
const input = fileInputRef.current as HTMLInputElement;
input.click();
}
};

const handleChangeImage = (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
const imagePreviewUrl = URL.createObjectURL(file);
setNewImageUrl(imagePreviewUrl);
setValue('image', file);
if (buttonRef.current) {
buttonRef.current.click();
}
}
};

const handleClickReset = () => {
setProfileFormValue('profileImageUrl', null);
setValue('image', '');
setNewImageUrl('');
toggleClick('resetConfirmModal');
};

const handleCloseReset = () => {
toggleClick('resetConfirmModal');
};

const handleCloseSave = () => {
toggleClick('saveAlertModal');
};

const { mutate: profileImageMutation } = useMutation({
mutationFn: Users.createImageUrl,
onSuccess(data) {
const { profileImageUrl } = data.data;
setProfileFormValue('profileImageUrl', profileImageUrl);
},
});

const { setUserData } = useUserStore();
const queryClient = useQueryClient();

const { mutate: profileMutation } = useMutation({
mutationFn: Users.edit,
mutationKey: [QUERY_KEYS.users.edit, userId],
onSuccess(data) {
const { profileImageUrl } = data.data;
setNewImageUrl(profileImageUrl);
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.users.get] });
setUserData(data.data);
toggleClick('saveAlertModal');
},
});

const { mutate: passwordMutation } = useMutation({
mutationFn: Users.edit,
onSuccess() {
toggleClick('saveAlertModal');
},
});

const handleProfileImageSubmit = () => {
profileImageMutation(getValues(image));
};

const handleProfileSubmit = (data: object) => {
profileMutation(data);
};

const handlePasswordSubmit = (data: object) => {
if ('passwordConfirm' in data) {
delete data?.passwordConfirm;
}
passwordMutation(data);
};
const [newImageUrl, setNewImageUrl] = useState(userProfileImageUrl || null);

return (
<>
<section className={cx('container')}>
<div className={cx('mypage')}>
<h1 className={cx('visually-hidden')}>계정 수정</h1>
<h2 className={cx('mypage-main-title')}>프로필 수정</h2>

<div className={cx('mypage-profile-group')}>
<h3 className={cx('title')}>프로필 이미지</h3>
<div className={cx('btn-outer-group')}>
<Avatar size='large' profileImageUrl={newImageUrl} />
<form onSubmit={profileImageMethods.handleSubmit(handleProfileImageSubmit)} className={cx('image-form')}>
<fieldset className={cx('fieldset')}>
<legend>프로필 이미지 등록</legend>
<input
{...register(image)}
ref={fileInputRef}
className={cx('image-field')}
type='file'
accept='.jpg, .png, .jpeg,'
onChange={handleChangeImage}
/>
<button ref={buttonRef} type='submit' aria-label='이미지 등록 버튼'></button>
<BaseButton theme='ghost' size='medium' onClick={handleAttach}>
이미지 등록
</BaseButton>
<BaseButton theme='ghost' size='medium' color='red' onClick={() => toggleClick('resetConfirmModal')}>
초기화
</BaseButton>
</fieldset>
</form>
</div>
</div>

<FormProvider {...profileMethods}>
<form onSubmit={profileMethods.handleSubmit(handleProfileSubmit)} className={cx('profile-form')}>
<fieldset>
<legend>닉네임 변경</legend>
<div className={cx('input-group')}>
<div className={cx('hidden')}>
<InputField name='profileImageUrl' />
</div>
<div className={cx('inner-group')}>
<span className={cx('title')}>이메일</span>
<div className={cx('input-field')}>
<InputField name='email' type='email' placeholder={userEmail} isErrorVisible isDisabled />
</div>
</div>
<div className={cx('inner-group')}>
<span className={cx('title')}>닉네임</span>
<div className={cx('input-field')}>
<InputField name={nickname} isErrorVisible maxLength={10} />
</div>
</div>
</div>
<div className={cx('btn-group')}>
<div className={cx('sm-btn', 'sm-hidden')}>
<BaseButton
type='submit'
theme='fill'
size='medium'
color='purple'
isDisabled={!isProfileFormDirty}
>
저장
</BaseButton>
</div>
<div className={cx('lg-btn', 'sm-only')}>
<BaseButton type='submit' theme='fill' size='large' color='purple' isDisabled={!isProfileFormDirty}>
저장
</BaseButton>
</div>
</div>
</fieldset>
</form>
</FormProvider>

<FormProvider {...passwordMethods}>
<h2 className={cx('mypage-main-title')}>비밀번호 수정</h2>
<form className={cx('password-form')} onSubmit={passwordMethods.handleSubmit(handlePasswordSubmit)}>
<fieldset className={cx('fieldset')}>
<legend>비밀번호 변경</legend>
<div className={cx('input-group')}>
<div className={cx('inner-group')}>
<span className={cx('title')}>비밀번호</span>
<div className={cx('input-field')}>
<InputField
name={password}
placeholder={ERROR_MESSAGE.password.placeholder}
type={'password'}
isErrorVisible
/>
</div>
</div>
<div className={cx('inner-group')}>
<span className={cx('title')}>비밀번호 확인</span>
<div className={cx('input-field')}>
<InputField
name={passwordConfirm}
type={'password'}
placeholder={ERROR_MESSAGE.password.placeholder}
isErrorVisible
/>
</div>
</div>
</div>
<div className={cx('btn-group')}>
<div className={cx('sm-btn', 'sm-hidden')}>
<BaseButton type='submit' theme='fill' size='medium' color='purple' isDisabled={!watchPassword}>
저장
</BaseButton>
</div>
<div className={cx('sm-only')}>
<BaseButton type='submit' theme='fill' size='large' color='purple'>
저장
</BaseButton>
</div>
</div>
</fieldset>
</form>
</FormProvider>
</div>
</section>

<ConfirmModal
openModal={multiState.resetConfirmModal}
onClose={handleCloseReset}
state='ALEART'
title='프로필 이미지를 초기화하시겠습니까?'
warning
renderButton={
<>
<ModalButton variant='warning' onClick={handleClickReset}>
초기화
</ModalButton>
<ModalButton onClick={handleCloseReset}>닫기</ModalButton>
</>
}
/>
<ConfirmModal
openModal={multiState.saveAlertModal}
onClose={handleCloseSave}
state='CONFIRM'
title='정보가 저장되었습니다'
renderButton={
<ModalButton variant='success' onClick={handleCloseSave}>
확인
</ModalButton>
}
/>
</>
<section className={cx('container')}>
<div className={cx('mypage')}>
<h1 className={cx('visually-hidden')}>계정 수정</h1>
<h2 className={cx('mypage-main-title')}>프로필 수정</h2>

<ProfileImageForm newImageUrl={newImageUrl} setNewImageUrl={setNewImageUrl} />
<ProfileForm
userEmail={userEmail}
userId={userId}
userNickname={userNickname}
userProfileImageUrl={userProfileImageUrl}
newImageUrl={newImageUrl}
setNewImageUrl={setNewImageUrl}
/>
<h2 className={cx('mypage-main-title')}>비밀번호 수정</h2>
<PasswordForm />
</div>
</section>
);
};

Expand Down

0 comments on commit 0b66666

Please sign in to comment.