Files
CFDivePlatform/frontend/src/views/coach/LoginView.vue
T
a620906209 3827223be3 feat:實作 Coach Portal — 教練後台課程管理
後端:
- Migration:diving_offers 新增 provider_id(nullable FK)
- Migration:users.role ENUM 加入 provider 值
- Migration:diving_offers.spot 改為 nullable
- AuthController:registerProvider business_name 改為選填
- AuthController:updateProviderProfile 補上 certifications / dive_sites / services / facilities / website / social_media
- ProviderOfferController:教練課程 CRUD(index/show/store/update/destroy),實作 provider_id 所有權不變式(404 → 403 兩步驟)

前端(frontend/):
- coachAuth store、coachAxios(獨立於 member auth)
- /coach/* 路由群組 + beforeEach guard
- CoachNavBar、CoachLayout(教練頁隱藏會員 NavBar)
- LoginView、RegisterView、DashboardView(表格 + 刪除確認)
- OfferFormView(新增/編輯共用)、ProfileView

OpenSpec:
- openspec/config.yaml 補入專案 context 與 rules
- 新增 specs:coach-offers-api / coach-portal-ui / provider-auth
- 更新 spec:diving-offers-api 加入 provider_id
- 歸檔:openspec/changes/archive/2026-05-10-coach-portal

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

77 lines
2.6 KiB
Vue
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.
<script setup>
import { ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import coachApi from '../../api/coachAxios'
import { useCoachAuthStore } from '../../stores/coachAuth'
const router = useRouter()
const route = useRoute()
const coachAuth = useCoachAuthStore()
const email = ref('')
const password = ref('')
const error = ref('')
const loading = ref(false)
const registeredMsg = route.query.registered ? '註冊成功,請登入。' : ''
async function submit() {
error.value = ''
loading.value = true
try {
const res = await coachApi.post('/provider/login', {
email: email.value,
password: password.value,
})
const { user, token } = res.data.data
coachAuth.setAuth(user, token)
router.push('/coach/dashboard')
} catch (e) {
error.value = e.response?.data?.message || '帳號或密碼錯誤'
} finally {
loading.value = false
}
}
</script>
<template>
<main class="min-h-screen bg-gray-50 flex items-center justify-center px-4">
<div class="bg-white rounded-2xl shadow-lg w-full max-w-md p-8">
<div class="text-center mb-8">
<p class="text-gray-500 text-sm mb-1">CFDive 教練後台</p>
<h1 class="text-2xl font-bold text-gray-800">教練登入</h1>
</div>
<div v-if="registeredMsg" class="bg-green-50 text-green-700 text-sm rounded-lg px-4 py-3 mb-4">
{{ registeredMsg }}
</div>
<div v-if="error" class="bg-red-50 text-red-600 text-sm rounded-lg px-4 py-3 mb-4">
{{ error }}
</div>
<form @submit.prevent="submit" class="flex flex-col gap-4">
<div>
<label class="block text-sm text-gray-600 mb-1">Email</label>
<input v-model="email" type="email" required
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-gray-400" />
</div>
<div>
<label class="block text-sm text-gray-600 mb-1">密碼</label>
<input v-model="password" type="password" required
class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-gray-400" />
</div>
<button type="submit" :disabled="loading"
class="bg-gray-900 hover:bg-gray-700 text-white font-semibold py-2.5 rounded-lg transition disabled:opacity-60">
{{ loading ? '登入中...' : '登入' }}
</button>
</form>
<p class="text-center text-sm text-gray-500 mt-6">
還沒有帳號
<RouterLink to="/coach/register" class="text-gray-700 hover:underline font-medium">申請教練帳號</RouterLink>
</p>
</div>
</main>
</template>