626 lines
15 KiB
Vue
626 lines
15 KiB
Vue
<template>
|
|
<v-app>
|
|
<v-navigation-drawer
|
|
v-model="drawer"
|
|
:rail="miniVariant"
|
|
:expand-on-hover="false"
|
|
permanent
|
|
:width="miniVariant ? 56 : 280"
|
|
class="enhanced-glass-drawer"
|
|
>
|
|
<!-- Logo Section with Enhanced Glass Effect -->
|
|
<v-list-item class="logo-section">
|
|
<template v-if="!miniVariant">
|
|
<v-img
|
|
src="/MONACOUSA-Flags_376x376.png"
|
|
width="80"
|
|
height="80"
|
|
class="mx-auto logo-image mb-2"
|
|
/>
|
|
<div class="logo-text">
|
|
<div class="text-h6 font-weight-bold monaco-red-text">
|
|
MonacoUSA Portal
|
|
</div>
|
|
<v-chip
|
|
size="x-small"
|
|
class="monaco-chip-gradient mt-1"
|
|
>
|
|
MEMBER
|
|
</v-chip>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
<v-img
|
|
src="/MONACOUSA-Flags_376x376.png"
|
|
width="40"
|
|
height="40"
|
|
class="mx-auto logo-image"
|
|
/>
|
|
</template>
|
|
</v-list-item>
|
|
|
|
<v-divider class="glass-divider" />
|
|
|
|
<!-- Navigation Menu with Enhanced Effects -->
|
|
<v-list nav class="enhanced-nav-list">
|
|
<template v-for="item in navigationItems" :key="item.value">
|
|
<v-tooltip
|
|
:text="item.title"
|
|
location="right"
|
|
:disabled="!miniVariant"
|
|
>
|
|
<template v-slot:activator="{ props }">
|
|
<v-list-item
|
|
:to="item.to"
|
|
:prepend-icon="item.icon"
|
|
:title="!miniVariant ? item.title : undefined"
|
|
:value="item.value"
|
|
class="nav-item-enhanced"
|
|
v-bind="props"
|
|
>
|
|
<template v-if="item.badge" v-slot:append>
|
|
<v-badge
|
|
:content="item.badge"
|
|
color="error"
|
|
inline
|
|
:dot="miniVariant"
|
|
/>
|
|
</template>
|
|
</v-list-item>
|
|
</template>
|
|
</v-tooltip>
|
|
</template>
|
|
</v-list>
|
|
|
|
<!-- Enhanced Profile Footer -->
|
|
<template v-slot:append>
|
|
<div class="profile-footer">
|
|
<v-divider class="mb-3" />
|
|
<div class="profile-card-footer">
|
|
<div class="profile-content">
|
|
<div class="profile-avatar-wrapper">
|
|
<ProfileAvatar
|
|
v-if="memberData"
|
|
:member-id="memberData?.member_id"
|
|
:first-name="memberData?.first_name || user?.firstName"
|
|
:last-name="memberData?.last_name || user?.lastName"
|
|
size="small"
|
|
:show-badge="false"
|
|
/>
|
|
<div class="online-indicator" />
|
|
</div>
|
|
<div class="profile-info">
|
|
<div class="profile-name">{{ fullName }}</div>
|
|
<div class="profile-email">{{ email }}</div>
|
|
</div>
|
|
<v-menu offset-y>
|
|
<template v-slot:activator="{ props }">
|
|
<v-btn
|
|
icon="mdi-dots-vertical"
|
|
size="x-small"
|
|
variant="text"
|
|
v-bind="props"
|
|
class="profile-menu-btn"
|
|
/>
|
|
</template>
|
|
<v-list class="glass-dropdown">
|
|
<v-list-item
|
|
prepend-icon="mdi-cog"
|
|
title="Settings"
|
|
to="/member/settings"
|
|
/>
|
|
<v-list-item
|
|
prepend-icon="mdi-logout"
|
|
title="Logout"
|
|
@click="handleLogout"
|
|
/>
|
|
</v-list>
|
|
</v-menu>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</v-navigation-drawer>
|
|
|
|
<v-app-bar elevation="0" flat class="glass-app-bar member-bar">
|
|
<v-btn
|
|
icon
|
|
@click="toggleDrawer"
|
|
class="glass-icon-btn mr-2"
|
|
>
|
|
<v-icon>{{ miniVariant ? 'mdi-menu' : 'mdi-menu-open' }}</v-icon>
|
|
</v-btn>
|
|
|
|
<v-toolbar-title class="font-weight-bold text-white">
|
|
Member Portal
|
|
</v-toolbar-title>
|
|
|
|
<v-spacer />
|
|
|
|
<!-- Quick Actions with Glass Effects -->
|
|
<v-btn icon class="glass-icon-btn">
|
|
<v-badge
|
|
:content="notifications"
|
|
:value="notifications > 0"
|
|
color="error"
|
|
>
|
|
<v-icon>mdi-bell</v-icon>
|
|
</v-badge>
|
|
</v-btn>
|
|
|
|
<!-- User Menu -->
|
|
<v-menu offset-y>
|
|
<template v-slot:activator="{ props }">
|
|
<v-btn icon v-bind="props" class="glass-icon-btn">
|
|
<ProfileAvatar
|
|
:member-id="memberData?.member_id"
|
|
:member-name="user?.name"
|
|
:first-name="user?.firstName || memberData?.first_name"
|
|
:last-name="user?.lastName || memberData?.last_name"
|
|
size="small"
|
|
:lazy="false"
|
|
show-border
|
|
/>
|
|
</v-btn>
|
|
</template>
|
|
|
|
<v-list min-width="250" class="glass-dropdown">
|
|
<v-list-item>
|
|
<v-list-item-title class="font-weight-bold">
|
|
{{ user?.name || 'Member' }}
|
|
</v-list-item-title>
|
|
<v-list-item-subtitle>
|
|
{{ user?.email }}
|
|
</v-list-item-subtitle>
|
|
</v-list-item>
|
|
|
|
<v-list-item>
|
|
<v-chip
|
|
size="x-small"
|
|
class="monaco-chip-gradient"
|
|
>
|
|
MEMBER
|
|
</v-chip>
|
|
</v-list-item>
|
|
|
|
<v-divider class="my-2 glass-divider" />
|
|
|
|
<v-list-item to="/member/profile" class="glass-dropdown-item">
|
|
<template v-slot:prepend>
|
|
<v-icon>mdi-account</v-icon>
|
|
</template>
|
|
<v-list-item-title>My Profile</v-list-item-title>
|
|
</v-list-item>
|
|
|
|
<v-list-item to="/member/settings" class="glass-dropdown-item">
|
|
<template v-slot:prepend>
|
|
<v-icon>mdi-cog</v-icon>
|
|
</template>
|
|
<v-list-item-title>Settings</v-list-item-title>
|
|
</v-list-item>
|
|
|
|
<v-divider class="my-2 glass-divider" />
|
|
|
|
<v-list-item @click="handleLogout" class="glass-dropdown-item text-error">
|
|
<template v-slot:prepend>
|
|
<v-icon color="error">mdi-logout</v-icon>
|
|
</template>
|
|
<v-list-item-title>Logout</v-list-item-title>
|
|
</v-list-item>
|
|
</v-list>
|
|
</v-menu>
|
|
</v-app-bar>
|
|
|
|
<v-main class="glass-main">
|
|
<!-- Dues Payment Banner with Glass Effect -->
|
|
<DuesPaymentBanner />
|
|
|
|
<v-container fluid class="pa-6">
|
|
<slot />
|
|
</v-container>
|
|
</v-main>
|
|
</v-app>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { Member } from '~/utils/types';
|
|
|
|
const { user, logout } = useAuth();
|
|
const drawer = ref(true);
|
|
const miniVariant = ref(false);
|
|
const notifications = ref(0);
|
|
|
|
// Navigation items configuration
|
|
const navigationItems = ref([
|
|
{
|
|
to: '/member/dashboard',
|
|
icon: 'mdi-view-dashboard',
|
|
title: 'Dashboard',
|
|
value: 'dashboard'
|
|
},
|
|
{
|
|
to: '/member/profile',
|
|
icon: 'mdi-account',
|
|
title: 'My Profile',
|
|
value: 'profile'
|
|
},
|
|
{
|
|
to: '/member/events',
|
|
icon: 'mdi-calendar',
|
|
title: 'Events',
|
|
value: 'events',
|
|
badge: '3' // Example badge
|
|
},
|
|
{
|
|
to: '/member/payments',
|
|
icon: 'mdi-credit-card',
|
|
title: 'Payments & Dues',
|
|
value: 'payments'
|
|
},
|
|
{
|
|
to: '/member/resources',
|
|
icon: 'mdi-book-open-variant',
|
|
title: 'Resources',
|
|
value: 'resources'
|
|
}
|
|
]);
|
|
|
|
// Fetch member data
|
|
const { data: sessionData } = await useFetch<{ success: boolean; member: Member | null }>('/api/auth/session', {
|
|
server: false
|
|
});
|
|
|
|
const memberData = computed<Member | null>(() => sessionData.value?.member || null);
|
|
|
|
// Computed properties for profile
|
|
const fullName = computed(() => {
|
|
if (memberData.value) {
|
|
return `${memberData.value.first_name} ${memberData.value.last_name}`;
|
|
}
|
|
return user.value?.name || 'Member';
|
|
});
|
|
|
|
const email = computed(() => memberData.value?.email || user.value?.email || '');
|
|
|
|
// Check for notifications
|
|
onMounted(async () => {
|
|
// Check for upcoming events, dues reminders, etc.
|
|
try {
|
|
const { data } = await $fetch('/api/member/notifications/count');
|
|
notifications.value = data?.count || 0;
|
|
} catch (error) {
|
|
console.error('Error fetching notifications:', error);
|
|
}
|
|
});
|
|
|
|
const toggleDrawer = () => {
|
|
miniVariant.value = !miniVariant.value;
|
|
};
|
|
|
|
const handleLogout = async () => {
|
|
await logout();
|
|
};
|
|
|
|
// Responsive drawer behavior
|
|
const { width } = useDisplay();
|
|
watch(width, (newWidth) => {
|
|
drawer.value = newWidth >= 1024;
|
|
}, { immediate: true });
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
@import '~/assets/scss/main.scss';
|
|
|
|
// Enhanced Glass Drawer Styles
|
|
.enhanced-glass-drawer {
|
|
@include enhanced-glass(0.95, 30px);
|
|
border-right: 1px solid rgba(255, 255, 255, 0.2) !important;
|
|
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.logo-section {
|
|
position: relative;
|
|
padding: 1.5rem;
|
|
background: linear-gradient(135deg,
|
|
rgba(255, 255, 255, 0.95) 0%,
|
|
rgba(248, 248, 248, 0.9) 100%);
|
|
border-radius: 16px;
|
|
margin: 0.5rem;
|
|
margin-bottom: 1rem;
|
|
transition: all 0.3s ease;
|
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
|
|
&--collapsed {
|
|
padding: 0.75rem;
|
|
|
|
.logo-image {
|
|
margin: 0 auto;
|
|
}
|
|
}
|
|
|
|
.logo-image {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.logo-text {
|
|
text-align: center;
|
|
}
|
|
|
|
.collapse-btn {
|
|
position: absolute;
|
|
right: -0.5rem;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
background: white;
|
|
border: 1px solid rgba(220, 38, 38, 0.2);
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
&:hover {
|
|
background: rgba(220, 38, 38, 0.05);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Monaco Text Colors
|
|
.monaco-red-text {
|
|
color: #dc2626 !important;
|
|
}
|
|
|
|
// Enhanced Navigation Items
|
|
.enhanced-nav-list {
|
|
background: transparent !important;
|
|
padding: 0.5rem;
|
|
}
|
|
|
|
.nav-item-enhanced {
|
|
border-radius: 12px !important;
|
|
margin: 4px 8px !important;
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
|
|
position: relative;
|
|
overflow: hidden;
|
|
@include ripple-effect();
|
|
|
|
&:hover {
|
|
background: linear-gradient(135deg,
|
|
rgba(220, 38, 38, 0.08) 0%,
|
|
rgba(220, 38, 38, 0.04) 100%) !important;
|
|
transform: translateX(4px);
|
|
|
|
&::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: linear-gradient(90deg,
|
|
transparent 0%,
|
|
rgba(220, 38, 38, 0.05) 50%,
|
|
transparent 100%);
|
|
animation: shimmer 1s ease-in-out;
|
|
}
|
|
}
|
|
|
|
&.v-list-item--active {
|
|
background: linear-gradient(135deg,
|
|
rgba(220, 38, 38, 0.15) 0%,
|
|
rgba(220, 38, 38, 0.08) 100%) !important;
|
|
color: #dc2626 !important;
|
|
@include sliding-indicator();
|
|
|
|
.v-icon {
|
|
color: #dc2626 !important;
|
|
animation: pulse 2s ease-in-out infinite;
|
|
}
|
|
}
|
|
|
|
.v-list-item__prepend {
|
|
.v-icon {
|
|
transition: all 0.3s ease;
|
|
}
|
|
}
|
|
}
|
|
|
|
@keyframes shimmer {
|
|
0% { transform: translateX(-100%); }
|
|
100% { transform: translateX(100%); }
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { transform: scale(1); }
|
|
50% { transform: scale(1.1); }
|
|
}
|
|
|
|
// Glass Divider
|
|
.glass-divider {
|
|
opacity: 0.2;
|
|
border-color: rgba(220, 38, 38, 0.2);
|
|
}
|
|
|
|
// Member App Bar with Gradient
|
|
.member-bar {
|
|
background: linear-gradient(135deg,
|
|
rgba(239, 68, 68, 0.9) 0%,
|
|
rgba(220, 38, 38, 0.9) 100%) !important;
|
|
backdrop-filter: blur(20px);
|
|
-webkit-backdrop-filter: blur(20px);
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
// Glass Icon Buttons
|
|
.glass-icon-btn {
|
|
background: rgba(255, 255, 255, 0.1) !important;
|
|
backdrop-filter: blur(10px);
|
|
color: white !important;
|
|
transition: all 0.3s ease !important;
|
|
|
|
&:hover {
|
|
background: rgba(255, 255, 255, 0.2) !important;
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
}
|
|
|
|
// Monaco Chip
|
|
.monaco-chip-gradient {
|
|
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%) !important;
|
|
color: white !important;
|
|
border: none;
|
|
box-shadow: 0 2px 8px rgba(220, 38, 38, 0.25);
|
|
}
|
|
|
|
// Glass Dropdown
|
|
.glass-dropdown {
|
|
@include glass-effect(0.95, 20px);
|
|
border-radius: 12px !important;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.glass-dropdown-item {
|
|
transition: all 0.2s ease !important;
|
|
|
|
&:hover {
|
|
background: rgba(220, 38, 38, 0.05) !important;
|
|
}
|
|
}
|
|
|
|
// Glass Main Background
|
|
.glass-main {
|
|
background: linear-gradient(180deg,
|
|
rgba(250, 250, 250, 0.9) 0%,
|
|
rgba(245, 245, 245, 0.9) 100%);
|
|
min-height: 100vh;
|
|
position: relative;
|
|
|
|
&::before {
|
|
content: '';
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-image:
|
|
radial-gradient(circle at 20% 50%, rgba(220, 38, 38, 0.03) 0%, transparent 50%),
|
|
radial-gradient(circle at 80% 80%, rgba(220, 38, 38, 0.03) 0%, transparent 50%),
|
|
radial-gradient(circle at 40% 20%, rgba(220, 38, 38, 0.03) 0%, transparent 50%);
|
|
pointer-events: none;
|
|
z-index: 0;
|
|
}
|
|
}
|
|
|
|
// Profile Footer Styles
|
|
.profile-footer {
|
|
padding: 0.5rem;
|
|
}
|
|
|
|
.profile-card-footer {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 1rem;
|
|
background: linear-gradient(135deg,
|
|
rgba(255, 255, 255, 0.9),
|
|
rgba(255, 255, 255, 0.7)
|
|
);
|
|
border-radius: 12px;
|
|
margin: 0.5rem;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
transition: all 0.3s ease;
|
|
position: relative;
|
|
|
|
&:hover {
|
|
background: linear-gradient(135deg,
|
|
rgba(255, 255, 255, 0.95),
|
|
rgba(255, 255, 255, 0.85)
|
|
);
|
|
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
.profile-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
width: 100%;
|
|
position: relative;
|
|
|
|
.profile-menu-btn {
|
|
position: absolute;
|
|
top: -0.5rem;
|
|
right: -0.5rem;
|
|
opacity: 0.6;
|
|
transition: opacity 0.2s ease;
|
|
|
|
&:hover {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.profile-avatar-wrapper {
|
|
position: relative;
|
|
flex-shrink: 0;
|
|
|
|
.online-indicator {
|
|
position: absolute;
|
|
bottom: -2px;
|
|
right: -2px;
|
|
width: 10px;
|
|
height: 10px;
|
|
background: #22c55e;
|
|
border: 2px solid white;
|
|
border-radius: 50%;
|
|
animation: pulse-online 2s ease-in-out infinite;
|
|
}
|
|
}
|
|
|
|
.profile-info {
|
|
text-align: center;
|
|
width: 100%;
|
|
|
|
.profile-name {
|
|
font-size: 0.925rem;
|
|
font-weight: 600;
|
|
color: rgb(31, 41, 55);
|
|
margin-bottom: 0.25rem;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.profile-email {
|
|
font-size: 0.8rem;
|
|
color: rgb(107, 114, 128);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
|
|
@keyframes pulse-online {
|
|
0%, 100% {
|
|
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.4);
|
|
}
|
|
50% {
|
|
box-shadow: 0 0 0 8px rgba(34, 197, 94, 0);
|
|
}
|
|
}
|
|
|
|
// Fade transition
|
|
.fade-enter-active,
|
|
.fade-leave-active {
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.fade-enter-from,
|
|
.fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
|
|
// Responsive adjustments
|
|
@media (max-width: 1024px) {
|
|
.nav-item-enhanced {
|
|
margin: 2px 8px !important;
|
|
}
|
|
}
|
|
</style> |