Skip to content

pfplay/pfplay-admin

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

10 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

PFPlay Admin Console

PFPlay ์„œ๋น„์Šค๋ฅผ ์œ„ํ•œ ๊ฐ€์ƒ ์œ ์ € ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๊ด€๋ฆฌ ์ฝ˜์†”์ž…๋‹ˆ๋‹ค.

๐ŸŽฏ ์ฃผ์š” ๊ธฐ๋Šฅ

1. ๊ฐ€์ƒ ์œ ์ € ๊ด€๋ฆฌ (Virtual Users)

  • ๊ฐ€์ƒ ์œ ์ € ์ƒ์„ฑ (๊ฐœ๋ณ„/๋Œ€๋Ÿ‰)
  • ์œ ์ € ๋ชฉ๋ก ์กฐํšŒ ๋ฐ ๊ด€๋ฆฌ
  • ์œ ์ € ๋“ฑ๊ธ‰ ๊ด€๋ฆฌ (Free, Premium, VIP)
  • ํŒŒํ‹ฐ๋ฃธ ๋ฐฐ์ • ์ƒํƒœ ์ถ”์ 
  • ๋Œ€๋Ÿ‰ ์ƒ์„ฑ (์ตœ๋Œ€ 100๋ช…)

2. ํŒŒํ‹ฐ๋ฃธ ๊ด€๋ฆฌ (Party Rooms)

  • ํŒŒํ‹ฐ๋ฃธ ์ƒ์„ฑ/์‚ญ์ œ
  • ์œ ์ € ๋ฐฐ์ • ๋ฐ ์ œ๊ฑฐ (๋ฉ€ํ‹ฐ ์…€๋ ‰ํŠธ)
  • DJ ํ ๊ด€๋ฆฌ (ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ + ํŠธ๋ž™ ์„ ํƒ)
  • ๋ฃธ ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง

3. ์‹œ๋‚˜๋ฆฌ์˜ค ์‹คํ–‰ (Scenarios)

  • ์ฑ„ํŒ… ์‹œ๋‚˜๋ฆฌ์˜ค: ์Šคํฌ๋ฆฝํŠธ ๊ธฐ๋ฐ˜ ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€ ์ž๋™ ์ƒ์„ฑ
  • ๋ฐ˜์‘ ์‹œ๋‚˜๋ฆฌ์˜ค: ์ข‹์•„์š”/์žก๊ธฐ ๋ฐ˜์‘ ์ž๋™ ์ƒ์„ฑ

๐Ÿ”ง ๊ธฐ์ˆ  ์Šคํƒ

Core

  • React 18.3.1 - UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • TypeScript 5 - ํƒ€์ž… ์•ˆ์ „์„ฑ
  • Vite 6.0.11 - ๋นŒ๋“œ ๋„๊ตฌ ๋ฐ ๊ฐœ๋ฐœ ์„œ๋ฒ„ (ํฌํŠธ 3000)
  • React Router DOM 7.1.3 - ํด๋ผ์ด์–ธํŠธ ๋ผ์šฐํŒ…

UI & Styling

  • Tailwind CSS 4.1.9 - ์œ ํ‹ธ๋ฆฌํ‹ฐ CSS ํ”„๋ ˆ์ž„์›Œํฌ
  • shadcn/ui - Radix UI ๊ธฐ๋ฐ˜ ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • Radix UI - ํ—ค๋“œ๋ฆฌ์Šค UI ํ”„๋ฆฌ๋ฏธํ‹ฐ๋ธŒ
  • Lucide React 0.454.0 - ์•„์ด์ฝ˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • Class Variance Authority 0.7.1 - ์ปดํฌ๋„ŒํŠธ variant ๊ด€๋ฆฌ

State Management

  • Zustand 5.0.9 - ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ
  • Immer - ๋ถˆ๋ณ€ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
  • TanStack Query 5.62.12 - ์„œ๋ฒ„ ์ƒํƒœ ๊ด€๋ฆฌ

Forms & Validation

  • React Hook Form 7.60.0 - ํผ ์ƒํƒœ ๊ด€๋ฆฌ
  • Zod 3.25.76 - TypeScript ์šฐ์„  ์Šคํ‚ค๋งˆ ๊ฒ€์ฆ

Utilities

  • date-fns 4.1.0 - ๋‚ ์งœ ์œ ํ‹ธ๋ฆฌํ‹ฐ
  • Sonner - ํ† ์ŠคํŠธ ์•Œ๋ฆผ
  • clsx 2.1.1 - ์กฐ๊ฑด๋ถ€ className ์œ ํ‹ธ๋ฆฌํ‹ฐ
  • tailwind-merge 3.3.1 - CSS ํด๋ž˜์Šค ๋ณ‘ํ•ฉ

๐Ÿ—๏ธ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

ํ”„๋กœ์ ํŠธ๋Š” Feature-Sliced Design (FSD) ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

src/
โ”œโ”€โ”€ app/                    # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ดˆ๊ธฐํ™” ๋ฐ ๋ ˆ์ด์•„์›ƒ
โ”‚   โ””โ”€โ”€ layout.tsx         # ์‚ฌ์ด๋“œ๋ฐ” ๋„ค๋น„๊ฒŒ์ด์…˜์ด ํฌํ•จ๋œ ๋ฉ”์ธ ๋ ˆ์ด์•„์›ƒ
โ”‚
โ”œโ”€โ”€ pages/                  # ๋ผ์šฐํŠธ๋ณ„ ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ
โ”‚   โ”œโ”€โ”€ users-page.tsx     # ๊ฐ€์ƒ ์œ ์ € ๊ด€๋ฆฌ ํŽ˜์ด์ง€
โ”‚   โ”œโ”€โ”€ rooms-page.tsx     # ํŒŒํ‹ฐ๋ฃธ ๊ด€๋ฆฌ ํŽ˜์ด์ง€
โ”‚   โ””โ”€โ”€ scenarios-page.tsx # ์‹œ๋‚˜๋ฆฌ์˜ค ์‹คํ–‰ ํŽ˜์ด์ง€
โ”‚
โ”œโ”€โ”€ widgets/                # ํŽ˜์ด์ง€ ๋ ˆ๋ฒจ ๋ณตํ•ฉ ์ปดํฌ๋„ŒํŠธ
โ”‚   โ”œโ”€โ”€ users/ui/
โ”‚   โ”‚   โ””โ”€โ”€ users-widget.tsx
โ”‚   โ”œโ”€โ”€ rooms/ui/
โ”‚   โ”‚   โ””โ”€โ”€ rooms-widget.tsx
โ”‚   โ””โ”€โ”€ scenarios/ui/
โ”‚       โ””โ”€โ”€ scenarios-widget.tsx
โ”‚
โ”œโ”€โ”€ features/               # ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ธฐ๋Šฅ ๋ชจ๋“ˆ
โ”‚   โ”œโ”€โ”€ users/ui/
โ”‚   โ”‚   โ”œโ”€โ”€ user-create-form.tsx
โ”‚   โ”‚   โ”œโ”€โ”€ users-list-table.tsx
โ”‚   โ”‚   โ””โ”€โ”€ bulk-actions.tsx
โ”‚   โ”œโ”€โ”€ rooms/ui/
โ”‚   โ”‚   โ”œโ”€โ”€ rooms-list-panel.tsx
โ”‚   โ”‚   โ”œโ”€โ”€ user-assignment-panel.tsx
โ”‚   โ”‚   โ”œโ”€โ”€ room-selector.tsx
โ”‚   โ”‚   โ””โ”€โ”€ dj-queue-panel.tsx
โ”‚   โ””โ”€โ”€ scenarios/ui/
โ”‚       โ”œโ”€โ”€ chat-scenario-panel.tsx
โ”‚       โ””โ”€โ”€ reaction-scenario-panel.tsx
โ”‚
โ”œโ”€โ”€ shared/                 # ๊ณต์œ  ๋ฆฌ์†Œ์Šค
โ”‚   โ”œโ”€โ”€ types/             # TypeScript ํƒ€์ž… ์ •์˜
โ”‚   โ”‚   โ””โ”€โ”€ index.ts
โ”‚   โ”œโ”€โ”€ store/             # Zustand ์ƒํƒœ ๊ด€๋ฆฌ
โ”‚   โ”‚   โ”œโ”€โ”€ users-store.ts
โ”‚   โ”‚   โ””โ”€โ”€ rooms-store.ts
โ”‚   โ”œโ”€โ”€ hooks/             # ์ปค์Šคํ…€ React ํ›…
โ”‚   โ”‚   โ”œโ”€โ”€ use-chat-scenario.ts
โ”‚   โ”‚   โ””โ”€โ”€ use-reaction-scenario.ts
โ”‚   โ””โ”€โ”€ lib/               # ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ฐ API ํด๋ผ์ด์–ธํŠธ
โ”‚       โ”œโ”€โ”€ api-client.ts
โ”‚       โ”œโ”€โ”€ constants.ts
โ”‚       โ”œโ”€โ”€ utils.ts
โ”‚       โ””โ”€โ”€ index.ts
โ”‚
โ”œโ”€โ”€ components/
โ”‚   โ””โ”€โ”€ ui/                # shadcn/ui ์ปดํฌ๋„ŒํŠธ
โ”‚       โ”œโ”€โ”€ badge.tsx
โ”‚       โ”œโ”€โ”€ button.tsx
โ”‚       โ”œโ”€โ”€ card.tsx
โ”‚       โ”œโ”€โ”€ checkbox.tsx
โ”‚       โ”œโ”€โ”€ input.tsx
โ”‚       โ”œโ”€โ”€ label.tsx
โ”‚       โ”œโ”€โ”€ select.tsx
โ”‚       โ”œโ”€โ”€ table.tsx
โ”‚       โ”œโ”€โ”€ tabs.tsx
โ”‚       โ””โ”€โ”€ textarea.tsx
โ”‚
โ”œโ”€โ”€ App.tsx                # ๋ผ์šฐํ„ฐ ์„ค์ •
โ”œโ”€โ”€ main.tsx               # ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ง„์ž…์ 
โ””โ”€โ”€ globals.css            # Tailwind๋ฅผ ํฌํ•จํ•œ ์ „์—ญ ์Šคํƒ€์ผ

๐Ÿ“ FSD ๋ ˆ์ด์–ด ๊ตฌ์กฐ

App Layer (app/)
    โ†“
Pages Layer (pages/)
    โ†“
Widgets Layer (widgets/)
    โ†“
Features Layer (features/)
    โ†“
Shared Layer (shared/)

๋ ˆ์ด์–ด ๊ทœ์น™:

  1. app: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ดˆ๊ธฐํ™”, ๋ผ์šฐํŒ…, ์ „์—ญ ๋ ˆ์ด์•„์›ƒ
  2. pages: ํŽ˜์ด์ง€๋ณ„ ์œ„์ ฏ ์กฐํ•ฉ
  3. widgets: ๋…๋ฆฝ์ ์ธ ํŽ˜์ด์ง€ ๋ธ”๋ก, ์—ฌ๋Ÿฌ feature ์กฐํ•ฉ
  4. features: ์‚ฌ์šฉ์ž ์‹œ๋‚˜๋ฆฌ์˜ค ๊ตฌํ˜„, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง
  5. shared: ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ, ๋ชจ๋“  ๋ ˆ์ด์–ด์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

๊ฐ ๋ ˆ์ด์–ด๋Š” ํ•˜์œ„ ๋ ˆ์ด์–ด๋งŒ ์˜์กดํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์ˆœํ™˜ ์ฐธ์กฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

๐Ÿช ์ƒํƒœ ๊ด€๋ฆฌ

useUsersStore

State:

{
  users: VirtualUser[]
  isLoading: boolean
  error: string | null
}

Actions:

fetchUsers(): Promise<void>           // ์œ ์ € ๋ชฉ๋ก ์กฐํšŒ
createUser(userData): Promise<void>   // ์œ ์ € ์ƒ์„ฑ
deleteUser(userId): Promise<void>     // ์œ ์ € ์‚ญ์ œ
updateUser(userId, updates): void     // ์œ ์ € ์ •๋ณด ์ˆ˜์ •
getUserById(userId): VirtualUser | undefined
getUsersInRoom(roomId): VirtualUser[] // ํŠน์ • ๋ฃธ์˜ ์œ ์ € ์กฐํšŒ
getAvailableUsers(): VirtualUser[]    // ๋ฐฐ์ • ๊ฐ€๋Šฅํ•œ ์œ ์ € ์กฐํšŒ

useRoomsStore

State:

{
  rooms: PartyRoom[]
  selectedRoomId: string | null
  isLoading: boolean
  error: string | null
}

Actions:

fetchRooms(): Promise<void>                      // ๋ฃธ ๋ชฉ๋ก ์กฐํšŒ
createRoom(roomData): Promise<void>              // ๋ฃธ ์ƒ์„ฑ
deleteRoom(roomId): Promise<void>                // ๋ฃธ ์‚ญ์ œ
selectRoom(roomId): void                         // ๋ฃธ ์„ ํƒ
getSelectedRoom(): PartyRoom | undefined         // ์„ ํƒ๋œ ๋ฃธ ์กฐํšŒ
assignUsers(roomId, userIds): Promise<void>      // ์œ ์ € ๋ฐฐ์ •
removeUser(roomId, userId): Promise<void>        // ์œ ์ € ์ œ๊ฑฐ

๐Ÿ“ฆ ๋„๋ฉ”์ธ ํƒ€์ž…

VirtualUser

{
  id: string
  username: string
  email: string
  profileImage?: string
  tier: "free" | "premium" | "vip"
  status: "active" | "inactive"
  isInRoom: boolean
  currentRoomId?: string
  createdAt: string
  lastActiveAt: string
}

PartyRoom

{
  id: string
  name: string
  maxCapacity: number
  currentUsers: number
  createdAt: string
  status: "active" | "inactive"
  userIds: string[]
}

DJQueueEntry

{
  id: string
  roomId: string
  userId: string
  playlistId: string
  trackId: string
  position: number
  createdAt: string
}

ChatAssignment

{
  userId: string
  username: string
  message: string
  order: number
}

ReactionAssignment

{
  userId: string
  username: string
  type: "like" | "grab"
  delay: number
}

๐ŸŽฃ ์ปค์Šคํ…€ ํ›…

useChatScenario

์ฑ„ํŒ… ์Šคํฌ๋ฆฝํŠธ ๋ถ„์„ ๋ฐ ์œ ์ € ๋ฐฐ์น˜ ๋กœ์ง์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋Šฅ:

  • ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋ผ์ธ๋ณ„๋กœ ํŒŒ์‹ฑ
  • ๋ฃธ ๋‚ด ์œ ์ €์—๊ฒŒ ์ˆœํ™˜ ๋ฐฐ์ •
  • ๋ฐฐ์น˜ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ œ๊ณต
  • ์‹คํ–‰ ์‹œ๋ฎฌ๋ ˆ์ด์…˜

์‚ฌ์šฉ ์˜ˆ:

const {
  script,
  setScript,
  assignments,
  analyzeScript
} = useChatScenario()

useReactionScenario

๋ฆฌ์•ก์…˜ ์ด๋ฒคํŠธ ์ž๋™ ์ƒ์„ฑ ๋กœ์ง์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋Šฅ:

  • ์ฐธ์—ฌ์œจ 70% ์ž๋™ ์ ์šฉ
  • ์ข‹์•„์š”/์žก๊ธฐ 50:50 ๋ถ„๋ฐฐ
  • 1-15์ดˆ ๋žœ๋ค ๋”œ๋ ˆ์ด
  • ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ›„ ์‹คํ–‰

์‚ฌ์šฉ ์˜ˆ:

const {
  assignments,
  generateAssignments,
  getLikeAssignments,
  getGrabAssignments
} = useReactionScenario()

๐ŸŽฌ ์‹œ๋‚˜๋ฆฌ์˜ค ์‹œ์Šคํ…œ

์ฑ„ํŒ… ์‹œ๋‚˜๋ฆฌ์˜ค ์›Œํฌํ”Œ๋กœ์šฐ

  1. ์—ฌ๋Ÿฌ ์ค„์˜ ์ฑ„ํŒ… ์Šคํฌ๋ฆฝํŠธ ์ž…๋ ฅ
  2. analyzeScript() ํ˜ธ์ถœํ•˜์—ฌ ํŒŒ์‹ฑ
  3. ๋ฃธ ๋‚ด ์œ ์ €์—๊ฒŒ ์ˆœํ™˜ ๋ฐฐ์ •
  4. ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋กœ ๋ฐฐ์ • ๊ฒฐ๊ณผ ํ™•์ธ
  5. ์‹คํ–‰ํ•˜์—ฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์ˆ˜ํ–‰

๋ฐ˜์‘ ์‹œ๋‚˜๋ฆฌ์˜ค ์›Œํฌํ”Œ๋กœ์šฐ

  1. ๋ฃธ๊ณผ ์œ ์ € ์„ ํƒ
  2. generateAssignments() ํ˜ธ์ถœ
  3. ์ž๋™์œผ๋กœ ์ฐธ์—ฌ์œจ 70% ์ ์šฉ
  4. ์ข‹์•„์š”/์žก๊ธฐ 50:50 ๋ถ„๋ฐฐ
  5. ๊ฐ ์œ ์ €์—๊ฒŒ 1-15์ดˆ ๋žœ๋ค ๋”œ๋ ˆ์ด
  6. ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ™•์ธ ํ›„ ์‹คํ–‰

๐Ÿš€ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •

์‚ฌ์ „ ์š”๊ตฌ์‚ฌํ•ญ

  • Node.js 18 ์ด์ƒ
  • npm ๋˜๋Š” pnpm

์„ค์น˜ ๋ฐ ์‹คํ–‰

# ์˜์กด์„ฑ ์„ค์น˜
npm install

# ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰ (ํฌํŠธ 3000)
npm run dev

# ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ
npm run build

# ๋นŒ๋“œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
npm run preview

# ๋ฆฐํŠธ ์‹คํ–‰
npm run lint

๐Ÿ”Œ API ์—ฐ๋™

ํ˜„์žฌ ์ƒํƒœ

ํ˜„์žฌ๋Š” Mock ๋ฐ์ดํ„ฐ๋กœ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค:

  • ๊ฐ€์ƒ ์œ ์ € 15๋ช… ์ž๋™ ์ƒ์„ฑ
  • ํŒŒํ‹ฐ๋ฃธ 2๊ฐœ (ํ•˜๋‚˜๋Š” 12๋ช… ํฌํ•จ, ํ•˜๋‚˜๋Š” ๋นˆ ๋ฃธ)

์‹ค์ œ API ์—ฐ๋™ ๋ฐฉ๋ฒ•

  1. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
VITE_API_BASE_URL=http://localhost:8080
  1. API Client ํ™œ์„ฑํ™” src/shared/lib/api-client.ts์˜ ์ฃผ์„์„ ํ•ด์ œํ•˜์„ธ์š”.

  2. Store ์ˆ˜์ • ๊ฐ store(users-store.ts, rooms-store.ts)์˜ API ํ˜ธ์ถœ ์ฃผ์„์„ ํ•ด์ œํ•˜๊ณ , Mock ๋ฐ์ดํ„ฐ ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ•˜์„ธ์š”.

๐ŸŽจ ๋””์ž์ธ ์‹œ์Šคํ…œ

ํ…Œ๋งˆ

  • ๊ธฐ๋ณธ: ๋‹คํฌ ๋ชจ๋“œ ์šฐ์„ 
  • ์˜๊ฐ: TOSS ๋””์ž์ธ ์‹œ์Šคํ…œ

์ƒ‰์ƒ ์‹œ์Šคํ…œ (OKLCH)

--primary: oklch(0.61 0.24 264)      /* Purple */
--background: oklch(0.11 0 0)        /* Near Black */
--foreground: oklch(0.98 0 0)        /* Near White */
--accent: oklch(0.89 0 0)            /* Gray */
--destructive: oklch(0.61 0.20 28)   /* Red */

ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ

  • Font Family: Geist (sans) & Geist Mono
  • Scale: Tailwind ๊ธฐ๋ณธ ์Šค์ผ€์ผ
  • Weight: 400 (Regular), 500 (Medium), 600 (Semibold)

Spacing & Radius

  • Base Radius: 0.5rem
  • Spacing: Tailwind ์Šค์ผ€์ผ (4, 6, 8, 12, 16, 20, 24...)

๐Ÿ“ ๊ฒฝ๋กœ ๋ณ„์นญ

TypeScript์™€ Vite๋Š” ๋‹ค์Œ ๊ฒฝ๋กœ ๋ณ„์นญ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค:

// @ = src/
import { cn } from "@/shared/lib/utils"
import { Button } from "@/components/ui/button"
import { useUsersStore } from "@/shared/store/users-store"

tsconfig.json ์„ค์ •:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

์ค‘์š”: @/ ๋ณ„์นญ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํŒŒ์ผ์ด src/ ํด๋” ๋‚ด๋ถ€์— ์œ„์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ”„ ๋ฐ์ดํ„ฐ ํ”Œ๋กœ์šฐ ์˜ˆ์‹œ

์œ ์ € ๋ฐฐ์ • ์›Œํฌํ”Œ๋กœ์šฐ

UserAssignmentPanel (UI)
    โ†“
User selects room & users
    โ†“
assignUsers() action called
    โ†“
useRoomsStore (Zustand)
    โ†“
State updated:
  - room.userIds += selected users
  - room.currentUsers += count
    โ†“
useUsersStore updated:
  - user.isInRoom = true
  - user.currentRoomId = roomId
    โ†“
UI re-renders
    โ†“
Toast notification (Sonner)

๐Ÿ“ ์ฝ”๋“œ ์ปจ๋ฒค์…˜

ํŒŒ์ผ๋ช…

  • ์ปดํฌ๋„ŒํŠธ: kebab-case.tsx
  • ์Šคํ† ์–ด: feature-store.ts
  • ํ›…: use-feature-name.ts
  • ํƒ€์ž…: index.ts

Import ์ˆœ์„œ

// 1. React ๋ฐ ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
import { useState } from "react"
import { useQuery } from "@tanstack/react-query"

// 2. ๋‚ด๋ถ€ ์ปดํฌ๋„ŒํŠธ
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"

// 3. ์•„์ด์ฝ˜
import { Plus, Trash2 } from "lucide-react"

// 4. ์Šคํ† ์–ด ๋ฐ ํ›…
import { useUsersStore } from "@/shared/store/users-store"
import { useChatScenario } from "@/shared/hooks/use-chat-scenario"

// 5. ์œ ํ‹ธ๋ฆฌํ‹ฐ
import { cn, formatDate } from "@/shared/lib/utils"

// 6. ํƒ€์ž…
import type { VirtualUser } from "@/shared/types"

์ปดํฌ๋„ŒํŠธ ๊ตฌ์กฐ

"use client" // ํ•„์š”ํ•œ ๊ฒฝ์šฐ๋งŒ

// Imports

export function ComponentName() {
  // 1. Hooks
  const store = useStore()
  const [state, setState] = useState()

  // 2. Derived state
  const computed = useMemo(() => ...)

  // 3. Handlers
  const handleClick = () => { ... }

  // 4. Effects
  useEffect(() => { ... }, [])

  // 5. Render
  return (...)
}

๐Ÿงช Mock ๋ฐ์ดํ„ฐ

๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์ž๋™ ์ƒ์„ฑ๋˜๋Š” Mock ๋ฐ์ดํ„ฐ:

๊ฐ€์ƒ ์œ ์ € (15๋ช…)

  • Free tier: 8๋ช…
  • Premium tier: 4๋ช…
  • VIP tier: 3๋ช…
  • ์ผ๋ถ€๋Š” ๋ฃธ์— ๋ฐฐ์ •๋จ

ํŒŒํ‹ฐ๋ฃธ (2๊ฐœ)

  • "๋ฉ”์ธ ํŒŒํ‹ฐ๋ฃธ": 12๋ช… ์œ ์ € ํฌํ•จ, ์ตœ๋Œ€ 20๋ช…
  • "VIP ๋ผ์šด์ง€": ๋นˆ ๋ฃธ, ์ตœ๋Œ€ 10๋ช…

๐Ÿ” ์ฃผ์š” ๋””๋ ‰ํ† ๋ฆฌ ์„ค๋ช…

/app - Application Layer

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์—ญ ์„ค์ •๊ณผ ๋ ˆ์ด์•„์›ƒ์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

  • ๋ผ์šฐํŒ… ๊ตฌ์„ฑ
  • ์ „์—ญ ํ”„๋กœ๋ฐ”์ด๋”
  • ๋ฉ”์ธ ๋ ˆ์ด์•„์›ƒ (์‚ฌ์ด๋“œ๋ฐ” ๋„ค๋น„๊ฒŒ์ด์…˜)

/pages - Pages Layer

๋ผ์šฐํŠธ๋ณ„ ํŽ˜์ด์ง€๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค. Widget์„ ์กฐํ•ฉํ•˜์—ฌ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

  • ์–‡์€ ๋ž˜ํผ ์—ญํ• 
  • ๋ผ์šฐํŠธ๋‹น ํ•˜๋‚˜์˜ ํŽ˜์ด์ง€

/widgets - Widgets Layer

ํŽ˜์ด์ง€ ์ˆ˜์ค€์˜ ๋ณตํ•ฉ ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค.

  • ์—ฌ๋Ÿฌ Feature ์กฐํ•ฉ
  • ํƒญ ๊ธฐ๋ฐ˜ ์ธํ„ฐํŽ˜์ด์Šค
  • ๋…๋ฆฝ์ ์ด๊ณ  ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ธ”๋ก

/features - Features Layer

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์ด ํฌํ•จ๋œ ๊ธฐ๋Šฅ ๋‹จ์œ„ ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค.

  • ์‚ฌ์šฉ์ž ๋Œ€๋ฉด ๊ธฐ๋Šฅ
  • UI๋งŒ ํฌํ•จ, ๋„๋ฉ”์ธ ๋กœ์ง์€ shared์—
  • Feature๋ณ„๋กœ ์กฐ์งํ™”

/shared - Shared Layer

๋ชจ๋“  ๋ ˆ์ด์–ด์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ณต์œ  ๋ฆฌ์†Œ์Šค์ž…๋‹ˆ๋‹ค.

  • types: ๋„๋ฉ”์ธ ํƒ€์ž… ์ •์˜
  • store: Zustand ์ƒํƒœ ๊ด€๋ฆฌ
  • hooks: ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปค์Šคํ…€ ํ›…
  • lib: ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ ๋ฐ API ํด๋ผ์ด์–ธํŠธ

/components/ui - UI Components

shadcn/ui ๊ธฐ๋ฐ˜์˜ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ UI ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค.

  • Radix UI ๋ž˜ํผ
  • Tailwind๋กœ ์Šคํƒ€์ผ๋ง
  • Variant ์‹œ์Šคํ…œ

๐ŸŽฏ ํ•ต์‹ฌ ์•„ํ‚คํ…์ฒ˜ ํŒจํ„ด

1. FSD ๋ ˆ์ด์–ด ๋ถ„๋ฆฌ

  • ๋ช…ํ™•ํ•œ ์˜์กด์„ฑ ๋ฐฉํ–ฅ: App โ†’ Pages โ†’ Widgets โ†’ Features โ†’ Shared
  • ์ˆœํ™˜ ์˜์กด์„ฑ ์—†์Œ
  • Feature๋Š” ๋‹ค๋ฅธ Feature์— ์˜์กดํ•  ์ˆ˜ ์—†์Œ

2. Zustand ์Šคํ† ์–ด ํŒจํ„ด

  • /shared/store์— ์ค‘์•™ ์ง‘์ค‘ํ™”
  • Devtools ๋ฏธ๋“ค์›จ์–ด
  • Mock ๋ฐ์ดํ„ฐ๋กœ ๊ฐœ๋ฐœ
  • ์‹ค์ œ API ์—ฐ๋™ ์ค€๋น„ ์™„๋ฃŒ

3. UI ์ปดํฌ๋„ŒํŠธ ์กฐํ•ฉ

  • shadcn/ui ๊ธฐ๋ณธ ์ปดํฌ๋„ŒํŠธ
  • Feature ์ปดํฌ๋„ŒํŠธ๊ฐ€ UI ์š”์†Œ ์กฐํ•ฉ
  • Widget์ด Feature ์กฐํ•ฉ
  • Page๊ฐ€ Widget ์‚ฌ์šฉ

4. ์ปค์Šคํ…€ ํ›… ์ถ”์ƒํ™”

  • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ํ›…์œผ๋กœ ๋ถ„๋ฆฌ
  • ์ปดํฌ๋„ŒํŠธ ๊ฐ„ ์žฌ์‚ฌ์šฉ
  • TypeScript๋กœ ํƒ€์ž… ์•ˆ์ „์„ฑ
  • ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅํ•œ ๋กœ์ง ๋ถ„๋ฆฌ

๐Ÿ“„ ๋ผ์ด์„ ์Šค

MIT

๐Ÿค ๊ธฐ์—ฌ

์ด์Šˆ ๋ฐ Pull Request๋ฅผ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค.


Built with โค๏ธ using React + Vite + Tailwind CSS

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages