Files
CFDivePlatform/openspec/changes/archive/2026-05-10-member-portal-mvp/tasks.md
T
a620906209 550e2fc97a feat:實作 Member Portal MVP 前端與後端整合
後端:
- 新增 DivingOffer Model / DivingOfferController(列表+詳情 API,支援搜尋/篩選/分頁)
- 修正 Google OAuth callback 改為 redirect 至前端(SocialAuthController)
- 新增 config/cors.php 允許前端 origin
- .gitignore 新增 frontend/ 排除規則

前端(frontend/):
- Vue 3 + Vite + Tailwind CSS + Pinia + Vue Router
- 頁面:首頁、課程列表、課程詳情、登入、註冊、個人資料、OAuth callback
- 整合至 Docker(multi-stage build,nginx 靜態服務於 port 5173)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 01:41:28 +08:00

74 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 1. [後端] 環境與 CORS 設定
- [x] 1.1 在 `.env` 新增 `FRONTEND_URL=http://localhost:5173``GOOGLE_CLIENT_ID``GOOGLE_CLIENT_SECRET``GOOGLE_REDIRECT_URI=http://localhost:80/api/auth/google/callback`
- [x] 1.2 執行 `php artisan config:publish cors` 建立 `config/cors.php`,設定 `allowed_origins=[FRONTEND_URL]``allowed_methods``allowed_headers``supports_credentials=false`(參考 design.md Contract 3
- [x] 1.3 確認 `bootstrap/app.php`(或 `app/Http/Kernel.php`)已啟用 `HandleCors` middleware
## 2. [後端] 修正 Google OAuth Callback
- [x] 2.1 修改 `SocialAuthController::handleGoogleCallback()`:成功時改為 `redirect(env('FRONTEND_URL') . '/auth/callback?token=' . $token)`
- [x] 2.2 修改 catch 區塊:失敗時改為 `redirect(env('FRONTEND_URL') . '/login?error=oauth_failed')`
- [x] 2.3 手動測試 OAuth 流程:點擊 Google 登入後確認瀏覽器最終落在 `:5173/auth/callback?token=...`
## 3. [後端] Diving Offers API
- [x] 3.1 更新 `DivingOffer` Model:設定 `$fillable``$table``badges` 欄位加上 `$casts = ['badges' => 'array']` 自動 JSON decode
- [x] 3.2 建立 `DivingOfferController`,實作 `index()` 方法(支援 q / region / tag 篩選,分頁預設 12 筆,max 50)
- [x] 3.3 實作 `show($id)` 方法:找不到時回傳 `{ "status": false, "message": "課程不存在" }`HTTP 404
- [x] 3.4 在 `routes/api.php` 新增公開路由:`GET /diving-offers``GET /diving-offers/{id}`
- [x] 3.5 用 Postman 驗證:列表(含 q / region / tag / 分頁)、詳情、404 情境,確認 response 結構符合 design.md Contract 1
## 4. [前端] 專案初始化
- [x] 4.1 在 Laravel repo 外建立新目錄 `cf-dive-frontend`,執行 `npm create vite@latest . -- --template vue`
- [x] 4.2 安裝依賴:`npm install`,再安裝 `vue-router@4 pinia axios`
- [x] 4.3 安裝並設定 Tailwind CSS`tailwindcss postcss autoprefixer`,初始化 `tailwind.config.js`
- [x] 4.4 建立 `.env` 文件,設定 `VITE_API_URL=http://localhost:80`
- [x] 4.5 建立 `src/api/axios.js`:設定 Axios instancebase URL 讀自 `import.meta.env.VITE_API_URL`request interceptor 讀 localStorage token 並附加 `Authorization: Bearer <token>`
- [x] 4.6 建立 `src/stores/auth.js`Pinia store 管理 `user``token``isLoggedIn``init()` 從 localStorage 還原狀態
- [x] 4.7 設定 Vue Router`src/router/index.js`):定義所有路由(含 `/auth/callback`),`/profile` 加上 beforeEach navigation guard(未登入導向 `/login`
- [x] 4.8 在 `App.vue` 呼叫 `authStore.init()`,並加入 `<RouterView>`
- [x] 4.9 執行 `npm run dev`,確認開發環境正常啟動無錯誤
## 5. [前端] Layout 與共用組件
- [x] 5.1 建立 `src/components/NavBar.vue`:顯示 logo、「探索課程」連結,已登入顯示「個人資料」和「登出」,未登入顯示「登入」和「註冊」
- [x] 5.2 建立 `src/components/CourseCard.vue`:接收 offer 資料,顯示標題、地點、價格、評分、標籤
## 6. [前端] 首頁
- [x] 6.1 建立 `src/views/HomeView.vue`Hero section(平台名稱、簡介)+ 「探索課程」CTA 按鈕,點擊導向 `/courses`
## 7. [前端] 課程列表頁
- [x] 7.1 建立 `src/views/CoursesView.vue`,掛載時呼叫 `GET /api/diving-offers`,渲染 `CourseCard` 列表
- [x] 7.2 新增搜尋框:輸入後按 Enter 或點搜尋重新呼叫 API(帶 `q` 參數)
- [x] 7.3 新增地區下拉選單:選擇後以 `region` 參數重新呼叫 API
- [x] 7.4 處理無結果狀態:顯示「找不到符合的課程」提示
## 8. [前端] 課程詳情頁
- [x] 8.1 建立 `src/views/CourseDetailView.vue`,掛載時呼叫 `GET /api/diving-offers/:id`
- [x] 8.2 顯示課程完整資訊:標題、地點、景點、價格、評分、評論數、描述、徽章(badges 陣列)、標籤
- [x] 8.3 處理 404 情境:顯示「課程不存在」並提供「返回列表」按鈕
## 9. [前端] 認證頁面
- [x] 9.1 建立 `src/views/LoginView.vue`email/password 表單,送出呼叫 `POST /api/member/login`,成功存 token + user 至 Pinia 並導向 `/courses`,失敗顯示錯誤訊息
- [x] 9.2 在 `LoginView.vue` 加入「以 Google 登入」按鈕:點擊執行 `window.location.href = VITE_API_URL + '/api/auth/google/redirect'`
- [x] 9.3 建立 `src/views/AuthCallbackView.vue`(路由 `/auth/callback`):讀取 `?token=` query param → 存入 Pinia + localStorage → 呼叫 `history.replaceState` 清除 URL token → 導向 `/courses`;若 `?error=oauth_failed` 則導向 `/login` 並顯示錯誤提示
- [x] 9.4 建立 `src/views/RegisterView.vue`name / email / password / password_confirmation 表單,送出呼叫 `POST /api/member/register`,成功導向 `/login` 並顯示成功提示,失敗顯示錯誤
## 10. [前端] 會員個人資料頁
- [x] 10.1 建立 `src/views/ProfileView.vue`,掛載時呼叫 `GET /api/member/profile`,顯示姓名、email、生日、性別、地址、緊急聯絡人
- [x] 10.2 實作編輯表單:使用者修改後點擊「儲存」呼叫 `PUT /api/member/profile`,成功顯示「資料已更新」提示
## 11. [整合測試] 端對端驗證
- [x] 11.1 驗證訪客流程:首頁 → 課程列表(搜尋/篩選)→ 課程詳情(無需登入)
- [x] 11.2 驗證 Email 認證流程:註冊 → 登入 → 個人資料 → 登出
- [x] 11.3 驗證 Google OAuth 流程:點擊 Google 登入 → 同意 → 回到前端 `/auth/callback` → 自動存 token → 導向課程列表
- [x] 11.4 驗證 navigation guard:未登入直接訪問 `/profile` 自動跳轉至 `/login`
- [x] 11.5 驗證 CORS:確認 Network tab 無 CORS 錯誤,所有 API 請求正常回應