๐๏ธ ํผํธ๋์ค ์ผํฐ ํ์๊ถ ์๊ฐ ํ์ด์ง - ์๊ธ์ ๋น๊ต & ๊ฐ์ ์ ์ฒญ ํผ
fitness-membership-landing-page
๋ฌธ์ ์ค๋ช
๐๏ธ ํผํธ๋์ค ์ผํฐ ํ์๊ถ ์๊ฐ ํ์ด์ง
ํฌ์ค์ฅ์ด๋ ํ๋ผํ ์ค ์คํ๋์ค๋ฅผ ์ด์ํ๋ ์๊ท๋ชจ ๋น์ฆ๋์ค๋ฅผ ์ํ ํ์๊ถ ์๊ฐ ๋๋ฉํ์ด์ง๋ฅผ ๋ง๋ญ๋๋ค. ๋ฐฉ๋ฌธ์๊ฐ ๋ค์ํ ์๊ธ์ ๋ฅผ ํ๋์ ๋น๊ตํ๊ณ , ์ํ๋ ํ๋์ ์ ํํ์ฌ ๋ฐ๋ก ๊ฐ์ ์ ์ฒญํ ์ ์๋ ์ค์ฉ์ ์ธ ํ์ด์ง์ ๋๋ค.
๐ ํ์ ๊ธฐ๋ฅ
- ์๊ธ์ ์นด๋ ๋ ์ด์์: 3๊ฐ์ง ํ์๊ถ ํ๋(Basic, Standard, Premium) ์นด๋ํ ๋์์ธ
- ํ๋๋ณ ์์ธ ์ ๋ณด: ๊ฐ๊ฒฉ, ์ด์ฉ ๊ธฐ๊ฐ, ํฌํจ ์๋น์ค(PT ํ์, ๋ฝ์ปค ์ ๊ณต ์ฌ๋ถ, ์ค์์ค ์ด์ฉ ๋ฑ) ํ์
- ์ถ์ฒ ๋ฐฐ์ง: ์ธ๊ธฐ ํ๋์ "Most Popular" ๋ฐฐ์ง ๊ฐ์กฐ ํ์
- ๊ฐ์ ์ ์ฒญ ํผ: ์ด๋ฆ, ์ฐ๋ฝ์ฒ, ์ ํํ ํ๋์ ์ ๋ ฅ๋ฐ์ DB ์ ์ฅ
- ์ ์ฒญ ์๋ฃ ํ์ด์ง: ์ ์ฒญ ํ ํ์ธ ๋ฉ์์ง ๋ฐ ์ ์ฒญ ๋ด์ญ ํ์
- ๋ฐ์ํ ๋์์ธ: ๋ชจ๋ฐ์ผ์์๋ ์นด๋๊ฐ ์ธ๋ก๋ก ๊น๋ํ๊ฒ ์ ๋ ฌ
๐ ๋ณด๋์ค ๊ธฐ๋ฅ (์ ํ)
- ํ ์ธ ์ด๋ฒคํธ ๋ฐฐ๋ (์: "์ฒซ ๋ฌ 50% ํ ์ธ!")
- ์์ค ์ฌ์ง ๊ฐค๋ฌ๋ฆฌ ์น์ ์ถ๊ฐ
- FAQ ์์ฝ๋์ธ ์น์ (์์ฃผ ๋ฌป๋ ์ง๋ฌธ)
- ์ด๋ฉ์ผ ์๋ฆผ ๊ธฐ๋ฅ (์ ์ฒญ ์ ์๋ ์ด๋ฉ์ผ ๋ฐ์ก)
- ๋คํฌ ๋ชจ๋ ํ ๊ธ
๐ฏ ํ์ต ๋ชฉํ
- Laravel Blade ์ปดํฌ๋ํธ๋ฅผ ํ์ฉํ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ UI ๊ตฌ์ฑ
- Tailwind CSS๋ก ์นด๋ ๋ ์ด์์ ๋ฐ ๊ทธ๋ฆฌ๋ ์์คํ ์ตํ๊ธฐ
- ๊ฐ๋จํ ํผ ๊ฒ์ฆ(Validation) ๋ฐ DB ์ ์ฅ ํ๋ฆ ์ดํด
- SQLite ๊ธฐ๋ณธ CRUD ์์ ์ค์ต
- ๋ผ์ฐํ ๊ณผ ์ปจํธ๋กค๋ฌ ์ฐ๊ฒฐ ํจํด ์ตํ๊ธฐ
์ ์ฝ ์กฐ๊ฑด
- โข Laravel 13.x ์ด์ ์ฌ์ฉ (์ต์ ๋ฒ์ ํ์)
- โข Blade ํ ํ๋ฆฟ ์์ง ์ฌ์ฉ (React, Vue ๋ฑ SPA ํ๋ ์์ํฌ ์ฌ์ฉ ๊ธ์ง)
- โข Tailwind CSS๋ก ์คํ์ผ๋ง (CDN ๋๋ npm ์ค์น)
- โข SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฌ์ฉ (๊ฐ๋จํ ๋ก์ปฌ DB)
- โข ํ์๊ถ ํ๋์ ํ๋์ฝ๋ฉ๋ ๋ฐฐ์ด๋ก ๊ด๋ฆฌ (DB ํ ์ด๋ธ๋ก ๊ด๋ฆฌํ์ง ์์)
- โข ์ ์ฒญ ํผ ๋ฐ์ดํฐ๋ 'applications' ํ ์ด๋ธ์ ์ ์ฅ
- โข ํผ ๊ฒ์ฆ์ Laravel์ ๊ธฐ๋ณธ Validation ์ฌ์ฉ
- โข ์ธ๋ถ API ์ฐ๋ ๊ธ์ง
- โข ์ธ์ฆ/๋ก๊ทธ์ธ ๊ธฐ๋ฅ ๋ถํ์
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
mkdir fitness-membership-landing-page && cd fitness-membership-landing-page ์ด ํด๋์์ Laravel 13.x ํ๋ก์ ํธ๋ฅผ ์์ํฉ๋๋ค. **์๊ตฌ์ฌํญ:** 1. Laravel 13.x ์ต์ ๋ฒ์ ์ผ๋ก ์ ํ๋ก์ ํธ ์์ฑ (composer create-project laravel/laravel .) 2. .env ํ์ผ์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ SQLite๋ก ์ค์ (DB_CONNECTION=sqlite) 3. database/database.sqlite ํ์ผ ์์ฑ (touch database/database.sqlite) 4. Tailwind CSS ์ค์น ๋ฐ ์ค์ (npm install -D tailwindcss postcss autoprefixer && npx tailwindcss init -p) 5. tailwind.config.js์์ content ๊ฒฝ๋ก ์ค์ : ["./resources/**/*.blade.php", "./resources/**/*.js"] 6. resources/css/app.css์ Tailwind ๋๋ ํฐ๋ธ ์ถ๊ฐ (@tailwind base; @tailwind components; @tailwind utilities;) 7. vite.config.js ํ์ธ ๋ฐ npm run dev ์คํ ์ค๋น 8. ๊ธฐ๋ณธ welcome ํ์ด์ง๊ฐ ์ ์ ์๋ํ๋์ง ํ์ธ (php artisan serve) ํ๋ก์ ํธ ์ด๊ธฐ ์ค์ ์ด ์๋ฃ๋๋ฉด "Laravel 13.x + Tailwind CSS ์ค์ ์๋ฃ" ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: Laravel 13.x ํ๋ก์ ํธ๊ฐ ์์ฑ๋๊ณ , SQLite DB ํ์ผ์ด ์์ฑ๋๋ฉฐ, Tailwind CSS๊ฐ ์ค์น ๋ฐ ์ค์ ๋ฉ๋๋ค. npm run dev์ php artisan serve ๋ช ๋ น์ผ๋ก ๋ก์ปฌ ์๋ฒ๊ฐ ์ ์ ์๋ํฉ๋๋ค.
**ํ์๊ถ ํ๋ ๋ฐ์ดํฐ ๊ตฌ์กฐ ๋ฐ ๋ฉ์ธ ํ์ด์ง ๋ ์ด์์ ๊ตฌ์ฑ:**
1. ๋ผ์ฐํธ ์ค์ :
- routes/web.php์ ๋ฉ์ธ ํ์ด์ง ๋ผ์ฐํธ ์ถ๊ฐ: Route::get('/', [MembershipController::class, 'index'])->name('home');
2. MembershipController ์์ฑ (php artisan make:controller MembershipController):
- index() ๋ฉ์๋์์ 3๊ฐ์ง ํ์๊ถ ํ๋ ๋ฐฐ์ด ์ ์:
* Basic: ์ 50,000์, 1๊ฐ์, ํฌ์ค์ฅ ์ด์ฉ๋ง ๊ฐ๋ฅ
* Standard: ์ 80,000์, 3๊ฐ์, ํฌ์ค์ฅ + PT 2ํ ํฌํจ
* Premium: ์ 120,000์, 6๊ฐ์, ํฌ์ค์ฅ + PT 8ํ + ๋ฝ์ปค + ์ค์์ค ์ด์ฉ
- Standard ํ๋์ 'popular' => true ํ๋๊ทธ ์ถ๊ฐ (์ถ์ฒ ๋ฐฐ์ง์ฉ)
- ํ๋ ๋ฐฐ์ด์ Blade ๋ทฐ๋ก ์ ๋ฌ: return view('home', ['plans' => $plans]);
3. resources/views/home.blade.php ์์ฑ:
- Tailwind CSS๋ก ์คํ์ผ๋ง๋ ๋ ์ด์์ ๊ตฌ์ฑ
- ์๋จ: ํฌ์ค์ฅ ์ด๋ฆ "FitZone Fitness" + ์บ์นํ๋ ์ด์ฆ (์: "๋น์ ์ ๊ฑด๊ฐํ ๋ณํ, ์ง๊ธ ์์ํ์ธ์!")
- ์ค์: 3๊ฐ ํ๋ ์นด๋๋ฅผ ๊ทธ๋ฆฌ๋ ๋ ์ด์์(grid grid-cols-1 md:grid-cols-3 gap-6)์ผ๋ก ๋ฐฐ์น
- ๊ฐ ํ๋ ์นด๋์๋:
* ํ๋ ์ด๋ฆ (ํฐ ์ ๋ชฉ)
* ๊ฐ๊ฒฉ (๊ฐ์กฐ ํ
์คํธ, ์: "์ 50,000์")
* ์ด์ฉ ๊ธฐ๊ฐ
* ํฌํจ ์๋น์ค ๋ชฉ๋ก (์ฒดํฌ ์์ด์ฝ + ํ
์คํธ)
* "๊ฐ์
์ ์ฒญ" ๋ฒํผ (ํด๋ฆญ ์ ๋ค์ ๋จ๊ณ์์ ๊ตฌํํ ํผ์ผ๋ก ์ด๋)
- popular ํ๋์๋ "Most Popular" ๋ฐฐ์ง๋ฅผ ์นด๋ ์๋จ์ ํ์ (๋ฐฐ๊ฒฝ์ ๊ฐ์กฐ)
4. Tailwind ์คํ์ผ ๊ฐ์ด๋:
- ์นด๋: bg-white shadow-lg rounded-lg p-6 border
- ์ธ๊ธฐ ๋ฐฐ์ง: bg-blue-500 text-white px-3 py-1 rounded-full text-sm
- ๋ฒํผ: bg-blue-600 hover:bg-blue-700 text-white px-6 py-3 rounded-lg
5. npm run dev ์คํ ํ ๋ธ๋ผ์ฐ์ ์์ ํ์ธ (http://localhost:8000)
์นด๋ ๋ ์ด์์์ด ์์๊ฒ ์ ๋ ฌ๋๊ณ , ๊ฐ ํ๋ ์ ๋ณด๊ฐ ๋ช
ํํ๊ฒ ๋ณด์ฌ์ผ ํฉ๋๋ค.
๊ธฐ๋ ๊ฒฐ๊ณผ: ๋ฉ์ธ ํ์ด์ง์ 3๊ฐ์ ํ์๊ถ ํ๋ ์นด๋๊ฐ ๊ทธ๋ฆฌ๋ ๋ ์ด์์์ผ๋ก ํ์๋ฉ๋๋ค. Standard ํ๋์ "Most Popular" ๋ฐฐ์ง๊ฐ ํ์๋๋ฉฐ, ๊ฐ ์นด๋์ ๊ฐ๊ฒฉ, ๊ธฐ๊ฐ, ์๋น์ค ๋ชฉ๋ก, ๊ฐ์ ์ ์ฒญ ๋ฒํผ์ด ํฌํจ๋ฉ๋๋ค. Tailwind CSS๋ก ๊น๋ํ๊ฒ ์คํ์ผ๋ง๋ฉ๋๋ค.
**๊ฐ์
์ ์ฒญ ํผ ํ์ด์ง ๋ฐ DB ๋ง์ด๊ทธ๋ ์ด์
์์ฑ:**
1. ๋ง์ด๊ทธ๋ ์ด์
ํ์ผ ์์ฑ (php artisan make:migration create_applications_table):
- ํ
์ด๋ธ ๊ตฌ์กฐ:
* id (primary key)
* name (string, ์ ์ฒญ์ ์ด๋ฆ)
* phone (string, ์ฐ๋ฝ์ฒ)
* plan (string, ์ ํํ ํ๋๋ช
)
* timestamps (์์ฑ์ผ, ์์ ์ผ)
- php artisan migrate ์คํํ์ฌ ํ
์ด๋ธ ์์ฑ
2. Application ๋ชจ๋ธ ์์ฑ (php artisan make:model Application):
- $fillable = ['name', 'phone', 'plan']; ์ค์
3. ๋ผ์ฐํธ ์ถ๊ฐ (routes/web.php):
- Route::get('/apply/{plan}', [MembershipController::class, 'showApplyForm'])->name('apply.form');
- Route::post('/apply', [MembershipController::class, 'submitApplication'])->name('apply.submit');
4. MembershipController์ ๋ฉ์๋ ์ถ๊ฐ:
- showApplyForm($plan): ์ ํํ ํ๋๋ช
์ ๋ฐ์ ์ ์ฒญ ํผ ํ์ด์ง๋ก ์ ๋ฌ (return view('apply', ['plan' => $plan]);)
- submitApplication(Request $request): ํผ ๋ฐ์ดํฐ ๊ฒ์ฆ ๋ฐ DB ์ ์ฅ ํ ์๋ฃ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ
5. resources/views/apply.blade.php ์์ฑ:
- ์๋จ: "ํ์๊ถ ๊ฐ์
์ ์ฒญ" ์ ๋ชฉ + ์ ํํ ํ๋๋ช
ํ์ (์: "์ ํํ ํ๋: Standard")
- ํผ ํ๋:
* ์ด๋ฆ (required, text)
* ์ฐ๋ฝ์ฒ (required, text, placeholder: "010-1234-5678")
* ์ ํํ ํ๋ (hidden input, value๋ URL ํ๋ผ๋ฏธํฐ์์ ๋ฐ์ ํ๋๋ช
)
- ์ ์ถ ๋ฒํผ: "์ ์ฒญ ์๋ฃ" (bg-green-600 hover:bg-green-700)
- ํผ action: {{ route('apply.submit') }}, method: POST, @csrf ํ ํฐ ํฌํจ
6. home.blade.php ์์ :
- ๊ฐ ํ๋ ์นด๋์ "๊ฐ์
์ ์ฒญ" ๋ฒํผ์ ๋งํฌ ์ถ๊ฐ: href="{{ route('apply.form', ['plan' => $plan['name']]) }}"
7. ๋ธ๋ผ์ฐ์ ์์ ํ
์คํธ:
- ๋ฉ์ธ ํ์ด์ง์์ ํ๋ ์ ํ โ ์ ์ฒญ ํผ ํ์ด์ง๋ก ์ด๋ ํ์ธ
- ํผ์ ์ ํํ ํ๋๋ช
์ด ์๋์ผ๋ก ํ์๋๋์ง ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ: applications ํ ์ด๋ธ์ด ์์ฑ๋๊ณ , ๋ฉ์ธ ํ์ด์ง์์ ํ๋ ์ ํ ์ ์ ์ฒญ ํผ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค. ํผ์๋ ์ด๋ฆ, ์ฐ๋ฝ์ฒ ์ ๋ ฅ ํ๋์ ์ ํํ ํ๋๋ช ์ด ํ์๋๋ฉฐ, ์ ์ถ ๋ฒํผ์ด ํฌํจ๋ฉ๋๋ค.
**ํผ ๊ฒ์ฆ ๋ฐ DB ์ ์ฅ ๋ก์ง ๊ตฌํ + ์ ์ฒญ ์๋ฃ ํ์ด์ง:**
1. MembershipController์ submitApplication ๋ฉ์๋ ๊ตฌํ:
- ํผ ๊ฒ์ฆ (Laravel Validation):
* name: required|string|max:100
* phone: required|string|regex:/^010-\d{4}-\d{4}$/ (010-1234-5678 ํ์)
* plan: required|string|in:Basic,Standard,Premium
- ๊ฒ์ฆ ์ฑ๊ณต ์ Application ๋ชจ๋ธ๋ก DB ์ ์ฅ:
```php
Application::create([
'name' => $request->name,
'phone' => $request->phone,
'plan' => $request->plan,
]);
```
- ์ ์ฅ ํ ์๋ฃ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ: return redirect()->route('apply.success')->with('application', $request->only(['name', 'plan']));
2. ๋ผ์ฐํธ ์ถ๊ฐ (routes/web.php):
- Route::get('/apply/success', [MembershipController::class, 'showSuccess'])->name('apply.success');
3. MembershipController์ showSuccess ๋ฉ์๋ ์ถ๊ฐ:
- return view('success'); (์ธ์
๋ฐ์ดํฐ๋ Blade์์ @session('application')๋ก ์ ๊ทผ)
4. resources/views/success.blade.php ์์ฑ:
- ์๋จ: ์ฑ๊ณต ๋ฉ์์ง "โ
๊ฐ์
์ ์ฒญ์ด ์๋ฃ๋์์ต๋๋ค!"
- ์ ์ฒญ ๋ด์ญ ํ์:
* ์ ์ฒญ์ ์ด๋ฆ
* ์ ํํ ํ๋
* ์๋ด ๋ฉ์์ง: "๋ด๋น์๊ฐ ๊ณง ์ฐ๋ฝ๋๋ฆด ์์ ์
๋๋ค."
- ๋ฉ์ธ ํ์ด์ง๋ก ๋์๊ฐ๊ธฐ ๋ฒํผ: href="{{ route('home') }}"
5. apply.blade.php์ ๊ฒ์ฆ ์๋ฌ ๋ฉ์์ง ํ์:
- @error ๋๋ ํฐ๋ธ๋ก ๊ฐ ํ๋๋ณ ์๋ฌ ๋ฉ์์ง ์ถ๋ ฅ (์: @error('name') <p class="text-red-500 text-sm mt-1">{{ $message }}</p> @enderror)
6. ๋ธ๋ผ์ฐ์ ์์ ์ ์ฒด ํ๋ก์ฐ ํ
์คํธ:
- ๋ฉ์ธ ํ์ด์ง โ ํ๋ ์ ํ โ ํผ ์์ฑ โ ์ ์ถ โ ์๋ฃ ํ์ด์ง
- ์๋ชป๋ ์
๋ ฅ ์ ๊ฒ์ฆ ์๋ฌ ๋ฉ์์ง ํ์ ํ์ธ
- DB์ ๋ฐ์ดํฐ๊ฐ ์ ์ ์ ์ฅ๋๋์ง ํ์ธ (php artisan tinker๋ก Application::all() ์คํ)
๋ชจ๋ ๊ธฐ๋ฅ์ด ์ ์ ์๋ํ๋ฉด "ํ์๊ถ ๊ฐ์
์ ์ฒญ ์์คํ
์์ฑ!" ๋ฉ์์ง ์ถ๋ ฅ.
๊ธฐ๋ ๊ฒฐ๊ณผ: ํผ ์ ์ถ ์ Laravel Validation์ผ๋ก ์ ๋ ฅ๊ฐ์ด ๊ฒ์ฆ๋๊ณ , ํต๊ณผํ๋ฉด DB์ ์ ์ฅ๋ฉ๋๋ค. ์ ์ฒญ ์๋ฃ ํ์ด์ง์์ ์ ์ฒญ ๋ด์ญ์ด ํ์๋๋ฉฐ, ์๋ฌ ๋ฐ์ ์ ํผ์ ๊ฒ์ฆ ๋ฉ์์ง๊ฐ ํ์๋ฉ๋๋ค. ์ ์ฒด ํ๋ก์ฐ๊ฐ ์ํํ๊ฒ ์๋ํฉ๋๋ค.
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!