-
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
Conversation
โจFeat: ํ๋กํ ์์ ์ปดํฌ๋ํธ ๊ตฌํ
โจFeat: ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ์ปดํฌ๋ํธ ๊ตฌํ
|
Caution Review failedThe pull request is closed. """ Walkthrough๋ง์ดํ์ด์ง(ํ๋กํ ํธ์ง ๋ฐ ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ) ๊ธฐ๋ฅ์ด ์ ๊ท ๋์
๋์์ต๋๋ค. ์ธ์ฆ ๋ฐ ๋์๋ณด๋ API ํด๋ผ์ด์ธํธ๊ฐ Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant MypagePage
participant ProfileEditForm
participant useUserQuery
participant useUploadProfileImageMutation
participant useUpdateMyProfileMutation
participant mypageApi
User->>MypagePage: ๋ง์ดํ์ด์ง ์ ์
MypagePage->>ProfileEditForm: ๋ ๋๋ง
ProfileEditForm->>useUserQuery: ์ฌ์ฉ์ ์ ๋ณด ์กฐํ
useUserQuery->>mypageApi: loadUser()
mypageApi-->>useUserQuery: ์ฌ์ฉ์ ์ ๋ณด ๋ฐํ
ProfileEditForm-->>User: ํผ ํ์
User->>ProfileEditForm: ํ๋กํ ์ ๋ณด ์์ /์ด๋ฏธ์ง ์
๋ก๋
ProfileEditForm->>useUploadProfileImageMutation: (์ด๋ฏธ์ง ์ ํ ์) ์ด๋ฏธ์ง ์
๋ก๋
useUploadProfileImageMutation->>mypageApi: uploadProfileImage()
mypageApi-->>useUploadProfileImageMutation: ์
๋ก๋ ๊ฒฐ๊ณผ ๋ฐํ
ProfileEditForm->>useUpdateMyProfileMutation: ํ๋กํ ์ ๋ณด ์ ์ถ
useUpdateMyProfileMutation->>mypageApi: updateMyProfile()
mypageApi-->>useUpdateMyProfileMutation: ์
๋ฐ์ดํธ ๊ฒฐ๊ณผ ๋ฐํ
ProfileEditForm-->>User: ์ฑ๊ณต/์คํจ ์๋ฆผ
User->>PasswordChangeForm: ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ์๋
PasswordChangeForm->>useChangePasswordMutation: ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ์์ฒญ
useChangePasswordMutation->>mypageApi: changePassword()
mypageApi-->>useChangePasswordMutation: ๊ฒฐ๊ณผ ๋ฐํ
PasswordChangeForm-->>User: ์ฑ๊ณต/์คํจ ์๋ฆผ
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. ๐ง ESLint
npm error Exit handler never called! ๐ Recent review detailsConfiguration used: CodeRabbit UI ๐ Files selected for processing (2)
โจ Finishing Touches
๐ชง TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Actionable comments posted: 3
๐งน Nitpick comments (6)
src/app/shared/components/common/CloseCircleIcon/CloseCircleIcon.tsx (1)
10-11: ๋ถํ์ํ React Fragment๋ฅผ ์ ๊ฑฐํด์ฃผ์ธ์.SVG ์์๋ฅผ ๊ฐ์ธ๋ ๋น Fragment(
<></>)๊ฐ ๋ถํ์ํฉ๋๋ค. SVG๋ฅผ ์ง์ ๋ฐํํ๋ ๊ฒ์ด ๋ ๊น๋ํฉ๋๋ค.- <> - <svg + <svg width={size} height={size} viewBox="0 0 24 24" fill="none" {...props} > {/* ... SVG content ... */} - </svg> - </> + </svg>Also applies to: 38-39
src/app/features/mypage/hook/useUserQurey.ts (1)
1-10: ํ์ผ๋ช ์คํ๋ฅผ ์์ ํด์ฃผ์ธ์.ํ์ผ๋ช ์ด
useUserQurey.ts๋ก ๋์ด์๋๋ฐ,useUserQuery.ts๋ก ์์ ํด์ผ ํฉ๋๋ค. 'Query'์ ์ฒ ์๊ฐ ์๋ชป๋์์ต๋๋ค.ํ ๊ตฌํ ์์ฒด๋ React Query ํจํด์ ์ ๋ฐ๋ฅด๊ณ ์์ผ๋ฉฐ, ์ฌ์ฉ์ ๋ฐ์ดํฐ ์กฐํ์ ์ ํฉํ ๊ตฌ์กฐ์ ๋๋ค.
src/app/features/mypage/components/ProfileImageUpload.tsx (4)
28-42: ํ์ผ ํฌ๊ธฐ ์ ํ ๊ฒ์ฆ์ ์ถ๊ฐํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์.ํ์ผ ์ ํ ๋ก์ง์ ์ ๊ตฌํ๋์ด ์์ง๋ง, ๋์ฉ๋ ํ์ผ์ ๋ํ ๊ฒ์ฆ์ด ์์ต๋๋ค. ์ฌ์ฉ์ ๊ฒฝํ ๊ฐ์ ์ ์ํด ํ์ผ ํฌ๊ธฐ ์ ํ์ ์ถ๊ฐํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ํ์ผ ํฌ๊ธฐ ๊ฒ์ฆ์ ์ถ๊ฐํ ์ ์์ต๋๋ค:
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const file = e.target.files?.[0] if (!file) return + // ํ์ผ ํฌ๊ธฐ ์ ํ (์: 5MB) + const maxSize = 5 * 1024 * 1024 + if (file.size > maxSize) { + alert('ํ์ผ ํฌ๊ธฐ๋ 5MB ์ดํ์ฌ์ผ ํฉ๋๋ค.') + return + } const url = URL.createObjectURL(file) setPreview(url) onChange(url)
55-55: ํ๋์ฝ๋ฉ๋ ํฌ๊ธฐ ๊ฐ ๋์ props๋ก ๋ฐ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์.
size-182,basis-182๊ฐ์ ํ๋์ฝ๋ฉ๋ ํฌ๊ธฐ ๊ฐ์ด ๋ฐ์ํ ๋์์ธ์ ์ ์ฝ์ด ๋ ์ ์์ต๋๋ค.๋ค์๊ณผ ๊ฐ์ด ํฌ๊ธฐ๋ฅผ props๋ก ๋ฐ๋๋ก ๊ฐ์ ํ ์ ์์ต๋๋ค:
interface Props { value: string | null onChange: (url: string | null) => void onFileChange?: (file: File) => void + size?: number // ๊ธฐ๋ณธ๊ฐ 182 } export default function ProfileImageUpload({ value, onChange, onFileChange, + size = 182, }: Props) { // ... return ( - <div className="relative size-182 basis-182"> + <div className="relative" style={{ width: size, height: size, flexBasis: size }}>
68-73: Image ์ปดํฌ๋ํธ์ ์ค๋ฅ ์ฒ๋ฆฌ๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์.Next.js Image ์ปดํฌ๋ํธ์
onErrorํธ๋ค๋ฌ๋ฅผ ์ถ๊ฐํ์ฌ ์ด๋ฏธ์ง ๋ก๋ฉ ์คํจ ์ ๋์ฒด ์ฒ๋ฆฌ๋ฅผ ํ ์ ์์ต๋๋ค.<Image src={preview} alt="ํ๋กํ ๋ฏธ๋ฆฌ๋ณด๊ธฐ" fill className="z-0 object-cover" + onError={() => { + // ์ด๋ฏธ์ง ๋ก๋ฉ ์คํจ ์ ์ฒ๋ฆฌ + setPreview(null) + onChange(null) + }} />
92-99: ์ ๊ทผ์ฑ ๊ฐ์ ์ ์ํ ์ถ๊ฐ ์์ฑ์ ๊ณ ๋ คํด๋ณด์ธ์.ํ์ผ ์ ๋ ฅ ์์์
aria-label๋ฑ์ ์ ๊ทผ์ฑ ์์ฑ์ ์ถ๊ฐํ๋ฉด ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ฌ์ฉ์์๊ฒ ๋ ๋์ ๊ฒฝํ์ ์ ๊ณตํ ์ ์์ต๋๋ค.<input id="userProfile" type="file" accept="image/*" className="hidden" ref={inputRef} onChange={handleChange} + aria-label="ํ๋กํ ์ด๋ฏธ์ง ์ ํ" />
๐ Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
๐ Files selected for processing (27)
src/app/features/auth/api/authApi.ts(1 hunks)src/app/features/auth/components/SignupForm.tsx(2 hunks)src/app/features/auth/hooks/useConfirmPasswordValidation.ts(0 hunks)src/app/features/auth/hooks/useLoginMutation.ts(1 hunks)src/app/features/auth/hooks/useSignupMutation.ts(1 hunks)src/app/features/mypage/api/mypageApi.ts(1 hunks)src/app/features/mypage/api/mypageEndPoint.ts(1 hunks)src/app/features/mypage/components/PasswordChangeForm.tsx(1 hunks)src/app/features/mypage/components/ProfileEditForm.tsx(1 hunks)src/app/features/mypage/components/ProfileImageUpload.tsx(1 hunks)src/app/features/mypage/hook/useChangePasswordMutation.ts(1 hunks)src/app/features/mypage/hook/useNewPasswordValidation.ts(1 hunks)src/app/features/mypage/hook/useUpdateMyProfileMutation.ts(1 hunks)src/app/features/mypage/hook/useUploadProfileImageMutation.ts(1 hunks)src/app/features/mypage/hook/useUserQurey.ts(1 hunks)src/app/features/mypage/schemas/mypageValidation.ts(1 hunks)src/app/features/mypage/types/mypage.type.ts(1 hunks)src/app/globals.css(1 hunks)src/app/mypage/page.tsx(2 hunks)src/app/shared/components/Input.tsx(2 hunks)src/app/shared/components/common/CloseCircleIcon/CloseCircleIcon.tsx(1 hunks)src/app/shared/components/common/PlusIcon/PlusIcon.tsx(1 hunks)src/app/shared/components/common/WhitePenIcon/WhitePenIcon.tsx(1 hunks)src/app/shared/components/common/sidebar/modal/CreateDashboardModal.tsx(2 hunks)src/app/shared/hooks/useConfirmPasswordValidation.ts(1 hunks)src/app/shared/hooks/useDashboard.ts(2 hunks)src/app/shared/lib/axios.ts(1 hunks)
๐ค Files with no reviewable changes (1)
- src/app/features/auth/hooks/useConfirmPasswordValidation.ts
๐งฐ Additional context used
๐ง Learnings (2)
src/app/features/auth/components/SignupForm.tsx (1)
Learnt from: Insung-Jo
PR: CoPlay-FE/coplan#57
File: src/app/features/auth/hooks/useSignupSubmit.ts:12-0
Timestamp: 2025-06-16T09:44:38.218Z
Learning: In src/app/features/auth/hooks/useSignupSubmit.ts, the developer intentionally uses SignupRequest type instead of SignupFormData for the submit function parameter to prevent sending unnecessary confirmPassword field to the API, aligning the function signature with server expectations.
src/app/shared/components/common/CloseCircleIcon/CloseCircleIcon.tsx (1)
Learnt from: Insung-Jo
PR: CoPlay-FE/coplan#78
File: src/app/shared/components/common/CloseCircleIcon/CloseCircleIcon.tsx:24-24
Timestamp: 2025-06-18T19:28:36.654Z
Learning: In the CloseCircleIcon component (src/app/shared/components/common/CloseCircleIcon/CloseCircleIcon.tsx), the stroke color for the X lines is intentionally hardcoded as "black" for contrast purposes, while the circle fill uses "currentColor" to adapt to themes. This is a deliberate design choice, not an oversight.
๐งฌ Code Graph Analysis (15)
src/app/features/auth/hooks/useLoginMutation.ts (1)
src/app/shared/lib/toast.ts (1)
showError(4-4)
src/app/shared/hooks/useDashboard.ts (1)
src/app/shared/types/dashboard.ts (1)
DashboardListResponse(13-17)
src/app/features/auth/api/authApi.ts (2)
src/app/features/auth/types/auth.type.ts (3)
LoginRequest(3-6)LoginResponse(8-11)SignupRequest(13-17)src/app/features/auth/api/authEndpoint.ts (1)
AUTH_ENDPOINT(1-4)
src/app/features/auth/hooks/useSignupMutation.ts (1)
src/app/shared/lib/toast.ts (1)
showError(4-4)
src/app/features/auth/components/SignupForm.tsx (1)
src/app/shared/hooks/useConfirmPasswordValidation.ts (1)
useConfirmPasswordValidation(1-7)
src/app/features/mypage/components/PasswordChangeForm.tsx (7)
src/app/features/mypage/types/mypage.type.ts (1)
PasswordChangeRequest(10-13)src/app/features/mypage/api/mypageApi.ts (1)
changePassword(39-43)src/app/features/mypage/hook/useChangePasswordMutation.ts (1)
useChangePasswordMutation(10-25)src/app/features/mypage/hook/useNewPasswordValidation.ts (1)
useNewPasswordValidation(3-13)src/app/shared/hooks/useConfirmPasswordValidation.ts (1)
useConfirmPasswordValidation(1-7)src/app/shared/lib/toast.ts (1)
showSuccess(3-3)src/app/shared/lib/cn.ts (1)
cn(4-6)
src/app/features/mypage/hook/useNewPasswordValidation.ts (1)
src/app/features/mypage/schemas/mypageValidation.ts (1)
mypageValidation(1-15)
src/app/shared/lib/axios.ts (1)
src/app/features/auth/store/useAuthStore.ts (1)
useAuthStore(5-20)
src/app/features/mypage/hook/useChangePasswordMutation.ts (3)
src/app/features/mypage/types/mypage.type.ts (1)
PasswordChangeRequest(10-13)src/app/features/mypage/api/mypageApi.ts (1)
changePassword(39-43)src/app/shared/lib/toast.ts (1)
showError(4-4)
src/app/features/mypage/hook/useUpdateMyProfileMutation.ts (2)
src/app/features/mypage/types/mypage.type.ts (1)
UpdateProfileRequest(1-4)src/app/features/mypage/api/mypageApi.ts (1)
updateMyProfile(17-25)
src/app/features/mypage/components/ProfileEditForm.tsx (6)
src/app/features/mypage/hook/useUserQurey.ts (1)
useUserQuery(5-10)src/app/features/mypage/hook/useUploadProfileImageMutation.ts (1)
useUploadProfileImageMutation(7-11)src/app/features/mypage/hook/useUpdateMyProfileMutation.ts (1)
useUpdateMyProfileMutation(9-17)src/app/shared/lib/toast.ts (2)
showSuccess(3-3)showError(4-4)src/app/features/mypage/components/ProfileImageUpload.tsx (1)
ProfileImageUpload(15-102)src/app/features/mypage/schemas/mypageValidation.ts (1)
mypageValidation(1-15)
src/app/features/mypage/hook/useUserQurey.ts (1)
src/app/features/mypage/api/mypageApi.ts (1)
loadUser(12-15)
src/app/features/mypage/hook/useUploadProfileImageMutation.ts (2)
src/app/features/mypage/types/mypage.type.ts (1)
UploadProfileImageResponse(6-8)src/app/features/mypage/api/mypageApi.ts (1)
uploadProfileImage(27-37)
src/app/features/mypage/components/ProfileImageUpload.tsx (3)
src/app/shared/components/common/WhitePenIcon/WhitePenIcon.tsx (1)
WhitePenIcon(5-20)src/app/shared/components/common/PlusIcon/PlusIcon.tsx (1)
PlusIcon(6-38)src/app/shared/components/common/CloseCircleIcon/CloseCircleIcon.tsx (1)
CloseCircleIcon(5-40)
src/app/features/mypage/api/mypageApi.ts (2)
src/app/features/mypage/api/mypageEndPoint.ts (1)
MYPAGE_ENDPOINT(1-5)src/app/features/mypage/types/mypage.type.ts (3)
UpdateProfileRequest(1-4)UploadProfileImageResponse(6-8)PasswordChangeRequest(10-13)
๐ช Biome (1.9.4)
src/app/features/mypage/components/ProfileImageUpload.tsx
[error] 55-56: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
๐ Additional comments (35)
src/app/globals.css (1)
81-83: ์๋ก์ด ํ ์คํธ ์์ ์ ํธ๋ฆฌํฐ ํด๋์ค๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์์ต๋๋ค.๊ธฐ์กด ์คํ์ผ ํจํด์ ์ ๋ฐ๋ฅด๊ณ ์์ผ๋ฉฐ, ๋ผ์ดํธ/๋คํฌ ๋ชจ๋ ์ง์์ด ์ ์ ํ๊ฒ ๊ตฌํ๋์ด ์์ต๋๋ค.
src/app/shared/lib/axios.ts (1)
5-23: ํ์ ์ ํ์ ์ ๊ฑฐํ๊ณ ์ ์ฒดsrcํด๋์์ ๋ค์ ๊ฒ์ํด ๋ณด๊ฒ ์ต๋๋ค.#!/bin/bash # ๋ก๊ทธ์ธ/ํ์๊ฐ์ API ํธ์ถ์์ authHttpClient ์ฌ์ฉ ํ์ธ (ts/tsx ํ์ผ ๋ชจ๋ ๊ฒ์) rg -A 5 -B 5 'login|signup' srcsrc/app/shared/hooks/useConfirmPasswordValidation.ts (1)
1-7: ๋น๋ฐ๋ฒํธ ํ์ธ ์ ํจ์ฑ ๊ฒ์ฌ ํ ์ด ์ ๊ตฌํ๋์์ต๋๋ค.์ฝ๋ฐฑ ํจ์๋ฅผ ํตํด ๋น๋ฐ๋ฒํธ ๊ฐ์ ๋ฐ์์ค๋ ์ ์ฐํ ๊ตฌ์กฐ๋ก ์ค๊ณ๋์ด ์๊ณ , ์ ํจ์ฑ ๊ฒ์ฌ ๋ก์ง์ด ๋ช ํํฉ๋๋ค.
src/app/features/mypage/api/mypageEndPoint.ts (1)
1-5: rg ๋ช ๋ น์ด๊ฐ--type tsx๋ฅผ ์ธ์ํ์ง ๋ชปํด ๊ฒ์์ด ์คํจํ์ต๋๋ค. ํ์ ํํฐ๋ฅผ ์ ๊ฑฐํ๊ณ ๋ชจ๋ ํ์ผ์์ ๋ค์ ๊ฒ์ํด๋ณด๊ฒ ์ต๋๋ค.#!/bin/bash # NEXT_PUBLIC_TEAM_ID ํ๊ฒฝ๋ณ์ ์ฌ์ฉ ํ์ธ (ํ์ ํํฐ ์ ๊ฑฐ) rg "NEXT_PUBLIC_TEAM_ID" -n -C 2src/app/features/auth/hooks/useLoginMutation.ts (1)
25-29: ์คํ ์์ ์๋ฃ
severMessage๋ฅผserverMessage๋ก ์์ ํ ๊ฒ์ด ์ฌ๋ฐ๋ฆ ๋๋ค. ์๋ฌ ๋ฉ์์ง ์ถ์ถ ๋ก์ง์ด ์ ์์ ์ผ๋ก ์๋ํ ๊ฒ์ ๋๋ค.src/app/shared/hooks/useDashboard.ts (2)
5-5: HTTP ํด๋ผ์ด์ธํธ ๋ณ๊ฒฝ ์ ์ ํจ๋์๋ณด๋ ๋ฐ์ดํฐ ์กฐํ๋ ์ธ์ฆ์ด ํ์ํ ์์ฒญ์ด๋ฏ๋ก
authHttpClient๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ฌ๋ฐ๋ฅธ ์ ๊ทผ์ ๋๋ค.
24-26: ์ผ๊ด๋ HTTP ํด๋ผ์ด์ธํธ ์ฌ์ฉ์ธ์ฆ๋ ์์ฒญ์
authHttpClient๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ ์ ํ๋ฉฐ, ๋ค๋ฅธ ์ธ์ฆ ๊ด๋ จ API์ ์ผ๊ด์ฑ์ ์ ์งํฉ๋๋ค.src/app/features/auth/hooks/useSignupMutation.ts (1)
24-28: ์คํ ์์ ์ผ๋ก ์ผ๊ด์ฑ ํ๋ณด
severMessage๋ฅผserverMessage๋ก ์์ ํ์ฌuseLoginMutation๊ณผ ๋์ผํ ํจํด์ ์ ์งํฉ๋๋ค. ์๋ฌ ๋ฉ์์ง ์ฒ๋ฆฌ๊ฐ ์ ์์ ์ผ๋ก ์๋ํ ๊ฒ์ ๋๋ค.src/app/shared/components/Input.tsx (2)
48-48: readOnly ์ํ ์๊ฐ์ ํ์ ์ถ๊ฐ
readOnlyprop์ด true์ผ ๋ ํ ์คํธ ์์์ ํ์์ผ๋ก, ์ปค์๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๋ณ๊ฒฝํ์ฌ ์ฌ์ฉ์๊ฐ ํด๋น ํ๋๊ฐ ํธ์ง ๋ถ๊ฐ๋ฅํจ์ ์ง๊ด์ ์ผ๋ก ์ ์ ์๋๋ก ๊ฐ์ ํ์ต๋๋ค.
76-76: ์ผ๊ด๋ readOnly ์คํ์ผ๋ง ์ ์ฉ์ผ๋ฐ ์ ๋ ฅ ํ๋์๋ ๋์ผํ readOnly ์คํ์ผ๋ง์ ์ ์ฉํ์ฌ ํจ์ค์๋ ์ ๋ ฅ๊ณผ ์ผ๊ด์ฑ์ ์ ์งํ์ต๋๋ค.
src/app/features/auth/api/authApi.ts (2)
1-1: HTTP ํด๋ผ์ด์ธํธ ๋ฆฌํฉํ ๋ง์ด ์ ์ ํฉ๋๋ค.์ธ์ฆ API์ ํนํ๋
authHttpClient์ฌ์ฉ์ผ๋ก ๋ณ๊ฒฝํ ๊ฒ์ ๊ด์ฌ์ฌ ๋ถ๋ฆฌ ์ธก๋ฉด์์ ์ข์ ๊ฐ์ ์ ๋๋ค.
8-11: ๋ฉํฐ๋ผ์ธ ํ์์ผ๋ก ๊ฐ์ ๋ ๊ฐ๋ ์ฑ์ ํ์ธํฉ๋๋ค.ํจ์ ํธ์ถ์ ๋ฉํฐ๋ผ์ธ์ผ๋ก ํฌ๋งทํ ํ ๊ฒ์ ์ฝ๋ ๊ฐ๋ ์ฑ์ ํฅ์์ํต๋๋ค.
authHttpClient.post์ฌ์ฉ๋ ์ผ๊ด์ฑ ์๊ฒ ์ ์ฉ๋์์ต๋๋ค.Also applies to: 16-19
src/app/shared/components/common/sidebar/modal/CreateDashboardModal.tsx (2)
7-7: ์ธ์ฆ๋ HTTP ํด๋ผ์ด์ธํธ๋ก์ ์ผ๊ด๋ ๋ณ๊ฒฝ์ ํ์ธํฉ๋๋ค.
authHttpClient์ฌ์ฉ์ผ๋ก ์ธ์ฆ์ด ํ์ํ ๋์๋ณด๋ ์์ฑ API ํธ์ถ์ด ๋ ๋ช ํํด์ก์ต๋๋ค.
49-52: ๋ฉํฐ๋ผ์ธ ํฌ๋งทํ ๊ณผ ์ผ๊ด๋ ํด๋ผ์ด์ธํธ ์ฌ์ฉ์ ์น์ธํฉ๋๋ค.์ฝ๋๋ฒ ์ด์ค ์ ๋ฐ์ ๊ฑธ์น ์ผ๊ด๋ ๋ฆฌํฉํ ๋ง์ด ์ ์ฉ๋์์ผ๋ฉฐ, ๊ฐ๋ ์ฑ๋ ํฅ์๋์์ต๋๋ค.
src/app/features/auth/components/SignupForm.tsx (2)
4-4: ๊ณต์ ํ ์ผ๋ก์ ์ด๋์ ์น์ธํฉ๋๋ค.
useConfirmPasswordValidationํ ์ ๊ณต์ ์์น๋ก ์ด๋์ํจ ๊ฒ์ ์ฝ๋ ์ฌ์ฌ์ฉ์ฑ์ ๋์ด๋ ์ข์ ๊ฐ์ ์ ๋๋ค.
32-32: ๋ ์์ ํ ํ ์ฌ์ฉ๋ฒ์ผ๋ก ๊ฐ์ ๋์์ต๋๋ค.๋๋ค ํจ์๋ฅผ ํตํด ํน์ ๋น๋ฐ๋ฒํธ ๊ฐ๋ง ์ ๋ฌํ๋ ๋ฐฉ์์ผ๋ก ๋ณ๊ฒฝ๋ ๊ฒ์ ํ์ ์์ ์ฑ๊ณผ ์ฑ๋ฅ ์ธก๋ฉด์์ ๋ ๋์ ์ ๊ทผ๋ฒ์ ๋๋ค. ์ ์ฒด
getValuesํจ์๋ฅผ ์ ๋ฌํ๋ ๊ฒ๋ณด๋ค ๋ช ํํ๊ณ ์ ์ด๋ ๋ฐฉ์์ ๋๋ค.src/app/features/mypage/schemas/mypageValidation.ts (1)
1-15: ๊ฒ์ฆ ์คํค๋ง๊ฐ ์ ๊ตฌ์ฑ๋์์ต๋๋ค.๋๋ค์ ๊ฒ์ฆ:
- ํ๊ธ/์์ด ๋ฌธ์๋ง ํ์ฉํ๋ ์ ๊ท์์ด ์ ํํฉ๋๋ค (
/^[a-zA-Z๊ฐ-ํฃ]{1,10}$/)- 1-10์ ๊ธธ์ด ์ ํ์ด ์ ์ ํฉ๋๋ค
- ์ฌ์ฉ์ ์นํ์ ์ธ ํ๊ธ ์ค๋ฅ ๋ฉ์์ง๊ฐ ์ ๊ณต๋ฉ๋๋ค
๋น๋ฐ๋ฒํธ ๊ฒ์ฆ:
- 8์ ์ด์ ์๊ตฌ์ฌํญ์ ๋ณด์ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ฐ๋ฆ ๋๋ค
- ๋ช ํํ ์ค๋ฅ ๋ฉ์์ง๊ฐ ์ ๊ณต๋ฉ๋๋ค
์ ์ฒด์ ์ผ๋ก ๋ง์ดํ์ด์ง ๊ธฐ๋ฅ์ ์ ํฉํ ๊ฒ์ฆ ๋ก์ง์ ๋๋ค.
src/app/shared/components/common/WhitePenIcon/WhitePenIcon.tsx (1)
1-20: LGTM! ์ ๊ตฌํ๋ SVG ์์ด์ฝ ์ปดํฌ๋ํธ์ ๋๋ค.ํ์ ์ ์๊ฐ ๋ช ํํ๊ณ
React.SVGProps๋ฅผ ์ ์ ํ ํ์ฅํ์ฌ ๋ชจ๋ ํ์ค SVG ์์ฑ์ ์ง์ํฉ๋๋ค.sizeprop์ ๊ธฐ๋ณธ๊ฐ ์ค์ ๊ณผ spread operator ์ฌ์ฉ๋ ์ ์ ํฉ๋๋ค.src/app/features/mypage/hook/useNewPasswordValidation.ts (1)
1-13: LGTM! ์ ๋น๋ฐ๋ฒํธ ๊ฒ์ฆ ๋ก์ง์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์์ต๋๋ค.๊ธฐ์กด ๊ฒ์ฆ ๊ท์น์ ํ์ฅํ์ฌ ํ์ฌ ๋น๋ฐ๋ฒํธ์์ ์ค๋ณต์ ๋ฐฉ์งํ๋ ๋ก์ง์ด ์ ํํฉ๋๋ค. ์ฝ๋ฐฑ ํจํด์ ์ฌ์ฉํ ์ค๊ณ๋ ์ ์ ํฉ๋๋ค.
src/app/features/mypage/hook/useUpdateMyProfileMutation.ts (1)
1-17: LGTM! ํ์ค์ ์ธ React Query ๋ฎคํ ์ด์ ํ ์ ๋๋ค.ํ์ ์ ์๊ฐ ๋ช ํํ๊ณ API ํจ์์ ์ ์ ํ ์ฐ๊ฒฐ๋์ด ์์ต๋๋ค. ๊ฐ๊ฒฐํ๊ณ ๋ชฉ์ ์ ๋ง๋ ๊ตฌํ์ ๋๋ค.
src/app/features/mypage/hook/useUploadProfileImageMutation.ts (1)
1-11: LGTM! ํ์ผ ์ ๋ก๋ ๋ฎคํ ์ด์ ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์์ต๋๋ค.
Fileํ์ ์ ์ ๋ ฅ์ผ๋ก ๋ฐ๊ณ ์ ์ ํ ์๋ต ํ์ ์ ๋ฐํํ๋ ๋ช ํํ ๊ตฌํ์ ๋๋ค. ์๋ฌ ์ฒ๋ฆฌ ํ์ ๋ ์ ์ ํฉ๋๋ค.src/app/features/mypage/hook/useChangePasswordMutation.ts (1)
10-25: LGTM! ๊ฒฌ๊ณ ํ ์๋ฌ ์ฒ๋ฆฌ๊ฐ ํฌํจ๋ ๋ฎคํ ์ด์ ํ ์ ๋๋ค.Axios ์๋ฌ์ ์ผ๋ฐ ์๋ฌ๋ฅผ ์ ์ ํ ๊ตฌ๋ถํ๊ณ , ์๋ฒ ๋ฉ์์ง๋ฅผ ์ฐ์ ์ ์ผ๋ก ํ์ํ๋ fallback ๋ฉ์์ง๋ ์ ๊ณตํ๋ ์์ฑ๋ ๋์ ์๋ฌ ์ฒ๋ฆฌ ๋ก์ง์ ๋๋ค. ์ฌ์ฉ์ ์นํ์ ์ธ ํ๊ธ ๋ฉ์์ง๋ ์ ์ ํฉ๋๋ค.
src/app/mypage/page.tsx (2)
3-9: ์ปดํฌ๋ํธ ๋ถ๋ฆฌ๋ฅผ ํตํ ๋ชจ๋ํ ์ ๊ตฌํ๋จ๊ธฐ์กด ์ธ๋ผ์ธ ํผ ๋งํฌ์ ์ ์ ์ฉ ์ปดํฌ๋ํธ๋ก ๊ต์ฒดํ ๊ฒ์ ๊ด์ฌ์ฌ ๋ถ๋ฆฌ์ ์ฝ๋ ์ฌ์ฌ์ฉ์ฑ ์ธก๋ฉด์์ ์ข์ ์ ๊ทผ์ ๋๋ค.
48-50: ํผ ์ปดํฌ๋ํธ ๋ถ๋ฆฌ ์ ๊ตฌํ๋จProfileEditForm๊ณผ PasswordChangeForm ์ปดํฌ๋ํธ๋ก ๋ถ๋ฆฌํ์ฌ ๊ฐ๊ฐ์ ์ฑ ์์ ๋ช ํํ ํ ๊ฒ์ด ์ข์ต๋๋ค.
src/app/features/mypage/types/mypage.type.ts (1)
1-13: ํ์ ์ ์ ์ ์ ํ๊ฒ ๊ตฌํ๋จAPI ์์ฒญ/์๋ต์ ๋ํ ํ์ ์ ์๊ฐ ๋ช ํํ๊ณ ์ ์ ํฉ๋๋ค.
profileImageUrl์ nullable๋ก ์ค์ ํ ๊ฒ์ ํ๋กํ ์ด๋ฏธ์ง๊ฐ ์ ํ์ฌํญ์ผ ์ ์์์ ์ ๋ฐ์ํฉ๋๋ค.src/app/features/mypage/components/ProfileEditForm.tsx (2)
22-53: ํผ ์ด๊ธฐํ ๋ก์ง ์ ์ ํ๊ฒ ๊ตฌํ๋จ๋น๋๊ธฐ ์ ์ ๋ฐ์ดํฐ ๋ก๋ฉ์ ๋์ํ๋ useEffect๋ฅผ ํตํ ํผ ๋ฆฌ์ ๋ก์ง์ด ์ ๊ตฌํ๋์ด ์์ต๋๋ค. SSR ๋์ ์ ๋ณ๊ฒฝ ์์ ์ด๋ผ๋ ์ฃผ์๋ ์ ์ ํฉ๋๋ค.
100-110: Controller ์ฌ์ฉ ์ ์ ํจProfileImageUpload ์ปดํฌ๋ํธ๋ฅผ React Hook Form์ Controller๋ก ๊ฐ์ธ์ ์ ์ด ์ปดํฌ๋ํธ๋ก ๋ง๋ ๊ฒ์ด ์ ์ ํฉ๋๋ค.
src/app/features/mypage/components/PasswordChangeForm.tsx (3)
24-31: ํผ ๋ชจ๋ ์ค์ ์ ์ ํจ
mode: 'onBlur'๋ก ์ค์ ํ์ฌ ์ฌ์ฉ์๊ฐ ํ๋๋ฅผ ๋ฒ์ด๋ ๋ ๊ฒ์ฆํ๋ ๊ฒ์ ๋น๋ฐ๋ฒํธ ํผ์ ์ ํฉํ UX์ ๋๋ค.
76-84: ์ํธ ์์กด์ ํ๋ ๊ฒ์ฆ ์ ๊ตฌํ๋จ
onBlur์ ์ฐ๊ด ํ๋๋ฅผ ์ฌ๊ฒ์ฆํ๋ ๋ก์ง(trigger)์ด ์ ์ ํ๊ฒ ๊ตฌํ๋์ด ์์ด, ๋น๋ฐ๋ฒํธ์ ํ์ธ ๋น๋ฐ๋ฒํธ ๊ฐ์ ์ผ๊ด์ฑ์ ์ ์งํฉ๋๋ค.Also applies to: 91-99
101-110: ๋ฒํผ ์ํ ๊ด๋ฆฌ ์ ์ ํจํผ ์ ํจ์ฑ๊ณผ ๋ก๋ฉ ์ํ์ ๋ฐ๋ฅธ ๋ฒํผ ๋นํ์ฑํ ๋ฐ ์คํ์ผ ๋ณ๊ฒฝ์ด ์ ๊ตฌํ๋์ด ์์ต๋๋ค.
src/app/features/mypage/api/mypageApi.ts (3)
12-15: ์ฌ์ฉ์ ๋ฐ์ดํฐ ์กฐํ API ์ ์ ํจ๊ฐ๋จํ๊ณ ๋ช ํํ GET ์์ฒญ ๊ตฌํ์ผ๋ก ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํฉ๋๋ค.
27-37: ํ์ผ ์ ๋ก๋ API ์ ์ ํ๊ฒ ๊ตฌํ๋จFormData๋ฅผ ์ฌ์ฉํ ํ์ผ ์ ๋ก๋ ๊ตฌํ์ด ํ์ค ๋ฐฉ์์ ๋ฐ๋ฅด๊ณ ์์ต๋๋ค.
๋ค๋ง ์๋ฒ ์ธก์์ ํ์ผ ํ์ , ํฌ๊ธฐ, ์ ์ฑ ํ์ผ ๊ฒ์ฆ์ด ์ด๋ฃจ์ด์ง๋์ง ํ์ธํ์๊ธฐ ๋ฐ๋๋๋ค.
39-43: ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ API ์ ์ ํจPUT ์์ฒญ์ ์ฌ์ฉํ ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ๊ตฌํ์ด RESTful API ๊ท์น์ ์ ๋ฐ๋ฅด๊ณ ์์ต๋๋ค.
src/app/features/mypage/components/ProfileImageUpload.tsx (2)
1-13: ์ปดํฌ๋ํธ ๊ตฌ์กฐ์ Props ์ธํฐํ์ด์ค๊ฐ ์ ์ค๊ณ๋์์ต๋๋ค.Props ์ธํฐํ์ด์ค๊ฐ ๋ช ํํ๊ณ ํ๊ตญ์ด ์ฃผ์์ผ๋ก ์ ๋ฌธ์ํ๋์ด ์์ต๋๋ค.
onChange์onFileChange๋ฅผ ๋ถ๋ฆฌํ ์ค๊ณ๋ ์ข์ต๋๋ค.
44-52: ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐฉ์ง ๋ก์ง์ด ์ ๊ตฌํ๋์ด ์์ต๋๋ค.
URL.revokeObjectURL์ ์ฌ์ฉํ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ์ blob URL ์ฒดํฌ ๋ก์ง์ด ์ ์ ํฉ๋๋ค.
| className={className} | ||
| width={size} | ||
| height={size} | ||
| viewBox="0 0 18 18" |
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
viewBox์ ๊ธฐ๋ณธ size ๋ถ์ผ์น ๋ฌธ์ ์์ ํ์
viewBox๊ฐ "0 0 18 18"๋ก ์ค์ ๋์ด ์์ง๋ง ๊ธฐ๋ณธ size๊ฐ 24๋ก ์ค์ ๋์ด ์์ด์ ์์ด์ฝ์ด ์๋ํ ๊ฒ๋ณด๋ค ํฌ๊ฒ ํ์๋ ์ ์์ต๋๋ค. ์ผ๊ด์ฑ์ ์ํด ๋ค์ ์ค ํ๋๋ก ์์ ํ๋ ๊ฒ์ด ์ข๊ฒ ์ต๋๋ค:
- viewBox๋ฅผ "0 0 24 24"๋ก ๋ณ๊ฒฝํ๊ณ path ์ขํ๋ฅผ ์กฐ์
- ๊ธฐ๋ณธ size๋ฅผ 18๋ก ๋ณ๊ฒฝ
๊ถ์ฅ ์์ ์ฌํญ:
export default function PlusIcon({
- size = 24,
+ size = 18,
weight = 3,
className,
...props
}: PlusIconProps) {๋๋ viewBox๋ฅผ 24x24๋ก ๋ณ๊ฒฝํ๊ณ path๋ฅผ ์กฐ์ :
<svg
className={className}
width={size}
height={size}
- viewBox="0 0 18 18"
+ viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
- d="M9 3V15"
+ d="M12 4V20"
stroke="currentColor"
strokeWidth={weight}
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
- d="M3 9H15"
+ d="M4 12H20"
stroke="currentColor"
strokeWidth={weight}
strokeLinecap="round"
strokeLinejoin="round"
/>๐ค Prompt for AI Agents
In src/app/shared/components/common/PlusIcon/PlusIcon.tsx at line 17, the
viewBox is set to "0 0 18 18" but the default icon size is 24, causing size
inconsistency. To fix this, either update the viewBox to "0 0 24 24" and adjust
the path coordinates accordingly to fit the new viewBox, or change the default
size of the icon to 18 to match the current viewBox. Choose one approach to
ensure the icon scales correctly and consistently.
| {/* ์์ ๋ฒํผ (๊ต์ฒด ์์ ) */} | ||
| <div> | ||
| <button | ||
| type="button" | ||
| onClick={() => router.back()} | ||
| className="text-xm flex items-center justify-start gap-8" | ||
| > | ||
| <path | ||
| d="M15 18L9 12L15 6" | ||
| stroke="currentColor" | ||
| strokeWidth="2" | ||
| strokeLinecap="round" | ||
| strokeLinejoin="round" | ||
| /> | ||
| </svg> | ||
| <button className="text-xm self-start">๋์๊ฐ๊ธฐ</button> | ||
| <svg | ||
| width={20} | ||
| height={20} | ||
| viewBox="0 0 24 24" | ||
| fill="none" | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| > | ||
| <path | ||
| d="M15 18L9 12L15 6" | ||
| stroke="currentColor" | ||
| strokeWidth="2" | ||
| strokeLinecap="round" | ||
| strokeLinejoin="round" | ||
| /> | ||
| </svg> | ||
| ๋์๊ฐ๊ธฐ | ||
| </button> | ||
| </div> |
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
์์ ๋ฒํผ ๊ตฌํ ๊ฐ์ ํ์
ํ๋์ฝ๋ฉ๋ SVG์ ์คํ์ผ์ด ํฌํจ๋ ์์ ๋ฒํผ์ด ์์ต๋๋ค. ์ฃผ์์์ ์ธ๊ธํ ๋๋ก ๊ต์ฒด๊ฐ ํ์ํฉ๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ์ ์ ์ํฉ๋๋ค:
- {/* ์์ ๋ฒํผ (๊ต์ฒด ์์ ) */}
- <div>
- <button
- type="button"
- onClick={() => router.back()}
- className="text-xm flex items-center justify-start gap-8"
- >
- <svg
- width={20}
- height={20}
- viewBox="0 0 24 24"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path
- d="M15 18L9 12L15 6"
- stroke="currentColor"
- strokeWidth="2"
- strokeLinecap="round"
- strokeLinejoin="round"
- />
- </svg>
- ๋์๊ฐ๊ธฐ
- </button>
- </div>
+ <BackButton onClick={() => router.back()} />๊ณตํต BackButton ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค์ด ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ๋๋ก ํ์๊ฒ ์ต๋๊น?
Committable suggestion skipped: line range outside the PR's diff.
๐ค Prompt for AI Agents
In src/app/mypage/page.tsx around lines 22 to 46, the temporary back button is
hardcoded with inline SVG and styles. To improve maintainability and
reusability, create a separate BackButton component that encapsulates the SVG
icon, styling, and onClick behavior. Replace the current button markup with this
new component to enable consistent use across the app and easier future updates.
| 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('์ ์ ์๋ ์๋ฌ ๋ฐ์') | ||
| } | ||
| } | ||
| } |
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
โผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 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('์ ์ ์๋ ์๋ฌ ๋ฐ์') | |
| } | |
| } | |
| } | |
| async function onSubmit(data: ProfileFormData) { | |
| try { | |
| // ํ์ฌ ์ด๋ฏธ์ง URL์ ์ด๊ธฐ๊ฐ์ผ๋ก ์ค์ (๋ณ๊ฒฝ์ด ์์ ์๋ ์๊ธฐ ๋๋ฌธ) | |
| let imageUrl = data.profileImageUrl | |
| // ์ ์ด๋ฏธ์ง๋ฅผ ์ ๋ก๋ํ ๊ฒฝ์ฐ โ ์๋ฒ์ ์ ๋ก๋ ์์ฒญ (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 | |
| } | |
| // ๋๋ค์๊ณผ ์ด๋ฏธ์ง 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('์ ์ ์๋ ์๋ฌ ๋ฐ์') | |
| } | |
| } | |
| } |
๐ค Prompt for AI Agents
In src/app/features/mypage/components/ProfileEditForm.tsx between lines 55 and
90, the onSubmit function uploads a profile image without validating the file
type or size on the client side. To fix this, add checks before uploading to
ensure the file is of an allowed type (e.g., image/jpeg, image/png) and within a
size limit (e.g., under 5MB). If the file fails validation, prevent the upload
and show an appropriate error message to the user.
๐ ๋ณ๊ฒฝ ์ฌํญ ๊ฐ์
develop์ ๋ณํฉโจFeat: ํ๋กํ ์์ ์ปดํฌ๋ํธ ๊ตฌํย #78
โจFeat: ๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ ์ปดํฌ๋ํธ ๊ตฌํย #80
Summary by CodeRabbit
์ ๊ท ๊ธฐ๋ฅ
๋ฒ๊ทธ ์์
์คํ์ผ
.Text-blueํ ์คํธ ์์ ์ ํธ๋ฆฌํฐ ํด๋์ค๊ฐ ์ถ๊ฐ๋์์ต๋๋ค.๋ฆฌํฉํฐ
๊ธฐํ