feat: Implement initial application layouts, global navigation, and course browsing pages with i18n support.
All checks were successful
Build and Deploy Frontend Learner / Build Frontend Learner Docker Image (push) Successful in 41s
Build and Deploy Frontend Learner / Deploy E-learning Frontend Learner to Dev Server (push) Successful in 4s
Build and Deploy Frontend Learner / Notify Deployment Status (push) Successful in 1s

This commit is contained in:
supalerk-ar66 2026-02-18 16:28:29 +07:00
parent b56f604890
commit 3fa236cff5
15 changed files with 993 additions and 392 deletions

View file

@ -41,6 +41,17 @@ const searchText = ref('')
<span class="text-[10px] font-bold uppercase tracking-[0.2em] leading-none mt-1 text-slate-500 dark:text-slate-400">Platform</span>
</div>
</div>
<!-- Desktop Navigation -->
<nav class="hidden md:flex items-center gap-6 ml-8 text-sm font-bold">
<NuxtLink to="/browse" class="text-slate-600 hover:text-blue-600 transition-colors">
{{ $t('sidebar.onlineCourses') }}
</NuxtLink>
<NuxtLink to="/browse/discovery" class="text-slate-600 hover:text-blue-600 transition-colors">
{{ $t('sidebar.recommendedCourses') }}
</NuxtLink>
</nav>
<q-space />

View file

@ -5,28 +5,10 @@
* Uses Quasar QList for structure.
*/
const { sidebarItems } = useNavItems()
const { t } = useI18n()
const navItems = sidebarItems
const navItems = computed(() => [
{
to: "/dashboard",
label: t('sidebar.overview'),
icon: "dashboard", // Using Material Icons names where possible or SVG paths
isSvg: false
},
{
to: "/browse/discovery",
label: t('sidebar.browseCourses'),
icon: "explore",
isSvg: false
},
{
to: "/dashboard/my-courses",
label: t('sidebar.myCourses'),
icon: "school",
isSvg: false
}
]);
const handleNavigate = (path: string) => {
if (import.meta.client) {
@ -55,7 +37,7 @@ const handleNavigate = (path: string) => {
</q-item-section>
<q-item-section>
<q-item-label class="font-bold text-sm">{{ item.label }}</q-item-label>
<q-item-label class="font-bold text-sm">{{ $t(item.labelKey) }}</q-item-label>
</q-item-section>
</q-item>
</q-list>

View file

@ -27,10 +27,8 @@ onMounted(() => {
: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">
<!-- Left Section: Logo & Desktop Navigation -->
<div class="flex items-center gap-8">
<!-- 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">
@ -53,35 +51,28 @@ onMounted(() => {
</NuxtLink>
<!-- Desktop Links -->
<nav class="hidden md:block">
<ul class="flex items-center gap-8 text-sm font-bold">
<li>
<NuxtLink
to="/browse"
class="transition-colors relative group"
:class="[isScrolled ? 'text-slate-400 hover:text-white' : 'text-slate-600 hover:text-blue-600']"
>
{{ $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="transition-colors relative group"
:class="[isScrolled ? 'text-slate-400 hover:text-white' : 'text-slate-600 hover:text-blue-600']"
>
{{ $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 class="flex items-center gap-6 text-sm font-bold">
<NuxtLink
to="/browse"
class="transition-colors relative group"
:class="[isScrolled ? 'text-slate-400 hover:text-white' : 'text-slate-600 hover:text-blue-600']"
>
{{ $t('sidebar.onlineCourses') }}
<span class="absolute -bottom-1 left-0 w-0 h-0.5 bg-blue-600 transition-all group-hover:w-full"/>
</NuxtLink>
<NuxtLink
to="/browse/recommended"
class="transition-colors relative group"
:class="[isScrolled ? 'text-slate-400 hover:text-white' : 'text-slate-600 hover:text-blue-600']"
>
{{ $t('sidebar.recommendedCourses') }}
<span class="absolute -bottom-1 left-0 w-0 h-0.5 bg-blue-600 transition-all group-hover:w-full"/>
</NuxtLink>
</nav>
</div>
<!--
Right Section: Action Buttons (Login/Register or Dashboard)
-->
<!-- Right Section: Action Buttons -->
<div class="flex items-center gap-4">
<template v-if="!isAuthenticated">
<NuxtLink

View file

@ -1,11 +1,6 @@
<script setup lang="ts">
const { t } = useI18n()
const navItems = computed(() => [
{ to: '/dashboard', icon: 'dashboard', label: t('sidebar.overview') },
{ to: '/browse/discovery', icon: 'explore', label: t('sidebar.browseCourses') },
{ to: '/dashboard/my-courses', icon: 'school', label: t('sidebar.myCourses') }
])
const { mobileItems } = useNavItems()
const navItems = mobileItems
const handleNavigate = (path: string) => {
if (import.meta.client) {
@ -27,7 +22,7 @@ const handleNavigate = (path: string) => {
:key="item.to"
@click="handleNavigate(item.to)"
:icon="item.icon"
:label="item.label"
:label="$t(item.labelKey)"
no-caps
class="py-2"
:class="{ 'q-tab--active text-primary': $route.path === item.to }"

View file

@ -29,12 +29,9 @@ const userInitials = computed(() => {
return f + l
})
const menuItems = computed(() => [
{ label: t('userMenu.home'), to: '/dashboard' },
{ label: t('userMenu.courseList'), to: '/browse/discovery' },
{ label: t('userMenu.myCourses'), to: '/dashboard/my-courses' },
{ label: t('userMenu.settings'), to: '/dashboard/profile' }
])
const { userMenuItems } = useNavItems()
const menuItems = userMenuItems
const handleLogout = async () => {
await logout()
@ -63,14 +60,14 @@ const handleLogout = async () => {
<q-list class="py-2">
<q-item
v-for="item in menuItems"
:key="item.label"
:key="item.labelKey"
clickable
v-close-popup
@click="navigateTo(item.to)"
class="hover:bg-slate-100 dark:hover:bg-white/5 transition-colors"
>
<q-item-section>
<q-item-label class="font-bold text-sm text-slate-800 dark:text-slate-100">{{ item.label }}</q-item-label>
<q-item-label class="font-bold text-sm text-slate-800 dark:text-slate-100">{{ $t(item.labelKey) }}</q-item-label>
</q-item-section>
</q-item>