feat: add initial frontend pages for course browsing, recommendations, and user dashboard.
All checks were successful
Build and Deploy Frontend Learner / Build Frontend Learner Docker Image (push) Successful in 38s
Build and Deploy Frontend Learner / Deploy E-learning Frontend Learner to Dev Server (push) Successful in 3s
Build and Deploy Frontend Learner / Notify Deployment Status (push) Successful in 1s
All checks were successful
Build and Deploy Frontend Learner / Build Frontend Learner Docker Image (push) Successful in 38s
Build and Deploy Frontend Learner / Deploy E-learning Frontend Learner to Dev Server (push) Successful in 3s
Build and Deploy Frontend Learner / Notify Deployment Status (push) Successful in 1s
This commit is contained in:
parent
0588ad7acd
commit
01d249c19a
14 changed files with 570 additions and 267 deletions
|
|
@ -85,6 +85,7 @@ const loadCourses = async (page = 1) => {
|
|||
|
||||
const res = await fetchCourses({
|
||||
category_id: categoryId,
|
||||
search: searchQuery.value,
|
||||
page: page,
|
||||
limit: itemsPerPage,
|
||||
forceRefresh: true,
|
||||
|
|
@ -155,31 +156,56 @@ onMounted(() => {
|
|||
<!-- CATALOG VIEW: Browse courses -->
|
||||
<div v-if="!showDetail">
|
||||
<!-- Top Header Area -->
|
||||
<div class="flex flex-col gap-6 mb-10">
|
||||
<div class="flex items-start gap-4 mb-4">
|
||||
<span
|
||||
class="w-1.5 h-10 md:h-12 bg-blue-600 rounded-full shadow-lg shadow-blue-500/50 mt-1 flex-shrink-0"
|
||||
></span>
|
||||
<div>
|
||||
<h1
|
||||
class="text-3xl md:text-4xl font-black text-slate-900 dark:text-white leading-tight"
|
||||
>
|
||||
{{ $t("discovery.title") }}
|
||||
</h1>
|
||||
<p
|
||||
v-if="filteredCourses.length > 0"
|
||||
class="text-slate-500 dark:text-slate-400 mt-1 font-medium"
|
||||
>
|
||||
{{ $t("discovery.foundTotal") }}
|
||||
<span class="text-blue-600 font-bold leading-none">{{
|
||||
filteredCourses.length
|
||||
}}</span>
|
||||
{{ $t("discovery.items") }}
|
||||
</p>
|
||||
</div>
|
||||
<!-- New Enhanced Search Section (Image 1 Style) -->
|
||||
<div class="bg-blue-50/50 dark:bg-blue-900/10 rounded-[2.5rem] p-8 md:p-10 mb-8 border border-blue-100/50 dark:border-blue-500/10 transition-colors duration-300">
|
||||
<div class="flex items-center gap-4 mb-2">
|
||||
<span class="w-2 h-8 bg-blue-600 rounded-full shadow-lg shadow-blue-500/30"></span>
|
||||
<h1 class="text-2xl md:text-3xl font-black text-slate-900 dark:text-white">
|
||||
{{ $t("discovery.title") }}
|
||||
</h1>
|
||||
</div>
|
||||
<p class="text-slate-500 dark:text-slate-400 font-medium mb-8">
|
||||
{{ $t("discovery.subtitle") }}
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<!-- Search Input -->
|
||||
<div class="relative flex-1 group">
|
||||
<div class="absolute left-5 top-1/2 -translate-y-1/2 text-slate-400 group-focus-within:text-blue-600 transition-colors">
|
||||
<q-icon name="search" size="24px" />
|
||||
</div>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
:placeholder="$t('discovery.searchPlaceholder') || 'ค้นหาคอร์สที่น่าสนใจที่นี่...'"
|
||||
class="w-full pl-14 pr-6 py-3.5 bg-white dark:bg-slate-800 border-2 border-transparent rounded-2xl text-slate-900 dark:text-white placeholder-slate-400 focus:outline-none focus:border-blue-500/20 focus:ring-4 focus:ring-blue-500/5 transition-all text-base font-medium shadow-sm"
|
||||
@keyup.enter="loadCourses(1)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Search Button -->
|
||||
<q-btn
|
||||
unelevated
|
||||
color="primary"
|
||||
class="px-8 h-[52px] rounded-2xl font-black shadow-lg shadow-blue-600/20 hover:scale-[1.02] transition-transform"
|
||||
no-caps
|
||||
@click="loadCourses(1)"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<q-icon name="search" size="20px" />
|
||||
<span class="text-base">ค้นหา</span>
|
||||
</div>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Unified Filter Section: Categories -->
|
||||
<div class="flex items-center justify-between mb-4 px-2">
|
||||
<div class="text-slate-500 dark:text-slate-400 text-sm font-bold uppercase tracking-wider">
|
||||
{{ $t("discovery.foundTotal") }} <span class="text-blue-600">{{ filteredCourses.length }}</span> {{ $t("discovery.items") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Unified Filter Section: Categories -->
|
||||
<div
|
||||
class="bg-white dark:bg-slate-900/50 p-2 rounded-2xl border border-slate-100 dark:border-white/5 inline-flex flex-wrap items-center gap-1.5 shadow-sm"
|
||||
>
|
||||
|
|
@ -212,7 +238,6 @@ onMounted(() => {
|
|||
:label="getLocalizedText(cat.name)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Layout: Grid Only -->
|
||||
<div class="w-full">
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ const filteredCourses = computed(() => {
|
|||
|
||||
<!-- Main Title -->
|
||||
<h1 class="text-4xl md:text-6xl font-black text-slate-900 mb-6 tracking-tight py-2 slide-up leading-[1.2] overflow-visible" style="animation-delay: 0.1s;">
|
||||
คอร์สเรียนออนไลน์<span class="text-gradient-cyan">ทั้งหมด</span>
|
||||
คอร์สเรียน<span class="text-gradient-cyan">ทั้งหมด</span>
|
||||
</h1>
|
||||
<!-- Subtitle -->
|
||||
<p class="text-slate-400 text-xl max-w-2xl mx-auto leading-relaxed slide-up" style="animation-delay: 0.2s;">
|
||||
|
|
@ -163,30 +163,42 @@ const filteredCourses = computed(() => {
|
|||
<!-- Content Frame Container -->
|
||||
<div class="glass-premium rounded-[3rem] p-8 md:p-12 shadow-xl shadow-blue-900/5">
|
||||
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-8 mb-8">
|
||||
<h2 class="text-2xl font-black text-slate-900 flex items-center gap-3">
|
||||
<span class="w-2 h-8 bg-blue-600 rounded-full"/>
|
||||
รายการคอร์สเรียน
|
||||
</h2>
|
||||
|
||||
<!-- Search Bar (Compact) -->
|
||||
<div class="relative max-w-md w-full">
|
||||
<div class="relative group">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
class="w-full pl-12 pr-6 py-3 bg-slate-100 border border-slate-200 rounded-xl text-slate-900 placeholder-slate-400 focus:outline-none focus:bg-white focus: focus:ring-2 focus:ring-blue-500/50 transition-all font-medium"
|
||||
placeholder="ค้นหาบทเรียน..."
|
||||
>
|
||||
<div class="absolute left-4 top-1/2 -translate-y-1/2 text-slate-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" 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>
|
||||
<!-- New Enhanced Search Section (Image 2 Style) -->
|
||||
<div class="bg-blue-50/50 rounded-[2.5rem] p-8 md:p-10 mb-6 border border-blue-100/50">
|
||||
<h2 class="text-2xl md:text-3xl font-black text-slate-900 mb-2">คอร์สเรียนทั้งหมด</h2>
|
||||
<p class="text-slate-500 font-medium mb-8">พัฒนาทักษะใหม่ๆ กับผู้เชี่ยวชาญจากทั่วโลก</p>
|
||||
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<!-- Search Input -->
|
||||
<div class="relative flex-1 group">
|
||||
<div class="absolute left-5 top-1/2 -translate-y-1/2 text-slate-400 group-focus-within:text-blue-600 transition-colors">
|
||||
<q-icon name="search" size="24px" />
|
||||
</div>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="ค้นหาชื่อคอร์ส..."
|
||||
class="w-full pl-14 pr-6 py-4 bg-white border-2 border-transparent rounded-2xl text-slate-900 placeholder-slate-400 focus:outline-none focus:border-blue-500/20 focus:ring-4 focus:ring-blue-500/5 transition-all text-lg font-medium shadow-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Search Button -->
|
||||
<q-btn
|
||||
unelevated
|
||||
color="primary"
|
||||
class="px-4 h-16 rounded-2xl font-black shadow-lg shadow-blue-600/20 hover:scale-[1.02] transition-transform"
|
||||
no-caps
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<q-icon name="search" size="20px" />
|
||||
<span class="text-base">ค้นหา</span>
|
||||
</div>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Category Filter Tabs with Scroll Buttons -->
|
||||
<div class="relative mb-8">
|
||||
<!-- Left Scroll Button -->
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ const filteredCourses = computed(() => {
|
|||
<!-- Tagline Badge -->
|
||||
<!-- Main Title -->
|
||||
<h1 class="text-4xl md:text-6xl font-black text-slate-900 mb-6 tracking-tight py-2 slide-up leading-[1.2] overflow-visible" style="animation-delay: 0.1s;">
|
||||
คอร์สเรียนออนไลน์<span class="text-gradient-cyan">แนะนำ</span>
|
||||
คอร์สเรียน<span class="text-gradient-cyan">แนะนำ</span>
|
||||
</h1>
|
||||
<!-- Subtitle -->
|
||||
<p class="text-slate-400 text-xl max-w-2xl mx-auto leading-relaxed slide-up" style="animation-delay: 0.2s;">
|
||||
|
|
@ -130,27 +130,37 @@ const filteredCourses = computed(() => {
|
|||
<!-- Content Frame Container -->
|
||||
<div class="glass-premium rounded-[3rem] p-8 md:p-12 shadow-xl shadow-blue-900/5">
|
||||
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-8 mb-8">
|
||||
<h2 class="text-2xl font-black text-slate-900 flex items-center gap-3">
|
||||
<span class="w-2 h-8 bg-blue-600 rounded-full"/>
|
||||
คอร์สที่คุณห้ามพลาด
|
||||
</h2>
|
||||
|
||||
<!-- Search Bar (Compact) -->
|
||||
<div class="relative max-w-md w-full">
|
||||
<div class="relative group">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
class="w-full pl-12 pr-6 py-3 bg-slate-100 border border-slate-200 rounded-xl text-slate-900 placeholder-slate-400 focus:outline-none focus:bg-white focus: focus:ring-2 focus:ring-blue-500/50 transition-all font-medium"
|
||||
placeholder="ค้นหาคอร์สแนะนำ..."
|
||||
>
|
||||
<div class="absolute left-4 top-1/2 -translate-y-1/2 text-slate-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" 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>
|
||||
<!-- New Enhanced Search Section (Image 2 Style) -->
|
||||
<div class="bg-blue-50/50 rounded-[2.5rem] p-8 md:p-10 mb-6 border border-blue-100/50">
|
||||
<h2 class="text-2xl md:text-3xl font-black text-slate-900 mb-2">คอร์สเรียนแนะนำ</h2>
|
||||
<p class="text-slate-500 font-medium mb-8">คัดสรรเนื้อหาคุณภาพสูงที่คุณไม่ควรพลาด</p>
|
||||
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<!-- Search Input -->
|
||||
<div class="relative flex-1 group">
|
||||
<div class="absolute left-5 top-1/2 -translate-y-1/2 text-slate-400 group-focus-within:text-blue-600 transition-colors">
|
||||
<q-icon name="search" size="24px" />
|
||||
</div>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="ค้นหาชื่อคอร์สแนะนำ..."
|
||||
class="w-full pl-14 pr-6 py-4 bg-white border-2 border-transparent rounded-2xl text-slate-900 placeholder-slate-400 focus:outline-none focus:border-blue-500/20 focus:ring-4 focus:ring-blue-500/5 transition-all text-lg font-medium shadow-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Search Button -->
|
||||
<q-btn
|
||||
unelevated
|
||||
color="primary"
|
||||
class="px-4 h-16 rounded-2xl font-black shadow-lg shadow-blue-600/20 hover:scale-[1.02] transition-transform"
|
||||
no-caps
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<q-icon name="search" size="20px" />
|
||||
<span class="text-base">ค้นหา</span>
|
||||
</div>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -171,11 +171,11 @@ const sideCourses = computed(() => enrolledCourses.value.slice(1, 3));
|
|||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 items-start">
|
||||
<!-- Hero Card (Left) -->
|
||||
<div
|
||||
v-if="heroCourse"
|
||||
class="relative group cursor-pointer rounded-2xl overflow-hidden bg-white dark:bg-[#1e293b] shadow-sm border border-gray-100 dark:border-slate-700 hover:shadow-md transition-all h-[320px]"
|
||||
class="relative group cursor-pointer rounded-2xl overflow-hidden bg-white dark:bg-[#1e293b] shadow-sm border border-gray-100 dark:border-slate-700 hover:shadow-md transition-all h-[260px] md:h-[320px]"
|
||||
@click="
|
||||
navigateTo(`/classroom/learning?course_id=${heroCourse.id}`)
|
||||
"
|
||||
|
|
@ -233,7 +233,7 @@ const sideCourses = computed(() => enrolledCourses.value.slice(1, 3));
|
|||
</div>
|
||||
|
||||
<!-- Side List (Right) -->
|
||||
<div class="flex flex-col gap-4 h-[320px]">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div
|
||||
v-for="course in sideCourses"
|
||||
:key="course.id"
|
||||
|
|
@ -313,7 +313,7 @@ const sideCourses = computed(() => enrolledCourses.value.slice(1, 3));
|
|||
<!-- Content when courses exist -->
|
||||
<div
|
||||
v-if="libraryCourses.length > 0"
|
||||
class="grid grid-cols-1 md:grid-cols-3 gap-6"
|
||||
class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-6"
|
||||
>
|
||||
<!-- Course Cards -->
|
||||
<CourseCard
|
||||
|
|
@ -382,7 +382,7 @@ const sideCourses = computed(() => enrolledCourses.value.slice(1, 3));
|
|||
|
||||
<!-- Recommended Grid (3 columns) -->
|
||||
<div
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 animate-fade-in"
|
||||
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 animate-fade-in"
|
||||
>
|
||||
<CourseCard
|
||||
v-for="course in recommendedCourses"
|
||||
|
|
|
|||
|
|
@ -151,48 +151,54 @@ const validCourseId = computed(() => {
|
|||
|
||||
|
||||
<!-- Page Header & Filters (Unified Layout) -->
|
||||
<div class="mb-8">
|
||||
<div class="flex items-start gap-4 mb-2">
|
||||
<span class="w-1.5 h-10 md:h-12 bg-blue-600 rounded-full shadow-lg shadow-blue-500/50 mt-1 flex-shrink-0"></span>
|
||||
<div>
|
||||
<h1 class="text-3xl md:text-4xl font-black text-slate-900 dark:text-white leading-tight">
|
||||
{{ $t('sidebar.myCourses') }}
|
||||
</h1>
|
||||
<!-- New Enhanced Search Section (Image 2 Style) -->
|
||||
<div class="bg-blue-50/50 dark:bg-blue-900/10 rounded-[2.5rem] p-8 md:p-10 mb-6 border border-blue-100/50 dark:border-blue-500/10">
|
||||
<h2 class="text-2xl md:text-3xl font-black text-slate-900 dark:text-white mb-2">คอร์สของฉัน</h2>
|
||||
<p class="text-slate-500 dark:text-slate-400 font-medium mb-8">ติดตามความคืบหน้าและเรียนรู้ต่อจากจุดที่ค้างไว้</p>
|
||||
|
||||
<div class="flex flex-col md:flex-row gap-4">
|
||||
<!-- Search Input -->
|
||||
<div class="relative flex-1 group">
|
||||
<div class="absolute left-5 top-1/2 -translate-y-1/2 text-slate-400 group-focus-within:text-blue-600 transition-colors">
|
||||
<q-icon name="search" size="24px" />
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
placeholder="ค้นหาชื่อคอร์สของฉัน..."
|
||||
class="w-full pl-14 pr-6 py-3.5 bg-white dark:bg-slate-800 border-2 border-transparent rounded-2xl text-slate-900 dark:text-white placeholder-slate-400 focus:outline-none focus:border-blue-500/20 focus:ring-4 focus:ring-blue-500/5 transition-all text-base font-medium shadow-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Search Button -->
|
||||
<q-btn
|
||||
unelevated
|
||||
color="primary"
|
||||
class="px-8 h-[52px] rounded-2xl font-black shadow-lg shadow-blue-600/20 hover:scale-[1.02] transition-transform"
|
||||
no-caps
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<q-icon name="search" size="20px" />
|
||||
<span class="text-base">ค้นหา</span>
|
||||
</div>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4 mt-4">
|
||||
<!-- Filter Tabs (Horizontal Bar) -->
|
||||
<div class="bg-white dark:bg-slate-900/50 p-1.5 rounded-2xl border border-slate-100 dark:border-white/5 inline-flex items-center gap-1 shadow-sm">
|
||||
<q-btn
|
||||
v-for="filter in ['all', 'progress', 'completed']"
|
||||
:key="filter"
|
||||
@click="activeFilter = filter as any"
|
||||
flat
|
||||
rounded
|
||||
dense
|
||||
class="px-5 py-2 font-bold transition-all text-[11px] uppercase tracking-wider"
|
||||
:class="activeFilter === filter ? 'bg-blue-600 text-white shadow-md shadow-blue-600/20' : 'text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800'"
|
||||
:label="$t(`myCourses.filter${filter.charAt(0).toUpperCase() + filter.slice(1)}`)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Search Input -->
|
||||
<div class="w-full md:w-72">
|
||||
<q-input
|
||||
v-model="searchQuery"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
:placeholder="$t('discovery.searchPlaceholder')"
|
||||
class="search-input shadow-sm"
|
||||
bg-color="transparent"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<q-icon name="search" class="text-slate-400" />
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-8">
|
||||
<!-- Filter Tabs (Horizontal Bar) -->
|
||||
<div class="bg-white dark:bg-slate-900/50 p-1.5 rounded-2xl border border-slate-100 dark:border-white/5 inline-flex items-center gap-1 shadow-sm">
|
||||
<q-btn
|
||||
v-for="filter in ['all', 'progress', 'completed']"
|
||||
:key="filter"
|
||||
@click="activeFilter = filter as any"
|
||||
flat
|
||||
rounded
|
||||
dense
|
||||
class="px-5 py-2 font-bold transition-all text-[11px] uppercase tracking-wider"
|
||||
:class="activeFilter === filter ? 'bg-blue-600 text-white shadow-md shadow-blue-600/20' : 'text-slate-600 dark:text-slate-400 hover:bg-slate-50 dark:hover:bg-slate-800'"
|
||||
:label="$t(`myCourses.filter${filter.charAt(0).toUpperCase() + filter.slice(1)}`)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue