π¨ 3λ¨κ³ : μ μ λλ©μΈ ν ν¬μ€ν - 100-hours-a-week/7-team-ddb-wiki GitHub Wiki
μμ±μΌ | 2025-04-23 |
---|---|
λ²μ | 1.0 |
μμ±μ | suzy.kang (κ°μμ§) |
κ²ν μ | kevin |
μν | μ΄μ |
λ²μ | λ μ§ | μμ±μ | κ²ν μ | λ³κ²½λ΄μ© |
---|---|---|---|---|
1.0 | 2025-04-23 | suzy | kevin | μ΄μ |
1.1 | 2025-05-20 | suzy | kevin | MVP κ°λ° μ΄ν λ¬Έμ λκΈ°ν |
- μΉ΄μΉ΄μ€ μμ λ‘κ·ΈμΈμΌλ‘ κ°νΈν μλΉμ€ μ κ·Ό μ 곡
- κ°μΈμ 보 λ° μμΉμ 보 λμ νλ‘μΈμ€λ₯Ό λͺ ννκ² κ΅¬ν
- ν ν° κΈ°λ° μμ ν μΈμ¦ μμ€ν ꡬμΆ
- μΉ΄μΉ΄μ€ μΈ λ€λ₯Έ μμ λ‘κ·ΈμΈ κ΅¬ν
- μ΄λ©μΌ κΈ°λ° νμκ°μ /λ‘κ·ΈμΈ μμ€ν
-
Next.js (v15.3.0) + React (v19.1.0)
- App Router κΈ°λ° λΌμ°ν
- SSR/ISR νμ©μΌλ‘ SEO μ΅μ ν
- React 19μ use() ν νμ©ν λ°μ΄ν° νμΉ
-
Zustand (v5.0.3)
- μμ λ²λ€ μ¬μ΄μ¦λ‘ λͺ¨λ°μΌ μΉ μ΅μ ν
- μλ² μ»΄ν¬λνΈ νΈνμ±
- μ§κ΄μ μΈ APIλ‘ λΉ λ₯Έ κ°λ° κ°λ₯
-
Tailwind CSS (v4.1)
- μ νΈλ¦¬ν° νΌμ€νΈ μ κ·ΌμΌλ‘ λΉ λ₯Έ κ°λ°
- 컀μ€ν ν λ§λ‘ λΈλλ λμμΈ μμ€ν ꡬμΆ
-
shadcn/ui
- Tailwind κΈ°λ° μ»΄ν¬λνΈ
- νμν μ»΄ν¬λνΈλ§ κ°μ Έμ λ²λ€ ν¬κΈ° μ΅μ ν
-
React use() + TanStack Query (v5)
- μλ² μ»΄ν¬λνΈ: use() ν μΌλ‘ SEO μ΅μ ν
- ν΄λΌμ΄μΈνΈ: TanStack Queryλ‘ λ³΅μ‘ν μν κ΄λ¦¬
-
Zod (v3.24.3)
- νμ μ€ν¬λ¦½νΈ μ°λ
- μλ²-ν΄λΌμ΄μΈνΈ μ€ν€λ§ 곡μ
-
react-hook-form (v7.56.2)
- κ°λ²Όμ΄ λ²λ€ μ¬μ΄μ¦λ‘ λͺ¨λ°μΌ μΉνμ
- Zodκ³Ό ν΅ν©ν΄ μ κ΅ν μ ν¨μ± κ²μ¬ κ°λ₯
- Server Actionκ³Ό λ³ν μ¬μ© κ°λ₯ (νμ μ SSR + λΉ λ₯Έ UX λͺ¨λ ν보)
-
Kakao Maps SDK (v2.7.5)
- κ΅λ΄ μ¬μ©μ μΉνμ UI
- 컀μ€ν λ§μ»€/ν΄λ¬μ€ν°λ§ μ§μ
-
Turbopack(v1.5.3) + webpack (v5)
- Next.js 15 λ΄μ₯ λΉλ λꡬ ( Turbopack : κ°λ° νκ²½, Webpack : νλ‘λμ νκ²½ )
- λΉ λ₯Έ κ°λ° νκ²½κ³Ό HMR μ§μ
-
/oauth/kakao/callback
: SSR μ¬μ©- ν λλ μ½κ΄ λμ νμ΄μ§λ‘ 리λ€μ΄λ νΈλ§ μ§ννλλ°, SSRμ μ¬μ©νλ©΄ νλ©΄ κΉλΉ‘μ μμ΄ λ°λ‘ 리λ€μ΄λ νΈ κ°λ₯
- query parameter codeλ₯Ό μ²λ¦¬ν΄μΌνκΈ° λλ¬Έμ SSGλ μ μ νμ§ μμ.
-
/auth/consent
: CSR μ¬μ©- κ²μμμ§μ λ ΈμΆλ νμ μλ νμ΄μ§
- μ¬μ©μ μΈν°λμ μ΄ μ£Όμ κΈ°λ₯ (체ν¬λ°μ€ μ ν, νΌ μ μΆ)
-
/auth/signup
: CSR μ¬μ©- μ¬μ©μ μΈν°λ μ μ΄ μ£Όμ κΈ°λ₯ (νΌ μ μΆ, μ€μκ° μ ν¨μ± κ²μ¦, νλ‘ν μ΄λ―Έμ§ μ λ‘λ)
-
/mypage
: CSR μ¬μ©- SSR ν μ λμ SEO μ΄μ μ΄ μμ. κ°μΈ μ 보μ΄κΈ° λλ¬Έμ μ μ νμ΄μ§λ‘ μΊμ±νλ©΄ μλ¨.
- μ¬μ©μκ° μ μ₯ν μ₯μ리μ€νΈλ₯Ό 무ν μ€ν¬λ‘€λ‘ μ²λ¦¬νκΈ° λλ¬Έμ CSR μ¬μ©
-
/mypage/edit
: CSR μ¬μ©- μ¬μ©μ μΈν°λ μ μ΄ μ£Όμ κΈ°λ₯ (νΌ μ μΆ, μ€μκ° μ ν¨μ± κ²μ¦, νλ‘ν μ΄λ―Έμ§ μ λ‘λ)
-
/mypage/delete
: CSR μ¬μ©- μ¬μ©μ μΈν°λ μ μ΄ μ£Όμ κΈ°λ₯ (μ¬μ©μ νμΈ λ° λμ)
-
oauth/kakao/callback
: μΉ΄μΉ΄μ€ OAuth μ½λ°± μ²λ¦¬ νμ΄μ§- μ£ΌμκΈ°λ₯ : μΉ΄μΉ΄μ€ μΈμ¦ μ½λλ‘ λ‘κ·ΈμΈ/νΈμκ°μ ν μ μ ν νμ΄μ§λ‘ λΌμ°ν
-
μ¬μ© μ»΄ν¬λνΈ :
-
KakaoCallbackClient
(ν΄λΌμ΄μΈνΈ μ¬μ΄λ λ‘μ§ μ²λ¦¬)
-
-
λ°μ΄ν° λ‘λ© μμ : νμ΄μ§ μ§μ
μ Query Parameter (
code
)λ₯Ό μ¬μ©νμ¬POST /api/v1/auth/tokens API
νΈμΆ (byKakaoCallbackClient
). -
λΌμ°ν
(API μλ΅μ λ°λΌ
KakaoCallbackClient
κ° μ²λ¦¬):- μ κ· νμ β
/auth/consent
- κΈ°μ‘΄ νμ β
/
- μλ¬ λ°μ β
/onboarding
- μ κ· νμ β
-
/auth/consent
: μ½κ΄ λμ νμ΄μ§- μ£Όμ κΈ°λ₯ : μλΉμ€ μ΄μ©μ½κ΄(κ°μΈμ 보 μ²λ¦¬λ°©μΉ¨, μμΉκΈ°λ° μλΉμ€) λμ.
-
μ¬μ© μ»΄ν¬λνΈ :
-
ConsentPage
(νμ΄μ§ λ μ΄μμ λ° λ‘μ§ μ»¨ν μ΄λ) -
PrivacyConsent
(@/features/user/components/consent/PrivacyConsent.tsx
) -
LocationConsent
(@/features/user/components/consent/LocationConsent.tsx
) -
useConsent
(@/features/user/hooks/useConsent.ts
)
-
-
λ°μ΄ν° λ‘λ© μμ :
- μ½κ΄ λ΄μ©μ κ°
PrivacyConsent
,LocationConsent
μ»΄ν¬λνΈ λ΄μ μ§μ μμ±λμ΄ μμ.
- μ½κ΄ λ΄μ©μ κ°
-
λΌμ°ν
:
useConsent
ν μhandleAgreement
ν¨μ λ΄μμPOST /api/v1/users/agreement
API νΈμΆ μ±κ³΅ μ β/auth/signup
-
/auth/signup
: μΆκ° μ 보 μ λ ₯ νμ΄μ§- μ£Όμ κΈ°λ₯ : λλ€μ μ€μ , μκ°κΈ μ€μ , νλ‘ν μ΄λ―Έμ§ μ λ‘λνμ¬ νμκ°μ μλ£.
-
μ¬μ© μ»΄ν¬λνΈ :
-
ProfileForm
(@/features/user/components/profile-form/ProfileForm.tsx
)
-
- λ°μ΄ν° λ‘λ© μμ : μμ (μ μ 보 μ λ ₯).
-
λΌμ°ν
:
ProfileForm
μonSubmit
νΈλ€λ¬μμPOST /api/v1/users
API νΈμΆ μ±κ³΅ μ β/
(λλ μ¨λ³΄λ© μλ£/νμ νμ΄μ§)
-
/mypage
: λ§μ΄νμ΄μ§ - λ΄ νλ‘ν- μ£ΌμκΈ°λ₯ : μ¬μ©μ νλ‘ν μ 보 μ‘°ν λ° νμ, νμμ 보 μμ /λ‘κ·Έμμ/νν΄ κΈ°λ₯ λ§ν¬ μ 곡.
-
μ¬μ© μ»΄ν¬λνΈ :
-
Profile
(@/features/user/components/profile/Profile.tsx
) -
useUser
(@/features/user/hooks/useUser.ts
) -
Header
(@/shared/components
)
-
-
λ°μ΄ν° λ‘λ© μμ : νμ΄μ§ μ§μ
μ
useUser
ν μ ν΅ν΄GET /api/v1/users/me API
νΈμΆ. -
λΌμ°ν
:
- "νμμ 보 μμ " λ²νΌ ν΄λ¦ μ β
/mypage/edit
- "λ‘κ·Έμμ" λ²νΌ ν΄λ¦ μ β
logout
API νΈμΆ ν/onboarding
- "νμ νν΄νκΈ°" λ²νΌ ν΄λ¦ μ β
/mypage/delete
- "νμμ 보 μμ " λ²νΌ ν΄λ¦ μ β
-
/mypage/places
: λ§μ΄νμ΄μ§ - μ μ₯ν μ₯μ- μ£ΌμκΈ°λ₯ : μ¬μ©μ νλ‘ν μ 보 μ‘°ν λ° νμ
-
μ¬μ© μ»΄ν¬λνΈ : (νμ¬ μ½λ λΆμ κΈ°μ€, μ΄ νμ΄μ§μ ꡬ체μ μΈ μ μ κ΄λ ¨ μ»΄ν¬λνΈλ νμΈλμ§ μμ. μ₯μ λͺ©λ‘ κ΄λ ¨ κΈ°λ₯μ΄ μ£Όκ° λ κ²μΌλ‘ μμ)
-
Profile
(@/features/user/components/profile/Profile.tsx
) SavedPlaceListCard (λ΄κ° μ μ₯ν μ₯μ λͺ©λ‘) , v2 μμ
-
-
λ°μ΄ν° λ‘λ© μμ : νμ΄μ§ μ§μ
μ
GET /api/v1/users/me API
νΈμΆ
-
/mypage/edit
: λ§μ΄νμ΄μ§ - μ μ μ 보 μμ - μ£Όμ κΈ°λ₯ : μ μ μ 보 μμ (λλ€μ μ€μ , μκ°κΈ μ€μ , νλ‘ν μ΄λ―Έμ§).
-
μ¬μ© μ»΄ν¬λνΈ :
-
ProfileForm
(@/features/user/components/profile-form/ProfileForm.tsx
) -
useUser
(@/features/user/hooks/useUser.ts
) (μμ μ μ΄κΈ° λ°μ΄ν° λ‘λ©μ©)
-
-
λ°μ΄ν° λ‘λ© μμ : νμ΄μ§ μ§μ
μ
useUser
ν μ ν΅ν΄GET /api/v1/users/me API
λ₯Ό νΈμΆνμ¬ κΈ°μ‘΄ νλ‘ν μ 보λ₯ΌProfileForm
μdefaultValues
λ‘ μ λ¬. -
λΌμ°ν
:
ProfileForm
μonSubmit
νΈλ€λ¬μμPATCH /api/v1/users
API νΈμΆ μ±κ³΅ μ β/mypage
-
/mypage/delete
: λ§μ΄νμ΄μ§ - νν΄- μ£Όμ κΈ°λ₯ : μ μ νν΄ μ²λ¦¬.
-
μ¬μ© μ»΄ν¬λνΈ :
-
DeleteAccountForm
(@/features/user/components/delete-account/DeleteAccountForm.tsx
)
-
-
λΌμ°ν
:
DeleteAccountForm
λ΄λΆμμDELETE /api/v1/users
API νΈμΆ μ±κ³΅ μ β/onboarding
-
κ°μ
-
μν :
src/app/(auth)/auth/consent/page.tsx
λ΄μμPrivacyConsent
,LocationConsent
μ»΄ν¬λνΈμuseConsent
ν μ μ‘°ν©νμ¬ μλΉμ€ μ΄μ©μ μν μ½κ΄ λμ κΈ°λ₯μ μ 곡ν©λλ€. κ° μ½κ΄ λ΄μ©μ ν΄λΉ μ»΄ν¬λνΈ λ΄μ μ§μ μμ±λμ΄ μμ΅λλ€.
-
μν :
-
κ΄λ ¨ μ»΄ν¬λνΈ λ° ν
-
ConsentPage
(src/app/(auth)/auth/consent/page.tsx
): μ½κ΄ λμ UI μ 체λ₯Ό ꡬμ±νλ νμ΄μ§ μ»΄ν¬λνΈμ λλ€. -
PrivacyConsent
(@/features/user/components/consent/PrivacyConsent.tsx
): κ°μΈμ 보 μ²λ¦¬λ°©μΉ¨ λμ UIλ₯Ό λ λλ§ν©λλ€. (λ΄λΆμ μΌλ‘ConsentCard
μ¬μ©)-
Props:
checked: boolean
,onCheckedChange: (checked: boolean) => void
-
Props:
-
LocationConsent
(@/features/user/components/consent/LocationConsent.tsx
): μμΉκΈ°λ° μλΉμ€ μ΄μ©μ½κ΄ λμ UIλ₯Ό λ λλ§ν©λλ€. (λ΄λΆμ μΌλ‘ConsentCard
μ¬μ©)-
Props:
checked: boolean
,onCheckedChange: (checked: boolean) => void
-
Props:
-
ConsentCard
(@/features/user/components/consent/ConsentCard.tsx
): κ°λ³ μ½κ΄ λ΄μ©μ νμνκ³ λμ 체ν¬λ°μ€λ₯Ό μ 곡νλ UI μ»΄ν¬λνΈμ λλ€.-
Props:
title: string
,label: string
,checked: boolean
,onCheckedChange: (checked: boolean) => void
,children: ReactNode
-
Props:
-
useConsent
(@/features/user/hooks/useConsent.ts
): μ½κ΄ λμ μν λ° κ΄λ ¨ λ‘μ§μ κ΄λ¦¬νλ 컀μ€ν ν μ λλ€.-
λ°ν κ°:
{ privacyConsent: boolean; setPrivacyConsent: (value: boolean) => void; locationConsent: boolean; setLocationConsent: (value: boolean) => void; handleAllConsent: () => void; handleAgreement: () => Promise<void>; }
-
-
-
μ£Όμ λ‘μ§ (
useConsent
ν λ΄λΆ )-
privacyConsent
,locationConsent
: κ° μ½κ΄μ λμ μν (boolean) -
handleAllConsent
: μ 체 λμ μνλ₯Ό ν κΈν©λλ€. -
handleAgreement
:-
postAgreement({ location_agreed: boolean, privacy_agreed: boolean })
API (POST /api/v1/users/agreement
)λ₯Ό νΈμΆνμ¬ λμ κ²°κ³Όλ₯Ό μλ²μ μ μ‘ν©λλ€. - API νΈμΆ μ±κ³΅ μ
/auth/signup
νμ΄μ§λ‘ λΌμ°ν ν©λλ€.
-
-
-
μλ¬ μ²λ¦¬ / μ£μ§ μΌμ΄μ€
-
handleAgreement
ν¨μ λ΄ API νΈμΆ μ€ν¨ μconsole.error
λ₯Ό ν΅ν΄ μλ¬κ° λ‘κΉ λ©λλ€. - νμ μ½κ΄ (κ°μΈμ 보, μμΉμ 보) λͺ¨λ λμν΄μΌ 'λ€μ' λ²νΌμ΄ νμ±νλ©λλ€.
-
-
μ€νμΌλ§
-
ConsentCard
λ΄λΆμμ μ½κ΄ λ΄μ© μ€ν¬λ‘€ μ²λ¦¬. - κ° μ½κ΄ λΌλ²¨μ "[νμ]" ν μ€νΈ ν¬ν¨.
-
-
κ°μ
-
μν : μ¬μ©μ νλ‘ν μ 보 (λλ€μ, μκ°κΈ, νλ‘ν μ΄λ―Έμ§) μ
λ ₯ λ° μμ μ μν νΌ μ»΄ν¬λνΈμ
λλ€. (
src/features/user/components/profile-form/ProfileForm.tsx
)
-
μν : μ¬μ©μ νλ‘ν μ 보 (λλ€μ, μκ°κΈ, νλ‘ν μ΄λ―Έμ§) μ
λ ₯ λ° μμ μ μν νΌ μ»΄ν¬λνΈμ
λλ€. (
-
Props & Interface
interface ProfileFormProps { onSubmit: (data: ProfileFormValues) => Promise<void>; defaultValues?: Partial<ProfileFormValues>; buttonText?: string; // κΈ°λ³Έκ°: 'μ μ₯' } type ProfileFormValues = { profile_image?: string; username: string; introduction?: string; };
-
onSubmit
(required): νΌ μ μΆ μ νΈμΆλ ν¨μ.ProfileFormValues
λ₯Ό μΈμλ‘ λ°μ΅λλ€. -
defaultValues
(optional): νΌ μ΄κΈ°κ°μ μ€μ ν©λλ€. (μ: νλ‘ν μμ μ κΈ°μ‘΄ λ°μ΄ν°) -
buttonText
(optional): μ μΆ λ²νΌμ ν μ€νΈλ₯Ό μ€μ ν©λλ€. (κΈ°λ³Έκ°: "μ μ₯")
-
-
λ΄λΆ μν & μ£Όμ κΈ°λ₯
-
νΌ κ΄λ¦¬:
react-hook-form
λΌμ΄λΈλ¬λ¦¬μzod
λ₯Ό μ¬μ©νμ¬ νΌ μν κ΄λ¦¬ λ° μ ν¨μ± κ²μ¦μ μνν©λλ€. -
νλ‘ν μ΄λ―Έμ§ μ²λ¦¬:
-
useImageUpload
컀μ€ν ν μ μ¬μ©νμ¬ μ΄λ―Έμ§ μ λ‘λ λ‘μ§μ μ²λ¦¬ν©λλ€. - μ¬μ©μκ° μ΄λ―Έμ§λ₯Ό μ ννλ©΄ 미리보기λ₯Ό μ 곡ν©λλ€.
-
νμΌ μ ν: μ΅λ 5MB, νμ© νμ₯μ (
.jpg
,.jpeg
,.png
,.webp
). - μ΄λ―Έμ§ μ ν¨μ± κ²μ¬ μ€ν¨ λλ μ λ‘λ μ€ν¨ μ μλ¬ λ©μμ§λ₯Ό νμν©λλ€.
- μ΄λ―Έμ§ μ
λ‘λ μ±κ³΅ μ,
onSubmit
μΌλ‘ μ λ¬λλprofile_image
νλμ μ λ‘λλ URLμ΄ ν¬ν¨λ©λλ€.
-
-
λλ€μ (
username
):- νμ μ λ ₯ νλͺ©μ λλ€.
- μ ν¨μ± κ²μ¦: μ΅μ 2μ, μ΅λ 10μ, νΉμλ¬Έμ μ¬μ© λΆκ° (
/^[κ°-ν£a-zA-Z0-9\s]+$/
). - μ ν¨μ± κ²μ¦ μ€ν¨ μ μλ¬ λ©μμ§κ° νμλ©λλ€.
- μ°Έκ³ : λλ€μ μ€λ³΅ μ€μκ° μ²΄ν¬ κΈ°λ₯μ
ProfileForm
μ»΄ν¬λνΈ μ체μλ ν¬ν¨λμ΄ μμ§ μμ΅λλ€. μ€λ³΅ 체ν¬λonSubmit
νΈλ€λ¬λ₯Ό ν΅ν΄ μλ² μΈ‘μμ μ²λ¦¬λκ±°λ, λ³λμ λ‘μ§μΌλ‘ ꡬνλ μ μμ΅λλ€.
-
μκ°κΈ (
introduction
):- μ ν μ λ ₯ νλͺ©μ λλ€.
- μ ν¨μ± κ²μ¦: μ΅λ 70μ.
- μ ν¨μ± κ²μ¦ μ€ν¨ μ μλ¬ λ©μμ§κ° νμλ©λλ€.
-
νΌ κ΄λ¦¬:
-
μ¬μ© μμ (Usage)
import { ProfileForm } from '@/features/user/components/profile-form'; function SignupPage() { const handleSignup = async (data: ProfileFormValues) => { // νμκ°μ API νΈμΆ (data.profile_imageλ URLμ) // μ: await api.signup(data); }; return <ProfileForm onSubmit={handleSignup} buttonText="κ°μ νκΈ°" />; } function ProfileEditPage() { const userInitialData = await api.getUser(); // μ μ μ 보 μ‘°ν API νΈμΆ const handleProfileUpdate = async (data: ProfileFormValues) => { // νλ‘ν μμ API νΈμΆ // μ: await api.updateProfile(data); }; return ( <ProfileForm defaultValues={userInitialData} onSubmit={handleProfileUpdate} buttonText="μμ μλ£" /> ); }
-
μλ¬μ²λ¦¬ / μ£μ§ μΌμ΄μ€
- μ΄λ―Έμ§ κ΄λ ¨: νμΌ ν¬κΈ° μ΄κ³Ό, μ§μνμ§ μλ νμ₯μ μ ν μ νΌ λ΄ μλ¬ λ©μμ§ νμ.
- νΌ μ ν¨μ±: κ° νλ(λλ€μ, μκ°κΈ)μ μ ν¨μ± κ·μΉ μλ° μ ν΄λΉ νλ μλ μλ¬ λ©μμ§ νμ.
- νμ νλ (λλ€μ) λλ½ μ μλ¬ λ©μμ§.
-
μ€νμΌλ§
- μ λ ₯ νλ λ μ΄μμ λ° μλ¬ λ©μμ§ μ€νμΌλ§.
- μ΄λ―Έμ§ 미리보기 μμ (μν, ν΄λ¦νμ¬ νμΌ μ ν).
- νΌ μ μΆ μ€ λ²νΌ λΉνμ±ν λ° λ‘λ© μν μκ°μ νΌλλ°± (μ: μ΄λ―Έμ§ μμμ "μ λ‘λμ€...").
-
Storybook
-
Empty Form:
defaultValues
μμ΄ κΈ°λ³Έ μν (νμκ°μ μλ리μ€). -
With Values:
defaultValues
λ₯Ό μ λ¬νμ¬ μ΄κΈ°κ°μ΄ μ±μμ§ μν (νλ‘ν μμ μλ리μ€). - With Errors: κ° νλμ μ ν¨μ± κ²μ¦ μλ¬κ° λ°μν μν.
-
Empty Form:
-
κ°μ
-
μν : μ¬μ©μμ νλ‘ν μ΄λ―Έμ§, λλ€μ, μκ°κΈμ μ’
ν©μ μΌλ‘ νμν©λλ€. (
src/features/user/components/profile/Profile.tsx
) - μ΄ μ»΄ν¬λνΈλ κΈ°μ‘΄ λ¬Έμμ
ProfileSummaryCard
μProfileDetailCard
μ κΈ°λ₯μ ν΅ν©νμ¬ μ 곡ν©λλ€.
-
μν : μ¬μ©μμ νλ‘ν μ΄λ―Έμ§, λλ€μ, μκ°κΈμ μ’
ν©μ μΌλ‘ νμν©λλ€. (
-
Props & Interface
interface ProfileProps { username: string; profile_image: string; // μ΄λ―Έμ§ URL introduction: string; }
-
username
(required): μ¬μ©μμ λλ€μ. -
profile_image
(required): μ¬μ©μμ νλ‘ν μ΄λ―Έμ§ URL. μ΄λ―Έμ§κ° μμ κ²½μ° λΉ λ¬Έμμ΄ μ λ¬ μ κΈ°λ³Έ μμ΄μ½μ΄ νμλ©λλ€. -
introduction
(required): μ¬μ©μμ μκ°κΈ. μ€λ°κΏμwhitespace-pre-line
μΌλ‘ μ²λ¦¬λ©λλ€.
-
-
μ¬μ© μμ (Usage)
<Profile username="λ©μ§λλ€μ" profile_image="/path/to/image.jpg" introduction="μλ νμΈμ.\nλ§λμ λ°κ°μ΅λλ€." />
-
μλ¬μ²λ¦¬ / μ£μ§ μΌμ΄μ€
-
profile_image
κ° μ 곡λμ§ μκ±°λ λΉ λ¬Έμμ΄μΌ κ²½μ°, κΈ°λ³Έ μ¬μ©μ μμ΄μ½μ νμν©λλ€.
-
-
μ€νμΌλ§
- νλ‘ν μ΄λ―Έμ§ μν μ€νμΌλ§ λ° κΈ°λ³Έ μμ΄μ½ μ€νμΌ.
- λλ€μ (heading-2), μκ°κΈ (body-text, text-gray-600) μ€νμΌλ§.
-
Storybook
- Default: λͺ¨λ μ 보(μ΄λ―Έμ§, λλ€μ, μκ°κΈ)κ° μ μμ μΌλ‘ νμλλ μν.
- No Image: νλ‘ν μ΄λ―Έμ§κ° μλ μν (κΈ°λ³Έ μμ΄μ½ νμ).
- Long Introduction: μ¬λ¬ μ€μ κΈ΄ μκ°κΈμ΄ μλ μν.
-
κ°μ
- μν : μ¬μ©μκ° μ μ₯ν μ₯μ λͺ©λ‘μ μΉ΄λ ννλ‘ νμν©λλ€.
- νμ¬ μν: (2024-07-29 κΈ°μ€ μ½λ) νμ¬ μμ v2 μ μΆκ° κ°λ° μμ
-
κ°μ
-
μν : νμ νν΄ κ΄λ ¨ μ μμ¬νμ μλ΄νκ³ , μ¬μ©μμ λμλ₯Ό λ°μ μ€μ νν΄λ₯Ό μ²λ¦¬νλ νΌ μ»΄ν¬λνΈμ
λλ€. (
src/features/user/components/delete-account/DeleteAccountForm.tsx
) - μ΄ μ»΄ν¬λνΈλ κΈ°μ‘΄ λ¬Έμμ
AccountDeletionNotice
μAccountDeletionButton
μ κΈ°λ₯μ ν΅ν©νμ¬ μ 곡ν©λλ€.
-
μν : νμ νν΄ κ΄λ ¨ μ μμ¬νμ μλ΄νκ³ , μ¬μ©μμ λμλ₯Ό λ°μ μ€μ νν΄λ₯Ό μ²λ¦¬νλ νΌ μ»΄ν¬λνΈμ
λλ€. (
- Props & Interface: λ³λμ Propsλ₯Ό λ°μ§ μμ΅λλ€.
-
λ΄λΆ μν & μ£Όμ κΈ°λ₯
- μ μμ¬ν μλ΄: μ»΄ν¬λνΈ λ΄μ νλμ½λ©λ νν΄ κ΄λ ¨ μ μμ¬νμ λͺ©λ‘ ννλ‘ νμν©λλ€.
- νν΄ λμ: μ¬μ©μλ "μ λ΄μ©μ λͺ¨λ νμΈνμμΌλ©°, νν΄μ λμν©λλ€." 체ν¬λ°μ€λ₯Ό μ νν΄μΌ ν©λλ€.
-
μν κ΄λ¦¬:
isAgreed
(boolean) λ΄λΆ μνλ₯Ό ν΅ν΄ μ¬μ©μμ λμ μ¬λΆλ₯Ό κ΄λ¦¬ν©λλ€. -
νν΄ μ€ν (
handleDelete
ν¨μ):-
isAgreed
κ°true
μΌ λ νμ±νλλ "νν΄νκΈ°" λ²νΌμ ν΅ν΄ μ€νλ©λλ€. -
deleteUser()
API (DELETE /api/v1/users
)λ₯Ό νΈμΆνμ¬ νμ νν΄λ₯Ό μμ²ν©λλ€.
-
-
μ€νμΌλ§
- μ μμ¬ν ν μ€νΈ, λμ 체ν¬λ°μ€, "νν΄νκΈ°" λ²νΌ (νμ±/λΉνμ± μν ν¬ν¨)μ λν μ€νμΌλ§μ΄ μ μ©λ©λλ€.
- "νν΄νκΈ°" λ²νΌμ λμ μ λΉ¨κ°μ κ³μ΄λ‘ κ°μ‘°λ©λλ€.
-
Storybook
- Default: κΈ°λ³Έ νμ νν΄ νΌ (μ μμ¬ν λ° λμ 체ν¬λ°μ€ νμ).
- Agreed: μ¬μ©μκ° νν΄μ λμνμ¬ λ²νΌμ΄ νμ±νλ μν.
-
Consent
κ΄λ ¨ κΈ°λ₯ (src/app/(auth)/auth/consent/page.tsx
μ€μ¬μΌλ‘):- μν : μ½κ΄ λμ νμ΄μ§μ μ 체μ μΈ UIμ μ¬μ©μ μΈν°λμ λ° λ°μ΄ν° νλ¦μ κ΄λ¦¬ν©λλ€.
-
κ΅¬μ± μμ λ° κ΄κ³:
-
ConsentPage
(page.tsx
):-
useConsent
ν μ μ¬μ©νμ¬ μ½κ΄ λμ μν (privacyConsent
,locationConsent
) λ° μ μΆ λ‘μ§ (handleAgreement
)μ κ΄λ¦¬ν©λλ€. -
PrivacyConsent
λ°LocationConsent
μ»΄ν¬λνΈλ₯Ό μ¬μ©νμ¬ κ° μ½κ΄ λ΄μ©μ νλ©΄μ νμνκ³ , μ΄λ€ μ»΄ν¬λνΈμuseConsent
ν μμ λ°μ μνμ μν λ³κ²½ ν¨μλ₯Ό propsλ‘ μ λ¬ν©λλ€.
-
-
useConsent
(@/features/user/hooks/useConsent.ts
):- μ½κ΄ λμ μ¬λΆ (
privacyConsent
,locationConsent
)λ₯ΌuseState
λ‘ κ΄λ¦¬ν©λλ€. -
setPrivacyConsent
,setLocationConsent
ν¨μλ₯Ό ν΅ν΄ κ° λμ μνλ₯Ό μ λ°μ΄νΈν©λλ€. -
handleAllConsent
ν¨μλ‘ μ 체 λμ/ν΄μ λ₯Ό μ²λ¦¬ν©λλ€. -
handleAgreement
ν¨μμμpostAgreement
APIλ₯Ό νΈμΆνμ¬ μλ²μ λμ λ°μ΄ν°λ₯Ό μ μ‘νκ³ , μ±κ³΅ μ λ€μ νμ΄μ§λ‘ λΌμ°ν ν©λλ€.
- μ½κ΄ λμ μ¬λΆ (
-
PrivacyConsent
/LocationConsent
(@/features/user/components/consent/
):- κ°κ° κ°μΈμ 보 μ²λ¦¬λ°©μΉ¨κ³Ό μμΉκΈ°λ° μλΉμ€ μ΄μ©μ½κ΄μ UIλ₯Ό λ΄λΉν©λλ€.
- λ΄λΆμ μΌλ‘
ConsentCard
μ»΄ν¬λνΈλ₯Ό μ¬μ©νμ¬ μ½κ΄ μ λͺ©, λ΄μ©, λμ 체ν¬λ°μ€λ₯Ό μΌκ΄λ ννλ‘ λ λλ§ν©λλ€. -
ConsentPage
λ‘λΆν°checked
(λμ μν)μonCheckedChange
(μν λ³κ²½ ν¨μ)λ₯Ό propsλ‘ λ°μConsentCard
λ° λ΄λΆCheckbox
μ μ°λν©λλ€.
-
ConsentCard
(@/features/user/components/consent/ConsentCard.tsx
):- μ½κ΄ μ λͺ©, μ€ν¬λ‘€ κ°λ₯ν μ½κ΄ λ΄μ© μμ, κ·Έλ¦¬κ³ νλ¨μ λμ 문ꡬμ
Checkbox
λ₯Ό ν¬ν¨νλ μ¬μ¬μ© κ°λ₯ν UI μΉ΄λμ λλ€. -
PrivacyConsent
λ°LocationConsent
μ»΄ν¬λνΈμκ² UI ꡬ쑰λ₯Ό μ 곡ν©λλ€.
- μ½κ΄ μ λͺ©, μ€ν¬λ‘€ κ°λ₯ν μ½κ΄ λ΄μ© μμ, κ·Έλ¦¬κ³ νλ¨μ λμ 문ꡬμ
-
-
νμκ°μ νμ΄μ§ (
src/app/(auth)/auth/signup/page.tsx
) κ΄λ ¨:- μν : μ¬μ©μκ° λλ€μ, νλ‘ν μ΄λ―Έμ§, μκ°κΈ λ±μ μΆκ° μ 보λ₯Ό μ λ ₯νμ¬ νμκ°μ μ μλ£νλ νμ΄μ§μ λλ€.
-
κ΅¬μ± μμ λ° κ΄κ³:
-
SignupPage
(page.tsx
):-
ProfileForm
μ»΄ν¬λνΈλ₯Ό μ¬μ©νμ¬ μ¬μ©μ μ λ ₯μ λ°μ΅λλ€. -
ProfileForm
μonSubmit
νΈλ€λ¬μμsignup
API (POST /api/v1/users
)λ₯Ό νΈμΆνμ¬ νμκ°μ μ²λ¦¬λ₯Ό μννκ³ , μ±κ³΅ μ λ©μΈ νμ΄μ§ λ±μΌλ‘ λΌμ°ν ν©λλ€.
-
-
ProfileForm
(@/features/user/components/profile-form/ProfileForm.tsx
):- νμκ°μ μ νμν νλ(λλ€μ, νλ‘ν μ΄λ―Έμ§, μκ°κΈ)λ₯Ό μ 곡νκ³ μ ν¨μ± κ²μ¦μ μνν©λλ€.
- μ΄λ―Έμ§ μ λ‘λ, μ λ ₯ κ° κ΄λ¦¬ λ±μ λ‘μ§μ λ΄λΆμ μΌλ‘ μ²λ¦¬ν©λλ€.
- μ¬μ©μκ° μ
λ ₯ν λ°μ΄ν°λ₯Ό
onSubmit
propμ ν΅ν΄SignupPage
λ‘ μ λ¬ν©λλ€.
-
-
λ§μ΄νμ΄μ§ (
src/app/(main)/mypage/page.tsx
) κ΄λ ¨:- μν : λ‘κ·ΈμΈν μ¬μ©μμ νλ‘ν μ 보λ₯Ό 보μ¬μ£Όκ³ , νλ‘ν μμ , λ‘κ·Έμμ, νμ νν΄ λ±μ κΈ°λ₯μ μ 곡νλ νμ΄μ§μ λλ€.
-
κ΅¬μ± μμ λ° κ΄κ³:
-
MyPage
(page.tsx
):-
useUser
ν μ μ¬μ©νμ¬ νμ¬ λ‘κ·ΈμΈλ μ¬μ©μ μ 보λ₯Ό μ‘°νν©λλ€. (λ΄λΆμ μΌλ‘getUser
API -GET /api/v1/users/me
νΈμΆ) - μ‘°νλ μ¬μ©μ μ 보λ₯Ό
Profile
μ»΄ν¬λνΈμ μ λ¬νμ¬ νλ©΄μ νλ‘νμ νμν©λλ€. - "νμμ 보 μμ ", "λ‘κ·Έμμ", "νμ νν΄νκΈ°" λ²νΌμ μ 곡νλ©°, κ° λ²νΌ ν΄λ¦ μ ν΄λΉ νμ΄μ§λ‘ λΌμ°ν
νκ±°λ κ΄λ ¨ API (
logout
)λ₯Ό νΈμΆν©λλ€.
-
-
useUser
(@/features/user/hooks/useUser.ts
):- μ¬μ©μ μ 보λ₯Ό κ°μ Έμ€κ³ μνλ₯Ό κ΄λ¦¬ν©λλ€.
-
Profile
(@/features/user/components/profile/Profile.tsx
):-
MyPage
λ‘λΆν° λ°μ μ¬μ©μ μ 보(λλ€μ, νλ‘ν μ΄λ―Έμ§ URL, μκ°κΈ)λ₯Ό UIμ νμν©λλ€.
-
-
-
νλ‘ν μμ νμ΄μ§ (
src/app/(main)/mypage/edit/page.tsx
) κ΄λ ¨:- μν : μ¬μ©μκ° κΈ°μ‘΄ νλ‘ν μ 보(λλ€μ, νλ‘ν μ΄λ―Έμ§, μκ°κΈ)λ₯Ό μμ νλ νμ΄μ§μ λλ€.
-
κ΅¬μ± μμ λ° κ΄κ³:
-
ProfileEditPage
(page.tsx
):-
useUser
ν μ μ¬μ©νμ¬ νμ¬ μ¬μ©μ μ 보λ₯Ό μ‘°ννκ³ , μ΄ μ 보λ₯ΌProfileForm
μdefaultValues
λ‘ μ λ¬ν©λλ€. -
ProfileForm
μ»΄ν¬λνΈλ₯Ό μ¬μ©νμ¬ μ¬μ©μ μ λ ₯μ λ°μ΅λλ€. -
ProfileForm
μonSubmit
νΈλ€λ¬μμpatchUser
API (PATCH /api/v1/users
)λ₯Ό νΈμΆνμ¬ νλ‘ν μμ μ μμ²νκ³ , μ±κ³΅ μ λ§μ΄νμ΄μ§ λ±μΌλ‘ λΌμ°ν ν©λλ€.
-
-
useUser
(@/features/user/hooks/useUser.ts
): νμ¬ μ¬μ©μ μ 보λ₯Ό κ°μ Έμ΅λλ€. -
ProfileForm
(@/features/user/components/profile-form/ProfileForm.tsx
):- νλ‘ν μμ μ νμν νλλ₯Ό μ 곡νκ³ ,
defaultValues
λ‘ κΈ°μ‘΄ μ 보λ₯Ό νμν©λλ€. - μ¬μ©μκ° μμ ν λ°μ΄ν°λ₯Ό
onSubmit
propμ ν΅ν΄ProfileEditPage
λ‘ μ λ¬ν©λλ€.
- νλ‘ν μμ μ νμν νλλ₯Ό μ 곡νκ³ ,
-
-
νμ νν΄ νμ΄μ§ (
src/app/(main)/mypage/delete/page.tsx
) κ΄λ ¨:- μν : μ¬μ©μκ° μλΉμ€μμ νν΄νλ μ μ°¨λ₯Ό μ§ννλ νμ΄μ§μ λλ€.
-
κ΅¬μ± μμ λ° κ΄κ³:
-
DeleteAccountPage
(page.tsx
):-
DeleteAccountForm
μ»΄ν¬λνΈλ₯Ό μ¬μ©νμ¬ νν΄ μ μμ¬ν μλ΄ λ° λμλ₯Ό λ°μ΅λλ€.
-
-
DeleteAccountForm
(@/features/user/components/delete-account/DeleteAccountForm.tsx
):- νν΄ μ μμ¬νμ νμνκ³ , μ¬μ©μλ‘λΆν° νν΄ λμλ₯Ό λ°μ΅λλ€.
- λμ μ, λ΄λΆμ μΌλ‘
deleteUser
API (DELETE /api/v1/users
)λ₯Ό νΈμΆνκ³ , μ±κ³΅ μ μ¨λ³΄λ© νμ΄μ§ λ±μΌλ‘ λΌμ°ν ν©λλ€.
-
-
User Hook (
useUser
-@/features/user/hooks/useUser.ts
):- μ¬μ©μ μ 보 (
User | null
)λ₯Ό ReactuseState
λ₯Ό ν΅ν΄ κ΄λ¦¬ν©λλ€. - μ»΄ν¬λνΈ λ§μ΄νΈ μ
useEffect
λ΄μμgetUser
API (GET /api/v1/users/me
)λ₯Ό νΈμΆνμ¬ μ¬μ©μ μ 보λ₯Ό κ°μ Έμ μνλ₯Ό μ λ°μ΄νΈν©λλ€. - Zustandμ κ°μ λ³λμ μ μ μν κ΄λ¦¬ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©νμ§ μκ³ , Reactμ κΈ°λ³Έ ν μ νμ©ν λ‘컬 μν κ΄λ¦¬ λ° API μ°λ λ°©μμ λ°λ¦ λλ€.
-
λ°νκ°:
{ user: User | null }
- μ¬μ©μ μ 보 (
-
Local State (React
useState
):-
ProfileForm
: νΌ μ λ ₯ κ°, μ ν¨μ± κ²μ¦ μν, μ΄λ―Έμ§ 미리보기 URL, μ μΆ μ€ μν λ±μuseState
λ°react-hook-form
μ λ΄λΆ μνλ‘ κ΄λ¦¬ν©λλ€. -
useConsent
: μ½κ΄ λμ μ¬λΆ (privacyConsent
,locationConsent
)λ₯ΌuseState
λ‘ κ΄λ¦¬ν©λλ€. -
DeleteAccountForm
: νν΄ λμ μ¬λΆ (isAgreed
)λ₯ΌuseState
λ‘ κ΄λ¦¬ν©λλ€.
-
-
Server Cache State (Tanstack Query λ±):
- νμ¬λ
useEffect
λλ μΌλ° λΉλκΈ° ν¨μ λ΄μμ 컀μ€ν API ν¨μλ₯Ό ν΅ν΄ νΈμΆνλ λ°©μμΌλ‘ ꡬνλμ΄μμ΅λλ€. -
νλ‘ν μ΄λ―Έμ§ μ
λ‘λ:
useImageUpload
ν μ ν΅ν΄ μ΄λ―Έμ§κ° μλ²(μ: S3)μ μ λ‘λλ ν λ°νλλ URLμ μ¬μ©ν©λλ€. - μ μ₯λ μ₯μ λ°μ΄ν°: v2 μμ
- νμ¬λ
-
νΈμΆν λ°±μλ API λͺ©λ‘
-
GET /api/v1/auth/oauth
: λ‘κ·ΈμΈ μμ² -
POST /api/v1/auth/tokens
: λ‘κ·ΈμΈ -
POST /api/v1/auth/token/refresh
: ν ν° μ¬λ°κΈ -
POST /api/v1/auth/logout
: λ‘κ·Έμμ -
POST /api/v1/users/agreement
: κ°μΈ μ 보 λ° μμΉ μ 보 λμ -
POST /api/v1/users
: νμκ°μ -
GET /api/v1/users/me
: λ³ΈμΈ νλ‘ν μ‘°ν -
PATCH /api/v1/users
: λ³ΈμΈ νλ‘ν μμ -
DELETE /api/v1/users
: μ μ νν΄
-
-
API νΈμΆ μ²λ¦¬
- API μμ²/μλ΅ μνμ λ°λ₯Έ λ‘λ© λ¬Έκ΅¬ νμ
-
fetch
μ¬μ© - API μ€λ₯ λ°μ μ μ¬μ©μμκ² μΉνμ μΈ λ©μμ§ νμ λ° λ‘κΉ .
- Tanstack Queryλ₯Ό νμ©νμ¬ μ€λ³΅ νΈμΆ λ°©μ§, μΊμ κ΄λ¦¬, μλ μ¬μμ² λ± μ²λ¦¬ μμ