๐ŸŽฏ ์˜จ๋ผ์ธ ํ€ด์ฆˆ ์ƒ์„ฑ & ๊ณต์œ  ํ”Œ๋žซํผ - ์นœ๊ตฌ๋“ค๊ณผ ํ•จ๊ป˜ํ•˜๋Š” ์ง€์‹ ๋ฐฐํ‹€ - ๋ฐ”์ด๋ธŒ์ฝ”๋”ฉ ๋ ˆ์‹œํ”ผ
์ค‘๊ธ‰ PHP + JavaScript Laravel + React + Inertia.js AI: Claude

๐ŸŽฏ ์˜จ๋ผ์ธ ํ€ด์ฆˆ ์ƒ์„ฑ & ๊ณต์œ  ํ”Œ๋žซํผ - ์นœ๊ตฌ๋“ค๊ณผ ํ•จ๊ป˜ํ•˜๋Š” ์ง€์‹ ๋ฐฐํ‹€

๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ช… quiz-maker-sharing-platform
์กฐํšŒ์ˆ˜ 222 0๊ฑด ์ œ์ถœ

๋ฌธ์ œ ์„ค๋ช…

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

๋ˆ„๊ตฌ๋‚˜ ์‰ฝ๊ฒŒ ํ€ด์ฆˆ๋ฅผ ๋งŒ๋“ค๊ณ  ์นœ๊ตฌ๋“ค๊ณผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋Š” ์›น ํ”Œ๋žซํผ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ํ€ด์ฆˆ ์ œ์ž‘์ž๋Š” ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„๋กœ ๋ฌธ์ œ๋ฅผ ๋“ฑ๋กํ•˜๊ณ , ์ฐธ๊ฐ€์ž๋“ค์€ ํ€ด์ฆˆ๋ฅผ ํ’€๊ณ  ์ ์ˆ˜๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Laravel + Inertia.js + React๋กœ ๋ชจ๋…ธ๋ฆฌ์Šค ํ’€์Šคํƒ ๊ตฌ์กฐ๋ฅผ ๊ตฌํ˜„ํ•˜๋ฉฐ, ์‹ค์‹œ๊ฐ„ ์ ์ˆ˜ ๊ณ„์‚ฐ๊ณผ ์ˆœ์œ„ ํ‘œ์‹œ ๊ธฐ๋Šฅ์„ ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

โœจ ํ•ต์‹ฌ ๊ธฐ๋Šฅ

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

  • ํ€ด์ฆˆ ์ƒ์„ฑ: ์ œ๋ชฉ, ์„ค๋ช…, ์นดํ…Œ๊ณ ๋ฆฌ, ๋‚œ์ด๋„ ์„ค์ •
  • ๋ฌธ์ œ ๋“ฑ๋ก: ๊ฐ๊ด€์‹ 4์ง€์„ ๋‹ค ๋ฌธ์ œ ์ถ”๊ฐ€ (์ •๋‹ต 1๊ฐœ ์„ ํƒ)
  • ํ€ด์ฆˆ ๋ชฉ๋ก: ์นดํ…Œ๊ณ ๋ฆฌ/๋‚œ์ด๋„ ํ•„ํ„ฐ๋ง, ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ
  • ํ€ด์ฆˆ ํ’€๊ธฐ: ๋ฌธ์ œ๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ํ’€๊ณ  ์ •๋‹ต ์ฒดํฌ
  • ๊ฒฐ๊ณผ ํŽ˜์ด์ง€: ์ด์ , ์ •๋‹ต๋ฅ , ์˜ค๋‹ต ๋ฌธ์ œ ๋ฆฌ๋ทฐ
  • ์ˆœ์œ„ํ‘œ: ํ€ด์ฆˆ๋ณ„ ์ƒ์œ„ 10๋ช… ์ ์ˆ˜ & ์ฐธ๊ฐ€์ž ์ˆœ์œ„
  • ๋งˆ์ดํŽ˜์ด์ง€: ๋‚ด๊ฐ€ ๋งŒ๋“  ํ€ด์ฆˆ, ์ฐธ์—ฌํ•œ ํ€ด์ฆˆ ํžˆ์Šคํ† ๋ฆฌ

๋ณด๋„ˆ์Šค ๊ธฐ๋Šฅ

  • ํ€ด์ฆˆ ๊ณต์œ  ๋งํฌ ์ƒ์„ฑ (URL ๋ณต์‚ฌ)
  • ๋ฌธ์ œ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ (์„ค๋ช… ์ด๋ฏธ์ง€ ์ถ”๊ฐ€)
  • ์‹œ๊ฐ„ ์ œํ•œ ๋ชจ๋“œ (๋ฌธ์ œ๋‹น ์ œํ•œ ์‹œ๊ฐ„)
  • ํ€ด์ฆˆ ์ข‹์•„์š” & ๋ถ๋งˆํฌ
  • ์˜ค๋‹ต ๋…ธํŠธ (ํ‹€๋ฆฐ ๋ฌธ์ œ ๋‹ค์‹œ ํ’€๊ธฐ)
  • ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ํ†ต๊ณ„ (๋‚ด๊ฐ€ ๊ฐ•ํ•œ ๋ถ„์•ผ ๋ถ„์„)

๐ŸŽจ UI/UX ํฌ์ธํŠธ

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

๐Ÿ› ๏ธ ๊ธฐ์ˆ  ์Šคํƒ

  • Laravel 13.x (๋ฐฑ์—”๋“œ ๋กœ์ง, DB, ์ธ์ฆ)
  • Inertia.js (Laravel โ†” React ์—ฐ๊ฒฐ)
  • React 19 (ํ”„๋ก ํŠธ์—”๋“œ UI)
  • Tailwind CSS (์Šคํƒ€์ผ๋ง)
  • SQLite (๊ฐœ๋ฐœ DB)

๐Ÿ“ฆ ๋ฐ์ดํ„ฐ ๋ชจ๋ธ

  • Users: ์‚ฌ์šฉ์ž ์ •๋ณด
  • Quizzes: ํ€ด์ฆˆ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ (์ œ๋ชฉ, ์„ค๋ช…, ์นดํ…Œ๊ณ ๋ฆฌ, ๋‚œ์ด๋„, ์ž‘์„ฑ์ž)
  • Questions: ํ€ด์ฆˆ ๋ฌธ์ œ (์งˆ๋ฌธ, ์„ ํƒ์ง€, ์ •๋‹ต, ์ด๋ฏธ์ง€)
  • Attempts: ํ€ด์ฆˆ ์ฐธ์—ฌ ๊ธฐ๋ก (์‚ฌ์šฉ์ž, ํ€ด์ฆˆ, ์ ์ˆ˜, ์ œ์ถœ ์‹œ๊ฐ„)
  • Answers: ์‚ฌ์šฉ์ž๊ฐ€ ์„ ํƒํ•œ ๋‹ต๋ณ€ ๊ธฐ๋ก

์ œ์•ฝ ์กฐ๊ฑด

  • โ€ข Laravel 13.x + Inertia.js + React 19 ๋ชจ๋…ธ๋ฆฌ์Šค ๊ตฌ์กฐ (๋ณ„๋„ API ๋ผ์šฐํŠธ ์—†์Œ)
  • โ€ข Laravel Breeze(React + Inertia) starter kit์œผ๋กœ ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐํ™”
  • โ€ข Inertia::render()๋กœ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ React ์ปดํฌ๋„ŒํŠธ์— ๋ฐ์ดํ„ฐ ์ „๋‹ฌ
  • โ€ข SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ (๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•„์ˆ˜)
  • โ€ข Eloquent ORM์œผ๋กœ ๋ชจ๋ธ ๊ด€๊ณ„ ์ •์˜ (hasMany, belongsTo)
  • โ€ข Tailwind CSS๋กœ ์Šคํƒ€์ผ๋ง (CDN ๋˜๋Š” Vite ๋นŒ๋“œ)
  • โ€ข React hooks(useState, useEffect) ํ™œ์šฉ
  • โ€ข ํผ ์ œ์ถœ ์‹œ Inertia form helper ์‚ฌ์šฉ (router.post, router.put ๋“ฑ)
  • โ€ข ์ธ์ฆ ๊ธฐ๋Šฅ (Laravel Breeze ๊ธฐ๋ณธ ์ œ๊ณต)
  • โ€ข ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ๊ธˆ์ง€ (HTTP ๊ธฐ๋ฐ˜ ๊ธฐ๋Šฅ๋งŒ ์‚ฌ์šฉ)

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

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

1
Step 1
mkdir quiz-maker-sharing-platform && cd quiz-maker-sharing-platform

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

Laravel 13.x + Inertia.js + React 19 ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜์„ธ์š”.

1. Laravel Breeze(React + Inertia) starter kit์œผ๋กœ ํ”„๋กœ์ ํŠธ ์ดˆ๊ธฐํ™”:
   - `composer create-project laravel/laravel .`
   - `composer require laravel/breeze --dev`
   - `php artisan breeze:install react`
   - `npm install && npm run dev`

2. SQLite ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ •:
   - `.env` ํŒŒ์ผ์—์„œ `DB_CONNECTION=sqlite` ์„ค์ •
   - `database/database.sqlite` ํŒŒ์ผ ์ƒ์„ฑ

3. ๊ธฐ๋ณธ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹คํ–‰:
   - `php artisan migrate`

4. Tailwind CSS ์„ค์ • ํ™•์ธ (Breeze๊ฐ€ ์ž๋™ ์„ค์ •)

5. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰:
   - `php artisan serve` (๋ฐฑ์—”๋“œ)
   - `npm run dev` (ํ”„๋ก ํŠธ์—”๋“œ Vite)

6. ๋ธŒ๋ผ์šฐ์ €์—์„œ `http://localhost:8000` ์ ‘์†ํ•˜์—ฌ Laravel Breeze ๊ธฐ๋ณธ ํŽ˜์ด์ง€ ํ™•์ธ

์™„๋ฃŒ ํ›„ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ์™€ Breeze ๊ธฐ๋ณธ ์ธ์ฆ ํŽ˜์ด์ง€(๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž…)๊ฐ€ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: Laravel 13.x + Breeze(React + Inertia) ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ์™„๋ฃŒ. ๊ธฐ๋ณธ ์ธ์ฆ ๊ธฐ๋Šฅ(๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž…) ์ž‘๋™. SQLite DB ์—ฐ๊ฒฐ ์™„๋ฃŒ. Tailwind CSS ์ ์šฉ๋œ ๊ธฐ๋ณธ ๋ ˆ์ด์•„์›ƒ ํ™•์ธ ๊ฐ€๋Šฅ.

2
Step 2
ํ€ด์ฆˆ ํ”Œ๋žซํผ์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ตฌ์กฐ๋ฅผ ์„ค๊ณ„ํ•˜๊ณ  ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ƒ์„ฑํ•˜์„ธ์š”.

1. Quizzes ํ…Œ์ด๋ธ” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑ:
   - `php artisan make:migration create_quizzes_table`
   - ์ปฌ๋Ÿผ: id, user_id(์™ธ๋ž˜ํ‚ค), title(string), description(text), category(string), difficulty(enum: easy/medium/hard), created_at, updated_at

2. Questions ํ…Œ์ด๋ธ” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑ:
   - `php artisan make:migration create_questions_table`
   - ์ปฌ๋Ÿผ: id, quiz_id(์™ธ๋ž˜ํ‚ค), question_text(text), option_a(string), option_b(string), option_c(string), option_d(string), correct_answer(enum: a/b/c/d), image_url(string, nullable), order(integer), created_at, updated_at

3. Attempts ํ…Œ์ด๋ธ” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑ:
   - `php artisan make:migration create_attempts_table`
   - ์ปฌ๋Ÿผ: id, user_id(์™ธ๋ž˜ํ‚ค), quiz_id(์™ธ๋ž˜ํ‚ค), score(integer), total_questions(integer), completed_at(timestamp), created_at, updated_at

4. Answers ํ…Œ์ด๋ธ” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑ:
   - `php artisan make:migration create_answers_table`
   - ์ปฌ๋Ÿผ: id, attempt_id(์™ธ๋ž˜ํ‚ค), question_id(์™ธ๋ž˜ํ‚ค), selected_answer(enum: a/b/c/d), is_correct(boolean), created_at, updated_at

5. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹คํ–‰:
   - `php artisan migrate`

6. ๋ชจ๋ธ ์ƒ์„ฑ:
   - `php artisan make:model Quiz`
   - `php artisan make:model Question`
   - `php artisan make:model Attempt`
   - `php artisan make:model Answer`

7. ๋ชจ๋ธ์— ๊ด€๊ณ„ ์ •์˜:
   - Quiz: hasMany(Questions), hasMany(Attempts), belongsTo(User)
   - Question: belongsTo(Quiz)
   - Attempt: belongsTo(User), belongsTo(Quiz), hasMany(Answers)
   - Answer: belongsTo(Attempt), belongsTo(Question)

์™„๋ฃŒ ํ›„ `php artisan tinker`๋กœ ๋ชจ๋ธ ๊ด€๊ณ„๊ฐ€ ์ œ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: 4๊ฐœ ํ…Œ์ด๋ธ”(quizzes, questions, attempts, answers) ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์™„๋ฃŒ. ๋ชจ๋ธ ํŒŒ์ผ ์ƒ์„ฑ ๋ฐ Eloquent ๊ด€๊ณ„ ์ •์˜ ์™„๋ฃŒ. ์™ธ๋ž˜ํ‚ค ์ œ์•ฝ์กฐ๊ฑด ์„ค์ • ์™„๋ฃŒ.

3
Step 3
ํ€ด์ฆˆ ์ƒ์„ฑ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.

1. QuizController ์ƒ์„ฑ:
   - `php artisan make:controller QuizController`

2. ๋ผ์šฐํŠธ ์ •์˜ (`routes/web.php`):
   - `Route::middleware('auth')->group(function() { ... });` ์•ˆ์— ์ถ”๊ฐ€
   - GET `/quizzes/create` โ†’ QuizController@create (ํ€ด์ฆˆ ์ƒ์„ฑ ํผ)
   - POST `/quizzes` โ†’ QuizController@store (ํ€ด์ฆˆ ์ €์žฅ)
   - GET `/quizzes/{quiz}/questions/create` โ†’ QuizController@createQuestion (๋ฌธ์ œ ์ถ”๊ฐ€ ํผ)
   - POST `/quizzes/{quiz}/questions` โ†’ QuizController@storeQuestion (๋ฌธ์ œ ์ €์žฅ)

3. QuizController ๋ฉ”์„œ๋“œ ๊ตฌํ˜„:
   - `create()`: `Inertia::render('Quiz/Create')` ๋ฐ˜ํ™˜
   - `store(Request $request)`: ํผ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ํ›„ Quiz ๋ชจ๋ธ ์ƒ์„ฑ, `/quizzes/{quiz}/questions/create`๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
   - `createQuestion(Quiz $quiz)`: ํ€ด์ฆˆ ์ •๋ณด์™€ ํ•จ๊ป˜ `Inertia::render('Quiz/AddQuestion', ['quiz' => $quiz])` ๋ฐ˜ํ™˜
   - `storeQuestion(Request $request, Quiz $quiz)`: ๋ฌธ์ œ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ํ›„ Question ๋ชจ๋ธ ์ƒ์„ฑ, ๊ฐ™์€ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ (๊ณ„์† ๋ฌธ์ œ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ)

4. React ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:
   - `resources/js/Pages/Quiz/Create.jsx`: ํ€ด์ฆˆ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ž…๋ ฅ ํผ (์ œ๋ชฉ, ์„ค๋ช…, ์นดํ…Œ๊ณ ๋ฆฌ ์„ ํƒ, ๋‚œ์ด๋„ ๋ผ๋””์˜ค ๋ฒ„ํŠผ)
   - `resources/js/Pages/Quiz/AddQuestion.jsx`: ๋ฌธ์ œ ์ž…๋ ฅ ํผ (์งˆ๋ฌธ, 4๊ฐœ ์„ ํƒ์ง€, ์ •๋‹ต ๋ผ๋””์˜ค ๋ฒ„ํŠผ, ์ด๋ฏธ์ง€ URL ์˜ต์…˜)
   - Inertia form helper ์‚ฌ์šฉ: `import { useForm } from '@inertiajs/react';`
   - `form.post('/quizzes')` ๋˜๋Š” `form.post('/quizzes/{quiz.id}/questions')` ํ˜•ํƒœ๋กœ ์ œ์ถœ

5. Tailwind CSS๋กœ ํผ ์Šคํƒ€์ผ๋ง:
   - ์ž…๋ ฅ ํ•„๋“œ, ๋ฒ„ํŠผ, ๋ผ๋””์˜ค ๋ฒ„ํŠผ ๋””์ž์ธ
   - ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ (Inertia errors prop ํ™œ์šฉ)

์™„๋ฃŒ ํ›„ ๋กœ๊ทธ์ธํ•œ ์ƒํƒœ์—์„œ `/quizzes/create`๋กœ ์ด๋™ํ•˜์—ฌ ํ€ด์ฆˆ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋ฌธ์ œ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ํ€ด์ฆˆ ์ƒ์„ฑ ํผ๊ณผ ๋ฌธ์ œ ์ถ”๊ฐ€ ํผ์ด ์ž‘๋™. ํ€ด์ฆˆ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ์™€ ๋ฌธ์ œ๊ฐ€ DB์— ์ €์žฅ๋จ. React ์ปดํฌ๋„ŒํŠธ์—์„œ Inertia form helper๋กœ ๋ฐ์ดํ„ฐ ์ œ์ถœ ๊ฐ€๋Šฅ. ํผ ๊ฒ€์ฆ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ.

4
Step 4
ํ€ด์ฆˆ ๋ชฉ๋ก ํŽ˜์ด์ง€์™€ ์ƒ์„ธ ํŽ˜์ด์ง€๋ฅผ ๊ตฌํ˜„ํ•˜์„ธ์š”.

1. QuizController์— ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€:
   - `index(Request $request)`: ํ€ด์ฆˆ ๋ชฉ๋ก ์กฐํšŒ (์นดํ…Œ๊ณ ๋ฆฌ/๋‚œ์ด๋„ ํ•„ํ„ฐ๋ง, ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ)
     - ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ `category`, `difficulty`, `search` ๋ฐ›๊ธฐ
     - `Quiz::with('user')->when(...)->paginate(12)` ํ˜•ํƒœ๋กœ ํ•„ํ„ฐ๋ง ๊ตฌํ˜„
     - `Inertia::render('Quiz/Index', ['quizzes' => $quizzes, 'filters' => $request->only(['category', 'difficulty', 'search'])])` ๋ฐ˜ํ™˜
   - `show(Quiz $quiz)`: ํ€ด์ฆˆ ์ƒ์„ธ ์ •๋ณด (๋ฌธ์ œ ๊ฐœ์ˆ˜, ์ž‘์„ฑ์ž, ์„ค๋ช… ๋“ฑ)
     - `$quiz->load('questions', 'user');`
     - `Inertia::render('Quiz/Show', ['quiz' => $quiz])` ๋ฐ˜ํ™˜

2. ๋ผ์šฐํŠธ ์ถ”๊ฐ€:
   - GET `/quizzes` โ†’ QuizController@index
   - GET `/quizzes/{quiz}` โ†’ QuizController@show

3. React ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:
   - `resources/js/Pages/Quiz/Index.jsx`: ํ€ด์ฆˆ ์นด๋“œ ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ
     - ํ•„ํ„ฐ๋ง UI (์นดํ…Œ๊ณ ๋ฆฌ ๋“œ๋กญ๋‹ค์šด, ๋‚œ์ด๋„ ์ฒดํฌ๋ฐ•์Šค, ๊ฒ€์ƒ‰ ์ž…๋ ฅ)
     - Inertia Link๋กœ ํ€ด์ฆˆ ์ƒ์„ธ ํŽ˜์ด์ง€ ์ด๋™: `<Link href={`/quizzes/${quiz.id}`}>`
     - ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ปดํฌ๋„ŒํŠธ (Inertia์—์„œ ์ œ๊ณตํ•˜๋Š” links prop ํ™œ์šฉ)
   - `resources/js/Pages/Quiz/Show.jsx`: ํ€ด์ฆˆ ์ƒ์„ธ ์ •๋ณด ํ‘œ์‹œ
     - ์ œ๋ชฉ, ์„ค๋ช…, ์นดํ…Œ๊ณ ๋ฆฌ, ๋‚œ์ด๋„, ๋ฌธ์ œ ๊ฐœ์ˆ˜, ์ž‘์„ฑ์ž ํ‘œ์‹œ
     - "ํ€ด์ฆˆ ์‹œ์ž‘ํ•˜๊ธฐ" ๋ฒ„ํŠผ โ†’ `/quizzes/{quiz.id}/play`๋กœ ์ด๋™

4. Tailwind CSS๋กœ ์Šคํƒ€์ผ๋ง:
   - ํ€ด์ฆˆ ์นด๋“œ: ๊ทธ๋ฆผ์ž, ํ˜ธ๋ฒ„ ํšจ๊ณผ, ๋ฐฐ์ง€(์นดํ…Œ๊ณ ๋ฆฌ/๋‚œ์ด๋„)
   - ํ•„ํ„ฐ๋ง UI: ๋“œ๋กญ๋‹ค์šด, ์ฒดํฌ๋ฐ•์Šค ์Šคํƒ€์ผ
   - ์ƒ์„ธ ํŽ˜์ด์ง€: ํฐ ์ œ๋ชฉ, ์„ค๋ช… ํ…์ŠคํŠธ, CTA ๋ฒ„ํŠผ

์™„๋ฃŒ ํ›„ `/quizzes`์—์„œ ํ€ด์ฆˆ ๋ชฉ๋ก์ด ํ‘œ์‹œ๋˜๊ณ , ํ•„ํ„ฐ๋ง/๊ฒ€์ƒ‰์ด ์ž‘๋™ํ•˜๋ฉฐ, ์ƒ์„ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

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

5
Step 5
ํ€ด์ฆˆ ํ’€์ด ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.

1. QuizController์— ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€:
   - `play(Quiz $quiz)`: ํ€ด์ฆˆ ํ’€์ด ์‹œ์ž‘
     - `$quiz->load('questions');`
     - ์ƒˆ๋กœ์šด Attempt ๋ ˆ์ฝ”๋“œ ์ƒ์„ฑ (user_id, quiz_id, score=0, total_questions)
     - `Inertia::render('Quiz/Play', ['quiz' => $quiz, 'attempt' => $attempt])` ๋ฐ˜ํ™˜
   - `submitAnswer(Request $request, Quiz $quiz, Attempt $attempt)`: ๋‹ต๋ณ€ ์ œ์ถœ
     - ์š”์ฒญ ๋ฐ์ดํ„ฐ: `question_id`, `selected_answer`
     - Answer ๋ ˆ์ฝ”๋“œ ์ƒ์„ฑ (attempt_id, question_id, selected_answer, is_correct)
     - ์ •๋‹ต ์—ฌ๋ถ€ ํ™•์ธ ํ›„ Attempt์˜ score ์—…๋ฐ์ดํŠธ
     - JSON ์‘๋‹ต: `{ correct: true/false, correct_answer: 'a' }`
   - `result(Quiz $quiz, Attempt $attempt)`: ๊ฒฐ๊ณผ ํŽ˜์ด์ง€
     - `$attempt->load('answers.question');`
     - Attempt์˜ completed_at ํƒ€์ž„์Šคํƒฌํ”„ ์—…๋ฐ์ดํŠธ
     - `Inertia::render('Quiz/Result', ['quiz' => $quiz, 'attempt' => $attempt])` ๋ฐ˜ํ™˜

2. ๋ผ์šฐํŠธ ์ถ”๊ฐ€:
   - GET `/quizzes/{quiz}/play` โ†’ QuizController@play (middleware: auth)
   - POST `/quizzes/{quiz}/attempts/{attempt}/answer` โ†’ QuizController@submitAnswer
   - GET `/quizzes/{quiz}/attempts/{attempt}/result` โ†’ QuizController@result

3. React ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:
   - `resources/js/Pages/Quiz/Play.jsx`: ํ€ด์ฆˆ ํ’€์ด ์ธํ„ฐํŽ˜์ด์Šค
     - useState๋กœ ํ˜„์žฌ ๋ฌธ์ œ ์ธ๋ฑ์Šค ๊ด€๋ฆฌ
     - ๋ฌธ์ œ ํ‘œ์‹œ: ์งˆ๋ฌธ, 4๊ฐœ ์„ ํƒ์ง€ ๋ฒ„ํŠผ
     - ์„ ํƒ์ง€ ํด๋ฆญ ์‹œ `/quizzes/{quiz.id}/attempts/{attempt.id}/answer`๋กœ POST ์š”์ฒญ (axios ์‚ฌ์šฉ)
     - ์‘๋‹ต ๋ฐ›์œผ๋ฉด ์ •๋‹ต/์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ ํ‘œ์‹œ (๋ฒ„ํŠผ ์ƒ‰์ƒ ๋ณ€๊ฒฝ: ์ •๋‹ต=์ดˆ๋ก, ์˜ค๋‹ต=๋นจ๊ฐ•)
     - 2์ดˆ ํ›„ ๋‹ค์Œ ๋ฌธ์ œ๋กœ ์ด๋™ (์ธ๋ฑ์Šค ์ฆ๊ฐ€)
     - ๋งˆ์ง€๋ง‰ ๋ฌธ์ œ ์™„๋ฃŒ ์‹œ `/quizzes/{quiz.id}/attempts/{attempt.id}/result`๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
     - ์ง„ํ–‰๋ฅ  ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ”: `(currentIndex + 1) / totalQuestions * 100`
   - `resources/js/Pages/Quiz/Result.jsx`: ๊ฒฐ๊ณผ ํŽ˜์ด์ง€
     - ์ด์ , ์ •๋‹ต๋ฅ  ํ‘œ์‹œ
     - ์˜ค๋‹ต ๋ฌธ์ œ ๋ฆฌ์ŠคํŠธ (์งˆ๋ฌธ, ๋‚ด๊ฐ€ ์„ ํƒํ•œ ๋‹ต, ์ •๋‹ต)
     - "๋‹ค์‹œ ํ’€๊ธฐ" ๋ฒ„ํŠผ, "ํ€ด์ฆˆ ๋ชฉ๋ก์œผ๋กœ" ๋ฒ„ํŠผ

4. Tailwind CSS๋กœ ์Šคํƒ€์ผ๋ง:
   - ์„ ํƒ์ง€ ๋ฒ„ํŠผ: ํฐ ํฌ๊ธฐ, ํ˜ธ๋ฒ„ ํšจ๊ณผ, ์ •๋‹ต/์˜ค๋‹ต ์ƒ‰์ƒ ์• ๋‹ˆ๋ฉ”์ด์…˜
   - ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ”: ์ƒ๋‹จ ๊ณ ์ •, ์ƒ‰์ƒ ๊ทธ๋ผ๋ฐ์ด์…˜
   - ๊ฒฐ๊ณผ ํŽ˜์ด์ง€: ์ ์ˆ˜ ์นด์šดํŠธ์—… ์• ๋‹ˆ๋ฉ”์ด์…˜ (CSS transition)

์™„๋ฃŒ ํ›„ ํ€ด์ฆˆ๋ฅผ ํ’€๊ณ  ๊ฒฐ๊ณผ ํŽ˜์ด์ง€์—์„œ ์ ์ˆ˜์™€ ์˜ค๋‹ต ๋ฆฌ๋ทฐ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ํ€ด์ฆˆ ํ’€์ด ํŽ˜์ด์ง€์—์„œ ๋ฌธ์ œ๋ฅผ ์ˆœ์„œ๋Œ€๋กœ ํ’€ ์ˆ˜ ์žˆ์Œ. ์„ ํƒ์ง€ ํด๋ฆญ ์‹œ ์ฆ‰๊ฐ์ ์ธ ์ •๋‹ต/์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ. ์ง„ํ–‰๋ฅ  ํ”„๋กœ๊ทธ๋ ˆ์Šค ๋ฐ” ์ž‘๋™. ๊ฒฐ๊ณผ ํŽ˜์ด์ง€์—์„œ ์ ์ˆ˜์™€ ์˜ค๋‹ต ๋ฆฌ์ŠคํŠธ ํ‘œ์‹œ.

6
Step 6
์ˆœ์œ„ํ‘œ์™€ ๋งˆ์ดํŽ˜์ด์ง€ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜์„ธ์š”.

1. QuizController์— ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€:
   - `leaderboard(Quiz $quiz)`: ํ€ด์ฆˆ๋ณ„ ์ˆœ์œ„ํ‘œ
     - `Attempt::where('quiz_id', $quiz->id)->with('user')->orderBy('score', 'desc')->orderBy('completed_at', 'asc')->take(10)->get();`
     - `Inertia::render('Quiz/Leaderboard', ['quiz' => $quiz, 'topAttempts' => $topAttempts])` ๋ฐ˜ํ™˜

2. UserController ์ƒ์„ฑ ๋ฐ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€:
   - `php artisan make:controller UserController`
   - `myQuizzes()`: ๋‚ด๊ฐ€ ๋งŒ๋“  ํ€ด์ฆˆ ๋ชฉ๋ก
     - `Quiz::where('user_id', auth()->id())->with('questions')->get();`
     - `Inertia::render('User/MyQuizzes', ['quizzes' => $quizzes])` ๋ฐ˜ํ™˜
   - `myAttempts()`: ๋‚ด๊ฐ€ ์ฐธ์—ฌํ•œ ํ€ด์ฆˆ ํžˆ์Šคํ† ๋ฆฌ
     - `Attempt::where('user_id', auth()->id())->with('quiz')->orderBy('completed_at', 'desc')->get();`
     - `Inertia::render('User/MyAttempts', ['attempts' => $attempts])` ๋ฐ˜ํ™˜

3. ๋ผ์šฐํŠธ ์ถ”๊ฐ€:
   - GET `/quizzes/{quiz}/leaderboard` โ†’ QuizController@leaderboard
   - GET `/my-quizzes` โ†’ UserController@myQuizzes (middleware: auth)
   - GET `/my-attempts` โ†’ UserController@myAttempts (middleware: auth)

4. React ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ:
   - `resources/js/Pages/Quiz/Leaderboard.jsx`: ์ˆœ์œ„ํ‘œ ํ…Œ์ด๋ธ”
     - ์ˆœ์œ„, ์‚ฌ์šฉ์ž ์ด๋ฆ„, ์ ์ˆ˜, ์™„๋ฃŒ ์‹œ๊ฐ„ ํ‘œ์‹œ
     - ์ƒ์œ„ 3๋ช…์—๊ฒŒ ๋ฉ”๋‹ฌ ์•„์ด์ฝ˜ ํ‘œ์‹œ (๐Ÿฅ‡๐Ÿฅˆ๐Ÿฅ‰)
     - ๋‚ด ์ˆœ์œ„ ํ•˜์ด๋ผ์ดํŠธ (๋ฐฐ๊ฒฝ ์ƒ‰์ƒ ๋ณ€๊ฒฝ)
   - `resources/js/Pages/User/MyQuizzes.jsx`: ๋‚ด๊ฐ€ ๋งŒ๋“  ํ€ด์ฆˆ ์นด๋“œ ๊ทธ๋ฆฌ๋“œ
     - ํ€ด์ฆˆ ์ œ๋ชฉ, ๋ฌธ์ œ ๊ฐœ์ˆ˜, ์ƒ์„ฑ ๋‚ ์งœ
     - "์ˆ˜์ •" ๋ฒ„ํŠผ, "์‚ญ์ œ" ๋ฒ„ํŠผ
   - `resources/js/Pages/User/MyAttempts.jsx`: ์ฐธ์—ฌ ํžˆ์Šคํ† ๋ฆฌ ๋ฆฌ์ŠคํŠธ
     - ํ€ด์ฆˆ ์ œ๋ชฉ, ์ ์ˆ˜, ์ •๋‹ต๋ฅ , ์ฐธ์—ฌ ๋‚ ์งœ
     - "๋‹ค์‹œ ํ’€๊ธฐ" ๋ฒ„ํŠผ, "๊ฒฐ๊ณผ ๋ณด๊ธฐ" ๋ฒ„ํŠผ

5. ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฉ”๋‰ด ์ถ”๊ฐ€:
   - `resources/js/Layouts/AuthenticatedLayout.jsx` ๋˜๋Š” ํ—ค๋” ์ปดํฌ๋„ŒํŠธ์— ๋ฉ”๋‰ด ์ถ”๊ฐ€
   - "ํ€ด์ฆˆ ๋งŒ๋“ค๊ธฐ", "๋‚ด ํ€ด์ฆˆ", "์ฐธ์—ฌ ํžˆ์Šคํ† ๋ฆฌ" ๋งํฌ

6. Tailwind CSS๋กœ ์Šคํƒ€์ผ๋ง:
   - ์ˆœ์œ„ํ‘œ: ํ…Œ์ด๋ธ” ์Šคํƒ€์ผ, ๋ฉ”๋‹ฌ ์•„์ด์ฝ˜, ๋‚ด ์ˆœ์œ„ ํ•˜์ด๋ผ์ดํŠธ
   - ๋งˆ์ดํŽ˜์ด์ง€: ์นด๋“œ ๋ ˆ์ด์•„์›ƒ, ๋ฒ„ํŠผ ์Šคํƒ€์ผ

์™„๋ฃŒ ํ›„ ์ˆœ์œ„ํ‘œ์—์„œ ์ƒ์œ„ 10๋ช…์ด ํ‘œ์‹œ๋˜๊ณ , ๋งˆ์ดํŽ˜์ด์ง€์—์„œ ๋‚ด๊ฐ€ ๋งŒ๋“  ํ€ด์ฆˆ์™€ ์ฐธ์—ฌ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

๊ธฐ๋Œ€ ๊ฒฐ๊ณผ: ํ€ด์ฆˆ๋ณ„ ์ˆœ์œ„ํ‘œ์—์„œ ์ƒ์œ„ 10๋ช… ํ‘œ์‹œ. ์ƒ์œ„ 3๋ช…์—๊ฒŒ ๋ฉ”๋‹ฌ ํ‘œ์‹œ. ๋งˆ์ดํŽ˜์ด์ง€์—์„œ ๋‚ด๊ฐ€ ๋งŒ๋“  ํ€ด์ฆˆ์™€ ์ฐธ์—ฌ ํžˆ์Šคํ† ๋ฆฌ ํ™•์ธ ๊ฐ€๋Šฅ. ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฉ”๋‰ด๋กœ ์‰ฝ๊ฒŒ ์ด๋™ ๊ฐ€๋Šฅ.

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

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

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

๋Œ“๊ธ€ (0)

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