monacousa-portal/components/dashboard/SimpleProfileCard.vue

296 lines
6.7 KiB
Vue

<template>
<div
v-motion
:initial="{ opacity: 0, scale: 0.98 }"
:enter="{
opacity: 1,
scale: 1,
transition: {
duration: 500,
type: 'spring',
stiffness: 200
}
}"
class="simple-profile-card"
>
<!-- Header with Avatar -->
<div class="profile-header">
<div class="profile-avatar-wrapper">
<ProfileAvatar
v-if="member"
:member-id="member.member_id"
:first-name="member.first_name"
:last-name="member.last_name"
size="x-large"
:show-badge="false"
/>
</div>
<div class="profile-title">
<h2 class="profile-name">{{ fullName }}</h2>
<p class="profile-member-id">{{ member?.member_id || 'MUSA-0000' }}</p>
</div>
</div>
<!-- Profile Information -->
<div class="profile-info-section">
<h3 class="section-title">Contact Information</h3>
<div class="info-grid">
<div class="info-item">
<v-icon size="18" color="grey-darken-1">mdi-email</v-icon>
<div class="info-content">
<span class="info-label">Email</span>
<span class="info-value">{{ member?.email || 'Not provided' }}</span>
<v-chip
v-if="emailVerified"
size="x-small"
color="success"
variant="tonal"
class="ml-2"
>
Verified
</v-chip>
</div>
</div>
<div class="info-item">
<v-icon size="18" color="grey-darken-1">mdi-phone</v-icon>
<div class="info-content">
<span class="info-label">Phone</span>
<span class="info-value">{{ member?.phone || 'Not provided' }}</span>
</div>
</div>
<div class="info-item">
<v-icon size="18" color="grey-darken-1">mdi-map-marker</v-icon>
<div class="info-content">
<span class="info-label">Address</span>
<span class="info-value">{{ member?.address || 'Not provided' }}</span>
</div>
</div>
</div>
</div>
<!-- Personal Information -->
<div class="profile-info-section">
<h3 class="section-title">Personal Information</h3>
<div class="info-grid">
<div class="info-item">
<v-icon size="18" color="grey-darken-1">mdi-flag</v-icon>
<div class="info-content">
<span class="info-label">Nationality</span>
<span class="info-value">{{ formatNationality(member?.nationality) }}</span>
</div>
</div>
<div class="info-item">
<v-icon size="18" color="grey-darken-1">mdi-cake</v-icon>
<div class="info-content">
<span class="info-label">Date of Birth</span>
<span class="info-value">{{ formatDate(member?.date_of_birth) }}</span>
</div>
</div>
<div class="info-item">
<v-icon size="18" color="grey-darken-1">mdi-calendar-account</v-icon>
<div class="info-content">
<span class="info-label">Member Since</span>
<span class="info-value">{{ formatDate(member?.member_since) }}</span>
</div>
</div>
</div>
</div>
<!-- Bio Section (if available) -->
<div v-if="member?.bio" class="profile-info-section">
<h3 class="section-title">About Me</h3>
<p class="bio-text">{{ member.bio }}</p>
</div>
<!-- Action Button -->
<v-btn
color="error"
variant="flat"
block
class="profile-action"
prepend-icon="mdi-account-edit"
@click="$emit('edit-profile')"
>
Edit Profile
</v-btn>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import type { Member } from '~/utils/types';
interface Props {
member: Member | null;
emailVerified?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
emailVerified: false
});
const emit = defineEmits<{
'edit-profile': [];
}>();
// Computed properties
const fullName = computed(() => {
if (props.member) {
return `${props.member.first_name} ${props.member.last_name}`;
}
return 'Member';
});
// Format nationality (handles multiple nationalities)
const formatNationality = (nationality?: string) => {
if (!nationality) return 'Not provided';
// Split by comma if multiple nationalities
const nationalities = nationality.split(',').map(n => n.trim());
// Map country codes to full names if needed
const countryMap: Record<string, string> = {
'US': 'United States',
'FR': 'France',
'MC': 'Monaco',
'IT': 'Italy',
'UK': 'United Kingdom',
'DE': 'Germany',
'ES': 'Spain'
};
return nationalities.map(n => countryMap[n] || n).join(', ');
};
// Format date
const formatDate = (dateString?: string) => {
if (!dateString) return 'Not provided';
try {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
} catch {
return dateString;
}
};
</script>
<style scoped lang="scss">
.simple-profile-card {
background: white;
border-radius: 1rem;
padding: 1.5rem;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
height: 100%;
display: flex;
flex-direction: column;
}
.profile-header {
display: flex;
align-items: center;
gap: 1.5rem;
padding-bottom: 1.5rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
margin-bottom: 1.5rem;
}
.profile-avatar-wrapper {
flex-shrink: 0;
}
.profile-title {
flex: 1;
}
.profile-name {
font-size: 1.5rem;
font-weight: 600;
color: rgb(31, 41, 55);
margin: 0 0 0.25rem 0;
}
.profile-member-id {
font-size: 0.875rem;
color: rgb(107, 114, 128);
margin: 0;
font-family: 'Courier New', monospace;
}
.profile-info-section {
margin-bottom: 1.5rem;
}
.section-title {
font-size: 0.875rem;
font-weight: 600;
color: rgb(107, 114, 128);
text-transform: uppercase;
letter-spacing: 0.05em;
margin: 0 0 1rem 0;
}
.info-grid {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.info-item {
display: flex;
align-items: flex-start;
gap: 0.75rem;
}
.info-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.125rem;
}
.info-label {
font-size: 0.75rem;
color: rgb(156, 163, 175);
text-transform: uppercase;
letter-spacing: 0.025em;
}
.info-value {
font-size: 0.875rem;
color: rgb(31, 41, 55);
line-height: 1.4;
}
.bio-text {
font-size: 0.875rem;
color: rgb(75, 85, 99);
line-height: 1.6;
margin: 0;
}
.profile-action {
margin-top: auto;
font-weight: 600;
text-transform: none;
letter-spacing: 0;
}
@media (max-width: 768px) {
.profile-header {
flex-direction: column;
text-align: center;
}
}
</style>