feat: Introduce dashboard, my courses, and browse pages for learner course discovery and management.

This commit is contained in:
supalerk-ar66 2026-01-28 16:59:44 +07:00
parent a24f8c4982
commit 240cafb571
4 changed files with 18 additions and 15 deletions

View file

@ -63,10 +63,12 @@ const displayDescription = computed(() => getLocalizedText(props.description))
<!-- Thumbnail -->
<div class="thumbnail-wrapper relative h-44 overflow-hidden rounded-t-[1.5rem]">
<img
:src="image || 'https://images.unsplash.com/photo-1498050108023-c5249f4df085?w=400&q=80'"
v-if="image"
:src="image"
:alt="displayTitle"
class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700"
>
<div v-else class="w-full h-full bg-gradient-to-br from-slate-200 to-slate-300 dark:from-slate-700 dark:to-slate-800 group-hover:scale-110 transition-transform duration-700"></div>
<div v-if="completed" class="absolute inset-0 bg-emerald-600/60 backdrop-blur-[2px] flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<span class="text-white bg-emerald-500 w-12 h-12 rounded-full flex items-center justify-center shadow-lg transform scale-0 group-hover:scale-100 transition-transform duration-500">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="20 20 24 24" stroke="currentColor">
@ -100,7 +102,7 @@ const displayDescription = computed(() => getLocalizedText(props.description))
<!-- Rating & Lessons -->
<div v-if="rating || lessons" class="flex items-center gap-4 text-[11px] font-bold mb-6 uppercase tracking-wider text-slate-500 dark:text-slate-400">
<span v-if="rating" class="flex items-center gap-1"><span class="text-amber-400 text-sm"></span> {{ rating }}</span>
<span v-if="lessons" class="flex items-center gap-1"><span class="text-blue-400">📚</span> {{ lessons }} {{ $t('course.lessonsUnit') }}</span>
<span v-if="lessons" class="flex items-center gap-1">{{ lessons }} {{ $t('course.lessonsUnit') }}</span>
</div>
<!-- Progress Bar -->

View file

@ -130,12 +130,9 @@ const filteredCourses = computed(() => {
@error="(e) => (e.target as HTMLImageElement).style.display = 'none'"
/>
<div
v-else-if="!course.thumbnail_url || true"
v-else
class="absolute inset-0 flex items-center justify-center bg-gradient-to-br from-slate-800 to-slate-900"
>
<div class="w-20 h-20 rounded-2xl bg-white/5 flex items-center justify-center text-5xl group-hover:scale-110 transition-transform duration-500 shadow-inner border border-white/5">
📚
</div>
</div>
<!-- Level Badge (Neutral/Warning/Success variants) - Optional based on data -->
<div v-if="course.levelType" class="absolute top-4 right-4">

View file

@ -91,9 +91,6 @@ onMounted(async () => {
<NuxtLink v-for="(course, idx) in recommendedCourses" :key="course.id" :to="`/course/${course.id}`" class="p-0 overflow-hidden group border border-slate-200 dark:border-white/5 rounded-3xl shadow-sm dark:shadow-xl transition-all hover:-translate-y-1 dark:hover:-translate-y-1 block" style="background-color: var(--bg-surface);">
<div class="h-48 overflow-hidden relative rounded-t-3xl">
<img v-if="course.image" :src="course.image" :alt="course.title" class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-700" >
<div v-else class="w-full h-full bg-slate-200 dark:bg-slate-700 flex items-center justify-center">
<span class="text-4xl">📚</span>
</div>
<div class="absolute inset-x-0 bottom-0 h-1/2 bg-gradient-to-t from-slate-900/20 dark:from-[#1e293b] to-transparent"/>
</div>
<div class="p-7" style="background-color: var(--bg-surface);">
@ -104,7 +101,7 @@ onMounted(async () => {
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-slate-600 dark:text-slate-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
{{ course.duration }}
</span>
<span class="text-[11px] font-black text-blue-700 dark:text-blue-500 uppercase tracking-widest hover:text-blue-800 dark:hover:text-blue-400 transition-colors">{{ $t('menu.viewDetails') }}</span>
<span class="px-4 py-2 bg-blue-600 text-white text-xs font-bold rounded-lg hover:bg-blue-700 transition-colors shadow-md">{{ $t('menu.viewDetails') }}</span>
</div>
</div>
</NuxtLink>

View file

@ -77,10 +77,18 @@ onMounted(() => {
loadEnrolledCourses()
})
// Certificate Handling
const selectedCertCourse = ref<any>(null)
const openCertModal = (course: any) => {
selectedCertCourse.value = course
showCertModal.value = true
}
// Mock certificate download action
const downloadCertificate = () => {
showCertModal.value = false
alert('เริ่มดาวน์โหลด PDF...')
alert(`Start downloading certificate for ${selectedCertCourse.value?.title || 'course'}...`)
}
</script>
@ -133,14 +141,13 @@ const downloadCertificate = () => {
:completed="true"
show-certificate
show-study-again
@view-certificate="showCertModal = true"
@view-certificate="openCertModal(course)"
/>
</template>
</div>
<!-- Empty State -->
<div v-if="!isLoading && enrolledCourses.length === 0" class="flex flex-col items-center justify-center py-20 bg-slate-50 dark:bg-slate-800/50 rounded-3xl border-2 border-dashed border-slate-200 dark:border-slate-700 mt-4">
<div class="text-6xl mb-4">📚</div>
<h3 class="text-xl font-bold text-slate-900 dark:text-white mb-2">{{ $t('myCourses.emptyTitle') }}</h3>
<p class="text-slate-500 dark:text-slate-400 text-center max-w-md">{{ $t('myCourses.emptyDesc') }}</p>
<NuxtLink to="/browse/discovery" class="mt-6 px-6 py-2 bg-blue-600 text-white rounded-lg font-bold hover:bg-blue-700 transition-colors">{{ $t('myCourses.goToDiscovery') }}</NuxtLink>
@ -197,11 +204,11 @@ const downloadCertificate = () => {
<p class="text-slate-500 mb-6 text-lg italic font-serif">{{ $t('certificate.completedDesc') }}</p>
<h3 class="text-2xl font-bold text-slate-800 mb-12">HTML5 Fundamentals</h3>
<h3 class="text-2xl font-bold text-slate-800 mb-12">{{ selectedCertCourse?.title || 'Course Title' }}</h3>
<div class="flex justify-between items-end px-12 mt-auto">
<div class="text-center">
<div class="w-48 border-b border-slate-800 mb-2 pb-2 italic font-serif text-lg">Somchai K.</div>
<div class="w-48 border-b border-slate-800 mb-2 pb-2 italic font-serif text-lg">Course Instructor</div>
<div class="text-xs text-slate-500 uppercase tracking-wider">{{ $t('certificate.directorSignature') }}</div>
</div>