feat: Implement course detail viewing and enrollment functionality with a new useCourse composable.
This commit is contained in:
parent
a65ded02f9
commit
23d9e44cc9
4 changed files with 65 additions and 27 deletions
|
|
@ -66,25 +66,31 @@ const handleEnroll = () => {
|
|||
<video
|
||||
controls
|
||||
class="w-full h-full object-cover"
|
||||
:poster="course.cover_image || 'https://placehold.co/800x450?text=Course+Preview'"
|
||||
:poster="course.thumbnail_url || course.cover_image || 'https://placehold.co/800x450?text=Course+Preview'"
|
||||
>
|
||||
<source :src="course.media.video_url" type="video/mp4">
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
</template>
|
||||
<!-- Placeholder if no video -->
|
||||
<!-- Beautiful Image Showcase if no video -->
|
||||
<template v-else>
|
||||
<div class="w-full h-full bg-gradient-to-br from-slate-200 to-slate-300 dark:from-slate-800 dark:to-slate-900 flex items-center justify-center relative overflow-hidden">
|
||||
<!-- Subtle background pattern or logo could go here -->
|
||||
<div class="absolute inset-0 opacity-10 dark:opacity-20">
|
||||
<div class="absolute top-0 left-0 w-full h-full" style="background-image: radial-gradient(circle at 2px 2px, currentColor 1px, transparent 0); background-size: 24px 24px;"></div>
|
||||
</div>
|
||||
<div class="w-full h-full flex items-center justify-center relative overflow-hidden bg-slate-950">
|
||||
<!-- Show Thumbnail as Background if exists (Blurred background fill) -->
|
||||
<img
|
||||
v-if="course.thumbnail_url || course.cover_image"
|
||||
:src="course.thumbnail_url || course.cover_image"
|
||||
class="absolute inset-0 w-full h-full object-cover opacity-30 blur-xl scale-110"
|
||||
/>
|
||||
|
||||
<div class="relative z-10 flex flex-col items-center gap-4">
|
||||
<div class="w-20 h-20 bg-white/30 dark:bg-white/10 backdrop-blur-md rounded-full flex items-center justify-center ring-1 ring-white/50 shadow-xl transition-transform group-hover:scale-110 duration-500">
|
||||
<q-icon name="play_arrow" size="48px" class="text-white drop-shadow-md" />
|
||||
</div>
|
||||
<span class="text-slate-500 dark:text-slate-400 font-bold tracking-wider uppercase text-xs">{{ $t('course.noVideoPreview') || 'Preview Not Available' }}</span>
|
||||
<!-- Main Sharp Image -->
|
||||
<img
|
||||
v-if="course.thumbnail_url || course.cover_image"
|
||||
:src="course.thumbnail_url || course.cover_image"
|
||||
class="relative z-10 w-full h-full object-contain shadow-2xl shadow-black/50"
|
||||
/>
|
||||
|
||||
<div v-else class="absolute inset-0 bg-gradient-to-br from-slate-800 to-slate-900 flex items-center justify-center">
|
||||
<q-icon name="image" size="80px" class="text-slate-700 opacity-50" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export interface Course {
|
|||
approved_at?: string
|
||||
approved_by?: number
|
||||
rejection_reason?: string
|
||||
enrolled?: boolean
|
||||
|
||||
|
||||
rating?: string
|
||||
|
|
@ -253,21 +254,20 @@ export const useCourse = () => {
|
|||
message: data.message
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('Enroll course failed:', err)
|
||||
|
||||
// เช็ค Error 409 Conflict หรือ 400 Bad Request (กรณีลงทะเบียนไปแล้ว)
|
||||
// API ใหม่ส่ง 400 พร้อม error.code = "VALIDATION_ERROR" และ message "Already enrolled..."
|
||||
const status = err.statusCode || err.status || err.response?.status
|
||||
const errorData = err.data?.error || err.data
|
||||
|
||||
// เช็ค Error 409 Conflict หรือ 400 Bad Request (กรณีลงทะเบียนไปแล้ว)
|
||||
// สำหรับกรณีนี้ เราจะไม่ log console.error ให้รกหน้าจอเพราะเป็นเรื่องที่ดักจับได้
|
||||
if (status === 409 || (status === 400 && errorData?.message?.includes('Already enrolled'))) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'ท่านได้ลงทะเบียนไปแล้ว',
|
||||
code: 409 // Treat as conflict logic internally
|
||||
code: 409 // treat internally as conflict
|
||||
}
|
||||
}
|
||||
|
||||
console.error('Enroll course failed:', err)
|
||||
return {
|
||||
success: false,
|
||||
error: errorData?.message || err.message || 'Error enrolling in course',
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export default defineNuxtConfig({
|
|||
extras: {
|
||||
fontIcons: ["material-icons"],
|
||||
},
|
||||
plugins: ["Notify"], // เปิดใช้ Plugin Notify
|
||||
plugins: ["Notify", "Dialog"], // เปิดใช้ Plugin Notify และ Dialog
|
||||
config: {
|
||||
brand: { // กำหนดชุดสีหลัก (Theme Colors)
|
||||
primary: "#4b82f7",
|
||||
|
|
|
|||
|
|
@ -31,6 +31,23 @@ const isEnrolling = ref(false)
|
|||
const handleEnroll = async () => {
|
||||
if (!course.value) return
|
||||
if (isEnrolling.value) return
|
||||
|
||||
// กรณีเคยกดลงทะเบียนไปแล้ว (Check จากสถานะคอร์ส)
|
||||
if (course.value.enrolled) {
|
||||
$q.dialog({
|
||||
message: `<div class="text-slate-800 text-base leading-relaxed">ท่านเคยลงทะเบียนคอร์ส <b class="text-blue-600">"${getLocalizedText(course.value.title)}"</b> นี้ไปเรียบร้อยแล้ว</div>`,
|
||||
html: true,
|
||||
ok: {
|
||||
label: 'ตกลง',
|
||||
color: 'primary',
|
||||
rounded: true,
|
||||
unelevated: true,
|
||||
padding: '8px 32px'
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
isEnrolling.value = true
|
||||
|
||||
// เรียก API ลงทะเบียนเรียน
|
||||
|
|
@ -38,7 +55,6 @@ const handleEnroll = async () => {
|
|||
|
||||
if (res.success) {
|
||||
// ถ้าสำเร็จ ให้เปลี่ยนหน้าไปที่ "คอร์สของฉัน" พร้อม params enrolled=true
|
||||
// Use object syntax for robust query param handling
|
||||
const targetId = route.params.id || course.value?.id
|
||||
return navigateTo({
|
||||
path: '/dashboard/my-courses',
|
||||
|
|
@ -47,14 +63,30 @@ const handleEnroll = async () => {
|
|||
course_id: String(targetId)
|
||||
}
|
||||
})
|
||||
// กรณี error แสดง Toast notification แทน alert
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: res.error || 'Failed to enroll',
|
||||
position: 'top',
|
||||
timeout: 3000,
|
||||
actions: [{ icon: 'close', color: 'white' }]
|
||||
})
|
||||
} else {
|
||||
// กรณี API แจ้งว่าเคยลงทะเบียนไปแล้ว (Code 409)
|
||||
if (res.code === 409) {
|
||||
$q.dialog({
|
||||
message: `<div class="text-slate-800 text-base leading-relaxed">ท่านเคยลงทะเบียนคอร์ส <b class="text-blue-600">"${getLocalizedText(course.value.title)}"</b> นี้ไปเรียบร้อยแล้ว</div>`,
|
||||
html: true,
|
||||
ok: {
|
||||
label: 'ตกลง',
|
||||
color: 'primary',
|
||||
rounded: true,
|
||||
unelevated: true,
|
||||
padding: '8px 32px'
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// กรณี error ทั่วไป แสดง Toast notification
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: res.error || 'Failed to enroll',
|
||||
position: 'top',
|
||||
timeout: 3000,
|
||||
actions: [{ icon: 'close', color: 'white' }]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
isEnrolling.value = false
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue