feat: Implement initial e-learning platform frontend including landing page, course discovery, dashboard, and foundational UI components with i18n.
This commit is contained in:
parent
5b9cf72046
commit
3a9da1007b
17 changed files with 1631 additions and 1524 deletions
|
|
@ -1,168 +1,116 @@
|
|||
<script setup lang="ts">
|
||||
/**
|
||||
* @file AppHeader.vue
|
||||
* @description The main header for the authenticated application dashboard.
|
||||
* Uses Quasar QToolbar.
|
||||
* @description The main header for the EduLearn application dashboard.
|
||||
*/
|
||||
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
const props = defineProps<{
|
||||
/** Controls visibility of the sidebar toggle button */
|
||||
showSidebarToggle?: boolean;
|
||||
/** Type of navigation links to display */
|
||||
navType?: "public" | "learner";
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
/** Emitted when the hamburger menu is clicked */
|
||||
toggleSidebar: [];
|
||||
/** Emitted when the mobile menu toggle is clicked */
|
||||
toggleRightDrawer: [];
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
// Automatically determine navType based on route if not explicitly passed
|
||||
const navTypeComputed = computed(() => {
|
||||
if (props.navType) return props.navType;
|
||||
// Show learner nav for dashboard, browse, classroom, and course details
|
||||
const learnerRoutes = ["/dashboard", "/browse", "/classroom", "/course"];
|
||||
return learnerRoutes.some((r) => route.path.startsWith(r))
|
||||
? "learner"
|
||||
: "public";
|
||||
});
|
||||
const { currentUser } = useAuth();
|
||||
const { locale, setLocale } = useI18n();
|
||||
const { isDark, set: setTheme } = useThemeMode();
|
||||
|
||||
const toggleLanguage = () => {
|
||||
setLocale(locale.value === 'th' ? 'en' : 'th');
|
||||
};
|
||||
|
||||
const toggleTheme = () => {
|
||||
setTheme(!isDark.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<q-toolbar class="bg-white dark:!bg-[#0f172a] text-slate-900 dark:!text-white h-16 border-none p-0 overflow-visible">
|
||||
<div class="w-full px-4 md:px-12 flex items-center h-full no-wrap relative">
|
||||
<q-toolbar class="bg-white dark:!bg-[#020617] text-slate-900 dark:!text-white h-20 border-b border-slate-50 dark:border-slate-800/50 px-6">
|
||||
|
||||
<!-- Left: Hamburger Toggle -->
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
dense
|
||||
icon="menu"
|
||||
class="text-slate-400 hover:text-blue-600 transition-colors"
|
||||
size="16px"
|
||||
@click="$emit('toggleSidebar')"
|
||||
/>
|
||||
|
||||
<q-space />
|
||||
|
||||
<!-- Right Section -->
|
||||
<div class="flex items-center gap-2 sm:gap-4 md:gap-6 no-wrap">
|
||||
|
||||
<!-- Mobile Sidebar Toggle (For non-learner routes) -->
|
||||
<!-- Theme Toggle -->
|
||||
<q-btn
|
||||
v-if="showSidebarToggle !== false && navTypeComputed !== 'learner' && $q.screen.lt.md"
|
||||
flat
|
||||
round
|
||||
dense
|
||||
icon="menu"
|
||||
class="mr-2 text-gray-500"
|
||||
@click="$emit('toggleSidebar')"
|
||||
/>
|
||||
|
||||
<!-- Branding: Logo + Name -->
|
||||
<div
|
||||
class="flex items-center gap-3 cursor-pointer group flex-shrink-0"
|
||||
@click="navigateTo('/dashboard')"
|
||||
:icon="isDark ? 'dark_mode' : 'light_mode'"
|
||||
:class="isDark ? 'text-blue-400' : 'text-amber-500'"
|
||||
class="transition-all active:scale-90"
|
||||
size="12px"
|
||||
@click="toggleTheme"
|
||||
>
|
||||
<div class="w-10 h-10 rounded-xl bg-blue-600 flex items-center justify-center text-white font-black shadow-lg shadow-blue-600/30 group-hover:scale-110 transition-transform flex-shrink-0">
|
||||
E
|
||||
<q-tooltip>{{ isDark ? 'โหมดกลางคืน' : 'โหมดกลางวัน' }}</q-tooltip>
|
||||
</q-btn>
|
||||
|
||||
<!-- Language Switcher (Pill Style) -->
|
||||
<div
|
||||
@click="toggleLanguage"
|
||||
class="flex items-center bg-slate-50 dark:bg-slate-800/50 border border-slate-100 dark:border-slate-800 rounded-xl p-0.5 sm:p-1 cursor-pointer hover:bg-slate-100 transition-all font-bold text-[11px] sm:text-[13px] select-none"
|
||||
>
|
||||
<div :class="locale === 'th' ? 'bg-white dark:bg-slate-700 shadow-sm text-blue-600' : 'text-slate-400'" class="px-2 sm:px-3 py-1 rounded-lg transition-all">TH</div>
|
||||
<div class="w-[1px] h-3 bg-slate-200 dark:bg-slate-700 mx-0.5"></div>
|
||||
<div :class="locale === 'en' ? 'bg-white dark:bg-slate-700 shadow-sm text-blue-600' : 'text-slate-400'" class="px-2 sm:px-3 py-1 rounded-lg transition-all">EN</div>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
<div class="hidden sm:block w-[1px] h-8 bg-slate-100 dark:bg-slate-800"></div>
|
||||
|
||||
<!-- ส่วนข้อมูลผู้ใช้งาน (User Profile) -->
|
||||
<div class="flex items-center gap-3 cursor-pointer group" @click="navigateTo('/dashboard/profile')">
|
||||
<!-- ชื่อและบทบาท (แสดงเฉพาะบนจอที่ใหญ่กว่า 600px) -->
|
||||
<div class="user-info-text flex flex-col items-end text-right">
|
||||
<span class="text-[15px] font-bold text-slate-900 dark:text-white leading-tight">
|
||||
{{ currentUser?.firstName || 'User' }} {{ currentUser?.lastName || '' }}
|
||||
</span>
|
||||
<span class="text-[11px] text-slate-500 font-medium">{{ $t('common.student') }}</span>
|
||||
</div>
|
||||
<div class="flex flex-col text-left">
|
||||
<span class="font-black text-[15px] md:text-lg leading-none tracking-tight text-slate-900 dark:text-white group-hover:text-blue-600 transition-colors">E-Learning</span>
|
||||
<span class="text-[9px] md:text-[10px] font-bold uppercase tracking-[0.2em] leading-none mt-1 text-slate-500">Platform</span>
|
||||
|
||||
<!-- รูปโปรไฟล์พร้อมวงแหวน Gradient นุ่มนวล -->
|
||||
<div class="relative p-[3px] rounded-full bg-gradient-to-tr from-[#FFD1D1] via-[#E2E8FF] to-[#D1F7FF] dark:from-slate-800 dark:to-slate-700 transition-transform group-hover:scale-105">
|
||||
<div class="bg-white dark:bg-[#020617] p-[1.5px] rounded-full shadow-sm">
|
||||
<UserAvatar
|
||||
:photoURL="currentUser?.photoURL"
|
||||
:firstName="currentUser?.firstName"
|
||||
:lastName="currentUser?.lastName"
|
||||
size="40"
|
||||
class="w-[40px] h-[40px]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
<nav class="header-desktop items-center gap-6 lg:gap-8 text-[14px] font-bold ml-12 flex-shrink-0 h-full">
|
||||
<NuxtLink to="/dashboard" class="nav-link" exact-active-class="active">{{ $t("sidebar.overview") }}</NuxtLink>
|
||||
<NuxtLink to="/browse/discovery" class="nav-link" active-class="active">{{ $t("landing.allCourses") }}</NuxtLink>
|
||||
<NuxtLink to="/dashboard/my-courses" class="nav-link" exact-active-class="active">{{ $t("sidebar.myCourses") || 'คอร์สเรียนของฉัน' }}</NuxtLink>
|
||||
</nav>
|
||||
|
||||
<q-space />
|
||||
|
||||
<!-- Right Section: Tools -->
|
||||
<div class="flex items-center gap-2 flex-shrink-0 no-wrap">
|
||||
|
||||
<!-- Desktop Only Tools -->
|
||||
<div class="header-desktop items-center gap-4 flex-shrink-0">
|
||||
<LanguageSwitcher />
|
||||
<UserMenu />
|
||||
</div>
|
||||
|
||||
<!-- Mobile/Tablet Tools Hidden (Moved to Drawer) -->
|
||||
<!-- Just keep space between logo and hamburger -->
|
||||
|
||||
<!-- Mobile/Tablet Hamburger -->
|
||||
<q-btn
|
||||
flat
|
||||
round
|
||||
dense
|
||||
icon="menu"
|
||||
class="header-mobile text-slate-700 dark:text-white bg-slate-100 dark:bg-slate-800 flex-shrink-0"
|
||||
style="width: 40px; height: 40px; min-width: 40px;"
|
||||
@click="$emit('toggleRightDrawer')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-toolbar>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* High Priority Visibility Logic */
|
||||
@media (max-width: 1023px) {
|
||||
.header-desktop {
|
||||
/* Ensure toolbar height is consistent */
|
||||
:deep(.q-toolbar) {
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
/* Hide user name only on small mobile screens */
|
||||
@media (max-width: 600px) {
|
||||
.user-info-text {
|
||||
display: none !important;
|
||||
}
|
||||
.header-mobile {
|
||||
display: flex !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.header-mobile {
|
||||
display: none !important;
|
||||
}
|
||||
.header-desktop {
|
||||
display: flex !important;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: #64748b; /* slate-500 */
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: #2563eb; /* blue-600 */
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
color: #2563eb; /* blue-600 */
|
||||
}
|
||||
|
||||
.nav-link::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: 0;
|
||||
height: 3px;
|
||||
background-color: #2563eb;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
transition: all 0.3s ease;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.nav-link.active::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.router-link-active {
|
||||
color: #2563eb !important;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue