Website Structure
This commit is contained in:
parent
62812f2090
commit
71f0676a62
22365 changed files with 4265753 additions and 791 deletions
45
Frontend-Learner/components/common/AppHeader.vue
Normal file
45
Frontend-Learner/components/common/AppHeader.vue
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<script setup lang="ts">
|
||||
/**
|
||||
* @file AppHeader.vue
|
||||
* @description The main header for the authenticated application dashboard.
|
||||
* Includes sidebar toggle, branding, search functionality, and user menu.
|
||||
*/
|
||||
|
||||
defineProps<{
|
||||
/** Controls visibility of the search bar */
|
||||
showSearch?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
/** Emitted when the hamburger menu is clicked */
|
||||
toggleSidebar: []
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="app-header transition-colors" style="background-color: var(--bg-surface); border-bottom: 1px solid var(--border-color);">
|
||||
<!-- Branding & Toggle -->
|
||||
<div class="flex items-center gap-2">
|
||||
<NuxtLink to="/dashboard" style="font-weight: 800; color: var(--primary); font-size: 20px;">
|
||||
e-Learning
|
||||
</NuxtLink>
|
||||
</div>
|
||||
|
||||
<!-- Right Actions -->
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- Search Bar (Optional) -->
|
||||
<div v-if="showSearch !== false" class="relative hidden-mobile" style="width: 300px;">
|
||||
<input
|
||||
type="text"
|
||||
class="input-field"
|
||||
placeholder="ค้นหาคอร์ส..."
|
||||
style="padding-left: 36px;"
|
||||
>
|
||||
<span style="position: absolute; left: 12px; top: 10px; color: var(--text-secondary);">🔍</span>
|
||||
</div>
|
||||
|
||||
<!-- User Profile Dropdown -->
|
||||
<UserMenu />
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
87
Frontend-Learner/components/common/FormInput.vue
Normal file
87
Frontend-Learner/components/common/FormInput.vue
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<script setup lang="ts">
|
||||
/**
|
||||
* @file FormInput.vue
|
||||
* @description Reusable input component with label, error handling, and support for disabled/required states.
|
||||
*/
|
||||
|
||||
defineProps<{
|
||||
modelValue: string
|
||||
label: string
|
||||
type?: string
|
||||
placeholder?: string
|
||||
error?: string
|
||||
required?: boolean
|
||||
disabled?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
/** Update v-model value */
|
||||
'update:modelValue': [value: string]
|
||||
}>()
|
||||
|
||||
const updateValue = (event: Event) => {
|
||||
emit('update:modelValue', (event.target as HTMLInputElement).value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="form-group" :class="{ 'has-error': error }">
|
||||
<label class="input-label">
|
||||
{{ label }}
|
||||
<span v-if="required" class="required-mark">*</span>
|
||||
</label>
|
||||
<input
|
||||
:type="type || 'text'"
|
||||
:value="modelValue"
|
||||
class="input-field"
|
||||
:class="{ 'input-error': error }"
|
||||
:placeholder="placeholder"
|
||||
:disabled="disabled"
|
||||
@input="updateValue"
|
||||
>
|
||||
<span v-if="error" class="error-message">
|
||||
<span class="error-icon">⚠</span>
|
||||
{{ error }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.required-mark {
|
||||
color: var(--error);
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.input-error {
|
||||
border-color: var(--error) !important;
|
||||
background-color: rgba(239, 68, 68, 0.05);
|
||||
}
|
||||
|
||||
.input-error:focus {
|
||||
outline-color: var(--error) !important;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--error);
|
||||
font-size: 12px;
|
||||
margin-top: 6px;
|
||||
animation: shake 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.error-icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-4px); }
|
||||
75% { transform: translateX(4px); }
|
||||
}
|
||||
</style>
|
||||
198
Frontend-Learner/components/common/LandingFooter.vue
Normal file
198
Frontend-Learner/components/common/LandingFooter.vue
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
<script setup lang="ts">
|
||||
/**
|
||||
* @file LandingFooter.vue
|
||||
* @description The main footer for the public landing pages.
|
||||
* Contains site links, social media icons, and app download buttons.
|
||||
*/
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer class="landing-footer">
|
||||
<div class="container">
|
||||
<!-- Main Footer Grid -->
|
||||
<div class="footer-grid">
|
||||
|
||||
<!-- Column 1: Brand & Social Media -->
|
||||
<div class="footer-brand">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<div class="logo-box">E</div>
|
||||
<span class="font-bold text-xl" style="color: white;">E-Learning System</span>
|
||||
</div>
|
||||
<p class="text-sm text-slate-400 mb-6">แพลตฟอร์มการเรียนออนไลน์สำหรับทักษแห่งอนาคต</p>
|
||||
<div class="flex gap-4 social-icons">
|
||||
<!-- Placeholder icons -->
|
||||
<a href="#" class="social-icon">f</a>
|
||||
<a href="#" class="social-icon">IG</a>
|
||||
<a href="#" class="social-icon">Y</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Column 2: Services Links -->
|
||||
<div class="footer-column">
|
||||
<h4 class="font-bold mb-4" style="color: white;">บริการทั้งหมด</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="#">คอร์สเรียนออนไลน์</a></li>
|
||||
<li><a href="#">หลักสูตร Onsite</a></li>
|
||||
<li><a href="#">หลักสูตร Class</a></li>
|
||||
<li><a href="#">สำหรับองค์กร</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Column 3: About Links -->
|
||||
<div class="footer-column">
|
||||
<h4 class="font-bold mb-4" style="color: white;">เกี่ยวกับเรา</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="#">แพ็กเกจรายปี</a></li>
|
||||
<li><a href="#">เส้นทางการเรียน</a></li>
|
||||
<li><a href="#">วัดระดับทักษะ <span class="badge-beta">Beta</span></a></li>
|
||||
<li><a href="#">บทความ</a></li>
|
||||
<li><a href="#">คำถามที่พบบ่อย</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Column 4: Join Us Links -->
|
||||
<div class="footer-column">
|
||||
<h4 class="font-bold mb-4" style="color: white;">ร่วมงานกับเรา</h4>
|
||||
<ul class="footer-links">
|
||||
<li><a href="#">สมัครงาน</a></li>
|
||||
<li><a href="#">สมัครเป็น Affiliate</a></li>
|
||||
<li><a href="#">สมัครเป็นผู้สอน</a></li>
|
||||
<li><a href="#">เกี่ยวกับเรา</a></li>
|
||||
<li><a href="#">ติดต่อเรา</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Column 5: App Download & Contact -->
|
||||
<div class="footer-column">
|
||||
<h4 class="font-bold mb-4" style="color: white;">ดาวน์โหลดแอปพลิเคชัน</h4>
|
||||
<div class="flex flex-col gap-2">
|
||||
<button class="app-btn">
|
||||
<span>🍎</span> App Store
|
||||
</button>
|
||||
<button class="app-btn">
|
||||
<span>▶️</span> Google Play
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h4 class="font-bold mt-6 mb-4" style="color: white;">ปรึกษาการเรียน</h4>
|
||||
<button class="btn btn-success w-full">
|
||||
<span>💬</span> เพิ่มเพื่อน
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Bottom: Copyright & Legal -->
|
||||
<div class="footer-bottom">
|
||||
<p class="text-xs text-slate-400">© Copyright 2019-2026 LIKE ME X CO.,LTD All rights reserved.</p>
|
||||
<div class="flex gap-4 text-xs text-slate-400">
|
||||
<a href="#">ข้อตกลงการใช้บริการ</a>
|
||||
<span>|</span>
|
||||
<a href="#">นโยบายความเป็นส่วนตัว</a>
|
||||
<span>|</span>
|
||||
<a href="#">นโยบายการคืนเงิน</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Main Footer Structure */
|
||||
.landing-footer {
|
||||
background-color: #080b14; /* Deep Navy Background */
|
||||
padding: 60px 0 20px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.05); /* Subtle border */
|
||||
margin-top: auto;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
/* 5-Column Grid Layout */
|
||||
.footer-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1fr 1fr 1fr; /* First column is wider for branding */
|
||||
gap: 40px;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.logo-box {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: #eff6ff;
|
||||
color: #3b82f6;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.footer-links li {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.footer-links a {
|
||||
color: #94a3b8;
|
||||
font-size: 14px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.footer-links a:hover {
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.badge-beta {
|
||||
background: #fee2e2;
|
||||
color: #ef4444;
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.app-btn {
|
||||
background: #000;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.footer-bottom {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.05);
|
||||
padding-top: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* Responsive Breakpoints */
|
||||
@media (max-width: 1024px) {
|
||||
.footer-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.footer-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 32px;
|
||||
}
|
||||
.footer-bottom {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
131
Frontend-Learner/components/common/LandingHeader.vue
Normal file
131
Frontend-Learner/components/common/LandingHeader.vue
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
<script setup lang="ts">
|
||||
/**
|
||||
* @file LandingHeader.vue
|
||||
* @description The main header for the public landing pages.
|
||||
* Features a transparent background that becomes solid/glass upon scrolling.
|
||||
*/
|
||||
|
||||
// Track scrolling state to adjust header styling
|
||||
const isScrolled = ref(false)
|
||||
const { isAuthenticated } = useAuth()
|
||||
|
||||
onMounted(() => {
|
||||
// Add scroll listener to toggle 'isScrolled' class
|
||||
window.addEventListener('scroll', () => {
|
||||
isScrolled.value = window.scrollY > 20
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!--
|
||||
Header Container
|
||||
- Transitions between transparent and glass effect based on scroll.
|
||||
-->
|
||||
<header
|
||||
class="landing-header transition-all duration-300"
|
||||
:class="[isScrolled ? 'h-16 glass-nav shadow-lg' : 'h-24 bg-transparent']"
|
||||
>
|
||||
<div class="container h-full flex items-center justify-between">
|
||||
<!--
|
||||
Left Section: Logo & Desktop Navigation
|
||||
-->
|
||||
<div class="flex items-center gap-12">
|
||||
<!-- Logo -->
|
||||
<NuxtLink to="/" class="flex items-center gap-3 group">
|
||||
<div class="logo-box bg-blue-600 text-white font-black rounded-xl w-10 h-10 flex items-center justify-center shadow-lg shadow-blue-600/30 group-hover:scale-110 transition-transform">
|
||||
E
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-black text-lg leading-none tracking-tight text-white group-hover:text-blue-400 transition-colors">E-Learning</span>
|
||||
<span class="text-[10px] font-bold text-slate-500 uppercase tracking-[0.2em] leading-none mt-1">Platform</span>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
|
||||
<!-- Desktop Links -->
|
||||
<nav class="hidden md:block">
|
||||
<ul class="flex items-center gap-8 text-sm font-bold">
|
||||
<li>
|
||||
<NuxtLink to="/browse" class="text-slate-400 hover:text-white transition-colors relative group">
|
||||
คอร์สทั้งหมด
|
||||
<span class="absolute -bottom-1 left-0 w-0 h-0.5 bg-blue-600 transition-all group-hover:w-full"/>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
<NuxtLink to="/browse/discovery" class="text-slate-400 hover:text-white transition-colors relative group">
|
||||
ค้นพบ
|
||||
<span class="absolute -bottom-1 left-0 w-0 h-0.5 bg-blue-600 transition-all group-hover:w-full"/>
|
||||
</NuxtLink>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
Right Section: Action Buttons (Login/Register or Dashboard)
|
||||
-->
|
||||
<div class="flex items-center gap-4">
|
||||
<template v-if="!isAuthenticated">
|
||||
<NuxtLink to="/auth/login" class="text-sm font-bold text-slate-300 hover:text-white px-4 py-2 transition-colors">เข้าสู่ระบบ</NuxtLink>
|
||||
<NuxtLink to="/auth/register" class="btn-primary-premium shadow-lg shadow-blue-600/20">
|
||||
เริ่มต้นใช้งาน
|
||||
</NuxtLink>
|
||||
</template>
|
||||
<template v-else>
|
||||
<NuxtLink to="/dashboard" class="btn-primary-premium shadow-lg shadow-blue-600/20">
|
||||
เข้าสู่หน้าจัดการเรียน
|
||||
</NuxtLink>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Fixed header to stay on top */
|
||||
.landing-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* Glassmorphism Effect for Scrolled Header */
|
||||
.glass-nav {
|
||||
background: rgba(15, 23, 42, 0.8);
|
||||
backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1440px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
/* Premium Primary Button Styling */
|
||||
.btn-primary-premium {
|
||||
background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 800;
|
||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-primary-premium:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 20px -4px rgba(37, 99, 235, 0.5);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 0 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
108
Frontend-Learner/components/common/LoadingSkeleton.vue
Normal file
108
Frontend-Learner/components/common/LoadingSkeleton.vue
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
type?: 'text' | 'avatar' | 'card' | 'button'
|
||||
width?: string
|
||||
height?: string
|
||||
count?: number
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="skeleton-wrapper">
|
||||
<template v-if="type === 'card'">
|
||||
<div v-for="i in (count || 1)" :key="i" class="skeleton-card">
|
||||
<div class="skeleton skeleton-image"/>
|
||||
<div class="skeleton-content">
|
||||
<div class="skeleton skeleton-title"/>
|
||||
<div class="skeleton skeleton-text"/>
|
||||
<div class="skeleton skeleton-text short"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="type === 'avatar'">
|
||||
<div class="skeleton skeleton-avatar"/>
|
||||
</template>
|
||||
|
||||
<template v-else-if="type === 'button'">
|
||||
<div class="skeleton skeleton-button" :style="{ width: width || '120px' }"/>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="i in (count || 1)"
|
||||
:key="i"
|
||||
class="skeleton skeleton-text"
|
||||
:style="{ width: width || '100%', height: height || '16px' }"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.skeleton-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.skeleton {
|
||||
background: linear-gradient(90deg, var(--neutral-100) 25%, var(--neutral-200) 50%, var(--neutral-100) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.skeleton-card {
|
||||
background: var(--bg-surface);
|
||||
border-radius: var(--radius-xl);
|
||||
border: 1px solid var(--border-color);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.skeleton-image {
|
||||
height: 160px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.skeleton-content {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.skeleton-title {
|
||||
height: 20px;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.skeleton-text {
|
||||
height: 14px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.skeleton-text.short {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.skeleton-avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.skeleton-button {
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
68
Frontend-Learner/components/common/LoadingSpinner.vue
Normal file
68
Frontend-Learner/components/common/LoadingSpinner.vue
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
text?: string
|
||||
fullPage?: boolean
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="['loading-container', { 'loading-fullpage': fullPage }]">
|
||||
<div :class="['spinner', size || 'md']"/>
|
||||
<span v-if="text" class="loading-text">{{ text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.loading-fullpage {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
border: 3px solid var(--neutral-200);
|
||||
border-top-color: var(--primary);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
.spinner.sm {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.spinner.md {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-width: 3px;
|
||||
}
|
||||
|
||||
.spinner.lg {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-width: 4px;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
29
Frontend-Learner/components/common/MobileNav.vue
Normal file
29
Frontend-Learner/components/common/MobileNav.vue
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<script setup lang="ts">
|
||||
const route = useRoute()
|
||||
|
||||
const navItems = [
|
||||
{ to: '/dashboard', icon: '🏠', label: 'หน้าหลัก' },
|
||||
{ to: '/browse/discovery', icon: '🔍', label: 'รายการคอร์ส' },
|
||||
{ to: '/dashboard/my-courses', icon: '📚', label: 'คอร์สของฉัน' }
|
||||
]
|
||||
|
||||
const isActive = (path: string) => {
|
||||
if (path === '/') return route.path === '/'
|
||||
return route.path.startsWith(path)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="mobile-nav">
|
||||
<NuxtLink
|
||||
v-for="item in navItems"
|
||||
:key="item.to"
|
||||
:to="item.to"
|
||||
class="mobile-nav-item"
|
||||
:class="{ active: isActive(item.to) }"
|
||||
>
|
||||
<span>{{ item.icon }}</span>
|
||||
<span>{{ item.label }}</span>
|
||||
</NuxtLink>
|
||||
</nav>
|
||||
</template>
|
||||
Loading…
Add table
Add a link
Reference in a new issue