feat: Reorganize platform into member, board, and admin sections
Some checks failed
Build And Push Image / docker (push) Failing after 55s
Some checks failed
Build And Push Image / docker (push) Failing after 55s
Major platform reorganization implementing role-based portal sections: ## Infrastructure Changes - Created role-based middleware for member, board, and admin access - Updated main dashboard router to redirect based on highest privilege - Implemented access hierarchy: Admin > Board > Member ## New Layouts - Member layout: Simplified navigation for regular members - Board layout: Enhanced tools for board member management - Admin layout: Full system administration capabilities ## Member Portal (/member/*) - Dashboard: Profile overview, events, payments, activity tracking - Events: Browse, register, and manage event participation - Profile: Complete personal and professional information management - Resources: Access to documents, guides, FAQs, and quick links ## Board Portal (/board/*) - Dashboard: Statistics, dues management, board-specific tools - Members: Comprehensive member management with filtering ## Admin Portal (/admin/*) - Dashboard: System overview and administrative controls (existing) ## Design Implementation - Monaco red (#dc2626) as primary accent color - Modern card-based layouts with consistent spacing - Responsive design for all screen sizes - Glass morphism effects for enhanced visual appeal This reorganization provides clear separation of concerns based on user privileges while maintaining a cohesive user experience across all sections. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
511
layouts/admin.vue
Normal file
511
layouts/admin.vue
Normal file
@@ -0,0 +1,511 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-navigation-drawer v-model="drawer" app width="300">
|
||||
<!-- Logo Section -->
|
||||
<v-list-item class="pa-4 text-center">
|
||||
<v-img
|
||||
src="/MONACOUSA-Flags_376x376.png"
|
||||
width="80"
|
||||
height="80"
|
||||
class="mx-auto mb-2"
|
||||
/>
|
||||
<div class="text-h6 font-weight-bold" style="color: #dc2626;">
|
||||
MonacoUSA Portal
|
||||
</div>
|
||||
<div class="text-caption text-error font-weight-bold">ADMINISTRATOR</div>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<!-- Navigation Menu -->
|
||||
<v-list nav density="comfortable">
|
||||
<!-- Admin Overview -->
|
||||
<v-list-item
|
||||
to="/admin/dashboard"
|
||||
prepend-icon="mdi-view-dashboard"
|
||||
title="Admin Dashboard"
|
||||
value="dashboard"
|
||||
/>
|
||||
|
||||
<!-- User Management -->
|
||||
<v-list-group value="users">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
prepend-icon="mdi-account-cog"
|
||||
title="User Management"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
to="/admin/users"
|
||||
title="All Users"
|
||||
value="users-list"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/admin/users/roles"
|
||||
title="Roles & Permissions"
|
||||
value="roles"
|
||||
/>
|
||||
<v-list-item
|
||||
@click="openKeycloak"
|
||||
title="Keycloak Admin"
|
||||
value="keycloak"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<v-icon size="small">mdi-open-in-new</v-icon>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list-group>
|
||||
|
||||
<!-- Member Management -->
|
||||
<v-list-group value="members">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
prepend-icon="mdi-account-group"
|
||||
title="Member Management"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
to="/admin/members"
|
||||
title="All Members"
|
||||
value="members-list"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/admin/members/import"
|
||||
title="Import Members"
|
||||
value="import"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/admin/members/export"
|
||||
title="Export Data"
|
||||
value="export"
|
||||
/>
|
||||
</v-list-group>
|
||||
|
||||
<!-- Financial Management -->
|
||||
<v-list-group value="financial">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
prepend-icon="mdi-currency-usd"
|
||||
title="Financial"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
to="/admin/payments"
|
||||
title="Payment Management"
|
||||
value="payments"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/admin/payments/stripe"
|
||||
title="Stripe Dashboard"
|
||||
value="stripe"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/admin/payments/reports"
|
||||
title="Financial Reports"
|
||||
value="financial-reports"
|
||||
/>
|
||||
</v-list-group>
|
||||
|
||||
<!-- System Configuration -->
|
||||
<v-list-group value="system">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
prepend-icon="mdi-cog"
|
||||
title="System"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
to="/admin/settings"
|
||||
title="General Settings"
|
||||
value="settings"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/admin/settings/email"
|
||||
title="Email Configuration"
|
||||
value="email"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/admin/settings/security"
|
||||
title="Security Settings"
|
||||
value="security"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/admin/logs"
|
||||
title="System Logs"
|
||||
value="logs"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/admin/backup"
|
||||
title="Backup & Restore"
|
||||
value="backup"
|
||||
/>
|
||||
</v-list-group>
|
||||
|
||||
<!-- Events Management -->
|
||||
<v-list-item
|
||||
to="/admin/events"
|
||||
prepend-icon="mdi-calendar"
|
||||
title="Events Management"
|
||||
value="events"
|
||||
/>
|
||||
|
||||
<!-- Analytics -->
|
||||
<v-list-item
|
||||
to="/admin/analytics"
|
||||
prepend-icon="mdi-chart-line"
|
||||
title="Analytics & Insights"
|
||||
value="analytics"
|
||||
/>
|
||||
|
||||
<v-divider class="my-2" />
|
||||
|
||||
<!-- Portal Access -->
|
||||
<v-list-subheader>Portal Access</v-list-subheader>
|
||||
<v-list-item
|
||||
to="/board/dashboard"
|
||||
prepend-icon="mdi-shield-account"
|
||||
title="Board Portal"
|
||||
value="board-view"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/member/dashboard"
|
||||
prepend-icon="mdi-account"
|
||||
title="Member Portal"
|
||||
value="member-view"
|
||||
/>
|
||||
</v-list>
|
||||
|
||||
<!-- Footer -->
|
||||
<template v-slot:append>
|
||||
<div class="pa-4">
|
||||
<v-card
|
||||
color="error"
|
||||
variant="flat"
|
||||
class="text-center pa-3"
|
||||
>
|
||||
<v-icon size="small" class="mb-1" color="white">mdi-shield-crown</v-icon>
|
||||
<div class="text-caption font-weight-bold text-white">ADMINISTRATOR</div>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-app-bar app elevation="0" flat>
|
||||
<!-- Admin header with special styling -->
|
||||
<template v-slot:extension>
|
||||
<div class="admin-header-gradient"></div>
|
||||
</template>
|
||||
|
||||
<!-- Navigation Toggle -->
|
||||
<v-btn
|
||||
icon
|
||||
@click="drawer = !drawer"
|
||||
class="mr-2"
|
||||
color="white"
|
||||
>
|
||||
<v-icon>mdi-menu</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-toolbar-title class="text-white font-weight-bold d-flex align-center">
|
||||
Admin Portal
|
||||
<v-chip
|
||||
size="x-small"
|
||||
color="error"
|
||||
variant="flat"
|
||||
class="ml-2"
|
||||
>
|
||||
FULL ACCESS
|
||||
</v-chip>
|
||||
</v-toolbar-title>
|
||||
|
||||
<v-spacer />
|
||||
|
||||
<!-- System Status Indicator -->
|
||||
<v-chip
|
||||
:color="systemStatus === 'healthy' ? 'success' : 'warning'"
|
||||
variant="flat"
|
||||
size="small"
|
||||
class="mr-2"
|
||||
>
|
||||
<v-icon start size="small">
|
||||
{{ systemStatus === 'healthy' ? 'mdi-check-circle' : 'mdi-alert' }}
|
||||
</v-icon>
|
||||
System {{ systemStatus }}
|
||||
</v-chip>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<v-btn
|
||||
icon
|
||||
color="white"
|
||||
@click="toggleCommandPalette"
|
||||
>
|
||||
<v-icon>mdi-console</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon color="white">
|
||||
<v-badge
|
||||
:content="alerts"
|
||||
:value="alerts > 0"
|
||||
color="error"
|
||||
>
|
||||
<v-icon>mdi-bell-alert</v-icon>
|
||||
</v-badge>
|
||||
</v-btn>
|
||||
|
||||
<!-- User Menu -->
|
||||
<v-menu offset-y>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn icon v-bind="props" color="white">
|
||||
<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">
|
||||
<v-list-item>
|
||||
<v-list-item-title class="font-weight-bold">
|
||||
{{ user?.name || 'Administrator' }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ user?.email }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item>
|
||||
<v-chip
|
||||
color="error"
|
||||
size="x-small"
|
||||
variant="flat"
|
||||
>
|
||||
ADMINISTRATOR
|
||||
</v-chip>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider class="my-2" />
|
||||
|
||||
<v-list-item to="/admin/profile">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-account</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Admin Profile</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item to="/board/dashboard">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-shield-account</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Board Portal</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item to="/member/dashboard">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-account-switch</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Member Portal</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item to="/admin/settings">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-cog</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>System Settings</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider class="my-2" />
|
||||
|
||||
<v-list-item @click="handleLogout" class="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>
|
||||
|
||||
<!-- Command Palette Dialog -->
|
||||
<v-dialog v-model="commandPaletteOpen" max-width="600">
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center">
|
||||
<v-icon class="mr-2">mdi-console</v-icon>
|
||||
Admin Command Palette
|
||||
<v-spacer />
|
||||
<v-btn icon @click="commandPaletteOpen = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="commandQuery"
|
||||
label="Type a command..."
|
||||
prepend-inner-icon="mdi-chevron-right"
|
||||
variant="outlined"
|
||||
autofocus
|
||||
@keyup.enter="executeCommand"
|
||||
/>
|
||||
<div class="text-caption">
|
||||
Available commands: clear-cache, rebuild-index, sync-users, export-data
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-main>
|
||||
<v-container fluid class="pa-6">
|
||||
<!-- System Alerts Banner -->
|
||||
<v-alert
|
||||
v-if="systemAlerts.length > 0"
|
||||
type="warning"
|
||||
variant="tonal"
|
||||
closable
|
||||
class="mb-4"
|
||||
>
|
||||
<v-alert-title>System Alerts</v-alert-title>
|
||||
<ul class="mt-2">
|
||||
<li v-for="alert in systemAlerts" :key="alert.id">
|
||||
{{ alert.message }}
|
||||
</li>
|
||||
</ul>
|
||||
</v-alert>
|
||||
|
||||
<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 alerts = ref(0);
|
||||
const systemStatus = ref<'healthy' | 'warning' | 'error'>('healthy');
|
||||
const systemAlerts = ref<Array<{ id: number; message: string }>>([]);
|
||||
const commandPaletteOpen = ref(false);
|
||||
const commandQuery = ref('');
|
||||
|
||||
// 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);
|
||||
|
||||
// Load admin-specific data
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// Check system health
|
||||
const healthCheck = await $fetch('/api/admin/system/health');
|
||||
systemStatus.value = healthCheck?.data?.status || 'healthy';
|
||||
|
||||
// Get critical alerts
|
||||
const alertsRes = await $fetch('/api/admin/alerts');
|
||||
alerts.value = alertsRes?.data?.count || 0;
|
||||
systemAlerts.value = alertsRes?.data?.alerts || [];
|
||||
} catch (error) {
|
||||
console.error('Error fetching admin data:', error);
|
||||
systemStatus.value = 'warning';
|
||||
}
|
||||
});
|
||||
|
||||
const openKeycloak = () => {
|
||||
window.open('https://auth.monacousa.org/admin', '_blank');
|
||||
};
|
||||
|
||||
const toggleCommandPalette = () => {
|
||||
commandPaletteOpen.value = true;
|
||||
};
|
||||
|
||||
const executeCommand = async () => {
|
||||
if (commandQuery.value) {
|
||||
console.log('Executing command:', commandQuery.value);
|
||||
// Implement command execution logic
|
||||
commandPaletteOpen.value = false;
|
||||
commandQuery.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
};
|
||||
|
||||
// Responsive drawer behavior
|
||||
const { width } = useDisplay();
|
||||
watch(width, (newWidth) => {
|
||||
drawer.value = newWidth >= 1024;
|
||||
}, { immediate: true });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.admin-header-gradient {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, #991b1b 0%, #450a0a 100%);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.v-navigation-drawer {
|
||||
background: linear-gradient(180deg, #ffffff 0%, #fef2f2 100%);
|
||||
border-right: 2px solid rgba(153, 27, 27, 0.15);
|
||||
}
|
||||
|
||||
.v-list-item {
|
||||
border-radius: 12px;
|
||||
margin: 4px 12px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.v-list-item:hover {
|
||||
background-color: rgba(153, 27, 27, 0.05);
|
||||
}
|
||||
|
||||
.v-list-item--active {
|
||||
background: linear-gradient(135deg, rgba(153, 27, 27, 0.2) 0%, rgba(153, 27, 27, 0.1) 100%) !important;
|
||||
color: #991b1b !important;
|
||||
border-left: 3px solid #991b1b;
|
||||
}
|
||||
|
||||
.v-list-item--active .v-icon {
|
||||
color: #991b1b !important;
|
||||
}
|
||||
|
||||
.v-list-group__items .v-list-item {
|
||||
padding-left: 52px !important;
|
||||
}
|
||||
|
||||
.v-list-subheader {
|
||||
color: #991b1b !important;
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.v-app-bar {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.v-main {
|
||||
background: linear-gradient(180deg, #fafafa 0%, #f5f5f5 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
398
layouts/board.vue
Normal file
398
layouts/board.vue
Normal file
@@ -0,0 +1,398 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-navigation-drawer v-model="drawer" app width="280">
|
||||
<!-- Logo Section -->
|
||||
<v-list-item class="pa-4 text-center">
|
||||
<v-img
|
||||
src="/MONACOUSA-Flags_376x376.png"
|
||||
width="80"
|
||||
height="80"
|
||||
class="mx-auto mb-2"
|
||||
/>
|
||||
<div class="text-h6 font-weight-bold" style="color: #dc2626;">
|
||||
MonacoUSA Portal
|
||||
</div>
|
||||
<div class="text-caption text-medium-emphasis">Board Portal</div>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<!-- Navigation Menu -->
|
||||
<v-list nav density="comfortable">
|
||||
<!-- Board Overview -->
|
||||
<v-list-item
|
||||
to="/board/dashboard"
|
||||
prepend-icon="mdi-view-dashboard"
|
||||
title="Board Dashboard"
|
||||
value="dashboard"
|
||||
/>
|
||||
|
||||
<!-- Member Management -->
|
||||
<v-list-group value="members">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
prepend-icon="mdi-account-group"
|
||||
title="Members"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
to="/board/members"
|
||||
title="Member Directory"
|
||||
value="member-list"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/board/members/dues"
|
||||
title="Dues Management"
|
||||
value="dues"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/board/members/applications"
|
||||
title="Applications"
|
||||
value="applications"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<v-badge
|
||||
:content="pendingApplications"
|
||||
:value="pendingApplications > 0"
|
||||
color="error"
|
||||
/>
|
||||
</template>
|
||||
</v-list-item>
|
||||
</v-list-group>
|
||||
|
||||
<!-- Events & Meetings -->
|
||||
<v-list-group value="events">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-list-item
|
||||
v-bind="props"
|
||||
prepend-icon="mdi-calendar"
|
||||
title="Events & Meetings"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<v-list-item
|
||||
to="/board/events"
|
||||
title="All Events"
|
||||
value="events"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/board/meetings"
|
||||
title="Board Meetings"
|
||||
value="meetings"
|
||||
/>
|
||||
<v-list-item
|
||||
to="/board/meetings/minutes"
|
||||
title="Meeting Minutes"
|
||||
value="minutes"
|
||||
/>
|
||||
</v-list-group>
|
||||
|
||||
<!-- Reports & Analytics -->
|
||||
<v-list-item
|
||||
to="/board/reports"
|
||||
prepend-icon="mdi-chart-box"
|
||||
title="Reports & Analytics"
|
||||
value="reports"
|
||||
/>
|
||||
|
||||
<!-- Governance -->
|
||||
<v-list-item
|
||||
to="/board/governance"
|
||||
prepend-icon="mdi-gavel"
|
||||
title="Governance"
|
||||
value="governance"
|
||||
/>
|
||||
|
||||
<!-- Communications -->
|
||||
<v-list-item
|
||||
to="/board/communications"
|
||||
prepend-icon="mdi-email-newsletter"
|
||||
title="Communications"
|
||||
value="communications"
|
||||
/>
|
||||
|
||||
<v-divider class="my-2" />
|
||||
|
||||
<!-- Member Section Access -->
|
||||
<v-list-subheader>Member Portal</v-list-subheader>
|
||||
<v-list-item
|
||||
to="/member/dashboard"
|
||||
prepend-icon="mdi-account"
|
||||
title="Member View"
|
||||
value="member-view"
|
||||
/>
|
||||
</v-list>
|
||||
|
||||
<!-- Footer -->
|
||||
<template v-slot:append>
|
||||
<div class="pa-4">
|
||||
<v-card
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
class="text-center pa-3"
|
||||
>
|
||||
<v-icon size="small" class="mb-1">mdi-shield-account</v-icon>
|
||||
<div class="text-caption font-weight-bold">BOARD ACCESS</div>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-app-bar app elevation="0" flat>
|
||||
<!-- Custom gradient background -->
|
||||
<template v-slot:extension>
|
||||
<div class="board-header-gradient"></div>
|
||||
</template>
|
||||
|
||||
<!-- Navigation Toggle -->
|
||||
<v-btn
|
||||
icon
|
||||
@click="drawer = !drawer"
|
||||
class="mr-2"
|
||||
color="white"
|
||||
>
|
||||
<v-icon>mdi-menu</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-toolbar-title class="text-white font-weight-bold">
|
||||
Board Portal
|
||||
</v-toolbar-title>
|
||||
|
||||
<v-spacer />
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<v-btn
|
||||
icon
|
||||
color="white"
|
||||
@click="toggleSearch"
|
||||
>
|
||||
<v-icon>mdi-magnify</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon color="white">
|
||||
<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" color="white">
|
||||
<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">
|
||||
<v-list-item>
|
||||
<v-list-item-title class="font-weight-bold">
|
||||
{{ user?.name || 'Board Member' }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ user?.email }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item>
|
||||
<v-chip
|
||||
color="primary"
|
||||
size="x-small"
|
||||
variant="flat"
|
||||
>
|
||||
BOARD MEMBER
|
||||
</v-chip>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider class="my-2" />
|
||||
|
||||
<v-list-item to="/board/profile">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-account</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Board Profile</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item to="/member/dashboard">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-account-switch</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Member Portal</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item to="/board/settings">
|
||||
<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" />
|
||||
|
||||
<v-list-item @click="handleLogout" class="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>
|
||||
|
||||
<!-- Search Overlay -->
|
||||
<v-dialog v-model="searchOpen" max-width="600" persistent>
|
||||
<v-card>
|
||||
<v-card-title class="d-flex align-center">
|
||||
<v-icon class="mr-2">mdi-magnify</v-icon>
|
||||
Search Members
|
||||
<v-spacer />
|
||||
<v-btn icon @click="searchOpen = false">
|
||||
<v-icon>mdi-close</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="searchQuery"
|
||||
label="Search by name, email, or member ID"
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
variant="outlined"
|
||||
autofocus
|
||||
@keyup.enter="performSearch"
|
||||
/>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<v-main>
|
||||
<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 notifications = ref(0);
|
||||
const pendingApplications = ref(0);
|
||||
const searchOpen = ref(false);
|
||||
const searchQuery = ref('');
|
||||
|
||||
// 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);
|
||||
|
||||
// Load board-specific notifications
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const [notificationsRes, applicationsRes] = await Promise.all([
|
||||
$fetch('/api/board/notifications/count'),
|
||||
$fetch('/api/board/applications/pending/count')
|
||||
]);
|
||||
|
||||
notifications.value = notificationsRes?.data?.count || 0;
|
||||
pendingApplications.value = applicationsRes?.data?.count || 0;
|
||||
} catch (error) {
|
||||
console.error('Error fetching board data:', error);
|
||||
}
|
||||
});
|
||||
|
||||
const toggleSearch = () => {
|
||||
searchOpen.value = true;
|
||||
};
|
||||
|
||||
const performSearch = () => {
|
||||
if (searchQuery.value) {
|
||||
navigateTo(`/board/members?search=${encodeURIComponent(searchQuery.value)}`);
|
||||
searchOpen.value = false;
|
||||
searchQuery.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = async () => {
|
||||
await logout();
|
||||
};
|
||||
|
||||
// Responsive drawer behavior
|
||||
const { width } = useDisplay();
|
||||
watch(width, (newWidth) => {
|
||||
drawer.value = newWidth >= 1024;
|
||||
}, { immediate: true });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.board-header-gradient {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, #dc2626 0%, #7c2d12 100%);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.v-navigation-drawer {
|
||||
background: linear-gradient(180deg, #ffffff 0%, #fef2f2 100%);
|
||||
border-right: 1px solid rgba(220, 38, 38, 0.1);
|
||||
}
|
||||
|
||||
.v-list-item {
|
||||
border-radius: 12px;
|
||||
margin: 4px 12px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.v-list-item:hover {
|
||||
background-color: rgba(220, 38, 38, 0.04);
|
||||
}
|
||||
|
||||
.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;
|
||||
border-left: 3px solid #dc2626;
|
||||
}
|
||||
|
||||
.v-list-item--active .v-icon {
|
||||
color: #dc2626 !important;
|
||||
}
|
||||
|
||||
.v-list-group__items .v-list-item {
|
||||
padding-left: 52px !important;
|
||||
}
|
||||
|
||||
.v-list-subheader {
|
||||
color: #dc2626 !important;
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.v-app-bar {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.v-main {
|
||||
background: linear-gradient(180deg, #fafafa 0%, #f5f5f5 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
253
layouts/member.vue
Normal file
253
layouts/member.vue
Normal file
@@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-navigation-drawer v-model="drawer" app width="280">
|
||||
<!-- Logo Section -->
|
||||
<v-list-item class="pa-4 text-center">
|
||||
<v-img
|
||||
src="/MONACOUSA-Flags_376x376.png"
|
||||
width="80"
|
||||
height="80"
|
||||
class="mx-auto mb-2"
|
||||
/>
|
||||
<div class="text-h6 font-weight-bold" style="color: #dc2626;">
|
||||
MonacoUSA Portal
|
||||
</div>
|
||||
<div class="text-caption text-medium-emphasis">Member Portal</div>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<!-- Navigation Menu -->
|
||||
<v-list nav>
|
||||
<v-list-item
|
||||
to="/member/dashboard"
|
||||
prepend-icon="mdi-view-dashboard"
|
||||
title="Dashboard"
|
||||
value="dashboard"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
to="/member/profile"
|
||||
prepend-icon="mdi-account"
|
||||
title="My Profile"
|
||||
value="profile"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
to="/member/events"
|
||||
prepend-icon="mdi-calendar"
|
||||
title="Events"
|
||||
value="events"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
to="/member/payments"
|
||||
prepend-icon="mdi-credit-card"
|
||||
title="Payments & Dues"
|
||||
value="payments"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
to="/member/resources"
|
||||
prepend-icon="mdi-book-open-variant"
|
||||
title="Resources"
|
||||
value="resources"
|
||||
/>
|
||||
|
||||
<v-list-item
|
||||
to="/member/directory"
|
||||
prepend-icon="mdi-contacts"
|
||||
title="Member Directory"
|
||||
value="directory"
|
||||
/>
|
||||
</v-list>
|
||||
|
||||
<!-- Footer -->
|
||||
<template v-slot:append>
|
||||
<div class="pa-4">
|
||||
<v-card
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
class="text-center pa-3"
|
||||
>
|
||||
<v-icon size="small" class="mb-1">mdi-account</v-icon>
|
||||
<div class="text-caption font-weight-bold">MEMBER ACCESS</div>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-app-bar app color="primary" elevation="0" flat>
|
||||
<!-- Navigation Toggle -->
|
||||
<v-btn
|
||||
icon
|
||||
@click="drawer = !drawer"
|
||||
class="mr-2"
|
||||
>
|
||||
<v-icon>mdi-menu</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-toolbar-title class="text-white font-weight-bold">
|
||||
Member Portal
|
||||
</v-toolbar-title>
|
||||
|
||||
<v-spacer />
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<v-btn icon color="white">
|
||||
<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" color="white">
|
||||
<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">
|
||||
<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
|
||||
color="info"
|
||||
size="x-small"
|
||||
variant="flat"
|
||||
>
|
||||
MEMBER
|
||||
</v-chip>
|
||||
</v-list-item>
|
||||
|
||||
<v-divider class="my-2" />
|
||||
|
||||
<v-list-item to="/member/profile">
|
||||
<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">
|
||||
<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" />
|
||||
|
||||
<v-list-item @click="handleLogout" class="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>
|
||||
<!-- Dues Payment Banner for Members -->
|
||||
<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 notifications = ref(0);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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 handleLogout = async () => {
|
||||
await logout();
|
||||
};
|
||||
|
||||
// Responsive drawer behavior
|
||||
const { width } = useDisplay();
|
||||
watch(width, (newWidth) => {
|
||||
drawer.value = newWidth >= 1024;
|
||||
}, { immediate: true });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-navigation-drawer {
|
||||
background: linear-gradient(180deg, #ffffff 0%, #fafafa 100%);
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.v-list-item {
|
||||
border-radius: 12px;
|
||||
margin: 4px 12px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.v-list-item:hover {
|
||||
background-color: rgba(220, 38, 38, 0.04);
|
||||
}
|
||||
|
||||
.v-list-item--active {
|
||||
background: linear-gradient(135deg, rgba(220, 38, 38, 0.1) 0%, rgba(220, 38, 38, 0.05) 100%) !important;
|
||||
color: #dc2626 !important;
|
||||
border-left: 3px solid #dc2626;
|
||||
}
|
||||
|
||||
.v-list-item--active .v-icon {
|
||||
color: #dc2626 !important;
|
||||
}
|
||||
|
||||
.v-app-bar {
|
||||
background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%) !important;
|
||||
box-shadow: 0 2px 8px rgba(220, 38, 38, 0.15) !important;
|
||||
}
|
||||
|
||||
.v-main {
|
||||
background: linear-gradient(180deg, #fafafa 0%, #f5f5f5 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user