๐ธ ๋ฐด๋ ๊ณต์ฐ ํฐ์ผ ํ๋งค ๋๋ฉํ์ด์ง - ์๋งค ํผ & ๊ณต์ฐ ์ ๋ณด
band-concert-ticket-landing
๋ฌธ์ ์ค๋ช
ํ๋ก์ ํธ ๊ฐ์
์ธ๋ ๋ฐด๋๋ ์๊ท๋ชจ ๊ณต์ฐ ๊ธฐํ์๋ฅผ ์ํ ๊ณต์ฐ ํฐ์ผ ํ๋งค ๋๋ฉํ์ด์ง๋ฅผ ๋ง๋ญ๋๋ค. Laravel 13.x + Blade ํ ํ๋ฆฟ + Tailwind CSS๋ก ๊ตฌ์ฑํ๋ฉฐ, ๊ณต์ฐ ์ ๋ณด ์๊ฐ, ํฐ์ผ ์๋งค ํผ, ์๋งค ๋ด์ญ ํ์ธ ๊ธฐ๋ฅ์ ํฌํจํฉ๋๋ค.
์ฌ์ฉ์๋ ๊ณต์ฐ ์ ๋ชฉ, ๋ ์ง, ์ฅ์, ๊ฐ๊ฒฉ ๋ฑ ์์ธ ์ ๋ณด๋ฅผ ํ์ธํ๊ณ , ํฐ์ผ ์๋งค ํผ์ ์์ฑํ์ฌ ์๋งค๋ฅผ ์๋ฃํ ์ ์์ต๋๋ค. ์๋งค ์๋ฃ ์ ๊ฐ๋จํ ํ์ธ ํ์ด์ง๊ฐ ํ์๋๋ฉฐ, ๊ด๋ฆฌ์๋ ์๋งค ๋ชฉ๋ก์ ํ์ธํ ์ ์์ต๋๋ค.
ํ์ ๊ธฐ๋ฅ
- ๊ณต์ฐ ์์ธ ์ ๋ณด ํ์ (์ ๋ชฉ, ์ผ์, ์ฅ์, ๊ฐ๊ฒฉ, ํฌ์คํฐ ์ด๋ฏธ์ง, ์ค๋ช )
- ํฐ์ผ ์๋งค ํผ (์ด๋ฆ, ์ด๋ฉ์ผ, ์ ํ๋ฒํธ, ๋งค์)
- ์๋งค ๋ด์ญ ์ ์ฅ (SQLite DB)
- ์๋งค ์๋ฃ ํ์ธ ํ์ด์ง
- ์๋งค ๋ชฉ๋ก ์กฐํ ํ์ด์ง (๊ฐ๋จํ ํ ์ด๋ธ ํํ)
- Blade ์ปดํฌ๋ํธ ํ์ฉํ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ UI
- Tailwind CSS๋ก ๋ชจ๋ํ ๋์์ธ
์ ํ ์ถ๊ฐ ๊ธฐ๋ฅ (๋ณด๋์ค)
- ๊ณต์ฐ ํฌ์คํฐ ์ด๋ฏธ์ง ์ ๋ก๋ ๊ธฐ๋ฅ
- ์๋งค ์ ์ด๋ฉ์ผ ์๋ฆผ (Laravel Mail)
- ์๋งค ์ทจ์ ๊ธฐ๋ฅ
- ๊ณต์ฐ๋ณ ์์ฌ ํฐ์ผ ์ ํ์
- ๋ฐ์ํ ๋์์ธ (๋ชจ๋ฐ์ผ ์ต์ ํ)
- ์๋งค ๋ฒํธ ์๋ ์์ฑ ๋ฐ QR ์ฝ๋ ์์ฑ
์ ์ฝ ์กฐ๊ฑด
- โข Laravel 13.x ์ฌ์ฉ (์ต์ ๋ฒ์ )
- โข Blade ํ ํ๋ฆฟ ์์ง ์ฌ์ฉ (React/Vue ์ฌ์ฉ ๊ธ์ง)
- โข Tailwind CSS๋ก ์คํ์ผ๋ง
- โข SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฌ์ฉ
- โข Eloquent ORM์ผ๋ก DB ์์
- โข ๋ผ์ฐํธ๋ web.php์ ์ ์
- โข ๋ง์ด๊ทธ๋ ์ด์ ์ผ๋ก DB ์คํค๋ง ๊ด๋ฆฌ
- โข Blade ์ปดํฌ๋ํธ๋ก ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ UI ๊ตฌ์ฑ
- โข ํผ ๊ฒ์ฆ(Validation) ํ์
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
mkdir band-concert-ticket-landing && cd band-concert-ticket-landing
์ด ํด๋์์ Laravel 13.x ํ๋ก์ ํธ๋ฅผ ์์ํฉ๋๋ค.
Laravel 13.x ํ๋ก์ ํธ๋ฅผ ์์ฑํ๊ณ ๊ธฐ๋ณธ ์ค์ ์ ์๋ฃํด์ฃผ์ธ์.
1. `composer create-project laravel/laravel .` ๋ช
๋ น์ผ๋ก Laravel ์ต์ ๋ฒ์ (13.x) ์ค์น
2. `.env` ํ์ผ์์ DB ์ค์ ์ SQLite๋ก ๋ณ๊ฒฝ:
- `DB_CONNECTION=sqlite`
- `DB_DATABASE` ํญ๋ชฉ์ ์ฃผ์ ์ฒ๋ฆฌํ๊ฑฐ๋ ์ญ์
3. `database/database.sqlite` ํ์ผ ์์ฑ (๋น ํ์ผ)
4. `php artisan migrate` ์คํํ์ฌ ๊ธฐ๋ณธ ๋ง์ด๊ทธ๋ ์ด์
์ ์ฉ
5. Tailwind CSS ์ค์น:
- `npm install -D tailwindcss postcss autoprefixer`
- `npx tailwindcss init -p`
- `tailwind.config.js`์์ `content` ๊ฒฝ๋ก ์ค์ :
```js
content: [
"./resources/**/*.blade.php",
"./resources/**/*.js",
],
```
- `resources/css/app.css`์ Tailwind ๋๋ ํฐ๋ธ ์ถ๊ฐ:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
- `npm run dev` ์คํํ์ฌ ๋น๋
6. `resources/views/welcome.blade.php`๋ฅผ ๊ฐ๋จํ "๊ณต์ฐ ํฐ์ผ ํ๋งค ๋๋ฉํ์ด์ง" ํค๋๋ก ์์ ํ์ฌ ์ค์น ํ์ธ
7. `php artisan serve`๋ก ์๋ฒ ์คํ ํ http://localhost:8000 ์ ์ํ์ฌ Tailwind CSS ์ ์ฉ ํ์ธ
๋ชจ๋ ์ค์ ์ด ์๋ฃ๋๋ฉด "Laravel + Tailwind CSS ์ค์น ์๋ฃ" ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํด์ฃผ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: Laravel 13.x ํ๋ก์ ํธ๊ฐ ์์ฑ๋๊ณ , SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์ ์ด ์๋ฃ๋๋ฉฐ, Tailwind CSS๊ฐ ์ค์น๋์ด ๊ธฐ๋ณธ ํ์ด์ง์ ์ ์ฉ๋ ์ํ์ ๋๋ค. ๊ฐ๋ฐ ์๋ฒ๊ฐ ์คํ ์ค์ด๋ฉฐ ๋ธ๋ผ์ฐ์ ์์ ํ์ธ ๊ฐ๋ฅํฉ๋๋ค.
๊ณต์ฐ ์ ๋ณด์ ์๋งค ๋ด์ญ์ ์ ์ฅํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ตฌ์กฐ๋ฅผ ์ค๊ณํ๊ณ ๋ง์ด๊ทธ๋ ์ด์
์ ์์ฑํด์ฃผ์ธ์.
1. `concerts` ํ
์ด๋ธ ๋ง์ด๊ทธ๋ ์ด์
์์ฑ:
- `php artisan make:migration create_concerts_table`
- ์ปฌ๋ผ: id, title(string), description(text), date(datetime), venue(string), price(integer), poster_url(string, nullable), available_tickets(integer, default 100), created_at, updated_at
2. `bookings` ํ
์ด๋ธ ๋ง์ด๊ทธ๋ ์ด์
์์ฑ:
- `php artisan make:migration create_bookings_table`
- ์ปฌ๋ผ: id, concert_id(foreignId, concerts ํ
์ด๋ธ ์ฐธ์กฐ), name(string), email(string), phone(string), tickets(integer), booking_number(string, unique), created_at, updated_at
- foreign key ์ ์ฝ ์กฐ๊ฑด ์ถ๊ฐ: `$table->foreignId('concert_id')->constrained()->onDelete('cascade');`
3. ๋ง์ด๊ทธ๋ ์ด์
์คํ: `php artisan migrate`
4. Concert ๋ชจ๋ธ ์์ฑ:
- `php artisan make:model Concert`
- `app/Models/Concert.php`์์ `$fillable` ์์ฑ ์ ์: ['title', 'description', 'date', 'venue', 'price', 'poster_url', 'available_tickets']
- `bookings` ๊ด๊ณ ๋ฉ์๋ ์ถ๊ฐ: `public function bookings() { return $this->hasMany(Booking::class); }`
5. Booking ๋ชจ๋ธ ์์ฑ:
- `php artisan make:model Booking`
- `app/Models/Booking.php`์์ `$fillable` ์์ฑ ์ ์: ['concert_id', 'name', 'email', 'phone', 'tickets', 'booking_number']
- `concert` ๊ด๊ณ ๋ฉ์๋ ์ถ๊ฐ: `public function concert() { return $this->belongsTo(Concert::class); }`
6. ์๋(Seeder)๋ก ์ํ ๊ณต์ฐ ๋ฐ์ดํฐ ์์ฑ:
- `php artisan make:seeder ConcertSeeder`
- `database/seeders/ConcertSeeder.php`์์ 1~2๊ฐ์ ์ํ ๊ณต์ฐ ๋ฐ์ดํฐ ์์ฑ (์: "์ธ๋ ๋ฐด๋ ๋ผ์ด๋ธ ์ฝ์ํธ", ๋ ์ง๋ ๋ฏธ๋ ๋ ์ง)
- `DatabaseSeeder.php`์์ `$this->call(ConcertSeeder::class);` ํธ์ถ
- `php artisan db:seed` ์คํ
์๋ฃ๋๋ฉด "DB ๋ง์ด๊ทธ๋ ์ด์
๋ฐ ๋ชจ๋ธ ์ค์ ์๋ฃ" ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํด์ฃผ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: concerts์ bookings ํ ์ด๋ธ์ด ์์ฑ๋๊ณ , Concert ๋ฐ Booking ๋ชจ๋ธ์ด ์ ์๋๋ฉฐ, ์ํ ๊ณต์ฐ ๋ฐ์ดํฐ๊ฐ DB์ ์ฝ์ ๋ ์ํ์ ๋๋ค. ๋ชจ๋ธ ๊ฐ ๊ด๊ณ(hasMany/belongsTo)๊ฐ ์ค์ ๋์ด ์์ต๋๋ค.
๊ณต์ฐ ์์ธ ์ ๋ณด๋ฅผ ํ์ํ๋ ๋๋ฉํ์ด์ง์ ํฐ์ผ ์๋งค ํผ์ ๊ตฌํํด์ฃผ์ธ์.
1. ๋ผ์ฐํธ ์ ์ (`routes/web.php`):
- `GET /` โ ๊ณต์ฐ ๋ชฉ๋ก ํ์ด์ง (ConcertController@index)
- `GET /concerts/{concert}` โ ๊ณต์ฐ ์์ธ & ์๋งค ํผ ํ์ด์ง (ConcertController@show)
- `POST /concerts/{concert}/book` โ ์๋งค ์ฒ๋ฆฌ (BookingController@store)
- `GET /bookings` โ ์๋งค ๋ชฉ๋ก ํ์ด์ง (BookingController@index)
- `GET /bookings/{booking}` โ ์๋งค ํ์ธ ํ์ด์ง (BookingController@show)
2. ConcertController ์์ฑ:
- `php artisan make:controller ConcertController`
- `index()` ๋ฉ์๋: ๋ชจ๋ ๊ณต์ฐ ๋ชฉ๋ก ๋ฐํ (`Concert::all()`), `concerts.index` ๋ทฐ ๋ ๋๋ง
- `show(Concert $concert)` ๋ฉ์๋: ํน์ ๊ณต์ฐ ์์ธ ์ ๋ณด ๋ฐํ, `concerts.show` ๋ทฐ ๋ ๋๋ง
3. BookingController ์์ฑ:
- `php artisan make:controller BookingController`
- `store(Request $request, Concert $concert)` ๋ฉ์๋:
- ํผ ๊ฒ์ฆ: name(required), email(required|email), phone(required), tickets(required|integer|min:1)
- ์๋งค ๋ฒํธ ์๋ ์์ฑ (์: 'BK' + ํ์์คํฌํ + ๋๋ค ๋ฌธ์์ด)
- Booking ๋ ์ฝ๋ ์์ฑ ๋ฐ ์ ์ฅ
- ๊ณต์ฐ ์์ฌ ํฐ์ผ ์ ๊ฐ์ (`$concert->decrement('available_tickets', $request->tickets)`)
- ์๋งค ํ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ (`redirect()->route('bookings.show', $booking)`)
- `index()` ๋ฉ์๋: ๋ชจ๋ ์๋งค ๋ชฉ๋ก ๋ฐํ (`Booking::with('concert')->latest()->get()`), `bookings.index` ๋ทฐ ๋ ๋๋ง
- `show(Booking $booking)` ๋ฉ์๋: ์๋งค ์์ธ ์ ๋ณด ๋ฐํ, `bookings.show` ๋ทฐ ๋ ๋๋ง
4. ๋ทฐ ํ์ผ ์์ฑ:
- `resources/views/layouts/app.blade.php`: ๊ธฐ๋ณธ ๋ ์ด์์ (Tailwind CSS ํฌํจ, @yield('content'))
- `resources/views/concerts/index.blade.php`: ๊ณต์ฐ ๋ชฉ๋ก (์นด๋ ํํ๋ก ํ์, ๊ฐ ์นด๋์ "์์ธ๋ณด๊ธฐ" ๋งํฌ)
- `resources/views/concerts/show.blade.php`: ๊ณต์ฐ ์์ธ ์ ๋ณด + ์๋งค ํผ (ํฌ์คํฐ ์ด๋ฏธ์ง, ์ ๋ชฉ, ๋ ์ง, ์ฅ์, ๊ฐ๊ฒฉ, ์ค๋ช
, ์์ฌ ํฐ์ผ ํ์)
- ์๋งค ํผ: name, email, phone, tickets(number input) ํ๋, "์๋งคํ๊ธฐ" ๋ฒํผ
- `resources/views/bookings/index.blade.php`: ์๋งค ๋ชฉ๋ก (ํ
์ด๋ธ ํํ)
- `resources/views/bookings/show.blade.php`: ์๋งค ํ์ธ ํ์ด์ง (์๋งค ๋ฒํธ, ๊ณต์ฐ ์ ๋ณด, ์๋งค์ ์ ๋ณด ํ์)
5. Tailwind CSS๋ก ๋์์ธ:
- ๊ณต์ฐ ์นด๋: ๊ทธ๋ฆผ์, ๋ฅ๊ทผ ๋ชจ์๋ฆฌ, ํธ๋ฒ ํจ๊ณผ
- ์๋งค ํผ: input ์คํ์ผ๋ง, ๋ฒํผ ์์ (primary), ๋ฐ์ํ ๋ ์ด์์
- ์๋งค ํ์ธ ํ์ด์ง: ์ฑ๊ณต ๋ฉ์์ง (์ด๋ก์ ๋ฐฐ๊ฒฝ), ์๋งค ๋ฒํธ ๊ฐ์กฐ
6. ์๋ฌ ์ฒ๋ฆฌ:
- ํผ ๊ฒ์ฆ ์คํจ ์ ์๋ฌ ๋ฉ์์ง ํ์ (`@error` ๋๋ ํฐ๋ธ)
- ์์ฌ ํฐ์ผ ๋ถ์กฑ ์ ์๋งค ๋ถ๊ฐ ๋ฉ์์ง
์๋ฃ๋๋ฉด "๊ณต์ฐ ๋๋ฉํ์ด์ง ๋ฐ ์๋งค ๊ธฐ๋ฅ ๊ตฌํ ์๋ฃ" ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํด์ฃผ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๊ณต์ฐ ๋ชฉ๋ก, ๊ณต์ฐ ์์ธ, ์๋งค ํผ, ์๋งค ๋ชฉ๋ก, ์๋งค ํ์ธ ํ์ด์ง๊ฐ ๋ชจ๋ ๊ตฌํ๋์ด ์์ผ๋ฉฐ, ์ฌ์ฉ์๊ฐ ๊ณต์ฐ์ ์ ํํ๊ณ ํฐ์ผ์ ์๋งคํ ์ ์์ต๋๋ค. Tailwind CSS๋ก ๋ชจ๋ํ ๋์์ธ์ด ์ ์ฉ๋์ด ์๊ณ , ํผ ๊ฒ์ฆ ๋ฐ ์๋ฌ ์ฒ๋ฆฌ๊ฐ ์๋ํฉ๋๋ค.
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!