[Closes #8] Added authentication
All checks were successful
Production Build and Deploy / Build (push) Successful in 1m7s
Production Build and Deploy / Deploy (push) Successful in 29s

This commit is contained in:
Liviu Burcusel 2026-01-07 11:11:35 +01:00
parent 6eefa137bb
commit 6d3cdb560d
Signed by: liviu
GPG key ID: 6CDB37A4AD2C610C
65 changed files with 5834 additions and 440 deletions

View file

@ -1,4 +1,5 @@
<script setup lang="ts">
import { useAuthStore } from "~/stores/auth";
import DefaultSidebar from "~/layouts/default/Sidebar.vue";
import { SidebarInset, SidebarProvider, SidebarTrigger } from "~/components/ui/sidebar";
@ -14,12 +15,19 @@ import {
import { Separator } from "~/components/ui/separator";
import { useRuntimeConfig } from "#app";
const currentYear = new Date().getFullYear();
const config = useRuntimeConfig();
const authStore = useAuthStore();
await authStore.init();
</script>
<template>
<SidebarProvider>
<DefaultSidebar />
<DefaultSidebar :user="authStore.user" />
<SidebarInset>
<header class="flex h-12 shrink-0 items-center gap-2 border-b px-4">
<SidebarTrigger class="-ml-1" />
@ -40,8 +48,12 @@ const currentYear = new Date().getFullYear();
<slot />
</main>
<footer class="flex h-12 shrink-0 items-center gap-2 border-b px-4" data-testid="footer">
<div v-if="currentYear === 2025" class="bg-muted/50 flex-1 rounded-xl p-2 text-center">Glowing Fiesta 2025</div>
<div v-else class="bg-muted/50 flex-1 rounded-xl p-2 text-center">Glowing Fiesta 2025 - {{ currentYear }}</div>
<div v-if="currentYear === 2025" class="bg-muted/50 flex-1 rounded-xl p-2 text-center">
Glowing Fiesta 2025 <span class="text-muted-foreground">({{ config.public.appVersion }})</span>
</div>
<div v-else class="bg-muted/50 flex-1 rounded-xl p-2 text-center">
Glowing Fiesta 2025 - {{ currentYear }} <span class="text-muted-foreground">({{ config.public.appVersion }})</span>
</div>
</footer>
</SidebarInset>
</SidebarProvider>

View file

@ -1,23 +1,10 @@
<script setup lang="ts">
import { computed } from "vue";
import {
BadgeCheck,
Bell,
ChevronRight,
ChevronsUpDown,
CreditCard,
BookOpen,
HandCoins,
LogOut,
Settings2,
Sparkles,
SquareTerminal,
} from "lucide-vue-next";
import { BookOpen, ChevronRight, HandCoins, Settings2, SquareTerminal } from "lucide-vue-next";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarHeader,
SidebarMenu,
@ -27,23 +14,14 @@ import {
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
useSidebar,
type SidebarProps,
} from "~/components/ui/sidebar";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "~/components/ui/dropdown-menu";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible";
import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar";
import AppSidebarFooter from "~/layouts/default/SidebarFooter.vue";
import type { User } from "better-auth";
const data = {
user: {
@ -99,6 +77,7 @@ interface NavItem {
interface SidebarLayoutProps extends /* @vue-ignore */ SidebarProps {
navItems?: NavItem[];
user?: User | null | undefined;
}
const props = withDefaults(defineProps<SidebarLayoutProps>(), {
@ -106,8 +85,6 @@ const props = withDefaults(defineProps<SidebarLayoutProps>(), {
});
const navMain = computed(() => props.navItems || data.navMain);
const { isMobile } = useSidebar();
</script>
<template>
@ -169,76 +146,7 @@ const { isMobile } = useSidebar();
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar class="h-8 w-8 rounded-lg">
<AvatarImage :src="data.user.avatar" :alt="data.user.name" />
<AvatarFallback class="rounded-lg"> LB </AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-medium">{{ data.user.name }}</span>
<span class="truncate text-xs">{{ data.user.email }}</span>
</div>
<ChevronsUpDown class="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-[--reka-dropdown-menu-trigger-width] min-w-56 rounded-lg"
:side="isMobile ? 'bottom' : 'right'"
align="end"
:side-offset="4"
>
<DropdownMenuLabel class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar class="h-8 w-8 rounded-lg">
<AvatarImage :src="data.user.avatar" :alt="data.user.name" />
<AvatarFallback class="rounded-lg">LB</AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ data.user.name }}</span>
<span class="truncate text-xs">{{ data.user.email }}</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<Sparkles />
Upgrade to Pro
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<BadgeCheck />
Account
</DropdownMenuItem>
<DropdownMenuItem>
<CreditCard />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<Bell />
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
<LogOut />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
<AppSidebarFooter :user="user" />
<SidebarRail></SidebarRail>
</Sidebar>

View file

@ -0,0 +1,149 @@
<script setup lang="ts">
import { computed } from "vue";
import { SidebarFooter, SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from "~/components/ui/sidebar";
import { Avatar, AvatarFallback, AvatarImage } from "~/components/ui/avatar";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "~/components/ui/dropdown-menu";
import { BadgeCheck, Bell, ChevronsUpDown, CreditCard, LogIn, LogOut } from "lucide-vue-next";
import type { User } from "better-auth";
const { isMobile } = useSidebar();
const props = defineProps<{ user?: User | null | undefined }>();
const userInititials = computed(() => {
if (!props.user) return "";
return props.user.name
.split(" ")
.map((name) => name.charAt(0).toUpperCase())
.join("");
});
const handleLogout = () => {
navigateTo("/member/auth/logout");
};
const handleLogin = () => {
navigateTo("/member/auth/login");
};
</script>
<template>
<ClientOnly>
<SidebarFooter>
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu v-if="user">
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar class="h-8 w-8 rounded-lg">
<AvatarImage :src="user?.image || ''" :alt="user?.name" />
<AvatarFallback class="rounded-lg">{{ userInititials }}</AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-medium">{{ user?.name }}</span>
<span class="truncate text-xs">{{ user?.email }}</span>
</div>
<ChevronsUpDown class="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-[--reka-dropdown-menu-trigger-width] min-w-56 rounded-lg"
:side="isMobile ? 'bottom' : 'right'"
align="end"
:side-offset="4"
>
<DropdownMenuLabel class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar class="h-8 w-8 rounded-lg">
<AvatarImage :src="user?.image || ''" :alt="user?.name" />
<AvatarFallback class="rounded-lg">{{ userInititials }}</AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">{{ user.name }}</span>
<span class="truncate text-xs">{{ user.email }}</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
<BadgeCheck />
Account
</DropdownMenuItem>
<DropdownMenuItem>
<CreditCard />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<Bell />
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem @click="handleLogout">
<LogOut />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu v-else>
<DropdownMenuTrigger as-child>
<SidebarMenuButton
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar class="h-8 w-8 rounded-lg">
<AvatarImage src="/images/human.png" alt="Anonymous" />
<AvatarFallback class="rounded-lg">Anon</AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-medium">Anonymous</span>
<span class="truncate text-xs">No email</span>
</div>
<ChevronsUpDown class="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
class="w-[--reka-dropdown-menu-trigger-width] min-w-56 rounded-lg"
:side="isMobile ? 'bottom' : 'right'"
align="end"
:side-offset="4"
>
<DropdownMenuLabel class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar class="h-8 w-8 rounded-lg">
<AvatarImage src="/images/human.png" alt="Anonymous" />
<AvatarFallback class="rounded-lg">Anon</AvatarFallback>
</Avatar>
<div class="grid flex-1 text-left text-sm leading-tight">
<span class="truncate font-semibold">Anonymous</span>
<span class="truncate text-xs">No email</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem @click="handleLogin">
<LogIn />
Log in
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
</SidebarFooter>
</ClientOnly>
</template>