-
Notifications
You must be signed in to change notification settings - Fork 2
develop <-- feature/mypage #81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
d676cdc
๐จstyle: ์ปค์คํ
ํด๋์ค ์ถ๊ฐ
Insung-Jo 8700381
โจfeat: PlusIcon ์ปดํฌ๋ํธ ๊ตฌํ
Insung-Jo 0644bd4
โจfeat: ์๋ํฌ์ธํธ ์์ฑ
Insung-Jo 460e2ef
โจfeat: ๋ง์ดํ์ด์ง ํ์
์์ฑ
Insung-Jo e38aad8
โจfeat: ๋ง์ดํ์ด์ง ์ฌ์ฉ์ ์ ๋ณด ์กฐํ ๋ฐ ์์ API ํจ์ ์ถ๊ฐ
Insung-Jo 437dd7a
โจfeat: ์๋ํฌ์ธํธ ์ถ๊ฐ
Insung-Jo 392fc77
โจfeat: ๋ง์ดํ์ด์ง ํ์
์ถ๊ฐ
Insung-Jo dab06ee
โจfeat: ๋ง์ดํ์ด์ง ์ด๋ฏธ์ง ์
๋ก๋ API ํจ์ ์ถ๊ฐ
Insung-Jo cec5cfc
โจfeat: ํ๋กํ ์ด๋ฏธ์ง ์
๋ก๋ mutation ํ
์์ฑ
Insung-Jo 4ae13db
โจfeat: ํ๋กํ ์
๋ฐ์ดํธ mutation ํ
์์ฑ
Insung-Jo 110345d
๐ซง modify: PlusIcon ํ์
์์ ๋ฐ ๋ฐ์
Insung-Jo 041933b
โจfeat: WhitePenIcon ์ปดํฌ๋ํธ ๊ตฌํ
Insung-Jo 2cec219
โจfeat: CloseCircleIcon ์ปดํฌ๋ํธ ๊ตฌํ
Insung-Jo 666650e
โจfeat: ๋ง์ดํ์ด์ง ์คํค๋ง ์์ฑ
Insung-Jo a0b027d
๐จstyle: Input ์กฐ๊ฑด๋ถ ์คํ์ผ ์ถ๊ฐ
Insung-Jo 89d13dc
โจfeat: ์ฌ์ฉ์ ์ ๋ณด ์กฐํ์ฉ useUserQuery ํ
์์ฑ
Insung-Jo 1cde8db
โจfeat: ProfileImageUpload ์ปดํฌ๋ํธ ๊ตฌํ
Insung-Jo adb0300
โจfeat: ProfileEditForm ์ปดํฌ๋ํธ ๊ตฌํ
Insung-Jo 34d0251
โจfeat: ํ์ด์ง ์ ์ฉ
Insung-Jo 118ea68
๐fix: ํ ํฐ ์ ๋์ด์ค๋ ๋ฌธ์ ์์
Insung-Jo 296bae0
โป๏ธrefactor: axios ์ธ์คํด์ค ๋ค์ด๋ฐ์ authHttpClient๋ก ๋ช
ํํ๊ฒ ๋ณ๊ฒฝ
Insung-Jo 5489a48
๐ซงmodify: ์คํ ์์
Insung-Jo 76fadb9
๐fix: ์ฝ๋๋๋น ๋ฆฌ๋ทฐ ๋ฐ์
Insung-Jo cbdb10e
โจfeat: ๋น๋ฐ๋ฒํธ ํ์ธ ํ
๊ณตํต์ผ๋ก ๋ถ๋ฆฌ ๋ฐ ์ ์ฉ
Insung-Jo dd14548
โจfeat: ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ์๋ ํฌ์ธํธ ์ถ๊ฐ
Insung-Jo d7ab214
โจfeat: ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ํ์
์ถ๊ฐ
Insung-Jo dadf4db
โจfeat: ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ API ์์ฑ
Insung-Jo 2b3f632
โจfeat: ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ Mutation ํ
๊ตฌํ
Insung-Jo a269775
โจfeat: ๋น๋ฐ๋ฒํธ ๊ด๋ จ ์คํค๋ง ์์ฑ
Insung-Jo 60f5c41
โจfeat: ์ ๋น๋ฐ๋ฒํธ ์ค๋ณต ๊ฒ์ฆ ํ
๊ตฌํ
Insung-Jo 232708d
โจfeat: ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ์ปดํฌ๋ํธ ๊ตฌํ
Insung-Jo 49049ef
โจfeat: ํ์ด์ง ์ ์ฉ
Insung-Jo f9c87dc
๐ซงmodify: ์ผ๋ถ ๊ตฌ์กฐ ๊ฐ์
Insung-Jo 254e3f3
๐ซงmodify: ์คํ ์์
Insung-Jo 8860060
Merge pull request #78 from CoPlay-FE/feature/mypage-ProfileForm
Insung-Jo 32cba40
Merge pull request #80 from CoPlay-FE/feature/mypage-PasswordChangeForm
Insung-Jo 9f11303
โจfeat: ์์ ๋ฒํผ ์์ฑ
Insung-Jo 6957a8b
๐จstyle: ๋ถํ์ํ ์คํ์ผ ์ ๊ฑฐ
Insung-Jo 6106654
Merge branch 'develop' into feature/mypage
Insung-Jo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,21 @@ | ||
| import api from '@/app/shared/lib/axios' | ||
| import authHttpClient from '@/app/shared/lib/axios' | ||
| import { User as SignupResponse } from '@/app/shared/types/user.type' | ||
|
|
||
| import { LoginRequest, LoginResponse, SignupRequest } from '../types/auth.type' | ||
| import { AUTH_ENDPOINT } from './authEndpoint' | ||
|
|
||
| export const login = async (data: LoginRequest): Promise<LoginResponse> => { | ||
| const response = await api.post<LoginResponse>(AUTH_ENDPOINT.LOGIN, data) | ||
| const response = await authHttpClient.post<LoginResponse>( | ||
| AUTH_ENDPOINT.LOGIN, | ||
| data, | ||
| ) | ||
| return response.data | ||
| } | ||
|
|
||
| export const signup = async (data: SignupRequest): Promise<SignupResponse> => { | ||
| const response = await api.post<SignupResponse>(AUTH_ENDPOINT.SIGNUP, data) | ||
| const response = await authHttpClient.post<SignupResponse>( | ||
| AUTH_ENDPOINT.SIGNUP, | ||
| data, | ||
| ) | ||
| return response.data | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 0 additions & 11 deletions
11
src/app/features/auth/hooks/useConfirmPasswordValidation.ts
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| import authHttpClient from '@lib/axios' | ||
|
|
||
| import { User as UserDataResponse } from '@/app/shared/types/user.type' | ||
|
|
||
| import { PasswordChangeRequest } from '../types/mypage.type' | ||
| import { | ||
| UpdateProfileRequest, | ||
| UploadProfileImageResponse, | ||
| } from '../types/mypage.type' | ||
| import { MYPAGE_ENDPOINT } from './mypageEndPoint' | ||
|
|
||
| export async function loadUser(): Promise<UserDataResponse> { | ||
| const response = await authHttpClient.get(MYPAGE_ENDPOINT.USER) | ||
| return response.data | ||
| } | ||
|
|
||
| export async function updateMyProfile( | ||
| data: UpdateProfileRequest, | ||
| ): Promise<UserDataResponse> { | ||
| const response = await authHttpClient.put<UserDataResponse>( | ||
| MYPAGE_ENDPOINT.USER, | ||
| data, | ||
| ) | ||
| return response.data | ||
| } | ||
|
|
||
| export async function uploadProfileImage( | ||
| image: File, | ||
| ): Promise<UploadProfileImageResponse> { | ||
| const formData = new FormData() | ||
| formData.append('image', image) | ||
| const response = await authHttpClient.post<UploadProfileImageResponse>( | ||
| MYPAGE_ENDPOINT.IMAGE, | ||
| formData, | ||
| ) | ||
| return response.data | ||
| } | ||
|
|
||
| export async function changePassword( | ||
| data: PasswordChangeRequest, | ||
| ): Promise<void> { | ||
| await authHttpClient.put(MYPAGE_ENDPOINT.CHANGE_PASSWORD, data) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export const MYPAGE_ENDPOINT = { | ||
| USER: `/${process.env.NEXT_PUBLIC_TEAM_ID}/users/me`, | ||
| IMAGE: `/${process.env.NEXT_PUBLIC_TEAM_ID}/users/me/image`, | ||
| CHANGE_PASSWORD: `/${process.env.NEXT_PUBLIC_TEAM_ID}/auth/password`, | ||
| } |
114 changes: 114 additions & 0 deletions
114
src/app/features/mypage/components/PasswordChangeForm.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| import Input from '@components/Input' | ||
| import { useConfirmPasswordValidation } from '@hooks/useConfirmPasswordValidation' | ||
| import { cn } from '@lib/cn' | ||
| import { useForm } from 'react-hook-form' | ||
|
|
||
| import { showSuccess } from '@/app/shared/lib/toast' | ||
|
|
||
| import { useChangePasswordMutation } from '../hook/useChangePasswordMutation' | ||
| import { useNewPasswordValidation } from '../hook/useNewPasswordValidation' | ||
| import { PasswordChangeRequest } from '../types/mypage.type' | ||
|
|
||
| interface PasswordChangeFormData extends PasswordChangeRequest { | ||
| confirmPassword: string | ||
| } | ||
|
|
||
| export default function PasswordChangeForm() { | ||
| const { | ||
| register, | ||
| handleSubmit, | ||
| trigger, | ||
| getValues, | ||
| reset, | ||
| formState: { errors, isValid }, | ||
| } = useForm<PasswordChangeFormData>({ | ||
| mode: 'onBlur', | ||
| defaultValues: { | ||
| password: '', | ||
| newPassword: '', | ||
| confirmPassword: '', | ||
| }, | ||
| }) | ||
|
|
||
| const { mutate: changePassword, isPending } = useChangePasswordMutation() | ||
| const newPasswordValidation = useNewPasswordValidation(() => | ||
| getValues('password'), | ||
| ) | ||
| const confirmPasswordValidation = useConfirmPasswordValidation(() => | ||
| getValues('newPassword'), | ||
| ) | ||
|
|
||
| function onSubmit(data: PasswordChangeFormData) { | ||
| changePassword( | ||
| { | ||
| password: data.password, | ||
| newPassword: data.newPassword, | ||
| }, | ||
| { | ||
| onSuccess: () => { | ||
| reset() | ||
| showSuccess('๋น๋ฐ๋ฒํธ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ๋ณ๊ฒฝ๋์์ต๋๋ค!') | ||
| }, | ||
| }, | ||
| ) | ||
| } | ||
|
|
||
| return ( | ||
| <div className="BG-white flex h-auto w-full max-w-672 flex-col gap-24 rounded-8 p-24"> | ||
| <h2 className="text-2xl font-bold">๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ</h2> | ||
| <form | ||
| onSubmit={handleSubmit(onSubmit)} | ||
| className="flex flex-col justify-between gap-16" | ||
| > | ||
| <Input | ||
| labelName="ํ์ฌ ๋น๋ฐ๋ฒํธ" | ||
| type="password" | ||
| placeholder="๋น๋ฐ๋ฒํธ ์ ๋ ฅ" | ||
| autoComplete="current-password" | ||
| {...register('password')} | ||
| /> | ||
|
|
||
| <Input | ||
| labelName="์ ๋น๋ฐ๋ฒํธ" | ||
| type="password" | ||
| placeholder="์ ๋น๋ฐ๋ฒํธ ์ ๋ ฅ" | ||
| autoComplete="new-password" | ||
| {...register('newPassword', { | ||
| ...newPasswordValidation, | ||
| onBlur: () => { | ||
| trigger('confirmPassword') | ||
| }, | ||
| })} | ||
| hasError={!!errors.newPassword} | ||
| errorMessage={errors.newPassword?.message} | ||
| /> | ||
|
|
||
| <Input | ||
| labelName="์ ๋น๋ฐ๋ฒํธ ํ์ธ" | ||
| type="password" | ||
| placeholder="์ ๋น๋ฐ๋ฒํธ ์ ๋ ฅ" | ||
| autoComplete="new-password" | ||
| {...register('confirmPassword', { | ||
| ...confirmPasswordValidation, | ||
| onBlur: () => { | ||
| trigger('newPassword') | ||
| }, | ||
| })} | ||
| hasError={!!errors.confirmPassword} | ||
| errorMessage={errors.confirmPassword?.message} | ||
| /> | ||
|
|
||
| <button | ||
| type="submit" | ||
| className={cn( | ||
| 'mt-8 h-50 w-full rounded-8 text-lg font-medium text-white', | ||
| isValid && !isPending ? 'BG-blue' : 'BG-blue-disabled', | ||
| )} | ||
| disabled={!isValid || isPending} | ||
| > | ||
| {isPending ? '๋ณ๊ฒฝ ์ค..' : '๋ณ๊ฒฝ'} | ||
| </button> | ||
| </form> | ||
| </div> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| 'use client' | ||
|
|
||
| import Input from '@components/Input' | ||
| import { showError, showSuccess } from '@lib/toast' | ||
| import { isAxiosError } from 'axios' | ||
| import { useRouter } from 'next/navigation' | ||
| import { useEffect, useState } from 'react' | ||
| import { Controller, useForm } from 'react-hook-form' | ||
|
|
||
| import { useUpdateMyProfileMutation } from '../hook/useUpdateMyProfileMutation' | ||
| import { useUploadProfileImageMutation } from '../hook/useUploadProfileImageMutation' | ||
| import { useUserQuery } from '../hook/useUserQurey' | ||
| import { mypageValidation } from '../schemas/mypageValidation' | ||
| import ProfileImageUpload from './ProfileImageUpload' | ||
|
|
||
| interface ProfileFormData { | ||
| profileImageUrl: string | null | ||
| nickname: string | ||
| email: string | ||
| } | ||
|
|
||
| export default function ProfileEditForm() { | ||
| const { data: user } = useUserQuery() // get์ผ๋ก ์ฌ์ฉ์ ๋ฐ์ดํฐ ์ต์ ํ | ||
| const router = useRouter() // useClientQuery๋ฅผ ์ฌ์ฉ ํ์ฌ ํด๋น ๋ถ๋ถ์๋ง ๋ ๋๋ง์ ์งํํ๋ ค ํ์ผ๋ ๋ค๋ฅธ ๋ถ๋ถ๋ ์ฐ๋๋๋ ๋ถ๋ถ์ด ์๊ธฐ ๋๋ฌธ์ ๋ผ์ฐํฐ ์ฌ์ฉ | ||
| const { | ||
| control, | ||
| register, | ||
| handleSubmit, | ||
| reset, | ||
| formState: { errors }, | ||
| } = useForm<ProfileFormData>({ | ||
| mode: 'onChange', | ||
| defaultValues: { | ||
| profileImageUrl: user?.profileImageUrl, | ||
| nickname: user?.nickname, | ||
| email: user?.email, | ||
| }, | ||
| }) | ||
|
|
||
| const [profileImageFile, setProfileImageFile] = useState<File | null>(null) | ||
| const { mutateAsync: uploadImage } = useUploadProfileImageMutation() | ||
| const { mutateAsync: updateProfile } = useUpdateMyProfileMutation() | ||
|
|
||
| // ์ ์ ์ ๋ณด๊ฐ ๋น๋๊ธฐ์ ์ผ๋ก ๋์ด์ค๊ธฐ ๋๋ฌธ์ ์ ์ ๊ฐ ๋ก๋ฉ๋ ์์ ์์ RHF์ ์ด๊ธฐํ ํ๊ธฐ ์ํจ (SSR ๋์ ์ ๋ณ๊ฒฝ ์์ ) | ||
| useEffect(() => { | ||
| if (user) { | ||
| reset({ | ||
| profileImageUrl: user.profileImageUrl ?? null, | ||
| nickname: user.nickname ?? '', | ||
| email: user.email ?? '', | ||
| }) | ||
| } | ||
| }, [user, reset]) | ||
|
|
||
| async function onSubmit(data: ProfileFormData) { | ||
| try { | ||
| // ํ์ฌ ์ด๋ฏธ์ง URL์ ์ด๊ธฐ๊ฐ์ผ๋ก ์ค์ (๋ณ๊ฒฝ์ด ์์ ์๋ ์๊ธฐ ๋๋ฌธ) | ||
| let imageUrl = data.profileImageUrl | ||
|
|
||
| // ์ ์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ํ ๊ฒฝ์ฐ โ ์๋ฒ์ ์ ๋ก๋ ์์ฒญ (POST) | ||
| if (profileImageFile) { | ||
| const { profileImageUrl } = await uploadImage(profileImageFile) | ||
| imageUrl = profileImageUrl | ||
| } | ||
|
|
||
| // ๋๋ค์๊ณผ ์ด๋ฏธ์ง URL์ ํฌํจํ ์ฌ์ฉ์ ํ๋กํ ์ ๋ณด ์์ฑ | ||
| const submitData = { | ||
| nickname: data.nickname, | ||
| profileImageUrl: imageUrl, | ||
| } | ||
|
|
||
| // ์๋ฒ์ ํ๋กํ ์ ๋ณด ์์ ์์ฒญ (PUT) | ||
| await updateProfile(submitData) | ||
|
|
||
| // ์ฌ์ฉ์์๊ฒ ์ฑ๊ณต ์๋ฆผ + ์ปดํฌ๋ํธ ์ต์ ํ | ||
| showSuccess('ํ๋กํ ๋ณ๊ฒฝ์ด ์๋ฃ๋์์ต๋๋ค.') | ||
| router.refresh() | ||
| } catch (error) { | ||
| if (isAxiosError(error)) { | ||
| // ์๋ฒ ์๋ฌ ๋ฉ์์ง ์ฐ์ ์ฒ๋ฆฌ, ์์ผ๋ฉด ๊ธฐ๋ณธ ๋ฉ์์ง | ||
| const serverMessage = ( | ||
| error.response?.data as { message?: string } | undefined | ||
| )?.message | ||
| const fallback = error.message || 'ํ๋กํ ๋ณ๊ฒฝ์ ์คํจํ์์ต๋๋ค.' | ||
| showError(serverMessage ?? fallback) | ||
| } else { | ||
| showError('์ ์ ์๋ ์๋ฌ ๋ฐ์') | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return ( | ||
| <form | ||
| onSubmit={handleSubmit(onSubmit)} | ||
| className="BG-white flex h-auto w-full max-w-672 flex-col gap-24 rounded-8 p-24 font-medium" | ||
| > | ||
| <h2 className="text-2xl font-bold">ํ๋กํ</h2> | ||
|
|
||
| <div className="flex justify-between gap-42 tablet:flex-col"> | ||
| <Controller | ||
| name="profileImageUrl" | ||
| control={control} | ||
| render={({ field: { value, onChange } }) => ( | ||
| <ProfileImageUpload | ||
| value={value} // ๋ฏธ๋ฆฌ๋ณด๊ธฐ์ฉ ์ด๋ฏธ์ง URL | ||
| onChange={onChange} // form ์ํ(profileImageUrl) ์ ๋ฐ์ดํธ | ||
| onFileChange={(file) => setProfileImageFile(file)} // ์๋ฒ ์ ์ก์ฉ ํ์ผ ์ ์ฅ | ||
| /> | ||
| )} | ||
| /> | ||
|
|
||
| <div className="flex flex-grow flex-col gap-16"> | ||
| <Input labelName="์ด๋ฉ์ผ" {...register('email')} readOnly /> | ||
| <Input | ||
| labelName="๋๋ค์" | ||
| type="text" | ||
| placeholder="๋๋ค์์ ์ ๋ ฅํด ์ฃผ์ธ์" | ||
| autoComplete="off" | ||
| {...register('nickname', mypageValidation.nickname)} | ||
| hasError={!!errors.nickname} | ||
| errorMessage={errors.nickname?.message} | ||
| /> | ||
| <button | ||
| type="submit" | ||
| className="BG-blue h-50 w-full rounded-8 text-white" | ||
| > | ||
| ์ ์ฅ | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </form> | ||
| ) | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ ๏ธ Refactor suggestion
ํ์ผ ์ ๋ก๋ ๋ณด์ ๊ฒ์ฆ ํ์
ํ์ผ ์ ๋ก๋ ๋ก์ง์ด ๊ตฌํ๋์ด ์์ง๋ง, ํด๋ผ์ด์ธํธ ์ธก ํ์ผ ํ์ ๋ฐ ํฌ๊ธฐ ๊ฒ์ฆ์ด ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ ํด๋ผ์ด์ธํธ ์ธก ๊ฒ์ฆ์ ์ถ๊ฐํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค:
// ์ ์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ํ ๊ฒฝ์ฐ โ ์๋ฒ์ ์ ๋ก๋ ์์ฒญ (POST) if (profileImageFile) { + // ํ์ผ ํ์ ๊ฒ์ฆ + const allowedTypes = ['image/jpeg', 'image/png', 'image/webp'] + if (!allowedTypes.includes(profileImageFile.type)) { + showError('์ง์๋์ง ์๋ ํ์ผ ํ์์ ๋๋ค. (JPEG, PNG, WebP๋ง ๊ฐ๋ฅ)') + return + } + + // ํ์ผ ํฌ๊ธฐ ๊ฒ์ฆ (์: 5MB) + const maxSize = 5 * 1024 * 1024 + if (profileImageFile.size > maxSize) { + showError('ํ์ผ ํฌ๊ธฐ๋ 5MB๋ฅผ ์ด๊ณผํ ์ ์์ต๋๋ค.') + return + } + const { profileImageUrl } = await uploadImage(profileImageFile) imageUrl = profileImageUrl }๐ Committable suggestion
๐ค Prompt for AI Agents