From d9d8627e97a1888078c8e2d16889551f4663997d Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 30 Aug 2025 22:00:59 +0200 Subject: [PATCH] feat: Reorganize platform into member, board, and admin sections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- layouts/admin.vue | 511 ++++++++++++++++++ layouts/board.vue | 398 ++++++++++++++ layouts/member.vue | 253 +++++++++ middleware/board.ts | 15 + middleware/member.ts | 15 + pages/admin/dashboard/index.vue | 889 +++++++++++++++++++++++++++++++ pages/board/dashboard/index.vue | 350 ++++++++++++ pages/board/members/index.vue | 602 +++++++++++++++++++++ pages/dashboard/index.vue | 20 +- pages/member/dashboard/index.vue | 473 ++++++++++++++++ pages/member/events/index.vue | 552 +++++++++++++++++++ pages/member/profile/index.vue | 640 ++++++++++++++++++++++ pages/member/resources/index.vue | 506 ++++++++++++++++++ pages/members/mockup.vue | 555 ++++++++++++------- pages/profile/mockup.vue | 709 +++++++++++++++--------- 15 files changed, 6039 insertions(+), 449 deletions(-) create mode 100644 layouts/admin.vue create mode 100644 layouts/board.vue create mode 100644 layouts/member.vue create mode 100644 middleware/board.ts create mode 100644 middleware/member.ts create mode 100644 pages/admin/dashboard/index.vue create mode 100644 pages/board/dashboard/index.vue create mode 100644 pages/board/members/index.vue create mode 100644 pages/member/dashboard/index.vue create mode 100644 pages/member/events/index.vue create mode 100644 pages/member/profile/index.vue create mode 100644 pages/member/resources/index.vue diff --git a/layouts/admin.vue b/layouts/admin.vue new file mode 100644 index 0000000..5492b7c --- /dev/null +++ b/layouts/admin.vue @@ -0,0 +1,511 @@ + + + + + \ No newline at end of file diff --git a/layouts/board.vue b/layouts/board.vue new file mode 100644 index 0000000..448e845 --- /dev/null +++ b/layouts/board.vue @@ -0,0 +1,398 @@ + + + + + \ No newline at end of file diff --git a/layouts/member.vue b/layouts/member.vue new file mode 100644 index 0000000..f85ed46 --- /dev/null +++ b/layouts/member.vue @@ -0,0 +1,253 @@ + + + + + \ No newline at end of file diff --git a/middleware/board.ts b/middleware/board.ts new file mode 100644 index 0000000..80f27db --- /dev/null +++ b/middleware/board.ts @@ -0,0 +1,15 @@ +export default defineNuxtRouteMiddleware((to, from) => { + const { isAuthenticated, isBoard, isAdmin } = useAuth(); + + if (!isAuthenticated.value) { + return navigateTo('/login'); + } + + // Only board members and admins can access board pages + if (!isBoard.value && !isAdmin.value) { + throw createError({ + statusCode: 403, + statusMessage: 'Access denied. Board member privileges required.' + }); + } +}); \ No newline at end of file diff --git a/middleware/member.ts b/middleware/member.ts new file mode 100644 index 0000000..0e919ca --- /dev/null +++ b/middleware/member.ts @@ -0,0 +1,15 @@ +export default defineNuxtRouteMiddleware((to, from) => { + const { isAuthenticated, isUser, isBoard, isAdmin } = useAuth(); + + if (!isAuthenticated.value) { + return navigateTo('/login'); + } + + // Members, board members, and admins can all access member pages + if (!isUser.value && !isBoard.value && !isAdmin.value) { + throw createError({ + statusCode: 403, + statusMessage: 'Access denied. Member privileges required.' + }); + } +}); \ No newline at end of file diff --git a/pages/admin/dashboard/index.vue b/pages/admin/dashboard/index.vue new file mode 100644 index 0000000..3a0c44c --- /dev/null +++ b/pages/admin/dashboard/index.vue @@ -0,0 +1,889 @@ + + + + + diff --git a/pages/board/dashboard/index.vue b/pages/board/dashboard/index.vue new file mode 100644 index 0000000..0d39900 --- /dev/null +++ b/pages/board/dashboard/index.vue @@ -0,0 +1,350 @@ + + + + + diff --git a/pages/board/members/index.vue b/pages/board/members/index.vue new file mode 100644 index 0000000..12f02d4 --- /dev/null +++ b/pages/board/members/index.vue @@ -0,0 +1,602 @@ + + + + + \ No newline at end of file diff --git a/pages/dashboard/index.vue b/pages/dashboard/index.vue index 96cf964..aa3b4ee 100644 --- a/pages/dashboard/index.vue +++ b/pages/dashboard/index.vue @@ -27,13 +27,23 @@ const loading = ref(true); // Route to tier-specific dashboard - auth middleware ensures user is authenticated onMounted(() => { - console.log('🔄 Dashboard mounted, routing to tier-specific page...'); + console.log('🔄 Dashboard mounted, routing to role-specific section...'); - // Auth middleware has already verified authentication - just route to tier page + // Auth middleware has already verified authentication - route based on highest privilege if (user.value && userTier.value) { - const tierRoute = `/dashboard/${userTier.value}`; - console.log('🔄 Routing to tier-specific dashboard:', tierRoute); - navigateTo(tierRoute, { replace: true }); + let targetRoute = '/member/dashboard'; + + // Route to the highest privilege level section + if (userTier.value === 'admin') { + targetRoute = '/admin/dashboard'; + } else if (userTier.value === 'board') { + targetRoute = '/board/dashboard'; + } else { + targetRoute = '/member/dashboard'; + } + + console.log('🔄 Routing to role-specific dashboard:', targetRoute); + navigateTo(targetRoute, { replace: true }); } else { console.warn('❌ No user or tier found - this should not happen after auth middleware'); // Fallback - middleware should have caught this diff --git a/pages/member/dashboard/index.vue b/pages/member/dashboard/index.vue new file mode 100644 index 0000000..2202771 --- /dev/null +++ b/pages/member/dashboard/index.vue @@ -0,0 +1,473 @@ + + + + + diff --git a/pages/member/events/index.vue b/pages/member/events/index.vue new file mode 100644 index 0000000..ff3bfd5 --- /dev/null +++ b/pages/member/events/index.vue @@ -0,0 +1,552 @@ + + + + + \ No newline at end of file diff --git a/pages/member/profile/index.vue b/pages/member/profile/index.vue new file mode 100644 index 0000000..d7aca97 --- /dev/null +++ b/pages/member/profile/index.vue @@ -0,0 +1,640 @@ + + + + + \ No newline at end of file diff --git a/pages/member/resources/index.vue b/pages/member/resources/index.vue new file mode 100644 index 0000000..ff6c99b --- /dev/null +++ b/pages/member/resources/index.vue @@ -0,0 +1,506 @@ + + + + + \ No newline at end of file diff --git a/pages/members/mockup.vue b/pages/members/mockup.vue index b829648..8d8f02d 100644 --- a/pages/members/mockup.vue +++ b/pages/members/mockup.vue @@ -8,8 +8,8 @@ :enter="{ opacity: 1, y: 0 }" class="members-header__content" > -

Members Directory

-

Connect with {{ totalMembers }} MonacoUSA members worldwide

+

Member Management

+

Manage {{ totalMembers }} MonacoUSA members

- Export Directory + Export Members - Invite Member + Add Member
@@ -37,7 +37,7 @@ @@ -78,16 +77,20 @@ class="members-stats" >
- {{ filteredMembers.length }} - Results + {{ totalMembers }} + Total Members
- {{ onlineCount }} - Online Now + {{ activeMembers }} + Active Members
- {{ newThisMonth }} - New This Month + {{ overdueDues }} + Overdue Dues +
+
+ {{ inactiveMembers }} + Inactive Members
@@ -101,35 +104,126 @@
-
- +
- - @@ -292,95 +347,90 @@ import FloatingInput from '~/components/ui/FloatingInput.vue' import MemberCard from '~/components/ui/MemberCard.vue' const searchQuery = ref('') -const viewMode = ref('grid') -const totalMembers = ref(1234) -const onlineCount = ref(89) -const newThisMonth = ref(23) +const viewMode = ref('table') +const totalMembers = ref(156) +const activeMembers = ref(142) +const overdueDues = ref(8) +const inactiveMembers = ref(14) const members = ref([ { id: 1, name: 'Alexandra Martin', + email: 'alexandra.martin@example.com', avatar: '/api/placeholder/150/150', title: 'CEO', company: 'Monaco Ventures', - role: 'Board Member', - status: 'online', - tags: ['Leadership', 'Strategy', 'Investment'], - joinDate: '2021', - connections: 234, - connected: false, - featured: true + memberStatus: 'active', + duesStatus: 'current', + hasPortalAccess: true, + joinDate: 'Jan 2021', + lastActive: '2 hours ago' }, { id: 2, name: 'Jean-Paul Rousseau', + email: 'jp.rousseau@example.com', avatar: '/api/placeholder/150/150', title: 'Managing Director', company: 'Riviera Capital', - role: 'Executive', - status: 'online', - tags: ['Finance', 'Real Estate', 'Banking'], - joinDate: '2020', - connections: 189, - connected: true, - featured: false + memberStatus: 'active', + duesStatus: 'current', + hasPortalAccess: true, + joinDate: 'Mar 2020', + lastActive: '1 day ago' }, { id: 3, name: 'Sarah Chen', + email: 'sarah.chen@example.com', avatar: '', title: 'VP of Technology', company: 'TechMonaco', - role: 'Member', - status: 'offline', - tags: ['Technology', 'AI', 'Innovation', 'Blockchain'], - joinDate: '2022', - connections: 156, - connected: false, - featured: false + memberStatus: 'active', + duesStatus: 'overdue', + hasPortalAccess: false, + joinDate: 'Jun 2022', + lastActive: 'Never' }, { id: 4, name: 'Marcus Williams', + email: 'marcus.w@example.com', avatar: '/api/placeholder/150/150', title: 'Partner', company: 'Monaco Law Group', - role: 'Member', - status: 'away', - tags: ['Legal', 'Corporate Law', 'M&A'], - joinDate: '2021', - connections: 98, - connected: false, - featured: false + memberStatus: 'inactive', + duesStatus: 'overdue', + hasPortalAccess: false, + joinDate: 'Sep 2021', + lastActive: '3 months ago' }, { id: 5, name: 'Isabella Romano', + email: 'isabella@example.com', avatar: '/api/placeholder/150/150', title: 'Creative Director', company: 'Romano Design Studio', - role: 'Member', - status: 'online', - tags: ['Design', 'Branding', 'Marketing'], - joinDate: '2023', - connections: 67, - connected: true, - featured: false + memberStatus: 'active', + duesStatus: 'current', + hasPortalAccess: true, + joinDate: 'Feb 2023', + lastActive: '5 days ago' }, { id: 6, name: 'Thomas Anderson', + email: 't.anderson@example.com', avatar: '', title: 'Investment Manager', company: 'Monte Carlo Holdings', - role: 'Honorary', - status: 'offline', - tags: ['Investment', 'Wealth Management', 'Private Equity'], - joinDate: '2019', - connections: 321, - connected: false, - featured: true + memberStatus: 'pending', + duesStatus: 'exempt', + hasPortalAccess: false, + joinDate: 'Nov 2019', + lastActive: 'Never' } ]) @@ -390,9 +440,8 @@ const filteredMembers = computed(() => { const query = searchQuery.value.toLowerCase() return members.value.filter(member => member.name.toLowerCase().includes(query) || - member.company?.toLowerCase().includes(query) || - member.title?.toLowerCase().includes(query) || - member.tags?.some(tag => tag.toLowerCase().includes(query)) + member.email?.toLowerCase().includes(query) || + member.company?.toLowerCase().includes(query) ) }) @@ -400,17 +449,19 @@ const getInitials = (name: string) => { return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2) } -const selectMember = (member: any) => { - console.log('Selected member:', member) +const editMember = (member: any) => { + console.log('Edit member:', member) + // Would open edit dialog } -const connectMember = (member: any) => { - member.connected = !member.connected - console.log('Connect with:', member) +const emailMember = (member: any) => { + console.log('Email member:', member) + // Would open email compose } -const messageMember = (member: any) => { - console.log('Message:', member) +const inviteToPortal = (member: any) => { + console.log('Invite to portal:', member) + // Would send portal invitation } @@ -566,25 +617,146 @@ const messageMember = (member: any) => { .members-container { margin-bottom: 2rem; - &--grid { + &--cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 1.5rem; } - &--list { - display: flex; - flex-direction: column; - gap: 1rem; + &--table { + overflow-x: auto; + } +} + +.members-table { + width: 100%; + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(20px); + border-radius: 12px; + overflow: hidden; + + table { + width: 100%; + border-collapse: collapse; } - &--compact { - .compact-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); - gap: 1rem; + th { + padding: 1rem; + text-align: left; + font-size: 0.875rem; + font-weight: 600; + color: #6b7280; + background: rgba(255, 255, 255, 0.5); + border-bottom: 2px solid rgba(220, 38, 38, 0.1); + } + + td { + padding: 1rem; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + } + + tr:hover { + background: rgba(255, 255, 255, 0.95); + } +} + +.member-cell { + display: flex; + align-items: center; + gap: 0.75rem; + + &__avatar { + width: 2.5rem; + height: 2.5rem; + border-radius: 8px; + overflow: hidden; + flex-shrink: 0; + + img { + width: 100%; + height: 100%; + object-fit: cover; } } + + &__name { + font-weight: 600; + color: #27272a; + margin-bottom: 0.125rem; + } + + &__email { + font-size: 0.75rem; + color: #6b7280; + } +} + +.status-badge { + display: inline-block; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 500; + text-transform: capitalize; + + &--active { + background: rgba(16, 185, 129, 0.1); + color: #10b981; + } + + &--inactive { + background: rgba(107, 114, 128, 0.1); + color: #6b7280; + } + + &--pending { + background: rgba(251, 146, 60, 0.1); + color: #fb923c; + } + + &--suspended { + background: rgba(239, 68, 68, 0.1); + color: #ef4444; + } +} + +.dues-badge { + display: inline-block; + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 500; + text-transform: capitalize; + + &--current { + background: rgba(16, 185, 129, 0.1); + color: #10b981; + } + + &--overdue { + background: rgba(239, 68, 68, 0.1); + color: #ef4444; + } + + &--exempt { + background: rgba(107, 114, 128, 0.1); + color: #6b7280; + } +} + +.portal-status { + font-size: 0.75rem; + color: #6b7280; + + &--active { + color: #10b981; + font-weight: 500; + } +} + +.table-actions { + display: flex; + gap: 0.5rem; } .member-list-item { @@ -798,7 +970,7 @@ const messageMember = (member: any) => { margin: 2rem auto; } -.network-widget { +.admin-widget { position: fixed; bottom: 2rem; right: 2rem; @@ -814,32 +986,10 @@ const messageMember = (member: any) => { } } -.network-stats { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 0.5rem; - margin-bottom: 1rem; -} - -.network-stat { +.admin-actions { display: flex; flex-direction: column; - align-items: center; - padding: 0.5rem; - background: rgba(255, 255, 255, 0.5); - border-radius: 8px; - - &__value { - font-size: 1.25rem; - font-weight: 700; - color: #dc2626; - } - - &__label { - font-size: 0.625rem; - color: #6b7280; - text-align: center; - } + gap: 0.75rem; } // Responsive @@ -876,8 +1026,21 @@ const messageMember = (member: any) => { } } - .network-widget { + .admin-widget { display: none; } + + .members-table { + font-size: 0.75rem; + + th, td { + padding: 0.5rem; + } + + .table-actions { + flex-direction: column; + gap: 0.25rem; + } + } } \ No newline at end of file diff --git a/pages/profile/mockup.vue b/pages/profile/mockup.vue index 568d99e..6538b2a 100644 --- a/pages/profile/mockup.vue +++ b/pages/profile/mockup.vue @@ -1,6 +1,6 @@