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:
640
pages/member/profile/index.vue
Normal file
640
pages/member/profile/index.vue
Normal file
@@ -0,0 +1,640 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Header -->
|
||||
<div class="mb-6">
|
||||
<h1 class="text-h4 font-weight-bold mb-2">My Profile</h1>
|
||||
<p class="text-body-1 text-medium-emphasis">Manage your personal information and preferences</p>
|
||||
</div>
|
||||
|
||||
<!-- Profile Completion Alert -->
|
||||
<v-alert
|
||||
v-if="profileCompletion < 100"
|
||||
type="info"
|
||||
variant="tonal"
|
||||
class="mb-6"
|
||||
closable
|
||||
>
|
||||
<v-alert-title>Complete Your Profile</v-alert-title>
|
||||
Your profile is {{ profileCompletion }}% complete. Add more information to help other members connect with you.
|
||||
<v-progress-linear
|
||||
:model-value="profileCompletion"
|
||||
color="info"
|
||||
class="mt-2"
|
||||
height="6"
|
||||
rounded
|
||||
/>
|
||||
</v-alert>
|
||||
|
||||
<v-row>
|
||||
<!-- Left Column - Profile Card -->
|
||||
<v-col cols="12" lg="4">
|
||||
<v-card elevation="2" class="mb-6">
|
||||
<v-card-text class="text-center pa-6">
|
||||
<!-- Avatar -->
|
||||
<div class="mb-4">
|
||||
<ProfileAvatar
|
||||
:member-id="profile.memberId"
|
||||
:first-name="profile.firstName"
|
||||
:last-name="profile.lastName"
|
||||
size="x-large"
|
||||
:show-badge="false"
|
||||
/>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="text"
|
||||
size="small"
|
||||
class="mt-2"
|
||||
@click="changeAvatar"
|
||||
>
|
||||
<v-icon start>mdi-camera</v-icon>
|
||||
Change Photo
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<!-- Basic Info -->
|
||||
<h2 class="text-h5 font-weight-bold mb-1">{{ profile.firstName }} {{ profile.lastName }}</h2>
|
||||
<p class="text-body-2 text-medium-emphasis mb-3">{{ profile.title }}</p>
|
||||
|
||||
<!-- Member Badge -->
|
||||
<v-chip
|
||||
color="error"
|
||||
variant="tonal"
|
||||
class="mb-4"
|
||||
>
|
||||
<v-icon start>mdi-shield-star</v-icon>
|
||||
{{ profile.memberType }} Member
|
||||
</v-chip>
|
||||
|
||||
<!-- Stats -->
|
||||
<v-row class="mt-4">
|
||||
<v-col cols="4">
|
||||
<div class="text-h6 font-weight-bold">{{ profile.eventsAttended }}</div>
|
||||
<div class="text-caption text-medium-emphasis">Events</div>
|
||||
</v-col>
|
||||
<v-col cols="4">
|
||||
<div class="text-h6 font-weight-bold">{{ profile.connections }}</div>
|
||||
<div class="text-caption text-medium-emphasis">Connections</div>
|
||||
</v-col>
|
||||
<v-col cols="4">
|
||||
<div class="text-h6 font-weight-bold">{{ profile.yearJoined }}</div>
|
||||
<div class="text-caption text-medium-emphasis">Joined</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<v-card elevation="1">
|
||||
<v-card-title class="text-body-1">Quick Actions</v-card-title>
|
||||
<v-list density="compact">
|
||||
<v-list-item @click="downloadMemberCard">
|
||||
<template v-slot:prepend>
|
||||
<v-icon color="error">mdi-card-account-details</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Download Member Card</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="exportData">
|
||||
<template v-slot:prepend>
|
||||
<v-icon color="error">mdi-download</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Export My Data</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="privacySettings">
|
||||
<template v-slot:prepend>
|
||||
<v-icon color="error">mdi-shield-lock</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>Privacy Settings</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- Right Column - Profile Details -->
|
||||
<v-col cols="12" lg="8">
|
||||
<!-- Tab Navigation -->
|
||||
<v-tabs
|
||||
v-model="activeTab"
|
||||
color="error"
|
||||
class="mb-6"
|
||||
>
|
||||
<v-tab value="personal">
|
||||
<v-icon start>mdi-account</v-icon>
|
||||
Personal Info
|
||||
</v-tab>
|
||||
<v-tab value="contact">
|
||||
<v-icon start>mdi-phone</v-icon>
|
||||
Contact
|
||||
</v-tab>
|
||||
<v-tab value="professional">
|
||||
<v-icon start>mdi-briefcase</v-icon>
|
||||
Professional
|
||||
</v-tab>
|
||||
<v-tab value="preferences">
|
||||
<v-icon start>mdi-cog</v-icon>
|
||||
Preferences
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
|
||||
<!-- Tab Content -->
|
||||
<v-window v-model="activeTab">
|
||||
<!-- Personal Info Tab -->
|
||||
<v-window-item value="personal">
|
||||
<v-card elevation="1">
|
||||
<v-card-title>
|
||||
Personal Information
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
v-if="!editingPersonal"
|
||||
variant="text"
|
||||
color="error"
|
||||
@click="editingPersonal = true"
|
||||
>
|
||||
<v-icon start>mdi-pencil</v-icon>
|
||||
Edit
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form v-model="personalFormValid">
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="profile.firstName"
|
||||
label="First Name"
|
||||
variant="outlined"
|
||||
:readonly="!editingPersonal"
|
||||
:rules="editingPersonal ? [v => !!v || 'Required'] : []"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="profile.lastName"
|
||||
label="Last Name"
|
||||
variant="outlined"
|
||||
:readonly="!editingPersonal"
|
||||
:rules="editingPersonal ? [v => !!v || 'Required'] : []"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="profile.dateOfBirth"
|
||||
label="Date of Birth"
|
||||
type="date"
|
||||
variant="outlined"
|
||||
:readonly="!editingPersonal"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
v-model="profile.nationality"
|
||||
label="Nationality"
|
||||
:items="nationalities"
|
||||
variant="outlined"
|
||||
:readonly="!editingPersonal"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-textarea
|
||||
v-model="profile.bio"
|
||||
label="Bio"
|
||||
variant="outlined"
|
||||
rows="3"
|
||||
:readonly="!editingPersonal"
|
||||
placeholder="Tell us about yourself..."
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions v-if="editingPersonal">
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="cancelEditPersonal">Cancel</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
:disabled="!personalFormValid"
|
||||
@click="savePersonal"
|
||||
>
|
||||
Save Changes
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-window-item>
|
||||
|
||||
<!-- Contact Tab -->
|
||||
<v-window-item value="contact">
|
||||
<v-card elevation="1">
|
||||
<v-card-title>
|
||||
Contact Information
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
v-if="!editingContact"
|
||||
variant="text"
|
||||
color="error"
|
||||
@click="editingContact = true"
|
||||
>
|
||||
<v-icon start>mdi-pencil</v-icon>
|
||||
Edit
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form v-model="contactFormValid">
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="profile.email"
|
||||
label="Email"
|
||||
type="email"
|
||||
variant="outlined"
|
||||
:readonly="!editingContact"
|
||||
:rules="editingContact ? [v => !!v || 'Required', v => /.+@.+/.test(v) || 'Invalid email'] : []"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="profile.phone"
|
||||
label="Phone"
|
||||
variant="outlined"
|
||||
:readonly="!editingContact"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-text-field
|
||||
v-model="profile.address"
|
||||
label="Address"
|
||||
variant="outlined"
|
||||
:readonly="!editingContact"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="profile.city"
|
||||
label="City"
|
||||
variant="outlined"
|
||||
:readonly="!editingContact"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="3">
|
||||
<v-text-field
|
||||
v-model="profile.state"
|
||||
label="State"
|
||||
variant="outlined"
|
||||
:readonly="!editingContact"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="3">
|
||||
<v-text-field
|
||||
v-model="profile.zipCode"
|
||||
label="ZIP Code"
|
||||
variant="outlined"
|
||||
:readonly="!editingContact"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions v-if="editingContact">
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="cancelEditContact">Cancel</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
:disabled="!contactFormValid"
|
||||
@click="saveContact"
|
||||
>
|
||||
Save Changes
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-window-item>
|
||||
|
||||
<!-- Professional Tab -->
|
||||
<v-window-item value="professional">
|
||||
<v-card elevation="1">
|
||||
<v-card-title>
|
||||
Professional Information
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
v-if="!editingProfessional"
|
||||
variant="text"
|
||||
color="error"
|
||||
@click="editingProfessional = true"
|
||||
>
|
||||
<v-icon start>mdi-pencil</v-icon>
|
||||
Edit
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form v-model="professionalFormValid">
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="profile.company"
|
||||
label="Company"
|
||||
variant="outlined"
|
||||
:readonly="!editingProfessional"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="profile.title"
|
||||
label="Job Title"
|
||||
variant="outlined"
|
||||
:readonly="!editingProfessional"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-select
|
||||
v-model="profile.industry"
|
||||
label="Industry"
|
||||
:items="industries"
|
||||
variant="outlined"
|
||||
:readonly="!editingProfessional"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="profile.linkedin"
|
||||
label="LinkedIn Profile"
|
||||
variant="outlined"
|
||||
:readonly="!editingProfessional"
|
||||
placeholder="https://linkedin.com/in/..."
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<v-textarea
|
||||
v-model="profile.expertise"
|
||||
label="Areas of Expertise"
|
||||
variant="outlined"
|
||||
rows="2"
|
||||
:readonly="!editingProfessional"
|
||||
placeholder="e.g., Finance, Marketing, Technology..."
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-card-actions v-if="editingProfessional">
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="cancelEditProfessional">Cancel</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
:disabled="!professionalFormValid"
|
||||
@click="saveProfessional"
|
||||
>
|
||||
Save Changes
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-window-item>
|
||||
|
||||
<!-- Preferences Tab -->
|
||||
<v-window-item value="preferences">
|
||||
<v-card elevation="1" class="mb-4">
|
||||
<v-card-title>Communication Preferences</v-card-title>
|
||||
<v-card-text>
|
||||
<v-switch
|
||||
v-model="preferences.emailNotifications"
|
||||
label="Email Notifications"
|
||||
color="error"
|
||||
hide-details
|
||||
class="mb-3"
|
||||
/>
|
||||
<v-switch
|
||||
v-model="preferences.eventReminders"
|
||||
label="Event Reminders"
|
||||
color="error"
|
||||
hide-details
|
||||
class="mb-3"
|
||||
/>
|
||||
<v-switch
|
||||
v-model="preferences.newsletter"
|
||||
label="Monthly Newsletter"
|
||||
color="error"
|
||||
hide-details
|
||||
class="mb-3"
|
||||
/>
|
||||
<v-switch
|
||||
v-model="preferences.memberUpdates"
|
||||
label="Member Updates"
|
||||
color="error"
|
||||
hide-details
|
||||
/>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<v-card elevation="1">
|
||||
<v-card-title>Privacy Settings</v-card-title>
|
||||
<v-card-text>
|
||||
<v-switch
|
||||
v-model="preferences.profileVisible"
|
||||
label="Profile visible to other members"
|
||||
color="error"
|
||||
hide-details
|
||||
class="mb-3"
|
||||
/>
|
||||
<v-switch
|
||||
v-model="preferences.showEmail"
|
||||
label="Show email in member directory"
|
||||
color="error"
|
||||
hide-details
|
||||
class="mb-3"
|
||||
/>
|
||||
<v-switch
|
||||
v-model="preferences.showPhone"
|
||||
label="Show phone in member directory"
|
||||
color="error"
|
||||
hide-details
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
@click="savePreferences"
|
||||
>
|
||||
Save Preferences
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-window-item>
|
||||
</v-window>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Member } from '~/utils/types';
|
||||
|
||||
definePageMeta({
|
||||
layout: 'member',
|
||||
middleware: 'member'
|
||||
});
|
||||
|
||||
const { user } = useAuth();
|
||||
|
||||
// State
|
||||
const activeTab = ref('personal');
|
||||
const editingPersonal = ref(false);
|
||||
const editingContact = ref(false);
|
||||
const editingProfessional = ref(false);
|
||||
const personalFormValid = ref(true);
|
||||
const contactFormValid = ref(true);
|
||||
const professionalFormValid = ref(true);
|
||||
|
||||
// Profile data
|
||||
const profile = ref({
|
||||
memberId: 'MUSA-0001',
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
email: 'john.doe@example.com',
|
||||
phone: '+1 234 567 8900',
|
||||
dateOfBirth: '1985-06-15',
|
||||
nationality: 'United States',
|
||||
bio: 'Passionate about business and innovation. Active member of the Monaco business community.',
|
||||
address: '123 Main Street',
|
||||
city: 'Monaco',
|
||||
state: 'MC',
|
||||
zipCode: '98000',
|
||||
company: 'Tech Innovations Inc.',
|
||||
title: 'CEO & Founder',
|
||||
industry: 'Technology',
|
||||
linkedin: 'https://linkedin.com/in/johndoe',
|
||||
expertise: 'Technology, Innovation, Business Strategy',
|
||||
memberType: 'Premium',
|
||||
eventsAttended: 24,
|
||||
connections: 156,
|
||||
yearJoined: '2021'
|
||||
});
|
||||
|
||||
// Preferences
|
||||
const preferences = ref({
|
||||
emailNotifications: true,
|
||||
eventReminders: true,
|
||||
newsletter: true,
|
||||
memberUpdates: false,
|
||||
profileVisible: true,
|
||||
showEmail: false,
|
||||
showPhone: false
|
||||
});
|
||||
|
||||
// Options
|
||||
const nationalities = ref([
|
||||
'United States',
|
||||
'Monaco',
|
||||
'France',
|
||||
'Italy',
|
||||
'United Kingdom',
|
||||
'Germany',
|
||||
'Spain',
|
||||
'Other'
|
||||
]);
|
||||
|
||||
const industries = ref([
|
||||
'Technology',
|
||||
'Finance',
|
||||
'Healthcare',
|
||||
'Real Estate',
|
||||
'Hospitality',
|
||||
'Manufacturing',
|
||||
'Retail',
|
||||
'Education',
|
||||
'Other'
|
||||
]);
|
||||
|
||||
// Computed
|
||||
const profileCompletion = computed(() => {
|
||||
let completed = 0;
|
||||
const fields = [
|
||||
profile.value.firstName,
|
||||
profile.value.lastName,
|
||||
profile.value.email,
|
||||
profile.value.phone,
|
||||
profile.value.dateOfBirth,
|
||||
profile.value.nationality,
|
||||
profile.value.bio,
|
||||
profile.value.address,
|
||||
profile.value.company,
|
||||
profile.value.title
|
||||
];
|
||||
|
||||
fields.forEach(field => {
|
||||
if (field) completed += 10;
|
||||
});
|
||||
|
||||
return completed;
|
||||
});
|
||||
|
||||
// Methods
|
||||
const changeAvatar = () => {
|
||||
console.log('Change avatar');
|
||||
};
|
||||
|
||||
const downloadMemberCard = () => {
|
||||
console.log('Download member card');
|
||||
};
|
||||
|
||||
const exportData = () => {
|
||||
console.log('Export user data');
|
||||
};
|
||||
|
||||
const privacySettings = () => {
|
||||
activeTab.value = 'preferences';
|
||||
};
|
||||
|
||||
const cancelEditPersonal = () => {
|
||||
editingPersonal.value = false;
|
||||
// Reset form if needed
|
||||
};
|
||||
|
||||
const savePersonal = () => {
|
||||
console.log('Saving personal info');
|
||||
editingPersonal.value = false;
|
||||
};
|
||||
|
||||
const cancelEditContact = () => {
|
||||
editingContact.value = false;
|
||||
};
|
||||
|
||||
const saveContact = () => {
|
||||
console.log('Saving contact info');
|
||||
editingContact.value = false;
|
||||
};
|
||||
|
||||
const cancelEditProfessional = () => {
|
||||
editingProfessional.value = false;
|
||||
};
|
||||
|
||||
const saveProfessional = () => {
|
||||
console.log('Saving professional info');
|
||||
editingProfessional.value = false;
|
||||
};
|
||||
|
||||
const savePreferences = () => {
|
||||
console.log('Saving preferences', preferences.value);
|
||||
};
|
||||
|
||||
// Load real member data on mount
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const { data: sessionData } = await $fetch<{ success: boolean; member: Member | null }>('/api/auth/session');
|
||||
if (sessionData?.member) {
|
||||
// Map real data to profile
|
||||
profile.value.firstName = sessionData.member.first_name || profile.value.firstName;
|
||||
profile.value.lastName = sessionData.member.last_name || profile.value.lastName;
|
||||
profile.value.email = sessionData.member.email || profile.value.email;
|
||||
profile.value.phone = sessionData.member.phone || profile.value.phone;
|
||||
profile.value.nationality = sessionData.member.nationality || profile.value.nationality;
|
||||
profile.value.memberId = sessionData.member.member_id || profile.value.memberId;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading member data:', error);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Custom styles if needed */
|
||||
</style>
|
||||
Reference in New Issue
Block a user