feat: Initialize core frontend application structure, including layouts, authentication pages, and common UI components.
This commit is contained in:
parent
ae84e7e879
commit
69eb60f901
16 changed files with 1178 additions and 1396 deletions
|
|
@ -57,6 +57,24 @@ const visibleCategories = computed(() => {
|
|||
return showAllCategories.value ? categories.value : categories.value.slice(0, 8);
|
||||
});
|
||||
|
||||
// เตรียม Options สำหรับ QSelect Dropdown
|
||||
const categoryOptions = computed(() => {
|
||||
return categories.value.map(c => ({
|
||||
id: c.id,
|
||||
label: getLocalizedText(c.name),
|
||||
value: c.id // q-select uses value by default if not constrained
|
||||
}))
|
||||
});
|
||||
|
||||
// ฟังก์ชัน Toggle Category สำหรับ Chip Remove
|
||||
const toggleCategory = (id: number) => {
|
||||
if (selectedCategoryIds.value.includes(id)) {
|
||||
selectedCategoryIds.value = selectedCategoryIds.value.filter(cid => cid !== id);
|
||||
} else {
|
||||
selectedCategoryIds.value.push(id);
|
||||
}
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// 4. จัดการข้อมูลคอร์ส (Courses)
|
||||
// ==========================================
|
||||
|
|
@ -151,311 +169,257 @@ const filteredCourses = computed(() => {
|
|||
|
||||
return result;
|
||||
});
|
||||
|
||||
// ==========================================
|
||||
// 6. User & Helpers
|
||||
// ==========================================
|
||||
const { currentUser } = useAuth();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="p-6 max-w-[1400px] mx-auto">
|
||||
|
||||
|
||||
<!-- CATALOG VIEW: Browse courses -->
|
||||
<div v-if="!showDetail">
|
||||
<!-- Search & Filters Header -->
|
||||
<div
|
||||
class="flex justify-between items-center mb-6"
|
||||
style="flex-wrap: wrap; gap: 16px"
|
||||
>
|
||||
<h1 class="text-[28px] font-bold text-slate-900 dark:text-white">
|
||||
{{ $t('discovery.title') }}
|
||||
</h1>
|
||||
<div class="flex gap-3" style="flex-wrap: wrap">
|
||||
|
||||
<!-- Top Header Area -->
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-8">
|
||||
<!-- Title -->
|
||||
<h2 class="text-2xl font-bold text-slate-900 dark:text-white">
|
||||
รายการคอร์สทั้งหมด
|
||||
</h2>
|
||||
|
||||
<!-- Right Side: Search & Sort -->
|
||||
<div class="flex items-center gap-3">
|
||||
<!-- Search Input -->
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
class="input-field text-slate-900 dark:text-white bg-white dark:bg-slate-800 placeholder:text-slate-500"
|
||||
:placeholder="$t('discovery.searchPlaceholder')"
|
||||
style="padding-left: 36px; width: 240px"
|
||||
/>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
class="pl-4 pr-10 py-2 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-900 dark:text-white focus:outline-none focus:border-slate-400 w-full md:w-64 text-sm"
|
||||
placeholder="ค้นหาคอร์ส..."
|
||||
/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="absolute right-3 top-2.5 h-4 w-4 text-slate-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Sort Dropdown (Dummy) -->
|
||||
<div class="relative">
|
||||
<select class="appearance-none pl-4 pr-10 py-2 rounded-lg border border-slate-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-200 text-sm focus:outline-none focus:border-slate-400 cursor-pointer">
|
||||
<option>เรียงตาม: ล่าสุด</option>
|
||||
<option>ราคา: ต่ำไปสูง</option>
|
||||
<option>ราคา: สูงไปต่ำ</option>
|
||||
</select>
|
||||
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-slate-500">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Sorting Select -->
|
||||
<select
|
||||
class="input-field bg-white dark:bg-slate-800"
|
||||
style="width: auto; color: #0f172a"
|
||||
>
|
||||
<option style="color: #0f172a">{{ $t('discovery.sortRecent') }}</option>
|
||||
<option style="color: #0f172a">{{ $t('discovery.sortPopular') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Grid Layout -->
|
||||
<div class="grid-12">
|
||||
<!-- Sidebar Filters -->
|
||||
<div class="col-span-3">
|
||||
<div class="card">
|
||||
<div class="mb-6">
|
||||
<div
|
||||
class="flex items-center justify-between mb-4 cursor-pointer"
|
||||
@click="isCategoryOpen = !isCategoryOpen"
|
||||
>
|
||||
<h4 class="text-lg font-bold text-slate-900 dark:text-white">
|
||||
{{ $t('discovery.categoryTitle') }} ({{ categories.length }})
|
||||
</h4>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-5 w-5 text-slate-400 transition-transform duration-200"
|
||||
:class="{ 'rotate-180': !isCategoryOpen }"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
<!-- Main Layout: Sidebar + Grid -->
|
||||
<div class="flex flex-col lg:flex-row gap-8 items-start">
|
||||
|
||||
<!-- LEFT SIDEBAR: Category Filter -->
|
||||
<div class="w-full lg:w-64 flex-shrink-0 sticky top-24">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-sm border border-slate-200 dark:border-slate-700 overflow-hidden">
|
||||
<q-expansion-item
|
||||
expand-separator
|
||||
:label="`หมวดหมู่ (${selectedCategoryIds.length})`"
|
||||
class="font-bold text-slate-800 dark:text-white"
|
||||
header-class="bg-white dark:bg-slate-800"
|
||||
default-opened
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 15l7-7 7 7"
|
||||
<q-list class="bg-white dark:bg-slate-800 border-t border-slate-100 dark:border-slate-700">
|
||||
<q-item
|
||||
v-for="cat in (showAllCategories ? categories : categories.slice(0, 4))"
|
||||
:key="cat.id"
|
||||
tag="label"
|
||||
clickable
|
||||
v-ripple
|
||||
dense
|
||||
>
|
||||
<q-item-section avatar>
|
||||
<q-checkbox v-model="selectedCategoryIds" :val="cat.id" color="primary" dense />
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label class="text-sm font-medium text-slate-700 dark:text-slate-300">{{ getLocalizedText(cat.name) }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<!-- Show More/Less Button -->
|
||||
<q-item
|
||||
v-if="categories.length > 4"
|
||||
clickable
|
||||
v-ripple
|
||||
@click="showAllCategories = !showAllCategories"
|
||||
class="text-blue-600 dark:text-blue-400 font-bold text-sm"
|
||||
>
|
||||
<q-item-section>
|
||||
<div class="flex items-center gap-1">
|
||||
{{ showAllCategories ? 'แสดงน้อยลง' : 'แสดงเพิ่มเติม' }}
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" :class="showAllCategories ? 'rotate-180' : ''">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
</div>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-expansion-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT CONTENT: Course Grid -->
|
||||
<div class="flex-1 w-full">
|
||||
<div v-if="filteredCourses.length > 0" class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-6">
|
||||
<CourseCard
|
||||
v-for="course in filteredCourses"
|
||||
:key="course.id"
|
||||
:title="course.title"
|
||||
:price="course.price"
|
||||
:description="course.description"
|
||||
:rating="course.rating"
|
||||
:lessons="course.lessons"
|
||||
:image="course.thumbnail_url"
|
||||
show-view-details
|
||||
@view-details="selectCourse(course.id)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div v-show="isCategoryOpen" class="flex flex-col gap-1">
|
||||
<div
|
||||
v-for="cat in visibleCategories"
|
||||
:key="cat.id"
|
||||
class="flex items-center justify-between py-3 border-b border-slate-100 dark:border-slate-700/50 group cursor-pointer hover:bg-slate-50 dark:hover:bg-slate-800/50 -mx-4 px-4 transition-colors"
|
||||
>
|
||||
<label
|
||||
class="flex items-center gap-3 text-slate-700 dark:text-slate-300 cursor-pointer w-full"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
:value="cat.id"
|
||||
v-model="selectedCategoryIds"
|
||||
class="w-4 h-4 rounded border-slate-300 text-primary focus:ring-primary"
|
||||
/>
|
||||
{{ getLocalizedText(cat.name) }}
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
class="text-primary text-sm mt-4 font-medium hover:underline flex items-center gap-1"
|
||||
@click="showAllCategories = !showAllCategories"
|
||||
>
|
||||
{{ showAllCategories ? $t('discovery.showLess') : $t('discovery.showMore') }}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-4 w-4 transition-transform duration-200"
|
||||
:class="{ 'rotate-180': showAllCategories }"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M19 9l-7 7-7-7"
|
||||
/>
|
||||
</svg>
|
||||
<!-- Empty State -->
|
||||
<div
|
||||
v-else
|
||||
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"
|
||||
>
|
||||
<div class="text-6xl mb-4">🔍</div>
|
||||
<h3 class="text-xl font-bold text-slate-900 dark:text-white mb-2">{{ $t('discovery.emptyTitle') }}</h3>
|
||||
<p class="text-slate-500 dark:text-slate-400 text-center max-w-md">
|
||||
{{ $t('discovery.emptyDesc') }}
|
||||
</p>
|
||||
<button class="mt-6 font-bold text-blue-600 hover:underline" @click="searchQuery = ''; selectedCategoryIds = []">
|
||||
{{ $t('discovery.showAll') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Course List -->
|
||||
<div class="col-span-9" style="min-width: 0">
|
||||
<div class="course-grid">
|
||||
<CourseCard
|
||||
v-for="course in filteredCourses"
|
||||
:key="course.id"
|
||||
:title="course.title"
|
||||
:price="course.price"
|
||||
:description="course.description"
|
||||
:rating="course.rating"
|
||||
:lessons="course.lessons"
|
||||
:image="course.thumbnail_url"
|
||||
show-view-details
|
||||
@view-details="selectCourse(course.id)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div
|
||||
v-if="filteredCourses.length === 0"
|
||||
class="empty-state"
|
||||
style="grid-column: 1 / -1"
|
||||
>
|
||||
<h3 class="empty-state-title">{{ $t('discovery.emptyTitle') }}</h3>
|
||||
<p class="empty-state-description">
|
||||
{{ $t('discovery.emptyDesc') }}
|
||||
</p>
|
||||
<button class="btn btn-secondary" @click="searchQuery = ''">
|
||||
{{ $t('discovery.showAll') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Pagination / Load More -->
|
||||
<div class="load-more-wrap">
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
style="
|
||||
border-color: #64748b;
|
||||
color: white;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
"
|
||||
>
|
||||
{{ $t('discovery.loadMore') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- COURSE DETAIL VIEW: Detailed information about a specific course -->
|
||||
<div v-else>
|
||||
<button
|
||||
@click="showDetail = false"
|
||||
class="btn btn-secondary mb-6 inline-flex items-center gap-2"
|
||||
class="mb-6 inline-flex items-center gap-2 text-slate-500 hover:text-slate-900 dark:hover:text-white transition-colors font-medium"
|
||||
>
|
||||
<span>←</span> {{ $t('discovery.backToCatalog') }}
|
||||
</button>
|
||||
|
||||
<div v-if="isLoadingDetail" class="flex justify-center py-20">
|
||||
<div class="spinner-border animate-spin inline-block w-8 h-8 border-4 rounded-full" role="status">
|
||||
<div class="spinner-border animate-spin inline-block w-8 h-8 border-4 border-blue-500 rounded-full border-t-transparent" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="selectedCourse" class="grid-12">
|
||||
<div v-else-if="selectedCourse" class="grid grid-cols-1 lg:grid-cols-12 gap-8">
|
||||
<!-- Main Content (Left Column) -->
|
||||
<div class="col-span-8">
|
||||
<div class="lg:col-span-8">
|
||||
<!-- Hero Video Placeholder -->
|
||||
<div
|
||||
style="
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
background: var(--neutral-900);
|
||||
border-radius: var(--radius-xl);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 24px;
|
||||
position: relative;
|
||||
border: 1px solid var(--border-color);
|
||||
overflow: hidden;
|
||||
"
|
||||
class="w-full aspect-video bg-slate-900 rounded-3xl overflow-hidden relative shadow-lg mb-8 group"
|
||||
>
|
||||
<img
|
||||
v-if="selectedCourse.thumbnail_url"
|
||||
:src="selectedCourse.thumbnail_url"
|
||||
class="absolute inset-0 w-full h-full object-cover opacity-50"
|
||||
class="absolute inset-0 w-full h-full object-cover opacity-60 group-hover:opacity-40 transition-opacity"
|
||||
/>
|
||||
<!-- Play Button -->
|
||||
<div
|
||||
class="relative z-10"
|
||||
style="
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: var(--primary);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
|
||||
"
|
||||
>
|
||||
<div
|
||||
style="
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 15px solid transparent;
|
||||
border-bottom: 15px solid transparent;
|
||||
border-left: 25px solid white;
|
||||
margin-left: 6px;
|
||||
"
|
||||
/>
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<div class="w-20 h-20 bg-white/20 backdrop-blur-md rounded-full flex items-center justify-center group-hover:scale-110 transition-transform cursor-pointer">
|
||||
<div class="w-16 h-16 bg-white rounded-full flex items-center justify-center shadow-xl">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-blue-600 ml-1" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="text-[32px] font-bold mb-4 text-slate-900 dark:text-white">
|
||||
<h1 class="text-3xl md:text-4xl font-bold mb-4 text-slate-900 dark:text-white tracking-tight">
|
||||
{{ getLocalizedText(selectedCourse.title) }}
|
||||
</h1>
|
||||
<p
|
||||
class="text-slate-700 dark:text-slate-400 mb-6"
|
||||
style="font-size: 1.1em; line-height: 1.7"
|
||||
>
|
||||
<p class="text-slate-600 dark:text-slate-400 text-lg leading-relaxed mb-8">
|
||||
{{ getLocalizedText(selectedCourse.description) }}
|
||||
</p>
|
||||
|
||||
<!-- Course Syllabus / Outline (Dynamic) -->
|
||||
<div class="card" v-if="selectedCourse.chapters && selectedCourse.chapters.length > 0">
|
||||
<h3 class="font-bold mb-4 text-slate-900 dark:text-white">
|
||||
{{ $t('course.courseContent') }}
|
||||
<!-- Course Syllabus -->
|
||||
<div class="bg-white dark:bg-slate-800 rounded-3xl p-8 shadow-sm border border-slate-100 dark:border-slate-700/50">
|
||||
<h3 class="text-xl font-bold mb-6 text-slate-900 dark:text-white flex items-center gap-2">
|
||||
<span class="w-1 h-6 bg-blue-500 rounded-full"></span>
|
||||
{{ $t('course.courseContent') }}
|
||||
</h3>
|
||||
<div v-for="chapter in selectedCourse.chapters" :key="chapter.id" class="mb-4">
|
||||
<div
|
||||
class="flex justify-between p-4 rounded mb-2"
|
||||
style="background: #f3f4f6; border: 1px solid #e5e7eb"
|
||||
>
|
||||
<span class="font-bold text-slate-900 dark:text-slate-900"
|
||||
>{{ getLocalizedText(chapter.title) }}</span
|
||||
>
|
||||
<span class="text-sm text-slate-600 dark:text-slate-400"
|
||||
>{{ chapter.lessons ? chapter.lessons.length : 0 }} {{ $t('course.lessons') }}</span
|
||||
>
|
||||
</div>
|
||||
<div style="padding-left: 16px" v-if="chapter.lessons">
|
||||
<div
|
||||
v-for="lesson in chapter.lessons"
|
||||
:key="lesson.id"
|
||||
class="flex justify-between py-2 border-b"
|
||||
style="border-color: var(--neutral-100)"
|
||||
>
|
||||
<span class="text-sm text-slate-700 dark:text-slate-300">{{ getLocalizedText(lesson.title) }}</span>
|
||||
<span class="text-sm text-slate-600 dark:text-slate-400"
|
||||
>{{ lesson.duration_minutes || 0 }}:00</span
|
||||
>
|
||||
<div class="space-y-4">
|
||||
<div v-for="(chapter, index) in selectedCourse.chapters" :key="chapter.id" class="border border-slate-200 dark:border-slate-700 rounded-xl overflow-hidden">
|
||||
<div class="bg-slate-50 dark:bg-slate-800/80 p-4 flex justify-between items-center cursor-pointer">
|
||||
<div class="font-bold text-slate-800 dark:text-slate-200">
|
||||
<span class="opacity-50 mr-2">Chapter {{ index + 1 }}</span>
|
||||
{{ getLocalizedText(chapter.title) }}
|
||||
</div>
|
||||
<span class="text-xs font-bold px-3 py-1 bg-white dark:bg-slate-700 rounded-full border border-slate-200 dark:border-slate-600">
|
||||
{{ chapter.lessons ? chapter.lessons.length : 0 }} Lessons
|
||||
</span>
|
||||
</div>
|
||||
<!-- Lessons -->
|
||||
<div class="divide-y divide-slate-100 dark:divide-slate-700/50 bg-white dark:bg-slate-800">
|
||||
<div v-for="(lesson, lIndex) in chapter.lessons" :key="lesson.id" class="p-4 flex justify-between items-center hover:bg-slate-50 dark:hover:bg-slate-700/30 transition-colors">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-6 h-6 rounded-full bg-slate-100 dark:bg-slate-700 flex items-center justify-center text-[10px] font-bold text-slate-500">
|
||||
{{ lIndex + 1 }}
|
||||
</div>
|
||||
<span class="text-slate-700 dark:text-slate-300 font-medium text-sm">{{ getLocalizedText(lesson.title) }}</span>
|
||||
</div>
|
||||
<span class="text-xs text-slate-400">{{ lesson.duration_minutes || 0 }}:00</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar (Right Column): Sticky CTA -->
|
||||
<div class="col-span-4">
|
||||
<div class="card" style="position: sticky; top: 88px">
|
||||
<div class="mb-6">
|
||||
<span
|
||||
class="text-sm text-slate-600 dark:text-slate-400"
|
||||
style="text-decoration: line-through"
|
||||
></span
|
||||
>
|
||||
<h2
|
||||
class="text-primary font-bold"
|
||||
style="font-size: 32px; margin: 0"
|
||||
>
|
||||
<!-- Sidebar (Right Column) -->
|
||||
<div class="lg:col-span-4">
|
||||
<div class="bg-white dark:bg-slate-800 rounded-3xl p-6 shadow-lg border border-slate-100 dark:border-slate-700 sticky top-24">
|
||||
<div class="mb-8 text-center bg-slate-50 dark:bg-slate-900/50 rounded-2xl p-6 border border-slate-100 dark:border-slate-700">
|
||||
<div class="text-sm text-slate-500 mb-1 font-medium">{{ selectedCourse.price ? 'ราคาคอร์ส' : 'ราคา' }}</div>
|
||||
<h2 class="text-4xl font-black text-blue-600 dark:text-blue-400">
|
||||
{{ selectedCourse.price || 'ฟรี' }}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click="handleEnroll(selectedCourse.id)"
|
||||
class="btn btn-primary w-full mb-4 text-white"
|
||||
style="height: 48px; font-size: 16px"
|
||||
class="w-full py-4 rounded-xl bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-bold text-lg shadow-lg hover:shadow-blue-500/30 hover:scale-[1.02] transform transition-all active:scale-95 disabled:opacity-70 disabled:cursor-not-allowed mb-4 flex items-center justify-center gap-2"
|
||||
:disabled="isEnrolling"
|
||||
>
|
||||
<span v-if="isEnrolling" class="spinner-border animate-spin inline-block w-4 h-4 border-2 rounded-full mr-2"></span>
|
||||
<span v-if="isEnrolling" class="block w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin"></span>
|
||||
{{ $t('course.enrollNow') }}
|
||||
</button>
|
||||
|
||||
<div class="text-sm text-slate-600 dark:text-slate-400 mb-4">
|
||||
<div
|
||||
class="flex justify-between py-2 border-b"
|
||||
style="border-color: var(--neutral-100)"
|
||||
>
|
||||
<span>{{ $t('course.certificate') }}</span>
|
||||
<span class="font-bold">{{ selectedCourse.have_certificate ? $t('course.available') : '-' }}</span>
|
||||
</div>
|
||||
<div class="text-sm space-y-3 pt-4 border-t border-slate-100 dark:border-slate-700">
|
||||
<div class="flex justify-between text-slate-600 dark:text-slate-400">
|
||||
<span>{{ $t('course.certificate') }}</span>
|
||||
<span class="font-bold text-slate-900 dark:text-white">{{ selectedCourse.have_certificate ? 'มีใบประกาศฯ' : 'ไม่มี' }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-slate-600 dark:text-slate-400">
|
||||
<span>ผู้สอน</span>
|
||||
<span class="font-bold text-slate-900 dark:text-white">Professional Instructor</span>
|
||||
</div>
|
||||
<div class="flex justify-between text-slate-600 dark:text-slate-400">
|
||||
<span>ระดับ</span>
|
||||
<span class="font-bold text-slate-900 dark:text-white">ทุกระดับ</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -463,3 +427,4 @@ const filteredCourses = computed(() => {
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue