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
|
<video
|
||||||
controls
|
controls
|
||||||
class="w-full h-full object-cover"
|
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">
|
<source :src="course.media.video_url" type="video/mp4">
|
||||||
Your browser does not support the video tag.
|
Your browser does not support the video tag.
|
||||||
</video>
|
</video>
|
||||||
</template>
|
</template>
|
||||||
<!-- Placeholder if no video -->
|
<!-- Beautiful Image Showcase if no video -->
|
||||||
<template v-else>
|
<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">
|
<div class="w-full h-full flex items-center justify-center relative overflow-hidden bg-slate-950">
|
||||||
<!-- Subtle background pattern or logo could go here -->
|
<!-- Show Thumbnail as Background if exists (Blurred background fill) -->
|
||||||
<div class="absolute inset-0 opacity-10 dark:opacity-20">
|
<img
|
||||||
<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>
|
v-if="course.thumbnail_url || course.cover_image"
|
||||||
</div>
|
: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">
|
<!-- Main Sharp Image -->
|
||||||
<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">
|
<img
|
||||||
<q-icon name="play_arrow" size="48px" class="text-white drop-shadow-md" />
|
v-if="course.thumbnail_url || course.cover_image"
|
||||||
</div>
|
:src="course.thumbnail_url || course.cover_image"
|
||||||
<span class="text-slate-500 dark:text-slate-400 font-bold tracking-wider uppercase text-xs">{{ $t('course.noVideoPreview') || 'Preview Not Available' }}</span>
|
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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ export interface Course {
|
||||||
approved_at?: string
|
approved_at?: string
|
||||||
approved_by?: number
|
approved_by?: number
|
||||||
rejection_reason?: string
|
rejection_reason?: string
|
||||||
|
enrolled?: boolean
|
||||||
|
|
||||||
|
|
||||||
rating?: string
|
rating?: string
|
||||||
|
|
@ -253,21 +254,20 @@ export const useCourse = () => {
|
||||||
message: data.message
|
message: data.message
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} 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 status = err.statusCode || err.status || err.response?.status
|
||||||
const errorData = err.data?.error || err.data
|
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'))) {
|
if (status === 409 || (status === 400 && errorData?.message?.includes('Already enrolled'))) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: 'ท่านได้ลงทะเบียนไปแล้ว',
|
error: 'ท่านได้ลงทะเบียนไปแล้ว',
|
||||||
code: 409 // Treat as conflict logic internally
|
code: 409 // treat internally as conflict
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.error('Enroll course failed:', err)
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: errorData?.message || err.message || 'Error enrolling in course',
|
error: errorData?.message || err.message || 'Error enrolling in course',
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export default defineNuxtConfig({
|
||||||
extras: {
|
extras: {
|
||||||
fontIcons: ["material-icons"],
|
fontIcons: ["material-icons"],
|
||||||
},
|
},
|
||||||
plugins: ["Notify"], // เปิดใช้ Plugin Notify
|
plugins: ["Notify", "Dialog"], // เปิดใช้ Plugin Notify และ Dialog
|
||||||
config: {
|
config: {
|
||||||
brand: { // กำหนดชุดสีหลัก (Theme Colors)
|
brand: { // กำหนดชุดสีหลัก (Theme Colors)
|
||||||
primary: "#4b82f7",
|
primary: "#4b82f7",
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,23 @@ const isEnrolling = ref(false)
|
||||||
const handleEnroll = async () => {
|
const handleEnroll = async () => {
|
||||||
if (!course.value) return
|
if (!course.value) return
|
||||||
if (isEnrolling.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
|
isEnrolling.value = true
|
||||||
|
|
||||||
// เรียก API ลงทะเบียนเรียน
|
// เรียก API ลงทะเบียนเรียน
|
||||||
|
|
@ -38,7 +55,6 @@ const handleEnroll = async () => {
|
||||||
|
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
// ถ้าสำเร็จ ให้เปลี่ยนหน้าไปที่ "คอร์สของฉัน" พร้อม params enrolled=true
|
// ถ้าสำเร็จ ให้เปลี่ยนหน้าไปที่ "คอร์สของฉัน" พร้อม params enrolled=true
|
||||||
// Use object syntax for robust query param handling
|
|
||||||
const targetId = route.params.id || course.value?.id
|
const targetId = route.params.id || course.value?.id
|
||||||
return navigateTo({
|
return navigateTo({
|
||||||
path: '/dashboard/my-courses',
|
path: '/dashboard/my-courses',
|
||||||
|
|
@ -47,14 +63,30 @@ const handleEnroll = async () => {
|
||||||
course_id: String(targetId)
|
course_id: String(targetId)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// กรณี error แสดง Toast notification แทน alert
|
} else {
|
||||||
$q.notify({
|
// กรณี API แจ้งว่าเคยลงทะเบียนไปแล้ว (Code 409)
|
||||||
type: 'negative',
|
if (res.code === 409) {
|
||||||
message: res.error || 'Failed to enroll',
|
$q.dialog({
|
||||||
position: 'top',
|
message: `<div class="text-slate-800 text-base leading-relaxed">ท่านเคยลงทะเบียนคอร์ส <b class="text-blue-600">"${getLocalizedText(course.value.title)}"</b> นี้ไปเรียบร้อยแล้ว</div>`,
|
||||||
timeout: 3000,
|
html: true,
|
||||||
actions: [{ icon: 'close', color: 'white' }]
|
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
|
isEnrolling.value = false
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue