@potenlab/ui 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -1
- package/dist/cli.js +756 -0
- package/package.json +8 -3
- package/template/admin/README.md +36 -0
- package/template/admin/_gitignore +41 -0
- package/template/admin/components.json +23 -0
- package/template/admin/docs/changes.json +295 -0
- package/template/admin/docs/dev-plan.md +822 -0
- package/template/admin/docs/frontend-plan.md +874 -0
- package/template/admin/docs/prd.md +408 -0
- package/template/admin/docs/progress.json +777 -0
- package/template/admin/docs/test-plan.md +790 -0
- package/template/admin/docs/ui-ux-plan.md +1664 -0
- package/template/admin/eslint.config.mjs +18 -0
- package/template/admin/next.config.ts +7 -0
- package/template/admin/package.json +43 -0
- package/template/admin/postcss.config.mjs +7 -0
- package/template/admin/public/avatars/user1.svg +4 -0
- package/template/admin/public/avatars/user2.svg +4 -0
- package/template/admin/public/avatars/user3.svg +4 -0
- package/template/admin/public/avatars/user4.svg +4 -0
- package/template/admin/public/avatars/user5.svg +4 -0
- package/template/admin/public/file.svg +1 -0
- package/template/admin/public/globe.svg +1 -0
- package/template/admin/public/next.svg +1 -0
- package/template/admin/public/profile/img1.svg +7 -0
- package/template/admin/public/profile/img2.svg +7 -0
- package/template/admin/public/profile/img3.svg +7 -0
- package/template/admin/public/vercel.svg +1 -0
- package/template/admin/public/window.svg +1 -0
- package/template/admin/src/app/favicon.ico +0 -0
- package/template/admin/src/app/layout.tsx +38 -0
- package/template/admin/src/app/page.tsx +5 -0
- package/template/admin/src/app/users/[id]/page.tsx +10 -0
- package/template/admin/src/components/layouts/app-sidebar.tsx +152 -0
- package/template/admin/src/components/user-management/profile-images.tsx +69 -0
- package/template/admin/src/components/user-management/user-detail-form.tsx +143 -0
- package/template/admin/src/features/user-management/components/user-columns.tsx +101 -0
- package/template/admin/src/features/user-management/components/user-detail.tsx +79 -0
- package/template/admin/src/features/user-management/components/user-list.tsx +74 -0
- package/template/admin/src/features/user-management/types/index.ts +113 -0
- package/template/admin/src/features/user-management/utils/format.ts +2 -0
- package/template/admin/src/lib/mock-data.ts +131 -0
- package/template/admin/src/styles/globals.css +26 -0
- package/template/admin/tsconfig.json +34 -0
|
@@ -0,0 +1,822 @@
|
|
|
1
|
+
# Development Plan
|
|
2
|
+
|
|
3
|
+
Single source of truth for the Potenlab Admin User Management UI.
|
|
4
|
+
|
|
5
|
+
- **Source PRD:** `template/docs/prd.md`
|
|
6
|
+
- **Source UI/UX:** `template/docs/ui-ux-plan.md`
|
|
7
|
+
- **Tech Stack:** Next.js 16 (App Router), TypeScript, shadcn/ui, Tailwind CSS 4, Lucide React, Bun
|
|
8
|
+
- **Fonts:** Pretendard Variable, Inter
|
|
9
|
+
- **Platform:** Desktop-only (1920px target)
|
|
10
|
+
- **Backend:** NONE -- all data is static/mock, hardcoded in the frontend
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Project Structure (Bulletproof React)
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
src/
|
|
18
|
+
├── app/ # Routes, providers, root layout
|
|
19
|
+
│ ├── layout.tsx # Root layout with sidebar + font loading
|
|
20
|
+
│ ├── page.tsx # User Management List (Dashboard) route
|
|
21
|
+
│ └── users/
|
|
22
|
+
│ └── [id]/
|
|
23
|
+
│ └── page.tsx # User Detail route
|
|
24
|
+
├── components/ # SHARED + STYLED components
|
|
25
|
+
│ ├── ui/ # shadcn/ui base components (auto-generated, then customized)
|
|
26
|
+
│ │ ├── accordion.tsx
|
|
27
|
+
│ │ ├── avatar.tsx
|
|
28
|
+
│ │ ├── badge.tsx
|
|
29
|
+
│ │ ├── button.tsx
|
|
30
|
+
│ │ ├── card.tsx
|
|
31
|
+
│ │ ├── input.tsx
|
|
32
|
+
│ │ ├── label.tsx
|
|
33
|
+
│ │ ├── select.tsx
|
|
34
|
+
│ │ ├── separator.tsx
|
|
35
|
+
│ │ ├── sidebar.tsx
|
|
36
|
+
│ │ ├── switch.tsx
|
|
37
|
+
│ │ ├── table.tsx
|
|
38
|
+
│ │ ├── tabs.tsx
|
|
39
|
+
│ │ └── tooltip.tsx
|
|
40
|
+
│ ├── layouts/ # Page layout wrappers
|
|
41
|
+
│ │ └── content-layout.tsx # Content area wrapper (ml-[324px], padding)
|
|
42
|
+
│ ├── common/ # Generic reusable components
|
|
43
|
+
│ │ └── page-header.tsx # Title + badge + subtitle + action button
|
|
44
|
+
│ └── user-management/ # Feature-specific PRESENTATIONAL components
|
|
45
|
+
│ ├── user-table.tsx # Data table with columns and row rendering
|
|
46
|
+
│ ├── pagination-controls.tsx # Pagination UI above table
|
|
47
|
+
│ ├── search-bar.tsx # Search input for dashboard
|
|
48
|
+
│ ├── tab-navigation.tsx # Tab bar (All, Tab, Tab)
|
|
49
|
+
│ ├── user-detail-form.tsx # Detail page form (Basic Info + Other Settings)
|
|
50
|
+
│ └── profile-images.tsx # 3x profile image display
|
|
51
|
+
├── features/ # BUSINESS LOGIC only
|
|
52
|
+
│ └── user-management/
|
|
53
|
+
│ ├── types/
|
|
54
|
+
│ │ └── index.ts # User interface, table column types
|
|
55
|
+
│ └── utils/
|
|
56
|
+
│ └── format.ts # Date formatting, number formatting (e.g., "100,000")
|
|
57
|
+
├── lib/ # Library wrappers, utilities
|
|
58
|
+
│ ├── utils.ts # shadcn/ui cn() utility
|
|
59
|
+
│ └── mock-data.ts # Static mock user data (5 rows)
|
|
60
|
+
├── types/ # Shared TypeScript types
|
|
61
|
+
│ └── index.ts # Global type definitions
|
|
62
|
+
└── styles/
|
|
63
|
+
└── globals.css # Tailwind CSS 4 + shadcn/ui theme overrides + font imports
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Phase 0: Foundation
|
|
69
|
+
|
|
70
|
+
Project setup, design tokens, fonts, Tailwind config, shadcn/ui initialization.
|
|
71
|
+
|
|
72
|
+
### P0-T1: Initialize shadcn/ui
|
|
73
|
+
|
|
74
|
+
- **Output:** `src/components/ui/` directory scaffold, `components.json`, `src/lib/utils.ts`
|
|
75
|
+
- **Behavior:** Run `bunx --bun shadcn@latest init` in the template directory. Select "New York" style, CSS variables enabled. This generates the base config and the `cn()` utility.
|
|
76
|
+
- **Verify:**
|
|
77
|
+
- `components.json` exists at project root
|
|
78
|
+
- `src/lib/utils.ts` exists and exports `cn()`
|
|
79
|
+
- `bun run build` completes without errors
|
|
80
|
+
|
|
81
|
+
### P0-T2: Configure design tokens in globals.css
|
|
82
|
+
|
|
83
|
+
- **Depends on:** P0-T1
|
|
84
|
+
- **Output:** `src/styles/globals.css` (or `src/app/globals.css` depending on shadcn init)
|
|
85
|
+
- **Behavior:** Replace/extend the generated globals.css with the full set of Tailwind CSS 4 `@theme` custom properties and shadcn CSS variable overrides from the ui-ux-plan. Include:
|
|
86
|
+
- `@theme` block with all custom colors: `--color-primary: #509594`, `--color-primary-hover: #3F7A79`, `--color-primary-active: #357070`, `--color-primary-light: #B9D5D4`, `--color-background: #FCFCFC`, `--color-surface: #FFFFFF`, `--color-border: #E2E8F0`, `--color-divider: #EFF1F4`, `--color-table-header: #F9FAFC`, `--color-sidebar-selected: #EEF2F6`, `--color-placeholder: #A0AEC0`, `--color-subtitle: #9DA0A8`, `--color-muted-fg: #5A5E6A`, `--color-table-cell: #3B3F4A`, `--color-inactive-tab: #EFF1F4`, `--color-inactive-tab-text: #1A202C`, `--color-badge-green-bg: #C6F6D5`, `--color-badge-green-text: #22543D`, `--color-toggle-on: #3B82F6`, `--color-toggle-off: #CBD5E0`, `--color-delete-text: #3F7A79`
|
|
87
|
+
- Font family tokens: `--font-pretendard`, `--font-inter`, `--font-mono`
|
|
88
|
+
- `:root` CSS variables for shadcn overrides: `--primary`, `--primary-foreground`, `--border`, `--background`, `--card`, `--muted`, `--muted-foreground`, `--accent`, `--accent-foreground`, `--radius`
|
|
89
|
+
- Base body styles: `background-color: #FCFCFC`, default font family Pretendard
|
|
90
|
+
- **Verify:**
|
|
91
|
+
- Open app in browser; page background is `#FCFCFC`
|
|
92
|
+
- Inspect `:root` in dev tools; all CSS variables are present
|
|
93
|
+
- Tailwind utility classes like `bg-primary`, `text-primary`, `bg-table-header`, `text-placeholder` resolve to correct hex values
|
|
94
|
+
|
|
95
|
+
### P0-T3: Load fonts (Pretendard Variable + Inter)
|
|
96
|
+
|
|
97
|
+
- **Depends on:** P0-T2
|
|
98
|
+
- **Output:** `src/app/layout.tsx` (font loading section)
|
|
99
|
+
- **Behavior:**
|
|
100
|
+
- Use `next/font/google` to load Inter with `subsets: ['latin']`, `variable: '--font-inter'`, `display: 'swap'`
|
|
101
|
+
- Add Pretendard Variable via CDN `<link>` tag in layout `<head>`: `https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/variable/pretendardvariable.css`
|
|
102
|
+
- Apply font CSS variables to `<html>` or `<body>` element
|
|
103
|
+
- Set `<html lang="ko">` for Korean platform
|
|
104
|
+
- **Verify:**
|
|
105
|
+
- Add a test heading "ADMIN" and body text; inspect computed font-family in dev tools
|
|
106
|
+
- Pretendard Variable renders for heading text
|
|
107
|
+
- Inter renders for a test button/input element
|
|
108
|
+
- No FOUT (flash of unstyled text) -- fonts load with swap strategy
|
|
109
|
+
|
|
110
|
+
### P0-T4: Create shared TypeScript types
|
|
111
|
+
|
|
112
|
+
- **Output:** `src/features/user-management/types/index.ts`, `src/types/index.ts`
|
|
113
|
+
- **Behavior:**
|
|
114
|
+
- Define `User` interface with all fields: `id`, `nickname`, `grade`, `avatar`, `phone`, `age`, `gender`, `region`, `joinDate`, `withdrawalDate`, `role`, `exerciseStyle`, `gymRelocation`, `bench`, `deadlift`, `squat`, `intro`, `profileImages: string[]`, `settings: { profilePublic: boolean; matchChatNotification: boolean; marketingNotification: boolean; }`
|
|
115
|
+
- Define `UserTableColumn` type for table column definitions
|
|
116
|
+
- Export shared navigation types if needed
|
|
117
|
+
- **Verify:**
|
|
118
|
+
- Import `User` type in a test file; TypeScript compiles without errors
|
|
119
|
+
- All fields from the PRD mock data structure are represented
|
|
120
|
+
|
|
121
|
+
### P0-T5: Create mock data
|
|
122
|
+
|
|
123
|
+
- **Depends on:** P0-T4
|
|
124
|
+
- **Output:** `src/lib/mock-data.ts`
|
|
125
|
+
- **Behavior:**
|
|
126
|
+
- Export `mockUsers: User[]` with 5 user objects, all following the PRD sample data pattern (nickname "NicknameHere", grade "Mania", phone "010-1234-1234", age "Born 1999", gender "Male", region "Gangnam-gu", joinDate "Nov 1, 2022", withdrawalDate "Nov 1, 2022", etc.)
|
|
127
|
+
- Each user gets a unique `id` ("1" through "5")
|
|
128
|
+
- Export `totalUserCount = 100000`
|
|
129
|
+
- Profile images use placeholder paths: `/profile/img1.jpg`, `/profile/img2.jpg`, `/profile/img3.jpg`
|
|
130
|
+
- Avatar paths: `/avatars/user1.jpg` through `/avatars/user5.jpg`
|
|
131
|
+
- **Verify:**
|
|
132
|
+
- Import `mockUsers` and `totalUserCount` in a test; values are correct
|
|
133
|
+
- `mockUsers.length === 5`
|
|
134
|
+
- Each user has all required fields populated
|
|
135
|
+
|
|
136
|
+
### P0-T6: Create utility functions
|
|
137
|
+
|
|
138
|
+
- **Depends on:** P0-T4
|
|
139
|
+
- **Output:** `src/features/user-management/utils/format.ts`
|
|
140
|
+
- **Behavior:**
|
|
141
|
+
- `formatNumber(n: number): string` -- formats numbers with comma separators (e.g., `100000` -> `"100,000"`)
|
|
142
|
+
- Any other string/date format helpers needed for display
|
|
143
|
+
- **Verify:**
|
|
144
|
+
- `formatNumber(100000)` returns `"100,000"`
|
|
145
|
+
- `formatNumber(0)` returns `"0"`
|
|
146
|
+
|
|
147
|
+
### P0-T7: Add placeholder images to public directory
|
|
148
|
+
|
|
149
|
+
- **Output:** `public/avatars/user1.jpg` through `public/avatars/user5.jpg`, `public/profile/img1.jpg` through `public/profile/img3.jpg`
|
|
150
|
+
- **Behavior:**
|
|
151
|
+
- Create simple placeholder images (solid color squares or use a placeholder service URL in mock data instead)
|
|
152
|
+
- Avatar images: 44x44px source (renders at 22x22)
|
|
153
|
+
- Profile images: 232x232px source (renders at 116x116)
|
|
154
|
+
- Alternative: use placeholder URLs like `https://placehold.co/44x44/EEF2F6/5A5E6A?text=U1` in mock data and skip file creation
|
|
155
|
+
- **Verify:**
|
|
156
|
+
- Images load without 404 errors when referenced in components
|
|
157
|
+
- Avatar images render as small circles; profile images render at 116x116 with 8px radius
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Phase 1: Shared UI Components
|
|
162
|
+
|
|
163
|
+
Install and customize all shadcn/ui components under `src/components/ui/` and build reusable common components.
|
|
164
|
+
|
|
165
|
+
### P1-T1: Install shadcn/ui components (batch)
|
|
166
|
+
|
|
167
|
+
- **Depends on:** P0-T1
|
|
168
|
+
- **Output:** `src/components/ui/button.tsx`, `card.tsx`, `table.tsx`, `input.tsx`, `select.tsx`, `badge.tsx`, `avatar.tsx`, `switch.tsx`, `tabs.tsx`, `separator.tsx`, `accordion.tsx`, `sidebar.tsx`, `label.tsx`, `tooltip.tsx`
|
|
169
|
+
- **Behavior:** Run:
|
|
170
|
+
```bash
|
|
171
|
+
bunx --bun shadcn@latest add card button table input select badge avatar switch tabs separator accordion sidebar label tooltip
|
|
172
|
+
```
|
|
173
|
+
This installs all 14 shadcn/ui components into `src/components/ui/`.
|
|
174
|
+
- **Verify:**
|
|
175
|
+
- All 14 `.tsx` files exist in `src/components/ui/`
|
|
176
|
+
- `bun run build` completes without errors
|
|
177
|
+
- Each component can be imported without TypeScript errors
|
|
178
|
+
|
|
179
|
+
### P1-T2: Customize Button component with design variants
|
|
180
|
+
|
|
181
|
+
- **Depends on:** P1-T1, P0-T2
|
|
182
|
+
- **Output:** `src/components/ui/button.tsx` (modified)
|
|
183
|
+
- **Behavior:** Add custom variants to the Button component's `cva` config:
|
|
184
|
+
- `primary`: `bg-primary hover:bg-primary-hover active:bg-primary-active text-white font-inter text-[18px] font-semibold h-10 px-4 rounded-lg transition-colors duration-150 ease-out active:scale-[0.98]`. Disabled: `opacity-50 cursor-not-allowed pointer-events-none`
|
|
185
|
+
- `secondary`: `bg-sidebar-selected text-black font-inter text-[18px] font-semibold h-12 px-4 rounded-md`
|
|
186
|
+
- `pagination`: `bg-primary-light text-primary h-10 w-10 rounded-md hover:bg-[#A0C4C3] hover:text-primary-hover`. Disabled: `opacity-50 cursor-not-allowed`
|
|
187
|
+
- `ghost`: `bg-transparent text-delete-text hover:underline hover:text-primary-hover p-0 h-auto text-[14px]`
|
|
188
|
+
- Focus ring on all variants: `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2`
|
|
189
|
+
- **Verify:**
|
|
190
|
+
- Render each variant in a test page; colors match design tokens
|
|
191
|
+
- Hover state changes background on primary buttons to `#3F7A79`
|
|
192
|
+
- Active state applies `scale(0.98)` on primary buttons
|
|
193
|
+
- Disabled state shows 50% opacity and `cursor: not-allowed`
|
|
194
|
+
- Focus ring appears on Tab focus
|
|
195
|
+
|
|
196
|
+
### P1-T3: Customize Table component
|
|
197
|
+
|
|
198
|
+
- **Depends on:** P1-T1, P0-T2
|
|
199
|
+
- **Output:** `src/components/ui/table.tsx` (modified)
|
|
200
|
+
- **Behavior:** Override default shadcn/ui table styles:
|
|
201
|
+
- `Table` wrapper: `border border-border bg-white` (no border-radius per Figma)
|
|
202
|
+
- `TableHeader`: `bg-table-header`
|
|
203
|
+
- `TableHead`: `h-14 text-[14px] font-semibold text-table-cell px-4 align-middle`
|
|
204
|
+
- `TableRow`: `h-[60px] border-b border-border hover:bg-table-header cursor-pointer transition-colors duration-150`
|
|
205
|
+
- `TableCell`: `px-4 text-[14px] text-table-cell align-middle`
|
|
206
|
+
- **Verify:**
|
|
207
|
+
- Render a test table with 2 columns and 2 rows
|
|
208
|
+
- Table header background is `#F9FAFC`
|
|
209
|
+
- Row hover changes background
|
|
210
|
+
- Row height is 60px; header row height is 56px
|
|
211
|
+
- Cell text color is `#3B3F4A`
|
|
212
|
+
|
|
213
|
+
### P1-T4: Customize Input component
|
|
214
|
+
|
|
215
|
+
- **Depends on:** P1-T1, P0-T2
|
|
216
|
+
- **Output:** `src/components/ui/input.tsx` (modified)
|
|
217
|
+
- **Behavior:** Override default input styles:
|
|
218
|
+
- Base: `border border-border rounded-md font-inter text-[18px] text-black placeholder:text-placeholder bg-white`
|
|
219
|
+
- Focus: `focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 transition-[border-color,box-shadow] duration-150`
|
|
220
|
+
- Hover: `hover:border-[#CBD5E0]`
|
|
221
|
+
- Disabled: `disabled:bg-table-header disabled:text-placeholder disabled:cursor-not-allowed`
|
|
222
|
+
- Two size presets used via className at call sites:
|
|
223
|
+
- Dashboard search: `h-12` (48px)
|
|
224
|
+
- Detail form: `h-[52px]` (52px)
|
|
225
|
+
- Default padding: `px-4`
|
|
226
|
+
- **Verify:**
|
|
227
|
+
- Render input with placeholder "Enter search keyword"; placeholder text is `#A0AEC0`
|
|
228
|
+
- Focus the input; border turns `#509594` with subtle ring shadow
|
|
229
|
+
- Hover border changes to `#CBD5E0`
|
|
230
|
+
|
|
231
|
+
### P1-T5: Customize Select component
|
|
232
|
+
|
|
233
|
+
- **Depends on:** P1-T1, P0-T2
|
|
234
|
+
- **Output:** `src/components/ui/select.tsx` (modified)
|
|
235
|
+
- **Behavior:**
|
|
236
|
+
- `SelectTrigger`: `h-[52px] border border-border rounded-md px-4 pr-12 font-inter text-[18px] text-[#2D3748] bg-white hover:border-[#CBD5E0] focus:border-primary focus:ring-2 focus:ring-primary/20`
|
|
237
|
+
- Chevron icon area: 48px right padding, chevron `#A0AEC0` default, `#509594` on focus/open
|
|
238
|
+
- `SelectContent`: `bg-white border border-border rounded-md shadow-md`
|
|
239
|
+
- `SelectItem`: `hover:bg-sidebar-selected px-4 py-2 text-[18px]`
|
|
240
|
+
- Transition: chevron rotates 180deg on open (300ms ease-in-out)
|
|
241
|
+
- **Verify:**
|
|
242
|
+
- Render select with 3 options; trigger shows chevron down icon
|
|
243
|
+
- Click opens dropdown with white bg and shadow
|
|
244
|
+
- Hover on item shows `#EEF2F6` background
|
|
245
|
+
- Focus/open state border is primary color
|
|
246
|
+
|
|
247
|
+
### P1-T6: Customize Badge component
|
|
248
|
+
|
|
249
|
+
- **Depends on:** P1-T1, P0-T2
|
|
250
|
+
- **Output:** `src/components/ui/badge.tsx` (modified)
|
|
251
|
+
- **Behavior:** Add a `green` variant:
|
|
252
|
+
- `bg-badge-green-bg text-badge-green-text font-inter text-[18px] font-bold px-3 py-1 rounded-full`
|
|
253
|
+
- Used for the count badge on the dashboard header ("100,000")
|
|
254
|
+
- **Verify:**
|
|
255
|
+
- Render `<Badge variant="green">100,000</Badge>`
|
|
256
|
+
- Background is `#C6F6D5`, text is `#22543D`, pill shape (fully rounded)
|
|
257
|
+
|
|
258
|
+
### P1-T7: Customize Avatar component
|
|
259
|
+
|
|
260
|
+
- **Depends on:** P1-T1, P0-T2
|
|
261
|
+
- **Output:** `src/components/ui/avatar.tsx` (modified)
|
|
262
|
+
- **Behavior:**
|
|
263
|
+
- Table avatar: `w-[22px] h-[22px] rounded-full`
|
|
264
|
+
- `AvatarFallback`: First letter of nickname, `bg-sidebar-selected text-muted-fg text-[10px]`
|
|
265
|
+
- Ensure `AvatarImage` has `alt` text for accessibility
|
|
266
|
+
- **Verify:**
|
|
267
|
+
- Render avatar with an image src; displays 22px circle
|
|
268
|
+
- Render avatar without image; shows fallback letter in gray circle
|
|
269
|
+
- Inspect: `alt` attribute present on `<img>`
|
|
270
|
+
|
|
271
|
+
### P1-T8: Customize Switch component
|
|
272
|
+
|
|
273
|
+
- **Depends on:** P1-T1, P0-T2
|
|
274
|
+
- **Output:** `src/components/ui/switch.tsx` (modified)
|
|
275
|
+
- **Behavior:**
|
|
276
|
+
- Track size: `w-[44px] h-[24px] rounded-full`
|
|
277
|
+
- OFF state: track `bg-toggle-off`, thumb white, positioned left
|
|
278
|
+
- ON state: track `bg-toggle-on` (blue `#3B82F6`), thumb white, positioned right
|
|
279
|
+
- Hover OFF: track `#B0BEC5`; hover ON: track `#2563EB`
|
|
280
|
+
- Thumb size: 20px with 2px inset from track edge
|
|
281
|
+
- Transition: `200ms ease-in-out` for thumb translation and track color
|
|
282
|
+
- Disabled: 50% opacity
|
|
283
|
+
- `aria-checked` attribute managed by Radix Switch
|
|
284
|
+
- **Verify:**
|
|
285
|
+
- Render switch in OFF state; track is gray, thumb is left
|
|
286
|
+
- Click switch; animates to ON -- track turns blue, thumb slides right
|
|
287
|
+
- Keyboard: Space toggles state
|
|
288
|
+
- Inspect: `aria-checked` toggles between `true`/`false`
|
|
289
|
+
|
|
290
|
+
### P1-T9: Customize Tabs component
|
|
291
|
+
|
|
292
|
+
- **Depends on:** P1-T1, P0-T2
|
|
293
|
+
- **Output:** `src/components/ui/tabs.tsx` (modified)
|
|
294
|
+
- **Behavior:**
|
|
295
|
+
- `TabsList`: `gap-2 bg-transparent p-0` (no background on the list container itself)
|
|
296
|
+
- `TabsTrigger` active: `bg-primary text-white font-pretendard font-semibold h-[52px] px-4 rounded-md data-[state=active]:bg-primary data-[state=active]:text-white`
|
|
297
|
+
- `TabsTrigger` inactive: `bg-inactive-tab text-inactive-tab-text font-pretendard font-medium h-[52px] px-4 rounded-md`
|
|
298
|
+
- Hover (inactive): `hover:bg-border`
|
|
299
|
+
- Transition: `200ms ease-in-out` on background-color and color
|
|
300
|
+
- `aria-selected` on active tab (handled by Radix Tabs)
|
|
301
|
+
- **Verify:**
|
|
302
|
+
- Render 3 tabs: "All" (active), "Tab", "Tab"
|
|
303
|
+
- Active tab is teal with white text
|
|
304
|
+
- Inactive tabs are light gray with dark text
|
|
305
|
+
- Click inactive tab; it becomes active (color swap)
|
|
306
|
+
- Keyboard: Arrow Left/Right switches tabs
|
|
307
|
+
|
|
308
|
+
### P1-T10: Customize Separator component
|
|
309
|
+
|
|
310
|
+
- **Depends on:** P1-T1, P0-T2
|
|
311
|
+
- **Output:** `src/components/ui/separator.tsx` (modified)
|
|
312
|
+
- **Behavior:**
|
|
313
|
+
- Default color: `bg-divider` (`#EFF1F4`)
|
|
314
|
+
- Height: 1px (horizontal)
|
|
315
|
+
- Full width of parent container
|
|
316
|
+
- **Verify:**
|
|
317
|
+
- Render separator; visible as a thin light gray line
|
|
318
|
+
- Inspect: background color is `#EFF1F4`
|
|
319
|
+
|
|
320
|
+
### P1-T11: Customize Accordion component
|
|
321
|
+
|
|
322
|
+
- **Depends on:** P1-T1, P0-T2
|
|
323
|
+
- **Output:** `src/components/ui/accordion.tsx` (modified)
|
|
324
|
+
- **Behavior:**
|
|
325
|
+
- Trigger: chevron icon rotates 180deg on expand (300ms ease-in-out)
|
|
326
|
+
- Content: animated height expand/collapse (300ms ease-in-out)
|
|
327
|
+
- Trigger text: `font-inter text-[16px] text-muted-fg`
|
|
328
|
+
- `aria-expanded` on trigger (handled by Radix Accordion)
|
|
329
|
+
- **Verify:**
|
|
330
|
+
- Render accordion with one section; click trigger, content expands with animation
|
|
331
|
+
- Chevron rotates on expand
|
|
332
|
+
- Keyboard: Enter/Space toggles
|
|
333
|
+
|
|
334
|
+
### P1-T12: Customize Label component
|
|
335
|
+
|
|
336
|
+
- **Depends on:** P1-T1, P0-T2
|
|
337
|
+
- **Output:** `src/components/ui/label.tsx` (modified)
|
|
338
|
+
- **Behavior:**
|
|
339
|
+
- Style: `font-pretendard text-[20px] font-semibold text-[#101010]`
|
|
340
|
+
- Margin bottom: `mb-2` (8px below label, above input)
|
|
341
|
+
- Uses `htmlFor` attribute to associate with input `id`
|
|
342
|
+
- **Verify:**
|
|
343
|
+
- Render label above an input; label text is 20px SemiBold, nearly black
|
|
344
|
+
- Click label; associated input receives focus (via `htmlFor`/`id` pairing)
|
|
345
|
+
|
|
346
|
+
### P1-T13: Build PageHeader common component
|
|
347
|
+
|
|
348
|
+
- **Depends on:** P1-T2, P1-T6, P1-T10
|
|
349
|
+
- **Output:** `src/components/common/page-header.tsx`
|
|
350
|
+
- **Behavior:**
|
|
351
|
+
- Props: `title: string`, `subtitle: string`, `badgeCount?: number`, `actionLabel: string`, `actionIcon?: LucideIcon`, `onAction?: () => void`
|
|
352
|
+
- Layout: Flex row. Left side: title (32px SemiBold Pretendard, black) + Badge (only if `badgeCount` provided) on same line. Subtitle below (18px Medium Pretendard, `#9DA0A8`). Right side: action Button (primary variant) with optional icon.
|
|
353
|
+
- Below the header content, render a `<Separator />` with vertical margin
|
|
354
|
+
- **Verify:**
|
|
355
|
+
- Render `<PageHeader title="User Management" subtitle="User list management page" badgeCount={100000} actionLabel="Write" />`
|
|
356
|
+
- Title "User Management" in 32px SemiBold
|
|
357
|
+
- Badge "100,000" in green pill beside title
|
|
358
|
+
- Subtitle below in gray
|
|
359
|
+
- "Write" button top-right in teal
|
|
360
|
+
- Separator line beneath
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Phase 2: Layout & Navigation
|
|
365
|
+
|
|
366
|
+
Sidebar, root layout, content area structure.
|
|
367
|
+
|
|
368
|
+
### P2-T1: Build AppSidebar component
|
|
369
|
+
|
|
370
|
+
- **Depends on:** P1-T1, P1-T11, P0-T2, P0-T3
|
|
371
|
+
- **Output:** `src/components/layouts/app-sidebar.tsx`
|
|
372
|
+
- **Behavior:**
|
|
373
|
+
- Uses shadcn/ui `Sidebar` primitives (or custom div-based implementation) + `Accordion`
|
|
374
|
+
- Fixed position left, 300px width, full viewport height, white background, right border `#E2E8F0`
|
|
375
|
+
- **Header section:**
|
|
376
|
+
- "ADMIN" title: Pretendard 24px SemiBold, `letter-spacing: 0.05em`
|
|
377
|
+
- Admin ID subtitle: 14px Regular `#9DA0A8`, e.g., "admin@potenlab.com"
|
|
378
|
+
- Bottom spacing 32px
|
|
379
|
+
- **Navigation section (Accordion):**
|
|
380
|
+
- "Home" item: `Home` icon (Lucide), no accordion, links to `/`
|
|
381
|
+
- "User" accordion: `Users` icon, expands to show "User Management" sub-item
|
|
382
|
+
- "Match" accordion: `Gamepad2` icon, expands to show "Match Management" sub-item
|
|
383
|
+
- "Admin" accordion: `Shield` icon, expands to show "Notice Management", "Report Management", "Terms Management" sub-items
|
|
384
|
+
- Menu item height: 48px, text: Inter 16px Regular `#5A5E6A`, icon: 20px `#5A5E6A`
|
|
385
|
+
- Menu item hover: `#F9FAFC` background (150ms ease-out)
|
|
386
|
+
- Sub-menu items: indented 40px, height 44px
|
|
387
|
+
- Active sub-menu: `#EEF2F6` background, `#509594` text, dot indicator
|
|
388
|
+
- Active state derived from `usePathname()` (Next.js)
|
|
389
|
+
- Sub-menu items use `next/link` for navigation
|
|
390
|
+
- **Footer section:**
|
|
391
|
+
- `Separator` above
|
|
392
|
+
- Logout button: `LogOut` icon + "Logout" text, centered, 16px Regular `#5A5E6A`
|
|
393
|
+
- Hover: text turns `#EF4444` (red hint for destructive action)
|
|
394
|
+
- Click: no-op (UI only)
|
|
395
|
+
- Keyboard: Tab navigates between items, Enter/Space toggles accordion, Arrow Up/Down navigates accordion items
|
|
396
|
+
- **Verify:**
|
|
397
|
+
- Sidebar renders at 300px width on left side of viewport
|
|
398
|
+
- "ADMIN" title visible with correct font
|
|
399
|
+
- Click "User" accordion; "User Management" sub-item appears with animation
|
|
400
|
+
- "User Management" has active highlight when on `/` route
|
|
401
|
+
- Logout button visible at bottom with separator above
|
|
402
|
+
- Hover logout; text turns red
|
|
403
|
+
- Keyboard: Tab through sidebar items; focus visible on each
|
|
404
|
+
|
|
405
|
+
### P2-T2: Build ContentLayout wrapper component
|
|
406
|
+
|
|
407
|
+
- **Depends on:** P2-T1
|
|
408
|
+
- **Output:** `src/components/layouts/content-layout.tsx`
|
|
409
|
+
- **Behavior:**
|
|
410
|
+
- Wraps page content with correct offset: `ml-[324px] pt-5 pr-8 pb-8`
|
|
411
|
+
- Content area offset: 300px sidebar + 24px gap = 324px from left
|
|
412
|
+
- Top padding: 20px
|
|
413
|
+
- Receives `children` as prop
|
|
414
|
+
- Background: inherits from body (`#FCFCFC`)
|
|
415
|
+
- **Verify:**
|
|
416
|
+
- Wrap test content with `<ContentLayout>`; content appears to the right of the sidebar
|
|
417
|
+
- Inspect: margin-left is 324px, padding-top is 20px
|
|
418
|
+
|
|
419
|
+
### P2-T3: Build root layout (app/layout.tsx)
|
|
420
|
+
|
|
421
|
+
- **Depends on:** P2-T1, P2-T2, P0-T3
|
|
422
|
+
- **Output:** `src/app/layout.tsx`
|
|
423
|
+
- **Behavior:**
|
|
424
|
+
- `<html lang="ko">` with font CSS variables applied
|
|
425
|
+
- Loads Inter via `next/font/google` with `variable: '--font-inter'`
|
|
426
|
+
- Loads Pretendard Variable via CDN link in `<head>`
|
|
427
|
+
- Renders `<AppSidebar />` persistent on all pages
|
|
428
|
+
- Renders `<ContentLayout>{children}</ContentLayout>` as main content area
|
|
429
|
+
- Skip link: `<a href="#main-content" className="sr-only focus:not-sr-only ...">Skip to main content</a>` as first element
|
|
430
|
+
- `<main id="main-content">` wraps content area for skip link target
|
|
431
|
+
- Imports `globals.css`
|
|
432
|
+
- **Verify:**
|
|
433
|
+
- Open app; sidebar visible on left, content on right
|
|
434
|
+
- Navigate between pages; sidebar persists
|
|
435
|
+
- Tab key first focuses "Skip to main content" link (visible on focus)
|
|
436
|
+
- Press Enter on skip link; focus moves to main content area
|
|
437
|
+
- Inspect `<html>`: `lang="ko"` attribute present
|
|
438
|
+
- Fonts loaded: Pretendard and Inter visible in Network tab
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## Phase 3: Features -- User Management List Page (Dashboard)
|
|
443
|
+
|
|
444
|
+
### P3-T1: Build TabNavigation component
|
|
445
|
+
|
|
446
|
+
- **Depends on:** P1-T9
|
|
447
|
+
- **Output:** `src/components/user-management/tab-navigation.tsx`
|
|
448
|
+
- **Behavior:**
|
|
449
|
+
- Uses shadcn/ui `Tabs`, `TabsList`, `TabsTrigger`
|
|
450
|
+
- 3 tabs: "All" (default active), "Tab", "Tab"
|
|
451
|
+
- Active tab: teal bg, white text. Inactive: light gray bg, dark text
|
|
452
|
+
- `defaultValue="all"`
|
|
453
|
+
- Tab switching updates local state (UI only, no filtering)
|
|
454
|
+
- Arrow Left/Right keyboard navigation between tabs
|
|
455
|
+
- **Verify:**
|
|
456
|
+
- Render component; "All" tab is active (teal)
|
|
457
|
+
- Click "Tab"; it becomes active, "All" becomes inactive
|
|
458
|
+
- Keyboard: Arrow Right moves focus and activates next tab
|
|
459
|
+
|
|
460
|
+
### P3-T2: Build SearchBar component
|
|
461
|
+
|
|
462
|
+
- **Depends on:** P1-T4
|
|
463
|
+
- **Output:** `src/components/user-management/search-bar.tsx`
|
|
464
|
+
- **Behavior:**
|
|
465
|
+
- Full-width `Input` component
|
|
466
|
+
- Height: 48px (`h-12`), border radius 6px
|
|
467
|
+
- Placeholder: "Enter search keyword"
|
|
468
|
+
- Placeholder color: `#A0AEC0`
|
|
469
|
+
- Accepts text input (state managed locally, no actual search)
|
|
470
|
+
- Focus highlights border to primary
|
|
471
|
+
- **Verify:**
|
|
472
|
+
- Render search bar; full width with placeholder text in gray
|
|
473
|
+
- Click/focus; border changes to teal
|
|
474
|
+
- Type text; placeholder disappears, text visible
|
|
475
|
+
|
|
476
|
+
### P3-T3: Build PaginationControls component
|
|
477
|
+
|
|
478
|
+
- **Depends on:** P1-T2, P1-T4, P1-T5
|
|
479
|
+
- **Output:** `src/components/user-management/pagination-controls.tsx`
|
|
480
|
+
- **Behavior:**
|
|
481
|
+
- Flex row with `justify-between`
|
|
482
|
+
- **Left group:**
|
|
483
|
+
- 4 pagination buttons: `<<` (ChevronsLeft), `<` (ChevronLeft), `>` (ChevronRight), `>>` (ChevronsRight)
|
|
484
|
+
- Button variant: `pagination` (40x40px, `#B9D5D4` bg, 6px radius)
|
|
485
|
+
- Each icon-only button has `aria-label` (e.g., "Go to first page", "Previous page", "Next page", "Go to last page")
|
|
486
|
+
- Page indicator between `<` and `>`: "1 / 1"
|
|
487
|
+
- Current page: 20px Bold
|
|
488
|
+
- Separator "/" and total: 16px Regular `#5A5E6A`
|
|
489
|
+
- All buttons visually present but disabled state (muted, since single page of mock data)
|
|
490
|
+
- **Right group:**
|
|
491
|
+
- Page jump `Input`: 100px width, 48px height, placeholder "Page", 6px radius
|
|
492
|
+
- "Go" button: `secondary` variant, 48px height
|
|
493
|
+
- Items per page `Select`: 96px width, 48px height, default "10 items", options: "10 items", "20 items", "50 items"
|
|
494
|
+
- State: current page, items per page managed locally (UI only)
|
|
495
|
+
- `aria-live="polite"` on page indicator region
|
|
496
|
+
- **Verify:**
|
|
497
|
+
- Render component; all 4 nav buttons visible in `#B9D5D4`
|
|
498
|
+
- "1 / 1" displayed between `<` and `>`
|
|
499
|
+
- Page jump input visible with "Page" placeholder
|
|
500
|
+
- "Go" button renders in gray
|
|
501
|
+
- Items per page dropdown shows "10 items"
|
|
502
|
+
- Click dropdown; opens with 3 options
|
|
503
|
+
- All icon buttons have `aria-label` (inspect DOM)
|
|
504
|
+
|
|
505
|
+
### P3-T4: Build UserTable component
|
|
506
|
+
|
|
507
|
+
- **Depends on:** P1-T3, P1-T7, P1-T2, P0-T5
|
|
508
|
+
- **Output:** `src/components/user-management/user-table.tsx`
|
|
509
|
+
- **Behavior:**
|
|
510
|
+
- Uses shadcn/ui `Table`, `TableHeader`, `TableHead`, `TableBody`, `TableRow`, `TableCell`
|
|
511
|
+
- 10 columns per spec:
|
|
512
|
+
- Nickname (flex-1), Grade (flex-1), Avatar (80px fixed, centered), Phone Number (flex-1), Age (flex-1), Gender (flex-1), Region (flex-1), Join Date (flex-1, sort icon), Withdrawal Date (flex-1, sort icon), Delete (57px fixed)
|
|
513
|
+
- Renders 5 rows from `mockUsers` data
|
|
514
|
+
- Avatar column: `Avatar` component (22px circular)
|
|
515
|
+
- Delete column: ghost button with teal text "Delete" (`#3F7A79`), underline on hover
|
|
516
|
+
- Sort icons on Join Date and Withdrawal Date: `ChevronDown` icon (visual only, no actual sort)
|
|
517
|
+
- Row `onClick`: navigates to `/users/[id]` via `useRouter().push()`
|
|
518
|
+
- Delete `onClick`: calls `event.stopPropagation()` to prevent row navigation (UI only, no deletion)
|
|
519
|
+
- Row hover: `#F9FAFC` background, `cursor: pointer`
|
|
520
|
+
- Table container: white bg, `#E2E8F0` border, no border-radius
|
|
521
|
+
- Header row: `#F9FAFC` bg, 56px height, 14px SemiBold text
|
|
522
|
+
- Body row: 60px height, 14px Regular text, `#3B3F4A`
|
|
523
|
+
- Empty state: "No users found" centered (not needed for 5 mock rows, but define for completeness)
|
|
524
|
+
- **Verify:**
|
|
525
|
+
- Table renders with 10 column headers and 5 data rows
|
|
526
|
+
- Avatar column shows small circular image (or fallback initial)
|
|
527
|
+
- Hover over row; background changes, cursor is pointer
|
|
528
|
+
- Click row; navigates to `/users/1` (check URL bar)
|
|
529
|
+
- Click "Delete" on a row; does NOT navigate (stopPropagation works)
|
|
530
|
+
- Sort icons visible on Join Date and Withdrawal Date columns
|
|
531
|
+
- Table container has border but no rounded corners
|
|
532
|
+
|
|
533
|
+
### P3-T5: Assemble Dashboard page (app/page.tsx)
|
|
534
|
+
|
|
535
|
+
- **Depends on:** P3-T1, P3-T2, P3-T3, P3-T4, P1-T13, P0-T5, P0-T6
|
|
536
|
+
- **Output:** `src/app/page.tsx`
|
|
537
|
+
- **Behavior:**
|
|
538
|
+
- Uses `Card` as the main container (white, 8px radius, `#E2E8F0` border, 32px padding)
|
|
539
|
+
- Composes inside Card, top to bottom:
|
|
540
|
+
1. `PageHeader` with title "User Management", subtitle "User list management page", badgeCount `totalUserCount` (formatted to "100,000"), actionLabel "Write", actionIcon `Pencil`
|
|
541
|
+
2. `TabNavigation` (3 tabs, "All" active)
|
|
542
|
+
3. `SearchBar` (full-width input)
|
|
543
|
+
4. `PaginationControls` (nav buttons + page jump + items per page)
|
|
544
|
+
5. `UserTable` (5 rows of mock data)
|
|
545
|
+
- Spacing between sections: consistent gaps (use `space-y-6` or explicit margins per Figma)
|
|
546
|
+
- Route: `/` (app/page.tsx)
|
|
547
|
+
- **Verify:**
|
|
548
|
+
- Open `/` in browser; full dashboard visible inside white card
|
|
549
|
+
- Header shows "User Management" + green badge "100,000" + subtitle + "Write" button
|
|
550
|
+
- Separator line below header
|
|
551
|
+
- Tabs render below separator
|
|
552
|
+
- Search bar below tabs
|
|
553
|
+
- Pagination controls below search
|
|
554
|
+
- Data table with 5 rows below pagination
|
|
555
|
+
- All spacing looks correct against Figma wireframe
|
|
556
|
+
- "Write" button click: no-op (UI only)
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
## Phase 4: Features -- User Detail Page
|
|
561
|
+
|
|
562
|
+
### P4-T1: Build ProfileImages component
|
|
563
|
+
|
|
564
|
+
- **Depends on:** P0-T5, P0-T7
|
|
565
|
+
- **Output:** `src/components/user-management/profile-images.tsx`
|
|
566
|
+
- **Behavior:**
|
|
567
|
+
- Props: `images: string[]` (array of 3 image URLs)
|
|
568
|
+
- Renders 3 images in a horizontal flex row with 16px gap
|
|
569
|
+
- Each image: 116x116px, 8px border-radius, `object-cover`
|
|
570
|
+
- Uses `<img>` or Next.js `<Image>` with appropriate alt text (e.g., "Profile photo 1", "Profile photo 2", "Profile photo 3")
|
|
571
|
+
- Fallback: if image fails to load, show gray placeholder block (116x116, `#EEF2F6` bg)
|
|
572
|
+
- **Verify:**
|
|
573
|
+
- Render with 3 placeholder images; 3 boxes appear horizontally
|
|
574
|
+
- Each image is 116x116 with rounded corners
|
|
575
|
+
- Alt text present on each `<img>` tag
|
|
576
|
+
|
|
577
|
+
### P4-T2: Build UserDetailForm component
|
|
578
|
+
|
|
579
|
+
- **Depends on:** P1-T4, P1-T5, P1-T8, P1-T12, P1-T10, P4-T1, P0-T5
|
|
580
|
+
- **Output:** `src/components/user-management/user-detail-form.tsx`
|
|
581
|
+
- **Behavior:**
|
|
582
|
+
- Props: `user: User`
|
|
583
|
+
- **Basic Info section:**
|
|
584
|
+
- Section label: "Basic Info" (Pretendard 20px Bold, `#A0AEC0`)
|
|
585
|
+
- `ProfileImages` component with user's 3 profile images (margin-bottom 24px)
|
|
586
|
+
- "One-Line Intro" field: `Label` + full-width `Input` (52px height), pre-filled with `user.intro`
|
|
587
|
+
- **Row 1** (CSS Grid `grid-cols-4 gap-6`): Role (Select: "User", "Admin"), Nickname (Input), Phone (Input), Age (Input)
|
|
588
|
+
- **Row 2** (CSS Grid `grid-cols-4 gap-6`): Gender (Select: "Male", "Female"), Exercise Style (Select: "Bodybuilding", "Crossfit", "Cardio"), Gym Relocation (Select: "Available", "Not Available"), Region (Select: "Seoul Mapo-gu", "Gangnam-gu", "Songpa-gu")
|
|
589
|
+
- **Row 3** (CSS Grid `grid-cols-3 gap-6`): Bench (Input), Deadlift (Input), Squat (Input)
|
|
590
|
+
- All inputs/selects are 52px height, 6px radius, 16px horizontal padding
|
|
591
|
+
- All fields pre-filled with mock data values
|
|
592
|
+
- Labels above each field: 20px SemiBold `#101010`
|
|
593
|
+
- **Separator** between sections
|
|
594
|
+
- **Other Settings section:**
|
|
595
|
+
- Section label: "Other Settings" (Pretendard 20px Bold, `#A0AEC0`)
|
|
596
|
+
- Horizontal flex layout with 100px gap between toggle items
|
|
597
|
+
- 3 toggles: "Profile Public" (OFF), "Match & Chat Notification" (ON), "Marketing Notification" (OFF)
|
|
598
|
+
- Each toggle: label text left + `Switch` component right (or text above, switch beside)
|
|
599
|
+
- Switch state reflects `user.settings` values
|
|
600
|
+
- Toggles are interactive (local state change on click, UI only)
|
|
601
|
+
- All form fields are editable (inputs accept typing, selects open, toggles toggle)
|
|
602
|
+
- No form validation needed
|
|
603
|
+
- **Verify:**
|
|
604
|
+
- Render with mock user data; all fields pre-filled correctly
|
|
605
|
+
- "Basic Info" section label visible in gray
|
|
606
|
+
- 3 profile images displayed
|
|
607
|
+
- "One-Line Intro" input shows "This is the one-line intro content."
|
|
608
|
+
- Row 1: 4 fields in equal columns with 24px gap
|
|
609
|
+
- Row 2: 4 fields, dropdowns open on click
|
|
610
|
+
- Row 3: 3 fields in equal columns
|
|
611
|
+
- Separator between sections
|
|
612
|
+
- "Other Settings" label visible
|
|
613
|
+
- 3 toggles in a row with 100px gap
|
|
614
|
+
- "Match & Chat Notification" toggle is ON (blue); others are OFF (gray)
|
|
615
|
+
- Click a toggle; it switches state with animation
|
|
616
|
+
|
|
617
|
+
### P4-T3: Assemble User Detail page (app/users/[id]/page.tsx)
|
|
618
|
+
|
|
619
|
+
- **Depends on:** P4-T2, P1-T13, P0-T5
|
|
620
|
+
- **Output:** `src/app/users/[id]/page.tsx`
|
|
621
|
+
- **Behavior:**
|
|
622
|
+
- Reads `params.id` from route (Next.js dynamic route)
|
|
623
|
+
- Looks up user from `mockUsers` by id (falls back to first user if not found)
|
|
624
|
+
- Uses `Card` as main container (same styling as dashboard card)
|
|
625
|
+
- Composes inside Card:
|
|
626
|
+
1. `PageHeader` with title "User Management", subtitle "You can edit user information.", actionLabel "Save Changes", actionIcon `Pencil`
|
|
627
|
+
2. `UserDetailForm` with the selected user
|
|
628
|
+
- "Save Changes" button click: no-op (UI only, no actual save)
|
|
629
|
+
- Route: `/users/[id]`
|
|
630
|
+
- **Verify:**
|
|
631
|
+
- Navigate to `/users/1`; detail page loads in white card
|
|
632
|
+
- Header shows "User Management" + subtitle "You can edit user information." + "Save Changes" button
|
|
633
|
+
- No badge on this page (badgeCount not passed)
|
|
634
|
+
- Form renders with all user fields pre-filled
|
|
635
|
+
- Sidebar shows "User Management" as active
|
|
636
|
+
- Click "Save Changes": no error (UI only)
|
|
637
|
+
- Navigate back via sidebar "User Management" link; returns to dashboard
|
|
638
|
+
|
|
639
|
+
---
|
|
640
|
+
|
|
641
|
+
## Phase 5: Polish
|
|
642
|
+
|
|
643
|
+
Accessibility, animations, micro-interactions, keyboard navigation, final QA.
|
|
644
|
+
|
|
645
|
+
### P5-T1: Accessibility audit -- keyboard navigation
|
|
646
|
+
|
|
647
|
+
- **Depends on:** P3-T5, P4-T3
|
|
648
|
+
- **Output:** Updates to existing components as needed
|
|
649
|
+
- **Behavior:** Verify and fix keyboard navigation across all components:
|
|
650
|
+
- **Skip link:** Tab first focuses "Skip to main content"; Enter jumps to `#main-content`
|
|
651
|
+
- **Sidebar:** Tab/Shift+Tab navigates between items. Enter/Space toggles accordion. Arrow Up/Down navigates within accordion.
|
|
652
|
+
- **Tabs:** Arrow Left/Right switches tabs. Enter/Space activates.
|
|
653
|
+
- **Table rows:** Tab focuses row (or first interactive element). Enter navigates to detail.
|
|
654
|
+
- **Delete action:** Enter/Space triggers delete. Tab reaches it separately from row.
|
|
655
|
+
- **Pagination buttons:** Tab + Enter navigates. Each has `aria-label`.
|
|
656
|
+
- **Inputs:** Tab focuses. Standard text input behavior.
|
|
657
|
+
- **Selects:** Enter/Space opens. Arrow Up/Down navigates. Enter selects. Escape closes.
|
|
658
|
+
- **Toggles:** Space toggles ON/OFF.
|
|
659
|
+
- **Buttons:** Enter/Space activates.
|
|
660
|
+
- Focus ring: 2px `#509594` outline with 2px offset on ALL interactive elements
|
|
661
|
+
- **Verify:**
|
|
662
|
+
- Start at top of page, Tab through every interactive element without mouse
|
|
663
|
+
- Focus ring visible on each element
|
|
664
|
+
- No keyboard traps (can always Tab out or Escape out)
|
|
665
|
+
- All actions achievable via keyboard alone
|
|
666
|
+
- Skip link works on both pages
|
|
667
|
+
|
|
668
|
+
### P5-T2: Accessibility audit -- ARIA and screen reader
|
|
669
|
+
|
|
670
|
+
- **Depends on:** P5-T1
|
|
671
|
+
- **Output:** Updates to existing components as needed
|
|
672
|
+
- **Behavior:** Verify and fix ARIA attributes:
|
|
673
|
+
- Badge: `aria-label="Total users: 100,000"` (screen reader reads meaningful text)
|
|
674
|
+
- Active tab: `aria-selected="true"` (Radix handles this)
|
|
675
|
+
- Table: ensure `<table>`, `<thead>`, `<th>`, `<tbody>`, `<tr>`, `<td>` semantic elements (shadcn/ui table uses these)
|
|
676
|
+
- Sort columns: `aria-label="Join Date, sortable column"` on header
|
|
677
|
+
- Delete buttons: `aria-label="Delete user NicknameHere"` with dynamic user name
|
|
678
|
+
- Toggle switches: `aria-checked` managed by Radix Switch
|
|
679
|
+
- Accordion: `aria-expanded` on triggers
|
|
680
|
+
- Pagination: `aria-live="polite"` on page indicator region
|
|
681
|
+
- All icon-only buttons: `aria-label` present
|
|
682
|
+
- Heading hierarchy: `<h1>` for page title, `<h2>` for section labels
|
|
683
|
+
- Form fields: `<label htmlFor="field-id">` paired with `<input id="field-id">`
|
|
684
|
+
- **Verify:**
|
|
685
|
+
- Use browser accessibility inspector (Chrome DevTools Accessibility tab)
|
|
686
|
+
- VoiceOver (macOS): navigate through page; all elements announced correctly
|
|
687
|
+
- Check: page title reads "User Management, heading level 1"
|
|
688
|
+
- Check: badge reads "Total users: 100,000"
|
|
689
|
+
- Check: toggle reads "Profile Public, switch, off"
|
|
690
|
+
- No accessibility warnings in browser dev tools
|
|
691
|
+
|
|
692
|
+
### P5-T3: Micro-interactions and transitions
|
|
693
|
+
|
|
694
|
+
- **Depends on:** P3-T5, P4-T3
|
|
695
|
+
- **Output:** Updates to existing component styles
|
|
696
|
+
- **Behavior:** Ensure all transitions from the ui-ux-plan are implemented:
|
|
697
|
+
- **Button hover:** `background-color 150ms ease-out` (primary -> `#3F7A79`)
|
|
698
|
+
- **Button active:** `scale(0.98)` + `background-color 100ms ease-out` (primary -> `#357070`)
|
|
699
|
+
- **Table row hover:** `background-color 150ms ease-out` (-> `#F9FAFC`)
|
|
700
|
+
- **Toggle switch:** thumb `transform 200ms ease-in-out`, track `background-color 200ms ease-in-out`
|
|
701
|
+
- **Accordion expand/collapse:** `height 300ms ease-in-out`, `opacity 200ms ease-in-out`. Chevron `transform 300ms ease-in-out` (rotate 180deg)
|
|
702
|
+
- **Tab switch:** `background-color 200ms ease-in-out`, `color 200ms ease-in-out`
|
|
703
|
+
- **Input focus:** `border-color 150ms ease-out`, `box-shadow 150ms ease-out` (ring appears)
|
|
704
|
+
- **Delete text hover:** `color 150ms ease-out`, `text-decoration: underline`
|
|
705
|
+
- **Sidebar menu item hover:** `background-color 150ms ease-out`
|
|
706
|
+
- **Logout hover:** text color transition to red
|
|
707
|
+
- **Verify:**
|
|
708
|
+
- Visually test each interaction; animations are smooth, not janky
|
|
709
|
+
- No transitions are instant (all have appropriate duration)
|
|
710
|
+
- Toggle thumb slides smoothly
|
|
711
|
+
- Accordion content height animates (not instant appear/disappear)
|
|
712
|
+
|
|
713
|
+
### P5-T4: Final visual QA against design specs
|
|
714
|
+
|
|
715
|
+
- **Depends on:** P5-T3
|
|
716
|
+
- **Output:** Bug fixes and adjustments to existing components/styles
|
|
717
|
+
- **Behavior:** Systematic check of every visual element against the Figma design specs and PRD:
|
|
718
|
+
- **Colors:** Verify every color token matches hex values in the PRD
|
|
719
|
+
- **Typography:** Verify font family, size, weight, line-height for every text style
|
|
720
|
+
- **Spacing:** Verify all paddings, margins, gaps match spec (card 32px padding, field gap 24px, etc.)
|
|
721
|
+
- **Sizing:** Verify all fixed dimensions (sidebar 300px, button heights, input heights, avatar 22px, profile images 116px, toggle 44x24px, table row heights)
|
|
722
|
+
- **Border radius:** Cards 8px, inputs/tabs/selects 6px, avatars full-round, table 0px
|
|
723
|
+
- **Borders:** Card `#E2E8F0`, dividers `#EFF1F4`, table `#E2E8F0`
|
|
724
|
+
- **Shadows:** Minimal/none on cards, `shadow-md` on dropdown menus only
|
|
725
|
+
- **Layout:** Sidebar 300px fixed, content offset 324px, 20px top padding
|
|
726
|
+
- **Dashboard page:** All elements match wireframe from Section 7.1
|
|
727
|
+
- **Detail page:** All elements match wireframe from Section 7.2
|
|
728
|
+
- **Empty/edge states:** Single page pagination shows disabled buttons
|
|
729
|
+
- **Verify:**
|
|
730
|
+
- Side-by-side comparison with Figma designs at 1920px viewport
|
|
731
|
+
- No visual regressions or misalignments
|
|
732
|
+
- All hover/focus states match spec
|
|
733
|
+
- Build succeeds: `bun run build` completes without errors
|
|
734
|
+
|
|
735
|
+
---
|
|
736
|
+
|
|
737
|
+
## Task Dependency Graph
|
|
738
|
+
|
|
739
|
+
```
|
|
740
|
+
P0-T1 (shadcn init)
|
|
741
|
+
├── P0-T2 (design tokens) ──> P0-T3 (fonts)
|
|
742
|
+
└── P1-T1 (install components)
|
|
743
|
+
├── P1-T2 (Button)
|
|
744
|
+
├── P1-T3 (Table)
|
|
745
|
+
├── P1-T4 (Input)
|
|
746
|
+
├── P1-T5 (Select)
|
|
747
|
+
├── P1-T6 (Badge)
|
|
748
|
+
├── P1-T7 (Avatar)
|
|
749
|
+
├── P1-T8 (Switch)
|
|
750
|
+
├── P1-T9 (Tabs)
|
|
751
|
+
├── P1-T10 (Separator)
|
|
752
|
+
├── P1-T11 (Accordion)
|
|
753
|
+
└── P1-T12 (Label)
|
|
754
|
+
|
|
755
|
+
P0-T4 (types) ──> P0-T5 (mock data)
|
|
756
|
+
P0-T4 (types) ──> P0-T6 (utils)
|
|
757
|
+
|
|
758
|
+
P1-T2 + P1-T6 + P1-T10 ──> P1-T13 (PageHeader)
|
|
759
|
+
|
|
760
|
+
P1-T11 + P0-T2 + P0-T3 ──> P2-T1 (AppSidebar)
|
|
761
|
+
P2-T1 ──> P2-T2 (ContentLayout)
|
|
762
|
+
P2-T1 + P2-T2 + P0-T3 ──> P2-T3 (Root Layout)
|
|
763
|
+
|
|
764
|
+
P1-T9 ──> P3-T1 (TabNavigation)
|
|
765
|
+
P1-T4 ──> P3-T2 (SearchBar)
|
|
766
|
+
P1-T2 + P1-T4 + P1-T5 ──> P3-T3 (PaginationControls)
|
|
767
|
+
P1-T3 + P1-T7 + P1-T2 + P0-T5 ──> P3-T4 (UserTable)
|
|
768
|
+
P3-T1 + P3-T2 + P3-T3 + P3-T4 + P1-T13 + P0-T5 + P0-T6 ──> P3-T5 (Dashboard page)
|
|
769
|
+
|
|
770
|
+
P0-T5 + P0-T7 ──> P4-T1 (ProfileImages)
|
|
771
|
+
P1-T4 + P1-T5 + P1-T8 + P1-T12 + P1-T10 + P4-T1 + P0-T5 ──> P4-T2 (UserDetailForm)
|
|
772
|
+
P4-T2 + P1-T13 + P0-T5 ──> P4-T3 (User Detail page)
|
|
773
|
+
|
|
774
|
+
P3-T5 + P4-T3 ──> P5-T1 (Keyboard a11y)
|
|
775
|
+
P5-T1 ──> P5-T2 (ARIA/SR a11y)
|
|
776
|
+
P3-T5 + P4-T3 ──> P5-T3 (Micro-interactions)
|
|
777
|
+
P5-T3 ──> P5-T4 (Visual QA)
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
782
|
+
## Task Summary
|
|
783
|
+
|
|
784
|
+
| ID | Phase | Task | Key Output |
|
|
785
|
+
|----|-------|------|------------|
|
|
786
|
+
| P0-T1 | 0 | Initialize shadcn/ui | `components.json`, `src/lib/utils.ts` |
|
|
787
|
+
| P0-T2 | 0 | Configure design tokens in globals.css | `src/styles/globals.css` |
|
|
788
|
+
| P0-T3 | 0 | Load fonts (Pretendard + Inter) | `src/app/layout.tsx` (fonts) |
|
|
789
|
+
| P0-T4 | 0 | Create shared TypeScript types | `src/features/user-management/types/index.ts` |
|
|
790
|
+
| P0-T5 | 0 | Create mock data | `src/lib/mock-data.ts` |
|
|
791
|
+
| P0-T6 | 0 | Create utility functions | `src/features/user-management/utils/format.ts` |
|
|
792
|
+
| P0-T7 | 0 | Add placeholder images | `public/avatars/`, `public/profile/` |
|
|
793
|
+
| P1-T1 | 1 | Install shadcn/ui components (batch) | 14 files in `src/components/ui/` |
|
|
794
|
+
| P1-T2 | 1 | Customize Button variants | `src/components/ui/button.tsx` |
|
|
795
|
+
| P1-T3 | 1 | Customize Table component | `src/components/ui/table.tsx` |
|
|
796
|
+
| P1-T4 | 1 | Customize Input component | `src/components/ui/input.tsx` |
|
|
797
|
+
| P1-T5 | 1 | Customize Select component | `src/components/ui/select.tsx` |
|
|
798
|
+
| P1-T6 | 1 | Customize Badge component | `src/components/ui/badge.tsx` |
|
|
799
|
+
| P1-T7 | 1 | Customize Avatar component | `src/components/ui/avatar.tsx` |
|
|
800
|
+
| P1-T8 | 1 | Customize Switch component | `src/components/ui/switch.tsx` |
|
|
801
|
+
| P1-T9 | 1 | Customize Tabs component | `src/components/ui/tabs.tsx` |
|
|
802
|
+
| P1-T10 | 1 | Customize Separator component | `src/components/ui/separator.tsx` |
|
|
803
|
+
| P1-T11 | 1 | Customize Accordion component | `src/components/ui/accordion.tsx` |
|
|
804
|
+
| P1-T12 | 1 | Customize Label component | `src/components/ui/label.tsx` |
|
|
805
|
+
| P1-T13 | 1 | Build PageHeader common component | `src/components/common/page-header.tsx` |
|
|
806
|
+
| P2-T1 | 2 | Build AppSidebar component | `src/components/layouts/app-sidebar.tsx` |
|
|
807
|
+
| P2-T2 | 2 | Build ContentLayout wrapper | `src/components/layouts/content-layout.tsx` |
|
|
808
|
+
| P2-T3 | 2 | Build root layout | `src/app/layout.tsx` |
|
|
809
|
+
| P3-T1 | 3 | Build TabNavigation component | `src/components/user-management/tab-navigation.tsx` |
|
|
810
|
+
| P3-T2 | 3 | Build SearchBar component | `src/components/user-management/search-bar.tsx` |
|
|
811
|
+
| P3-T3 | 3 | Build PaginationControls component | `src/components/user-management/pagination-controls.tsx` |
|
|
812
|
+
| P3-T4 | 3 | Build UserTable component | `src/components/user-management/user-table.tsx` |
|
|
813
|
+
| P3-T5 | 3 | Assemble Dashboard page | `src/app/page.tsx` |
|
|
814
|
+
| P4-T1 | 4 | Build ProfileImages component | `src/components/user-management/profile-images.tsx` |
|
|
815
|
+
| P4-T2 | 4 | Build UserDetailForm component | `src/components/user-management/user-detail-form.tsx` |
|
|
816
|
+
| P4-T3 | 4 | Assemble User Detail page | `src/app/users/[id]/page.tsx` |
|
|
817
|
+
| P5-T1 | 5 | Accessibility -- keyboard navigation | Updates across components |
|
|
818
|
+
| P5-T2 | 5 | Accessibility -- ARIA and screen reader | Updates across components |
|
|
819
|
+
| P5-T3 | 5 | Micro-interactions and transitions | Style updates across components |
|
|
820
|
+
| P5-T4 | 5 | Final visual QA against design specs | Bug fixes, adjustments |
|
|
821
|
+
|
|
822
|
+
**Total: 31 tasks across 6 phases**
|