Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion design-system/ui/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const Button = ({ label, onClick, disabled = false, className = '', type = 'subm
type={type}
onClick={onClick}
disabled={disabled}
className={`py-2 px-4 text-white font-semibold transition text-base sm:text-xs md:text-sm lg:text-base
className={`inline-flex items-center justify-center py-2 px-4 sm:px-2 md:px-4 text-white font-semibold transition text-base sm:text-xs md:text-sm lg:text-base
${disabled ? 'bg-gray-400 cursor-not-allowed' : 'bg-main hover:bg-mainDark'}
${className}`}
>
Expand Down
4 changes: 3 additions & 1 deletion design-system/ui/buttons/TertiaryButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ const TertiaryButton = ({ label, type, color, size, disabled, onClick, className
? 'border-main text-main hover:bg-main hover:text-white hover:font-bold'
: 'border-black text-black hover:bg-black hover:text-white hover:font-bold';

const disabledStyle = 'bg-gray-200 text-gray-400 border-gray-200 cursor-not-allowed';

return (
<button
type={type}
disabled={disabled}
className={`${baseStyle} ${sizeClasses[size]} ${colorStyle} ${className}`}
className={`${baseStyle} ${sizeClasses[size]} ${disabled ? disabledStyle : colorStyle} ${className}`}
onClick={onClick}
>
{label}
Expand Down
124 changes: 99 additions & 25 deletions src/entities/user/ui/ProfileInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import ProfileCircle from '../../../../design-system/ui/Profile';
import TertiaryButton from '../../../../design-system/ui/buttons/TertiaryButton';
import DefaultTextField from '../../../../design-system/ui/textFields/DefaultTextField';
import useAuthStore from '../../../app/provider/authStore';
import { useUserInfo, useUserUpdate } from '../../../features/join/hooks/useUserHook';
import { useUserInfo, useUserUpdate} from '../../../features/join/hooks/useUserHook';
import { formatProfilName } from '../../../shared/lib/formatProfileName';
import Button from '../../../../design-system/ui/Button';
import { usePhoneVerification } from '../../../shared/utils/phoneVerification';

const ProfileInfo = () => {
const isLoggedIn = useAuthStore(state => state.isLoggedIn);
Expand All @@ -17,16 +19,31 @@ const ProfileInfo = () => {
const { mutate: updateUser } = useUserUpdate();
const [isEditing, setIsEditing] = useState(false);

const {
isVerified,
isVerifyVisible,
verificationCode,
setVerificationCode,
timer,
formatTime,
requestVerificationCode,
submitVerificationCode,
resetVerification,
} = usePhoneVerification();

const {
register,
handleSubmit,
formState: { errors },
setValue,
watch
} = useForm<{ name: string; phone: string }>({
defaultValues: { name: data?.name || '', phone: data?.phoneNumber || '' },
resolver: zodResolver(myPageSchema),
});

const phoneValue = watch('phone');

useEffect(() => {
if (data) {
setValue('name', data.name);
Expand Down Expand Up @@ -58,20 +75,22 @@ const ProfileInfo = () => {
setName(name);
refetch();
setIsEditing(false);
alert('정보가 성공적으로 업데이트 되었습니다.')
},
onError: () => {
alert('정보 업데이트에 실패했습니다. 다시 시도해주세요.');
},
});
};



if (isLoading) {
return <div>로딩 중...</div>;
}
if (error) {
return <div>정보를 불러오는데 실패했습니다. 다시 시도해주세요.</div>;
}

return (
<div className="relative w-full h-52 md:h-56">
<div className="absolute inset-0 bg-main rounded-[10px]" />
Expand Down Expand Up @@ -107,42 +126,97 @@ const ProfileInfo = () => {
name={formatProfilName(data?.name || '')}
className="w-16 h-16 md:w-18 md:h-18 text-xl md:text-2xl"
/>
<div className="flex flex-col gap-1">
<div className="flex flex-col gap-1 flex-1">
<DefaultTextField
{...register('name')}
errorPosition="right"
errorPosition="bottom"
errorMessage={errors.name?.message}
className="h-9"
/>
<DefaultTextField
{...register('phone')}
onChange={handlePhoneChange}
errorPosition="right"
errorMessage={errors.phone?.message}
className="h-9"
/>
<div className="flex gap-2 items-center">
<DefaultTextField
{...register('phone')}
onChange={handlePhoneChange}
className="h-9 flex-1"
/>
<Button
type="button"
label="인증하기"
onClick={() => requestVerificationCode(phoneValue)}
className="h-9 sm:h-8 rounded-md w-24"
/>
</div>

{isVerifyVisible && (
<div className="mt-3 p-4 bg-white rounded-md shadow-md border border-gray-300">
<div className="flex gap-2 mb-2">
<DefaultTextField
placeholder="인증번호 6자리"
value={verificationCode}
onChange={(e) => setVerificationCode(e.target.value)}
className="h-9 flex-1"
/>
<Button
type="button"
label="확인"
onClick={() => submitVerificationCode(phoneValue)}
className="h-9 px-3 rounded-md"
/>
</div>
<span className="text-xs text-gray-500 pl-1 mb-3 block">
남은 시간: {formatTime(timer)}
</span>
<TertiaryButton
label="취소하기"
type="button"
color="pink"
size="full"
onClick={() => {
setIsEditing(false);
setValue('name', data?.name || '');
setValue('phone', data?.phoneNumber || '');
resetVerification();
setVerificationCode('');
}}
/>
</div>
)}

{!isVerifyVisible && (
<div className="flex gap-2 mt-2">
<TertiaryButton
label="취소하기"
type="button"
color="pink"
size="full"
onClick={() => {
setIsEditing(false);
setValue('name', data?.name || '');
setValue('phone', data?.phoneNumber || '');
resetVerification();
setVerificationCode('');
}}
/>
<TertiaryButton
label="수정하기"
type="submit"
color="pink"
size="full"
disabled={
phoneValue !== data?.phoneNumber && !isVerified
}
/>
</div>
)}
</div>
</div>
<div className="flex gap-2">
<TertiaryButton
label="취소하기"
type="button"
color="pink"
size="full"
onClick={() => {
setIsEditing(false);
setValue('name', data?.name || '');
setValue('phone', data?.phoneNumber || '');
}}
/>
<TertiaryButton label="수정하기" type="submit" color="pink" size="full" />
</div>
</form>
)}
</div>
</div>
</div>
);

};

export default ProfileInfo;
15 changes: 10 additions & 5 deletions src/features/join/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,16 @@ export const agreeTerms = async (data: TermsAgreementRequest) => {
};

//인증번호 발급
export const sendCertificationCode = async (phoneNum: string): Promise<void> => {
await axiosClient.post('/sms/send', { phoneNum });
export const sendCertificationCode = async (data: { phoneNumber: string }) => {
const response = await axiosClient.post('/sms/send', data, {
headers: { isPublicApi: true },
});
return response.data;
};

//인증번호 검증
export const verifyCertificationCode = async (phoneNum: string, certificationCode: string): Promise<void> => {
await axiosClient.post('/sms/verify', { phoneNum, certificationCode });
export const verifyCertificationCode = async (data: {phoneNumber: string, certificationCode: string}) => {
const response = await axiosClient.post('/sms/verify', data, {
headers: { isPublicApi: true },
});
return response.data;
};
25 changes: 18 additions & 7 deletions src/features/join/hooks/useUserHook.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useMutation, useQuery } from '@tanstack/react-query';
import { agreeTerms, readUser, sendCertificationCode, updateUser, verifyCertificationCode } from '../api/user';
import { UserInfoRequest, UserInfoResponse } from '../model/userInformation';
import { AxiosError } from 'axios';

export const useUserInfo = (enabled: boolean = true) => {
return useQuery<UserInfoResponse>({
Expand Down Expand Up @@ -32,26 +33,36 @@ export const useAgreeTerms = () => {
// 인증번호 발급
export const useSendCertificationCode = () => {
return useMutation({
mutationFn: (phoneNum: string) => sendCertificationCode(phoneNum),
mutationFn: (data: { phoneNumber: string }) => sendCertificationCode(data),
onSuccess: () => {
alert('인증번호를 발송했습니다.');
},
onError: () => {
alert('인증번호 전송에 실패했습니다.');
onError: (error: AxiosError<any>) => {
if (error.result) {
const allMessages = Object.values(error.result).join('\n');
alert(allMessages);
} else {
alert(error.message || '인증번호 발송에 실패하였습니다.');
}
},
});
};

// 인증번호 확인
export const useVerifyCertificationCode = () => {
return useMutation({
mutationFn: (params: { phoneNum: string; certificationCode: string }) =>
verifyCertificationCode(params.phoneNum, params.certificationCode),
mutationFn: (params: { phoneNumber: string; certificationCode: string }) =>
verifyCertificationCode(params),
onSuccess: () => {
alert('인증에 성공했습니다.');
},
onError: () => {
alert('인증번호가 일치하지 않습니다.');
onError: (error: AxiosError<any>) => {
if (error.result) {
const allMessages = Object.values(error.result).join('\n');
alert(allMessages);
} else {
alert(error.message || '인증에 실패하였습니다.');
}
},
});
}
Loading