All files / src/components/pages/user/profile/profile-edit-modal index.tsx

95.71% Statements 134/140
70.58% Branches 12/17
100% Functions 5/5
95.71% Lines 134/140

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 1411x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 6x 3x 3x 3x 3x 3x 3x 3x 3x 3x     45x 45x 45x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 3x 2x 1x 2x 2x 3x     2x 3x     3x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 6x 6x 6x 6x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x 45x  
'use client';
import { useForm } from '@tanstack/react-form';
 
import { API } from '@/api';
import {
  Button,
  ImageRecord,
  ModalContent,
  ModalDescription,
  ModalTitle,
  useModal,
} from '@/components/ui';
import { useUpdateUser } from '@/hooks/use-user';
import { useUserImageUpdate } from '@/hooks/use-user/use-user-image-update';
import {
  mbtiOnBlurSchema,
  mbtiOnChangeSchema,
  nickNameOnChangeSchema,
  profileImageOnChangeSchema,
  profileMessageOnChangeSchema,
} from '@/lib/schema/mypage';
import { UpdateMyInfoPayloads, User } from '@/types/service/user';
 
import { ImageField, MBTIField, MessageField, NickNameField } from '../profile-edit-fields';
 
interface Props {
  user: User;
}
 
export const ProfileEditModal = ({ user }: Props) => {
  const { profileImage: image, nickName, profileMessage, mbti } = user;
 
  const { close } = useModal();
 
  const { mutateAsync: updateUser, error: _userInfoError } = useUpdateUser();
  const { mutateAsync: updateUserImage, error: _userImageError } = useUserImageUpdate();
 
  const form = useForm({
    defaultValues: {
      profileImage: { [image]: null } as ImageRecord,
      nickName,
      profileMessage,
      mbti,
    },
    validators: {
      onSubmitAsync: async ({ value }) => {
        if (value.nickName === nickName) return null;
        const res = await API.userService.getNicknameAvailability({ nickName: value.nickName });
        if (!res.available) {
          return {
            form: '입력값을 확인해주세요',
            fields: {
              nickName: { message: '이미 사용중인 닉네임 입니다.' },
            },
          };
        }
        return null;
      },
    },
 
    onSubmit: async ({ value }) => {
      const { profileImage, nickName, profileMessage, mbti } = value;
      const nextMbti = mbti.toUpperCase();
      // 프로필 항목 업데이트 조건 체크
      const nextProfileInfo: UpdateMyInfoPayloads = {
        ...(user.nickName !== nickName && { nickName }),
        ...(user.profileMessage !== profileMessage && { profileMessage }),
        ...(user.mbti !== nextMbti && { mbti: nextMbti }),
      };
 
      /*
      Promise 체이닝 사용 시 catch를 먹어버리기 때문에 각 mutation의 error가 업데이트 되지않음
      따라서 try catch 방식 사용
      */
      try {
        // 프로필 정보 업데이트 조건 체크
        if (Object.keys(nextProfileInfo).length > 0) {
          await updateUser(nextProfileInfo);
        }
        // 프로필 이미지 업데이트 조건 체크
        const imageFileObject = Object.values(profileImage)[0];
        if (imageFileObject) {
          await updateUserImage({ file: imageFileObject });
        }
        close();
      } catch {
        alert(`업데이트에 실패했습니다. 잠시 후 다시 시도해주세요`);
      }
    },
  });
 
  return (
    <ModalContent className='max-w-82.5'>
      <ModalTitle>프로필 수정</ModalTitle>
      <ModalDescription className='sr-only'>
        이 모달은 자신의 프로필을 수정할 수 있는 모달입니다.
      </ModalDescription>
      <form
        onSubmit={(e) => {
          e.preventDefault();
          e.stopPropagation();
          form.handleSubmit();
        }}
      >
        <form.Field
          validators={{ onChange: profileImageOnChangeSchema }}
          children={(field) => <ImageField field={field} />}
          name='profileImage'
        />
        <form.Field
          validators={{ onChange: nickNameOnChangeSchema }}
          children={(field) => <NickNameField field={field} />}
          name='nickName'
        />
        <form.Field
          validators={{ onChange: profileMessageOnChangeSchema }}
          children={(field) => <MessageField field={field} />}
          name='profileMessage'
        />
        <form.Field
          validators={{ onChange: mbtiOnChangeSchema, onBlur: mbtiOnBlurSchema }}
          children={(field) => <MBTIField field={field} />}
          name='mbti'
        />
        <div className='mt-6 flex gap-2'>
          <Button variant='tertiary' onClick={close}>
            취소
          </Button>
          <form.Subscribe selector={(state) => [state.canSubmit, state.isSubmitting]}>
            {([canSubmit, isSubmitting]) => (
              <Button disabled={!canSubmit} type='submit'>
                {isSubmitting ? '수정 중...' : '수정하기'}
              </Button>
            )}
          </form.Subscribe>
        </div>
      </form>
    </ModalContent>
  );
};