๐ญ ๊ณต์ฐ ํฐ์ผ ๊ตฌ๋ ์๋น์ค - ์๊ฐ ๊ณต์ฐ ํ๋ ์ด์ & ์๋ ์๋งค
performance-subscription-service
๋ฌธ์ ์ค๋ช
ํ๋ก์ ํธ ๊ฐ์
๋งค๋ฌ ์์ ๋ ๊ณต์ฐ(์ฐ๊ทน, ๋ฎค์ง์ปฌ, ์ฝ์ํธ)์ ๊ตฌ๋ ์์๊ฒ ์ถ์ฒํ๊ณ ์๋์ผ๋ก ํฐ์ผ์ ์๋งคํด์ฃผ๋ ๊ตฌ๋ ์๋น์ค ํ๋ซํผ์ ๋๋ค. ๋ทํ๋ฆญ์ค์ฒ๋ผ ๊ณต์ฐ์ ๊ตฌ๋ ํ๋ ์๋ก์ด ๋ฌธํ ์๋น ๋ฐฉ์์ ์ ์ํฉ๋๋ค.
ํ์ ๊ธฐ๋ฅ
- ๐๏ธ ๊ตฌ๋ ํ๋ ์๊ฐ ํ์ด์ง: ์๊ฐ/๋ถ๊ธฐ๋ณ/์ฐ๊ฐ ํ๋ ๋น๊ต ์นด๋, ๊ฐ๊ฒฉ ๋ฐ ํํ ์๋ด
- ๐ญ ์ด๋ฒ ๋ฌ ํ๋ ์ด์ ๊ณต์ฐ ๋ชฉ๋ก: ์ฅ๋ฅด๋ณ ํํฐ๋ง(์ฐ๊ทน/๋ฎค์ง์ปฌ/์ฝ์ํธ), ๊ณต์ฐ ํฌ์คํฐ ์ธ๋ค์ผ, ์์ธ ์ ๋ณด ๋ชจ๋ฌ
- ๐ ๊ตฌ๋ ์ ์ฒญ ํผ: ์ ํธ ์ฅ๋ฅด ์ ํ, ๊ด๋ ๊ฐ๋ฅ ์์ผ ์ค์ , ๊ฒฐ์ ์ ๋ณด ์ ๋ ฅ
- โญ ๊ตฌ๋ ์ ์ ์ฉ ๋ฆฌ๋ทฐ ๊ฒ์ํ: ๊ด๋ํ ๊ณต์ฐ ๋ณ์ ๋ฐ ํ๊ธฐ ์์ฑ, ๋ค๋ฅธ ๊ตฌ๋ ์ ๋ฆฌ๋ทฐ ์ด๋
- ๐ ๋ง์ดํ์ด์ง: ๊ตฌ๋ ์ํ ํ์ธ, ์ด๋ฒ ๋ฌ ์๋งค๋ ๊ณต์ฐ ์ ๋ณด, ๊ตฌ๋ ํ์คํ ๋ฆฌ
- ๐ณ ๊ฒฐ์ ๋ด์ญ ์กฐํ: ์๋ณ ๊ฒฐ์ ๊ธ์ก, ํ๋ถ ์ ์ฑ ์๋ด
์ ํ ์ถ๊ฐ ๊ธฐ๋ฅ (๋ณด๋์ค)
- ๐ ์น๊ตฌ ์ด๋ ๋ฆฌ์๋: ์ถ์ฒ ์ฝ๋ ์์ฑ ๋ฐ ํ ์ธ ์ฟ ํฐ ์ง๊ธ
- ๐ ๊ตฌ๋ ์ ์ทจํฅ ๋ถ์: ๊ด๋ ์ด๋ ฅ ๊ธฐ๋ฐ ์ ํธ ์ฅ๋ฅด ์ฐจํธ
- ๐จ ๊ณต์ฐ ํฌ์คํฐ ๊ฐค๋ฌ๋ฆฌ: ๊ณผ๊ฑฐ ํ๋ ์ด์ ์์นด์ด๋ธ, ๋ฌดํ ์คํฌ๋กค
- ๐ ๊ณต์ฐ ๋ณ๊ฒฝ ์์ฒญ: ์ด๋ฒ ๋ฌ ๊ณต์ฐ์ด ๋ง์์ ์ ๋ค ๊ฒฝ์ฐ ๋์ฒด ๊ณต์ฐ ์ ํ
- ๐ฑ ๋ชจ๋ฐ์ผ ๋ฐ์ํ ๋์์ธ: ํฐ์น ์ ์ค์ฒ ๊ธฐ๋ฐ ์ค์์ดํ ๋ค๋น๊ฒ์ด์
๊ธฐ์ ์คํ
- ๋ฐฑ์๋: 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์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
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 ํ์ผ์ด ์ค๋น๋ฉ๋๋ค.
์ด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง๋ฅผ ์ค๊ณํฉ๋๋ค. ๋ค์ 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๋ก ๋ชจ๋ธ ๊ด๊ณ๋ฅผ ํ ์คํธํ ์ ์์ต๋๋ค.
์ด์ ์๋(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()๋ก ๋ฐ์ดํฐ ํ์ธ ๊ฐ๋ฅ.
์ด์ ๋ฉ์ธ ํ์ด์ง์ ๊ตฌ๋
ํ๋ ์๊ฐ ํ์ด์ง๋ฅผ ๋ง๋ญ๋๋ค.
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๊ฐ ์์ฑ๋ฉ๋๋ค.
์ด์ ๊ณต์ฐ ๋ชฉ๋ก ํ์ด์ง์ ์์ธ ๋ชจ๋ฌ์ ๊ตฌํํฉ๋๋ค.
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๊ฐ์ฉ ๋ฐ์ดํฐ๋ฅผ ๋๋ ์ ๋ณผ ์ ์์ต๋๋ค.
์ด์ ๊ตฌ๋
์ ์ฒญ ํผ๊ณผ ๋ง์ดํ์ด์ง๋ฅผ ๊ตฌํํฉ๋๋ค.
๋จผ์ ์ปจํธ๋กค๋ฌ๋ฅผ ์์ฑํ์ธ์:
```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 ๊ฒฝ๋ก์์ ์ฌ์ฉ์์ ๊ตฌ๋ ์ ๋ณด๊ฐ ํ์๋ฉ๋๋ค. ์ธ์ฆ๋ ์ฌ์ฉ์๋ง ์ ๊ทผ ๊ฐ๋ฅํ๋ฉฐ, ๋ฏธ์ธ์ฆ ์ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ๋ฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก ๋ฆฌ๋ทฐ ์์ฑ ๋ฐ ์กฐํ ๊ธฐ๋ฅ์ ์ถ๊ฐํฉ๋๋ค.
์ปจํธ๋กค๋ฌ ์์ฑ:
```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)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!