feat: add curriculum sidebar component for course navigation and announcements
This commit is contained in:
parent
ff841c7638
commit
827b016114
5 changed files with 1 additions and 329 deletions
|
|
@ -8,7 +8,7 @@
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modelValue: boolean; // Sidebar open state (v-model)
|
modelValue: boolean; // Sidebar open state (v-model)
|
||||||
courseData: any;
|
courseData: any;
|
||||||
currentLessonId: number;
|
currentLessonId?: number;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
hasUnreadAnnouncements: boolean;
|
hasUnreadAnnouncements: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
/**
|
|
||||||
* @file AppHeader.vue
|
|
||||||
* @description The main header for the authenticated application dashboard.
|
|
||||||
* Uses Quasar QToolbar.
|
|
||||||
*/
|
|
||||||
|
|
||||||
defineProps<{
|
|
||||||
/** Controls visibility of the search bar */
|
|
||||||
showSearch?: boolean
|
|
||||||
}>()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const searchText = ref('')
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<q-toolbar class="bg-transparent text-slate-800 dark:text-white h-16 px-4">
|
|
||||||
<!-- Mobile Menu Toggle -->
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Branding -->
|
|
||||||
<div class="flex items-center gap-2 cursor-pointer" @click="navigateTo('/dashboard')">
|
|
||||||
<div class="w-8 h-8 rounded-lg bg-blue-600 flex items-center justify-center text-white font-bold">
|
|
||||||
E
|
|
||||||
</div>
|
|
||||||
<span class="font-bold text-xl text-blue-600 dark:text-blue-400 hidden xs:block">e-Learning</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<q-space />
|
|
||||||
|
|
||||||
<!-- Center Search (Optional) -->
|
|
||||||
<div v-if="showSearch !== false" class="hidden md:block w-1/3 max-w-md mx-4">
|
|
||||||
<q-input
|
|
||||||
dense
|
|
||||||
outlined
|
|
||||||
rounded
|
|
||||||
v-model="searchText"
|
|
||||||
:placeholder="$t('menu.searchCourses')"
|
|
||||||
class="bg-slate-50 dark:bg-slate-700/50 search-input"
|
|
||||||
bg-color="transparent"
|
|
||||||
>
|
|
||||||
<template v-slot:prepend>
|
|
||||||
<q-icon name="search" class="text-slate-400" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<q-space />
|
|
||||||
|
|
||||||
<!-- Right Actions -->
|
|
||||||
<div class="flex items-center gap-2">
|
|
||||||
<!-- Language Switcher -->
|
|
||||||
<LanguageSwitcher />
|
|
||||||
|
|
||||||
<!-- User Profile Dropdown -->
|
|
||||||
<UserMenu />
|
|
||||||
</div>
|
|
||||||
</q-toolbar>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.search-input :deep(.q-field__control) {
|
|
||||||
border-radius: 9999px; /* Full rounded */
|
|
||||||
}
|
|
||||||
.search-input :deep(.q-field__control:before) {
|
|
||||||
border-color: #e2e8f0; /* slate-200 */
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
/**
|
|
||||||
* @file AppSidebar.vue
|
|
||||||
* @description Sidebar navigation for the authenticated dashboard.
|
|
||||||
* Uses Quasar QList for structure.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const navItems = computed(() => [
|
|
||||||
{
|
|
||||||
to: "/dashboard",
|
|
||||||
label: t('sidebar.overview'),
|
|
||||||
icon: "dashboard", // Using Material Icons names where possible or SVG paths
|
|
||||||
isSvg: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: "/dashboard/my-courses",
|
|
||||||
label: t('sidebar.myCourses'),
|
|
||||||
icon: "school",
|
|
||||||
isSvg: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: "/browse/discovery",
|
|
||||||
label: t('sidebar.browseCourses'),
|
|
||||||
icon: "explore",
|
|
||||||
isSvg: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: "/dashboard/announcements",
|
|
||||||
label: t('sidebar.announcements'),
|
|
||||||
icon: "campaign",
|
|
||||||
isSvg: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: "/dashboard/profile",
|
|
||||||
label: t('sidebar.profile'),
|
|
||||||
icon: "person",
|
|
||||||
isSvg: false
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="flex flex-col h-full bg-transparent">
|
|
||||||
<!-- Branding Area (Optional if not in Header) -->
|
|
||||||
|
|
||||||
<q-list padding class="text-slate-600 dark:text-slate-300 flex-grow">
|
|
||||||
|
|
||||||
|
|
||||||
<q-item
|
|
||||||
v-for="item in navItems"
|
|
||||||
:key="item.to"
|
|
||||||
:to="item.to"
|
|
||||||
clickable
|
|
||||||
v-ripple
|
|
||||||
class="rounded-r-full mr-2 text-slate-700 dark:text-slate-200 hover:bg-slate-100 dark:hover:bg-white/5"
|
|
||||||
>
|
|
||||||
<q-item-section avatar>
|
|
||||||
<q-icon :name="item.icon" />
|
|
||||||
</q-item-section>
|
|
||||||
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{ item.label }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
|
|
||||||
<!-- Footer / Version -->
|
|
||||||
<div class="mt-auto p-4 text-xs text-center opacity-50 pb-8">
|
|
||||||
<p>e-Learning v0.1.0</p>
|
|
||||||
<p>© 2026</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Light Mode Active State */
|
|
||||||
.q-item.q-router-link--active {
|
|
||||||
background: rgb(239 246 255); /* blue-50 */
|
|
||||||
color: rgb(29 78 216); /* blue-700 */
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark Mode Active State */
|
|
||||||
.dark .q-item.q-router-link--active {
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
color: rgb(147 197 253); /* blue-300 */
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,134 +0,0 @@
|
||||||
<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-slate-900 dark:text-white group-hover:text-blue-600 dark: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-600 dark:text-slate-300 hover:text-blue-600 dark:hover:text-white transition-colors relative group">
|
|
||||||
{{ $t('landing.allCourses') }}
|
|
||||||
<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-600 dark:text-slate-300 hover:text-blue-600 dark:hover:text-white transition-colors relative group">
|
|
||||||
{{ $t('landing.discovery') }}
|
|
||||||
<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-600 dark:text-slate-300 hover:text-blue-600 dark:hover:text-white px-6 py-2.5 rounded-xl border border-slate-200 dark:border-white/10 hover:bg-slate-50 dark:hover:bg-white/5 transition-all">{{ $t('auth.login') }}</NuxtLink>
|
|
||||||
<NuxtLink to="/auth/register" class="btn-primary-premium shadow-lg shadow-blue-600/20">
|
|
||||||
{{ $t('auth.getStarted') }}
|
|
||||||
</NuxtLink>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<NuxtLink to="/dashboard" class="btn-primary-premium shadow-lg shadow-blue-600/20">
|
|
||||||
{{ $t('landing.goToDashboard') }}
|
|
||||||
</NuxtLink>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* Header content */
|
|
||||||
.landing-header {
|
|
||||||
width: 100%;
|
|
||||||
z-index: 100;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Glassmorphism Effect for Scrolled Header */
|
|
||||||
.glass-nav {
|
|
||||||
background: rgba(255, 255, 255, 0.85);
|
|
||||||
backdrop-filter: blur(12px);
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.dark) .glass-nav {
|
|
||||||
background: rgba(15, 23, 42, 0.85);
|
|
||||||
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>
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
const navItems = [
|
|
||||||
{ to: '/dashboard', icon: 'dashboard', label: 'หน้าหลัก' },
|
|
||||||
{ to: '/browse/discovery', icon: 'explore', label: 'รายการคอร์ส' },
|
|
||||||
{ to: '/dashboard/my-courses', icon: 'school', label: 'คอร์สของฉัน' }
|
|
||||||
]
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<q-tabs
|
|
||||||
indicator-color="primary"
|
|
||||||
active-color="primary"
|
|
||||||
class="bg-white text-slate-500 shadow-up-1"
|
|
||||||
align="justify"
|
|
||||||
dense
|
|
||||||
>
|
|
||||||
<q-route-tab
|
|
||||||
v-for="item in navItems"
|
|
||||||
:key="item.to"
|
|
||||||
:to="item.to"
|
|
||||||
:icon="item.icon"
|
|
||||||
:label="item.label"
|
|
||||||
no-caps
|
|
||||||
class="py-2"
|
|
||||||
/>
|
|
||||||
</q-tabs>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* Optional shadow for better separation */
|
|
||||||
.shadow-up-1 {
|
|
||||||
box-shadow: 0 -1px 3px rgba(0,0,0,0.05);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue