๐ŸŽญ ๊ณต์—ฐ ํ‹ฐ์ผ“ ๊ตฌ๋… ์„œ๋น„์Šค - ์›”๊ฐ„ ๊ณต์—ฐ ํ๋ ˆ์ด์…˜ & ์ž๋™ ์˜ˆ๋งค - ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ๋ ˆ์‹œํ”ผ
์ค‘๊ธ‰ PHP + JavaScript Laravel + React + Inertia.js AI: Claude

๐ŸŽญ ๊ณต์—ฐ ํ‹ฐ์ผ“ ๊ตฌ๋… ์„œ๋น„์Šค - ์›”๊ฐ„ ๊ณต์—ฐ ํ๋ ˆ์ด์…˜ & ์ž๋™ ์˜ˆ๋งค

๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ช… performance-subscription-service
์กฐํšŒ์ˆ˜ 50 0๊ฑด ์ œ์ถœ

๋ฌธ์ œ ์„ค๋ช…

ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

๋งค๋‹ฌ ์—„์„ ๋œ ๊ณต์—ฐ(์—ฐ๊ทน, ๋ฎค์ง€์ปฌ, ์ฝ˜์„œํŠธ)์„ ๊ตฌ๋…์ž์—๊ฒŒ ์ถ”์ฒœํ•˜๊ณ  ์ž๋™์œผ๋กœ ํ‹ฐ์ผ“์„ ์˜ˆ๋งคํ•ด์ฃผ๋Š” ๊ตฌ๋… ์„œ๋น„์Šค ํ”Œ๋žซํผ์ž…๋‹ˆ๋‹ค. ๋„ทํ”Œ๋ฆญ์Šค์ฒ˜๋Ÿผ ๊ณต์—ฐ์„ ๊ตฌ๋…ํ•˜๋Š” ์ƒˆ๋กœ์šด ๋ฌธํ™” ์†Œ๋น„ ๋ฐฉ์‹์„ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค.

ํ•„์ˆ˜ ๊ธฐ๋Šฅ

  • ๐ŸŽŸ๏ธ ๊ตฌ๋… ํ”Œ๋žœ ์†Œ๊ฐœ ํŽ˜์ด์ง€: ์›”๊ฐ„/๋ถ„๊ธฐ๋ณ„/์—ฐ๊ฐ„ ํ”Œ๋žœ ๋น„๊ต ์นด๋“œ, ๊ฐ€๊ฒฉ ๋ฐ ํ˜œํƒ ์•ˆ๋‚ด
  • ๐ŸŽญ ์ด๋ฒˆ ๋‹ฌ ํ๋ ˆ์ด์…˜ ๊ณต์—ฐ ๋ชฉ๋ก: ์žฅ๋ฅด๋ณ„ ํ•„ํ„ฐ๋ง(์—ฐ๊ทน/๋ฎค์ง€์ปฌ/์ฝ˜์„œํŠธ), ๊ณต์—ฐ ํฌ์Šคํ„ฐ ์ธ๋„ค์ผ, ์ƒ์„ธ ์ •๋ณด ๋ชจ๋‹ฌ
  • ๐Ÿ“… ๊ตฌ๋… ์‹ ์ฒญ ํผ: ์„ ํ˜ธ ์žฅ๋ฅด ์„ ํƒ, ๊ด€๋žŒ ๊ฐ€๋Šฅ ์š”์ผ ์„ค์ •, ๊ฒฐ์ œ ์ •๋ณด ์ž…๋ ฅ
  • โญ ๊ตฌ๋…์ž ์ „์šฉ ๋ฆฌ๋ทฐ ๊ฒŒ์‹œํŒ: ๊ด€๋žŒํ•œ ๊ณต์—ฐ ๋ณ„์  ๋ฐ ํ›„๊ธฐ ์ž‘์„ฑ, ๋‹ค๋ฅธ ๊ตฌ๋…์ž ๋ฆฌ๋ทฐ ์—ด๋žŒ
  • ๐Ÿ”” ๋งˆ์ดํŽ˜์ด์ง€: ๊ตฌ๋… ์ƒํƒœ ํ™•์ธ, ์ด๋ฒˆ ๋‹ฌ ์˜ˆ๋งค๋œ ๊ณต์—ฐ ์ •๋ณด, ๊ตฌ๋… ํžˆ์Šคํ† ๋ฆฌ
  • ๐Ÿ’ณ ๊ฒฐ์ œ ๋‚ด์—ญ ์กฐํšŒ: ์›”๋ณ„ ๊ฒฐ์ œ ๊ธˆ์•ก, ํ™˜๋ถˆ ์ •์ฑ… ์•ˆ๋‚ด

์„ ํƒ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ (๋ณด๋„ˆ์Šค)

  • ๐ŸŽ ์นœ๊ตฌ ์ดˆ๋Œ€ ๋ฆฌ์›Œ๋“œ: ์ถ”์ฒœ ์ฝ”๋“œ ์ƒ์„ฑ ๋ฐ ํ• ์ธ ์ฟ ํฐ ์ง€๊ธ‰
  • ๐Ÿ“Š ๊ตฌ๋…์ž ์ทจํ–ฅ ๋ถ„์„: ๊ด€๋žŒ ์ด๋ ฅ ๊ธฐ๋ฐ˜ ์„ ํ˜ธ ์žฅ๋ฅด ์ฐจํŠธ
  • ๐ŸŽจ ๊ณต์—ฐ ํฌ์Šคํ„ฐ ๊ฐค๋Ÿฌ๋ฆฌ: ๊ณผ๊ฑฐ ํ๋ ˆ์ด์…˜ ์•„์นด์ด๋ธŒ, ๋ฌดํ•œ ์Šคํฌ๋กค
  • ๐Ÿ”„ ๊ณต์—ฐ ๋ณ€๊ฒฝ ์š”์ฒญ: ์ด๋ฒˆ ๋‹ฌ ๊ณต์—ฐ์ด ๋งˆ์Œ์— ์•ˆ ๋“ค ๊ฒฝ์šฐ ๋Œ€์ฒด ๊ณต์—ฐ ์„ ํƒ
  • ๐Ÿ“ฑ ๋ชจ๋ฐ”์ผ ๋ฐ˜์‘ํ˜• ๋””์ž์ธ: ํ„ฐ์น˜ ์ œ์Šค์ฒ˜ ๊ธฐ๋ฐ˜ ์Šค์™€์ดํ”„ ๋„ค๋น„๊ฒŒ์ด์…˜

๊ธฐ์ˆ  ์Šคํƒ

  • ๋ฐฑ์—”๋“œ: Laravel 13.x (Eloquent ORM, SQLite DB)
  • ํ”„๋ก ํŠธ์—”๋“œ: React 19 + Inertia.js
  • ์Šคํƒ€์ผ๋ง: Tailwind CSS
  • ์•„์ด์ฝ˜: Heroicons ๋˜๋Š” Lucide React

ํ•™์Šต ํฌ์ธํŠธ

  • Laravel + Inertia.js ๋ชจ๋…ธ๋ฆฌ์Šค ์•„ํ‚คํ…์ฒ˜ ์ดํ•ด
  • ๋‹ค๋Œ€๋‹ค ๊ด€๊ณ„ (๊ตฌ๋…์ž-๊ณต์—ฐ-๋ฆฌ๋ทฐ) ๋ชจ๋ธ๋ง
  • React ์ปดํฌ๋„ŒํŠธ ์žฌ์‚ฌ์šฉ (์นด๋“œ, ๋ชจ๋‹ฌ, ํผ)
  • Inertia ํผ ํ—ฌํผ๋ฅผ ํ™œ์šฉํ•œ CRUD ๊ตฌํ˜„
  • Tailwind CSS ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ ๋ฐ ์• ๋‹ˆ๋ฉ”์ด์…˜

์ œ์•ฝ ์กฐ๊ฑด

  • โ€ข Laravel 13.x + Inertia.js + React 19 ๋ชจ๋…ธ๋ฆฌ์Šค ํ’€์Šคํƒ ํ”„๋กœ์ ํŠธ
  • โ€ข Laravel Breeze (React + Inertia) starter kit์œผ๋กœ ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐํ™”
  • โ€ข SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ (๊ฐ„ํŽธํ•œ ๋กœ์ปฌ ๊ฐœ๋ฐœ)
  • โ€ข ๋ณ„๋„ REST API ์—”๋“œํฌ์ธํŠธ ์—†์ด Inertia::render()๋กœ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ
  • โ€ข Tailwind CSS๋กœ ๋ชจ๋“  ์Šคํƒ€์ผ๋ง ์ฒ˜๋ฆฌ (CSS ํŒŒ์ผ ์ž‘์„ฑ ๊ธˆ์ง€)
  • โ€ข React ์ปดํฌ๋„ŒํŠธ๋Š” resources/js/Pages/ ๋””๋ ‰ํ† ๋ฆฌ์— ๋ฐฐ์น˜
  • โ€ข ๋ผ์šฐํŠธ๋Š” routes/web.php์—์„œ Inertia ๋ฐฉ์‹์œผ๋กœ ์ •์˜
  • โ€ข ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์œผ๋กœ DB ์Šคํ‚ค๋งˆ ๊ด€๋ฆฌ
  • โ€ข Eloquent ๊ด€๊ณ„(hasMany, belongsToMany ๋“ฑ) ํ™œ์šฉ
  • โ€ข ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ๊ธฐ๋Šฅ ๊ธˆ์ง€ (WebSocket ์„ค์ • ๋ณต์žก๋„ ํšŒํ”ผ)
  • โ€ข ํ”„๋ก ํŠธ์—”๋“œ ์ƒํƒœ๊ด€๋ฆฌ๋Š” useState/useReducer๋งŒ ์‚ฌ์šฉ (์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ธˆ์ง€)

ํ”„๋กฌํ”„ํŠธ ๋ ˆ์‹œํ”ผ

์•„๋ž˜ ํ”„๋กฌํ”„ํŠธ๋ฅผ ์ˆœ์„œ๋Œ€๋กœ AI์—๊ฒŒ ๋ณด๋‚ด๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋ณต์‚ฌ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ๋ฐ”๋กœ ์‚ฌ์šฉํ•˜์„ธ์š”.

1
Step 1
mkdir performance-subscription-service && cd performance-subscription-service

์ด ํด๋”์—์„œ Laravel + Inertia.js + React 19 ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

๋‹ค์Œ ๋ช…๋ น์œผ๋กœ Laravel Breeze (React + Inertia) starter kit์„ ์„ค์น˜ํ•˜์„ธ์š”:

```bash
composer create-project laravel/laravel .
composer require laravel/breeze --dev
php artisan breeze:install react
php artisan migrate
npm install && npm run dev
```

ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐ ์„ค์ •์ด ์™„๋ฃŒ๋˜๋ฉด ๋‹ค์Œ์„ ํ™•์ธํ•˜์„ธ์š”:
- resources/js/Pages/ ๋””๋ ‰ํ† ๋ฆฌ์— Welcome.jsx ๊ฐ™์€ ์˜ˆ์ œ ํŽ˜์ด์ง€๊ฐ€ ์žˆ๋Š”์ง€
- routes/web.php์— Inertia::render() ๋ฐฉ์‹์˜ ๋ผ์šฐํŠธ๊ฐ€ ์žˆ๋Š”์ง€
- .env ํŒŒ์ผ์—์„œ DB_CONNECTION=sqlite๋กœ ์„ค์ •๋˜์–ด ์žˆ๋Š”์ง€ (์—†์œผ๋ฉด ์ˆ˜์ •)
- database/database.sqlite ํŒŒ์ผ์ด ์ƒ์„ฑ๋˜์—ˆ๋Š”์ง€ (์—†์œผ๋ฉด touch database/database.sqlite๋กœ ์ƒ์„ฑ)

์ดˆ๊ธฐ ์„ค์ • ์™„๋ฃŒ ํ›„ ๋ธŒ๋ผ์šฐ์ €์—์„œ http://localhost:8000 ์ ‘์†ํ•˜์—ฌ Laravel Breeze ํ™˜์˜ ํŽ˜์ด์ง€๊ฐ€ ํ‘œ์‹œ๋˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: Laravel 13.x + Breeze + Inertia.js + React 19 ํ”„๋กœ์ ํŠธ๊ฐ€ ์ƒ์„ฑ๋˜๊ณ , npm run dev๋กœ Vite ๊ฐœ๋ฐœ ์„œ๋ฒ„๊ฐ€ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €์—์„œ ๊ธฐ๋ณธ ํ™˜์˜ ํŽ˜์ด์ง€๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋ Œ๋”๋ง๋˜๋ฉฐ, SQLite DB ํŒŒ์ผ์ด ์ค€๋น„๋ฉ๋‹ˆ๋‹ค.

2
Step 2
์ด์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ๋ฅผ ์„ค๊ณ„ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ 3๊ฐœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜์„ธ์š”:

```bash
php artisan make:migration create_subscription_plans_table
php artisan make:migration create_performances_table
php artisan make:migration create_subscriptions_table
php artisan make:migration create_reviews_table
```

๊ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ ๋‚ด์šฉ:

**1. subscription_plans ํ…Œ์ด๋ธ”** (๊ตฌ๋… ํ”Œ๋žœ)
- id (primary key)
- name (string) - ํ”Œ๋žœ๋ช… (์˜ˆ: "์›”๊ฐ„", "๋ถ„๊ธฐ", "์—ฐ๊ฐ„")
- price (decimal) - ๊ฐ€๊ฒฉ
- duration_months (integer) - ๊ธฐ๊ฐ„(๊ฐœ์›”)
- benefits (text) - ํ˜œํƒ ์„ค๋ช… (JSON ํ˜•์‹)
- timestamps

**2. performances ํ…Œ์ด๋ธ”** (๊ณต์—ฐ ์ •๋ณด)
- id (primary key)
- title (string) - ๊ณต์—ฐ ์ œ๋ชฉ
- genre (string) - ์žฅ๋ฅด (์—ฐ๊ทน/๋ฎค์ง€์ปฌ/์ฝ˜์„œํŠธ)
- description (text) - ๊ณต์—ฐ ์„ค๋ช…
- poster_url (string, nullable) - ํฌ์Šคํ„ฐ ์ด๋ฏธ์ง€ URL
- venue (string) - ๊ณต์—ฐ์žฅ
- date (date) - ๊ณต์—ฐ ๋‚ ์งœ
- available_seats (integer) - ์ž”์—ฌ ์ขŒ์„
- timestamps

**3. subscriptions ํ…Œ์ด๋ธ”** (๊ตฌ๋… ์ •๋ณด)
- id (primary key)
- user_id (foreign key to users)
- subscription_plan_id (foreign key to subscription_plans)
- preferred_genres (text) - ์„ ํ˜ธ ์žฅ๋ฅด (JSON ๋ฐฐ์—ด)
- available_days (text) - ๊ด€๋žŒ ๊ฐ€๋Šฅ ์š”์ผ (JSON ๋ฐฐ์—ด)
- status (string) - ๊ตฌ๋… ์ƒํƒœ (active/paused/cancelled)
- started_at (date) - ๊ตฌ๋… ์‹œ์ž‘์ผ
- expires_at (date) - ๋งŒ๋ฃŒ์ผ
- timestamps

**4. reviews ํ…Œ์ด๋ธ”** (๊ณต์—ฐ ๋ฆฌ๋ทฐ)
- id (primary key)
- user_id (foreign key to users)
- performance_id (foreign key to performances)
- rating (integer) - ๋ณ„์  (1~5)
- content (text) - ๋ฆฌ๋ทฐ ๋‚ด์šฉ
- timestamps

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ž‘์„ฑ ํ›„ `php artisan migrate`๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ํ…Œ์ด๋ธ”์„ ์ƒ์„ฑํ•˜์„ธ์š”.

๊ทธ๋ฆฌ๊ณ  ๋‹ค์Œ Eloquent ๋ชจ๋ธ์„ ์ƒ์„ฑํ•˜์„ธ์š”:

```bash
php artisan make:model SubscriptionPlan
php artisan make:model Performance
php artisan make:model Subscription
php artisan make:model Review
```

๊ฐ ๋ชจ๋ธ์— ๊ด€๊ณ„ ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”:
- Subscription ๋ชจ๋ธ: belongsTo(User), belongsTo(SubscriptionPlan)
- Review ๋ชจ๋ธ: belongsTo(User), belongsTo(Performance)
- Performance ๋ชจ๋ธ: hasMany(Review)
- User ๋ชจ๋ธ: hasMany(Subscription), hasMany(Review)

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: 4๊ฐœ์˜ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ์ด ์ƒ์„ฑ๋˜๊ณ  ์‹คํ–‰๋˜์–ด SQLite DB์— ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. Eloquent ๋ชจ๋ธ 4๊ฐœ๊ฐ€ ์ƒ์„ฑ๋˜๋ฉฐ, ๊ฐ ๋ชจ๋ธ์— ๊ด€๊ณ„ ๋ฉ”์„œ๋“œ๊ฐ€ ์ •์˜๋ฉ๋‹ˆ๋‹ค. php artisan tinker๋กœ ๋ชจ๋ธ ๊ด€๊ณ„๋ฅผ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3
Step 3
์ด์ œ ์‹œ๋”(Seeder)๋ฅผ ์ž‘์„ฑํ•˜์—ฌ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

```bash
php artisan make:seeder SubscriptionPlanSeeder
php artisan make:seeder PerformanceSeeder
```

**SubscriptionPlanSeeder**์—์„œ 3๊ฐœ์˜ ํ”Œ๋žœ์„ ์ƒ์„ฑํ•˜์„ธ์š”:
- ์›”๊ฐ„ ํ”Œ๋žœ: 29,000์›, 1๊ฐœ์›”, ํ˜œํƒ: ["์›” 1ํšŒ ๊ณต์—ฐ ์ž๋™ ์˜ˆ๋งค", "๊ตฌ๋…์ž ์ „์šฉ ๋ฆฌ๋ทฐ ๊ฒŒ์‹œํŒ"]
- ๋ถ„๊ธฐ ํ”Œ๋žœ: 79,000์›, 3๊ฐœ์›”, ํ˜œํƒ: ["๋ถ„๊ธฐ๋‹น 3ํšŒ ๊ณต์—ฐ", "๊ณต์—ฐ ๋ณ€๊ฒฝ 1ํšŒ ๋ฌด๋ฃŒ", "์นœ๊ตฌ ์ดˆ๋Œ€ 10% ํ• ์ธ"]
- ์—ฐ๊ฐ„ ํ”Œ๋žœ: 299,000์›, 12๊ฐœ์›”, ํ˜œํƒ: ["์—ฐ๊ฐ„ 12ํšŒ ๊ณต์—ฐ", "VIP ์ขŒ์„ ์šฐ์„  ๋ฐฐ์ •", "๊ณต์—ฐ ๋ณ€๊ฒฝ ๋ฌด์ œํ•œ"]

**PerformanceSeeder**์—์„œ 10๊ฐœ ์ด์ƒ์˜ ๊ณต์—ฐ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”. ์žฅ๋ฅด๋Š” "์—ฐ๊ทน", "๋ฎค์ง€์ปฌ", "์ฝ˜์„œํŠธ" ์ค‘ ๋žœ๋ค์œผ๋กœ ๋ฐฐ์ •ํ•˜๊ณ , ํฌ์Šคํ„ฐ URL์€ Unsplash Placeholder (https://source.unsplash.com/800x1200/?theater,musical,concert)๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ๊ณต์—ฐ ๋‚ ์งœ๋Š” ํ˜„์žฌ ๋‚ ์งœ ๊ธฐ์ค€ +7์ผ ~ +60์ผ ์‚ฌ์ด๋กœ ๋žœ๋ค ์ƒ์„ฑ.

database/seeders/DatabaseSeeder.php์—์„œ ๋‘ ์‹œ๋”๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ์ˆ˜์ •ํ•œ ํ›„:

```bash
php artisan db:seed
```

์‹คํ–‰ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฝ์ž…ํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: SubscriptionPlan 3๊ฐœ์™€ Performance 10๊ฐœ ์ด์ƒ์˜ ๋”๋ฏธ ๋ฐ์ดํ„ฐ๊ฐ€ SQLite DB์— ์‚ฝ์ž…๋ฉ๋‹ˆ๋‹ค. php artisan tinker์—์„œ SubscriptionPlan::all(), Performance::all()๋กœ ๋ฐ์ดํ„ฐ ํ™•์ธ ๊ฐ€๋Šฅ.

4
Step 4
์ด์ œ ๋ฉ”์ธ ํŽ˜์ด์ง€์™€ ๊ตฌ๋… ํ”Œ๋žœ ์†Œ๊ฐœ ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

routes/web.php์— ๋‹ค์Œ ๋ผ์šฐํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

```php
use App\Models\SubscriptionPlan;
use App\Models\Performance;
use Inertia\Inertia;

Route::get('/', function () {
    return Inertia::render('Home', [
        'plans' => SubscriptionPlan::all(),
        'featuredPerformances' => Performance::latest()->take(6)->get()
    ]);
});

Route::get('/plans', function () {
    return Inertia::render('Plans', [
        'plans' => SubscriptionPlan::all()
    ]);
});
```

resources/js/Pages/ ๋””๋ ‰ํ† ๋ฆฌ์— ๋‹ค์Œ React ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”:

**Home.jsx** (๋ฉ”์ธ ํŽ˜์ด์ง€)
- ํžˆ์–ด๋กœ ์„น์…˜: ์„œ๋น„์Šค ์†Œ๊ฐœ ํ—ค๋“œ๋ผ์ธ "๋งค๋‹ฌ ์ƒˆ๋กœ์šด ๊ณต์—ฐ์„ ๋งŒ๋‚˜๋ณด์„ธ์š”", CTA ๋ฒ„ํŠผ "๊ตฌ๋… ์‹œ์ž‘ํ•˜๊ธฐ"
- ๊ตฌ๋… ํ”Œ๋žœ ์นด๋“œ 3๊ฐœ ๋‚˜๋ž€ํžˆ ๋ฐฐ์น˜ (Tailwind ๊ทธ๋ฆฌ๋“œ ์‚ฌ์šฉ)
- ์ด๋ฒˆ ๋‹ฌ ์ถ”์ฒœ ๊ณต์—ฐ 6๊ฐœ ์ธ๋„ค์ผ ๊ฐค๋Ÿฌ๋ฆฌ (2ํ–‰ 3์—ด ๊ทธ๋ฆฌ๋“œ)
- ๊ฐ ๊ณต์—ฐ ์นด๋“œ์— ํฌ์Šคํ„ฐ ์ด๋ฏธ์ง€, ์ œ๋ชฉ, ์žฅ๋ฅด ๋ฐฐ์ง€, ๋‚ ์งœ ํ‘œ์‹œ

**Plans.jsx** (๊ตฌ๋… ํ”Œ๋žœ ์ƒ์„ธ ํŽ˜์ด์ง€)
- 3๊ฐœ ํ”Œ๋žœ์„ ์นด๋“œ ํ˜•ํƒœ๋กœ ๋น„๊ต (๊ฐ€๊ฒฉ, ๊ธฐ๊ฐ„, ํ˜œํƒ ๋ชฉ๋ก)
- ๊ฐ ์นด๋“œ์— "๊ตฌ๋…ํ•˜๊ธฐ" ๋ฒ„ํŠผ (์•„์ง ๋™์ž‘ ์•ˆ ํ•จ, ๋‹ค์Œ ๋‹จ๊ณ„์—์„œ ๊ตฌํ˜„)
- Tailwind CSS๋กœ ํ˜ธ๋ฒ„ ํšจ๊ณผ (scale-105, shadow-xl ๋“ฑ) ์ถ”๊ฐ€

Tailwind CSS๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ฐ˜์‘ํ˜• ๋””์ž์ธ ์ ์šฉ (๋ชจ๋ฐ”์ผ์—์„œ๋Š” 1์—ด, ํƒœ๋ธ”๋ฆฟ 2์—ด, ๋ฐ์Šคํฌํƒ‘ 3์—ด).

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋ธŒ๋ผ์šฐ์ €์—์„œ / ๊ฒฝ๋กœ ์ ‘์† ์‹œ ๋ฉ”์ธ ํŽ˜์ด์ง€๊ฐ€ ๋ Œ๋”๋ง๋˜๋ฉฐ, ๊ตฌ๋… ํ”Œ๋žœ 3๊ฐœ์™€ ์ถ”์ฒœ ๊ณต์—ฐ 6๊ฐœ๊ฐ€ ์นด๋“œ ํ˜•ํƒœ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. /plans ๊ฒฝ๋กœ์—์„œ๋Š” ํ”Œ๋žœ ๋น„๊ต ํŽ˜์ด์ง€๊ฐ€ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค. Tailwind CSS ์Šคํƒ€์ผ์ด ์ ์šฉ๋˜์–ด ์‹œ๊ฐ์ ์œผ๋กœ ๋งค๋ ฅ์ ์ธ UI๊ฐ€ ์™„์„ฑ๋ฉ๋‹ˆ๋‹ค.

5
Step 5
์ด์ œ ๊ณต์—ฐ ๋ชฉ๋ก ํŽ˜์ด์ง€์™€ ์ƒ์„ธ ๋ชจ๋‹ฌ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

routes/web.php์— ๋‹ค์Œ ๋ผ์šฐํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

```php
Route::get('/performances', function () {
    $genre = request('genre');
    $query = Performance::query();
    if ($genre) {
        $query->where('genre', $genre);
    }
    return Inertia::render('Performances', [
        'performances' => $query->latest()->paginate(12),
        'selectedGenre' => $genre
    ]);
});

Route::get('/performances/{id}', function ($id) {
    $performance = Performance::with('reviews.user')->findOrFail($id);
    return Inertia::render('PerformanceDetail', [
        'performance' => $performance
    ]);
});
```

**Performances.jsx** ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:
- ์ƒ๋‹จ์— ์žฅ๋ฅด ํ•„ํ„ฐ ๋ฒ„ํŠผ 3๊ฐœ (์ „์ฒด/์—ฐ๊ทน/๋ฎค์ง€์ปฌ/์ฝ˜์„œํŠธ)
- ํ•„ํ„ฐ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ?genre=์—ฐ๊ทน ๊ฐ™์€ ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ URL์— ์ถ”๊ฐ€ํ•˜์—ฌ ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ (Inertia Link ์‚ฌ์šฉ)
- ๊ณต์—ฐ ์นด๋“œ 12๊ฐœ์”ฉ ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ์œผ๋กœ ํ‘œ์‹œ
- ๊ฐ ์นด๋“œ ํด๋ฆญ ์‹œ /performances/{id} ๊ฒฝ๋กœ๋กœ ์ด๋™
- ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ปดํฌ๋„ŒํŠธ ์ถ”๊ฐ€ (Inertia ๊ธฐ๋ณธ ํŽ˜์ด์ง€๋„ค์ด์…˜ ํ™œ์šฉ)

**PerformanceDetail.jsx** ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:
- ๊ณต์—ฐ ํฌ์Šคํ„ฐ ์ด๋ฏธ์ง€ ํฌ๊ฒŒ ํ‘œ์‹œ
- ์ œ๋ชฉ, ์žฅ๋ฅด, ๊ณต์—ฐ์žฅ, ๋‚ ์งœ, ์ž”์—ฌ ์ขŒ์„ ์ •๋ณด
- ๊ณต์—ฐ ์„ค๋ช… ํ…์ŠคํŠธ
- ํ•˜๋‹จ์— "์ด ๊ณต์—ฐ ๋ฆฌ๋ทฐ ๋ณด๊ธฐ" ๋ฒ„ํŠผ (๋‹ค์Œ ๋‹จ๊ณ„์—์„œ ๋ฆฌ๋ทฐ ์„น์…˜ ์ถ”๊ฐ€)
- ๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ (Inertia Link๋กœ /performances๋กœ ์ด๋™)

Tailwind CSS๋กœ ์นด๋“œ ํ˜ธ๋ฒ„ ์• ๋‹ˆ๋ฉ”์ด์…˜, ๋ฐฐ์ง€ ์Šคํƒ€์ผ๋ง ์ ์šฉ.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: /performances ๊ฒฝ๋กœ์—์„œ ๋ชจ๋“  ๊ณต์—ฐ ๋ชฉ๋ก์ด ํ‘œ์‹œ๋˜๋ฉฐ, ์žฅ๋ฅด ํ•„ํ„ฐ๋ง์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ๊ณต์—ฐ ์นด๋“œ ํด๋ฆญ ์‹œ ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋ฉฐ, ๊ณต์—ฐ ์ •๋ณด๊ฐ€ ์ƒ์„ธํ•˜๊ฒŒ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๋„ค์ด์…˜์œผ๋กœ 12๊ฐœ์”ฉ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚˜๋ˆ ์„œ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

6
Step 6
์ด์ œ ๊ตฌ๋… ์‹ ์ฒญ ํผ๊ณผ ๋งˆ์ดํŽ˜์ด์ง€๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.

๋จผ์ € ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”:

```bash
php artisan make:controller SubscriptionController
```

app/Http/Controllers/SubscriptionController.php์— ๋‹ค์Œ ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

```php
public function create($planId)
{
    $plan = SubscriptionPlan::findOrFail($planId);
    return Inertia::render('SubscriptionForm', [
        'plan' => $plan,
        'genres' => ['์—ฐ๊ทน', '๋ฎค์ง€์ปฌ', '์ฝ˜์„œํŠธ'],
        'days' => ['์›”', 'ํ™”', '์ˆ˜', '๋ชฉ', '๊ธˆ', 'ํ† ', '์ผ']
    ]);
}

public function store(Request $request)
{
    $validated = $request->validate([
        'subscription_plan_id' => 'required|exists:subscription_plans,id',
        'preferred_genres' => 'required|array',
        'available_days' => 'required|array'
    ]);

    $subscription = auth()->user()->subscriptions()->create([
        'subscription_plan_id' => $validated['subscription_plan_id'],
        'preferred_genres' => json_encode($validated['preferred_genres']),
        'available_days' => json_encode($validated['available_days']),
        'status' => 'active',
        'started_at' => now(),
        'expires_at' => now()->addMonths(SubscriptionPlan::find($validated['subscription_plan_id'])->duration_months)
    ]);

    return redirect()->route('my-subscription')->with('success', '๊ตฌ๋…์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!');
}

public function mySubscription()
{
    $subscription = auth()->user()->subscriptions()->with('subscriptionPlan')->latest()->first();
    return Inertia::render('MySubscription', [
        'subscription' => $subscription
    ]);
}
```

routes/web.php์— ๋ผ์šฐํŠธ ์ถ”๊ฐ€:

```php
Route::middleware('auth')->group(function () {
    Route::get('/subscribe/{planId}', [SubscriptionController::class, 'create'])->name('subscribe.create');
    Route::post('/subscribe', [SubscriptionController::class, 'store'])->name('subscribe.store');
    Route::get('/my-subscription', [SubscriptionController::class, 'mySubscription'])->name('my-subscription');
});
```

**SubscriptionForm.jsx** ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:
- ์„ ํƒํ•œ ํ”Œ๋žœ ์ •๋ณด ํ‘œ์‹œ (์นด๋“œ ํ˜•ํƒœ)
- ์„ ํ˜ธ ์žฅ๋ฅด ์ฒดํฌ๋ฐ•์Šค 3๊ฐœ (์—ฐ๊ทน/๋ฎค์ง€์ปฌ/์ฝ˜์„œํŠธ)
- ๊ด€๋žŒ ๊ฐ€๋Šฅ ์š”์ผ ์ฒดํฌ๋ฐ•์Šค 7๊ฐœ (์›”~์ผ)
- ๊ฒฐ์ œ ์ •๋ณด ์ž…๋ ฅ ํ•„๋“œ (์นด๋“œ๋ฒˆํ˜ธ, ์œ ํšจ๊ธฐ๊ฐ„, CVV) - ์‹ค์ œ ๊ฒฐ์ œ ์—ฐ๋™์€ ํ•˜์ง€ ์•Š๊ณ  UI๋งŒ
- "๊ตฌ๋… ์™„๋ฃŒ" ๋ฒ„ํŠผ (Inertia ํผ ์ œ์ถœ)

**MySubscription.jsx** ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:
- ํ˜„์žฌ ๊ตฌ๋… ํ”Œ๋žœ ์ •๋ณด (ํ”Œ๋žœ๋ช…, ์‹œ์ž‘์ผ, ๋งŒ๋ฃŒ์ผ, ์ƒํƒœ)
- ์„ ํ˜ธ ์žฅ๋ฅด ๋ฐ ๊ด€๋žŒ ๊ฐ€๋Šฅ ์š”์ผ ํ‘œ์‹œ
- "๊ตฌ๋… ์ทจ์†Œ" ๋ฒ„ํŠผ (์•„์ง ๋™์ž‘ ์•ˆ ํ•จ, ์ถ”ํ›„ ๊ตฌํ˜„ ๊ฐ€๋Šฅ)
- ์ด๋ฒˆ ๋‹ฌ ์˜ˆ์ •๋œ ๊ณต์—ฐ ๋ชฉ๋ก (์•„์ง ์—ฐ๋™ ์•ˆ ๋จ, ๋‹ค์Œ ๋‹จ๊ณ„์—์„œ ์ถ”๊ฐ€)

Inertia useForm ํ›…์„ ์‚ฌ์šฉํ•˜์—ฌ ํผ ์ƒํƒœ ๊ด€๋ฆฌ ๋ฐ ์ œ์ถœ ์ฒ˜๋ฆฌ.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: /subscribe/{planId} ๊ฒฝ๋กœ์—์„œ ๊ตฌ๋… ์‹ ์ฒญ ํผ์ด ํ‘œ์‹œ๋˜๋ฉฐ, ํผ ์ œ์ถœ ์‹œ subscriptions ํ…Œ์ด๋ธ”์— ๋ฐ์ดํ„ฐ๊ฐ€ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค. /my-subscription ๊ฒฝ๋กœ์—์„œ ์‚ฌ์šฉ์ž์˜ ๊ตฌ๋… ์ •๋ณด๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ๋ฏธ์ธ์ฆ ์‹œ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋ฉ๋‹ˆ๋‹ค.

7
Step 7
๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฆฌ๋ทฐ ์ž‘์„ฑ ๋ฐ ์กฐํšŒ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

์ปจํŠธ๋กค๋Ÿฌ ์ƒ์„ฑ:

```bash
php artisan make:controller ReviewController
```

app/Http/Controllers/ReviewController.php์— ๋‹ค์Œ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€:

```php
public function store(Request $request, $performanceId)
{
    $validated = $request->validate([
        'rating' => 'required|integer|min:1|max:5',
        'content' => 'required|string|min:10'
    ]);

    auth()->user()->reviews()->create([
        'performance_id' => $performanceId,
        'rating' => $validated['rating'],
        'content' => $validated['content']
    ]);

    return back()->with('success', '๋ฆฌ๋ทฐ๊ฐ€ ๋“ฑ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค!');
}

public function index()
{
    $reviews = Review::with(['user', 'performance'])
        ->latest()
        ->paginate(10);
    return Inertia::render('Reviews', [
        'reviews' => $reviews
    ]);
}
```

routes/web.php์— ๋ผ์šฐํŠธ ์ถ”๊ฐ€:

```php
Route::middleware('auth')->group(function () {
    Route::post('/performances/{performanceId}/reviews', [ReviewController::class, 'store'])->name('reviews.store');
    Route::get('/reviews', [ReviewController::class, 'index'])->name('reviews.index');
});
```

**PerformanceDetail.jsx ์ˆ˜์ •**:
- ํ•˜๋‹จ์— ๋ฆฌ๋ทฐ ์ž‘์„ฑ ํผ ์ถ”๊ฐ€ (๋ณ„์  ์„ ํƒ 5๊ฐœ ๋ณ„ ์•„์ด์ฝ˜, ๋ฆฌ๋ทฐ ๋‚ด์šฉ textarea)
- ํผ ์ œ์ถœ ์‹œ POST /performances/{id}/reviews๋กœ ์ „์†ก (Inertia useForm ์‚ฌ์šฉ)
- ํ•ด๋‹น ๊ณต์—ฐ์˜ ๊ธฐ์กด ๋ฆฌ๋ทฐ ๋ชฉ๋ก ํ‘œ์‹œ (์ž‘์„ฑ์ž๋ช…, ๋ณ„์ , ๋‚ด์šฉ, ์ž‘์„ฑ์ผ)

**Reviews.jsx** ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:
- ๋ชจ๋“  ๋ฆฌ๋ทฐ ๋ชฉ๋ก์„ ์‹œ๊ฐ„์ˆœ์œผ๋กœ ํ‘œ์‹œ
- ๊ฐ ๋ฆฌ๋ทฐ ์นด๋“œ์— ๊ณต์—ฐ ์ œ๋ชฉ, ์ž‘์„ฑ์ž๋ช…, ๋ณ„์ (๋ณ„ ์•„์ด์ฝ˜), ๋ฆฌ๋ทฐ ๋‚ด์šฉ, ์ž‘์„ฑ์ผ ํ‘œ์‹œ
- ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ถ”๊ฐ€

๋ณ„์ ์€ Heroicons์˜ StarIcon์„ ์‚ฌ์šฉํ•˜์—ฌ ์ฑ„์›Œ์ง„ ๋ณ„(text-yellow-400)๊ณผ ๋นˆ ๋ณ„(text-gray-300)๋กœ ํ‘œํ˜„.

์ด์ œ ์ „์ฒด ํ”„๋กœ์ ํŠธ๊ฐ€ ์™„์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์„ ํ™•์ธํ•˜์„ธ์š”:
1. ํšŒ์›๊ฐ€์ž…/๋กœ๊ทธ์ธ ํ›„ ๊ตฌ๋… ์‹ ์ฒญ ๊ฐ€๋Šฅ
2. ๊ณต์—ฐ ๋ชฉ๋ก ํ•„ํ„ฐ๋ง ๋ฐ ์ƒ์„ธ ํŽ˜์ด์ง€ ์ด๋™
3. ๊ณต์—ฐ ์ƒ์„ธ ํŽ˜์ด์ง€์—์„œ ๋ฆฌ๋ทฐ ์ž‘์„ฑ
4. ๋งˆ์ดํŽ˜์ด์ง€์—์„œ ๊ตฌ๋… ์ •๋ณด ํ™•์ธ
5. ์ „์ฒด ๋ฆฌ๋ทฐ ๊ฒŒ์‹œํŒ์—์„œ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž ๋ฆฌ๋ทฐ ์—ด๋žŒ

์ตœ์ข…์ ์œผ๋กœ npm run build๋กœ ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , php artisan serve๋กœ ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์ „์ฒด ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ๋ฆฌ๋ทฐ ์ž‘์„ฑ ๊ธฐ๋Šฅ์ด ๋™์ž‘ํ•˜๋ฉฐ, ๊ณต์—ฐ ์ƒ์„ธ ํŽ˜์ด์ง€์—์„œ ๋ณ„์ ๊ณผ ํ…์ŠคํŠธ ๋ฆฌ๋ทฐ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. /reviews ๊ฒฝ๋กœ์—์„œ ๋ชจ๋“  ๋ฆฌ๋ทฐ ๋ชฉ๋ก์„ ๋ณผ ์ˆ˜ ์žˆ์œผ๋ฉฐ, ํŽ˜์ด์ง€๋„ค์ด์…˜์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ „์ฒด ํ”„๋กœ์ ํŠธ๊ฐ€ ์™„์„ฑ๋˜์–ด ๊ตฌ๋… ํ”Œ๋žœ ์„ ํƒ โ†’ ๊ตฌ๋… ์‹ ์ฒญ โ†’ ๊ณต์—ฐ ํƒ์ƒ‰ โ†’ ๋ฆฌ๋ทฐ ์ž‘์„ฑ ํ๋ฆ„์ด ์›ํ™œํ•˜๊ฒŒ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

์ œ์ถœ๋œ ์ž‘ํ’ˆ (0)

๋กœ๊ทธ์ธ ํ›„ ์ œ์ถœ

์•„์ง ์ œ์ถœ๋œ ์ž‘ํ’ˆ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋„์ „์ž๊ฐ€ ๋˜์–ด๋ณด์„ธ์š”!

๋Œ“๊ธ€ (0)

๋กœ๊ทธ์ธ ํ›„ ๋Œ“๊ธ€์„ ๋‚จ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ ๋Œ“๊ธ€์„ ๋‚จ๊ฒจ๋ณด์„ธ์š”!