Redesign ContactListPage with clean, professional contact cards layout

- Separate responsive layouts for desktop and mobile
- Desktop: Clean 4-column layout (Avatar | Info | Vouches/Tags | Status)
- Mobile: Optimized 3-column layout (Avatar | Info | Status)
- Fix contact card alignment and spacing issues
- Restore directional arrows for vouches/praises (↑ sent, ↓ received)
- Right-align status buttons (Invite/NAO Member/Invited) on both layouts
- Improve mobile spacing and chip sizing
- Remove unused formatDate function and fix imports

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
main
Claude Code Assistant 2 months ago
parent 9849318490
commit e0ce0fbcd4
  1. 641
      src/pages/ContactListPage.tsx
  2. 30
      src/services/dataService.ts

@ -118,13 +118,6 @@ const ContactListPage = () => {
}
};
const formatDate = (date: Date) => {
return new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
}).format(date);
};
const getNaoStatusIndicator = (contact: Contact) => {
switch (contact.naoStatus) {
@ -390,60 +383,47 @@ const ContactListPage = () => {
borderColor: 'divider',
'&:hover': !isSelectionMode ? {
borderColor: 'primary.main',
boxShadow: theme.shadows[4],
transform: 'translateY(-2px)',
boxShadow: theme.shadows[2],
transform: 'translateY(-1px)',
} : {},
}}
>
<CardContent sx={{ p: { xs: 2, md: 3 } }}>
<Box sx={{
display: 'flex',
flexDirection: { xs: 'column', md: 'row' },
alignItems: { xs: 'stretch', md: 'center' },
gap: { xs: 2, md: 2 }
}}>
{/* Top row on mobile: Avatar + Name + Action Button */}
<Box sx={{
display: 'flex',
alignItems: 'center',
gap: 2,
width: { xs: '100%', md: 'auto' },
flexShrink: 0
}}>
<CardContent sx={{ p: 3 }}>
{/* Desktop Layout */}
<Box sx={{ display: { xs: 'none', md: 'flex' }, alignItems: 'flex-start', width: '100%' }}>
{/* Avatar */}
<Box
sx={{
width: { xs: 48, md: 64 },
height: { xs: 48, md: 64 },
width: 56,
height: 56,
borderRadius: '50%',
backgroundImage: contact.profileImage ? `url(${contact.profileImage})` : 'none',
backgroundSize: 'cover',
backgroundPosition: 'center center',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: contact.profileImage ? 'transparent' : 'primary.main',
color: 'white',
fontSize: { xs: '1.2rem', md: '1.5rem' },
fontWeight: 'bold',
flexShrink: 0
fontSize: '1.25rem',
fontWeight: 600,
flexShrink: 0,
mr: 3
}}
>
{!contact.profileImage && contact.name.charAt(0)}
</Box>
<Box sx={{ flexGrow: 1, minWidth: 0 }}>
{/* Left Column - Basic Info */}
<Box sx={{ width: 280, flexShrink: 0, mr: 3 }}>
{/* Name with source icon inline */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}>
<Typography
variant="h6"
component="div"
sx={{
fontWeight: 600,
fontSize: { xs: '1rem', md: '1.25rem' },
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
flexGrow: 1
fontSize: '1.125rem',
color: 'text.primary'
}}
>
{contact.name}
@ -451,76 +431,21 @@ const ContactListPage = () => {
{getSourceIcon(contact.source)}
</Box>
{/* Job Title & Company */}
<Typography
variant="body2"
color="text.secondary"
sx={{
fontSize: { xs: '0.75rem', md: '0.875rem' },
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}
sx={{ mb: 1, lineHeight: 1.4 }}
>
{contact.position}{contact.company && ` at ${contact.company}`}
</Typography>
</Box>
{/* Action buttons - always visible on the right */}
<Box sx={{ flexShrink: 0 }}>
{/* Select button for selection mode */}
{isSelectionMode && (
<Button
variant="contained"
onClick={(e) => {
e.stopPropagation();
handleSelectContact(contact);
}}
sx={{
borderRadius: 2,
textTransform: 'none',
minWidth: { xs: 60, md: 80 },
fontSize: { xs: '0.75rem', md: '0.875rem' }
}}
>
Select
</Button>
)}
{/* Invite to NAO button for non-members (not in selection mode) */}
{!isSelectionMode && contact.naoStatus === 'not_invited' && (
<Button
variant="outlined"
startIcon={<Send sx={{ fontSize: 16 }} />}
onClick={(e) => {
e.stopPropagation();
handleInviteToNao(contact);
}}
sx={{
borderRadius: 2,
textTransform: 'none',
minWidth: { xs: 60, md: 80 },
fontSize: { xs: '0.75rem', md: '0.875rem' },
py: 0.5,
px: { xs: 1, md: 1.5 }
}}
>
Invite
</Button>
)}
</Box>
</Box>
{/* Bottom section: Email + Status chips + Tags */}
<Box sx={{
width: '100%',
display: { xs: 'block', md: 'none' } // Only show on mobile
}}>
{/* Email */}
<Typography
variant="body2"
color="text.secondary"
sx={{
mb: 1,
fontSize: '0.75rem',
fontSize: '0.875rem',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
@ -528,50 +453,21 @@ const ContactListPage = () => {
>
{contact.email}
</Typography>
</Box>
{/* Compact status and metrics chips */}
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', mb: 1 }}>
{contact.groupIds && contact.groupIds.length > 0 && (
<Chip
icon={<Group sx={{ fontSize: 12 }} />}
label={contact.groupIds.length}
size="small"
variant="outlined"
sx={{
fontSize: '0.65rem',
height: 18,
borderRadius: 1,
backgroundColor: alpha(theme.palette.success.main, 0.04),
borderColor: alpha(theme.palette.success.main, 0.12),
color: 'success.main',
'& .MuiChip-icon': { fontSize: 12 },
}}
/>
)}
{/* NAO Status Indicator */}
{(() => {
const naoStatus = getNaoStatusIndicator(contact);
return naoStatus ? (
<Chip
icon={naoStatus.icon}
label={naoStatus.label}
size="small"
variant="outlined"
sx={{
fontSize: '0.65rem',
height: 18,
borderRadius: 1,
backgroundColor: naoStatus.bgColor,
borderColor: naoStatus.borderColor,
color: naoStatus.color,
'& .MuiChip-icon': { fontSize: 12 },
}}
/>
) : null;
})()}
{/* Simplified Vouch and Praise counts */}
{/* Middle Column - Vouch & Praise Counts + Tags */}
<Box sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
justifyContent: 'flex-start',
gap: 1,
flexGrow: 1,
pt: 0.5,
minWidth: 200
}}>
{/* Vouches and Praises Row */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, height: 24 }}>
{(() => {
const counts = getVouchPraiseCounts(contact);
const totalVouches = counts.vouchesSent + counts.vouchesReceived;
@ -579,49 +475,100 @@ const ContactListPage = () => {
return (
<>
{totalVouches > 0 && (
<Chip
icon={<VerifiedUser sx={{ fontSize: 12 }} />}
label={totalVouches}
icon={<VerifiedUser sx={{ fontSize: 14 }} />}
label={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
{counts.vouchesReceived > 0 && (
<>
<ArrowDownward sx={{ fontSize: 10 }} />
<span>{counts.vouchesReceived}</span>
</>
)}
{counts.vouchesSent > 0 && counts.vouchesReceived > 0 && (
<span style={{ fontSize: '0.6rem', opacity: 0.6 }}></span>
)}
{counts.vouchesSent > 0 && (
<>
<ArrowUpward sx={{ fontSize: 10 }} />
<span>{counts.vouchesSent}</span>
</>
)}
{totalVouches === 0 && <span>0</span>}
</Box>
}
size="small"
variant="outlined"
title={contact.naoStatus === 'member'
? `Vouches: ${counts.vouchesReceived} received, ${counts.vouchesSent} sent`
: `Vouches: ${counts.vouchesSent} sent (hidden until they join)`}
sx={{
fontSize: '0.65rem',
height: 18,
fontSize: '0.75rem',
height: 24,
borderRadius: 1,
backgroundColor: alpha(theme.palette.primary.main, 0.04),
borderColor: alpha(theme.palette.primary.main, 0.12),
color: 'primary.main',
'& .MuiChip-icon': { fontSize: 12 },
fontWeight: 500,
'& .MuiChip-icon': { fontSize: 14 },
'& .MuiChip-label': {
fontSize: '0.75rem',
padding: '0 4px',
},
}}
/>
)}
{totalPraises > 0 && (
<Chip
icon={<Favorite sx={{ fontSize: 12 }} />}
label={totalPraises}
icon={<Favorite sx={{ fontSize: 14 }} />}
label={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
{counts.praisesReceived > 0 && (
<>
<ArrowDownward sx={{ fontSize: 10 }} />
<span>{counts.praisesReceived}</span>
</>
)}
{counts.praisesSent > 0 && counts.praisesReceived > 0 && (
<span style={{ fontSize: '0.6rem', opacity: 0.6 }}></span>
)}
{counts.praisesSent > 0 && (
<>
<ArrowUpward sx={{ fontSize: 10 }} />
<span>{counts.praisesSent}</span>
</>
)}
{totalPraises === 0 && <span>0</span>}
</Box>
}
size="small"
variant="outlined"
title={contact.naoStatus === 'member'
? `Praises: ${counts.praisesReceived} received, ${counts.praisesSent} sent`
: `Praises: ${counts.praisesSent} sent (hidden until they join)`}
sx={{
fontSize: '0.65rem',
height: 18,
fontSize: '0.75rem',
height: 24,
borderRadius: 1,
backgroundColor: alpha('#f8bbd9', 0.3),
borderColor: alpha('#d81b60', 0.3),
color: '#d81b60',
'& .MuiChip-icon': { fontSize: 12 },
fontWeight: 500,
'& .MuiChip-icon': { fontSize: 14 },
'& .MuiChip-label': {
fontSize: '0.75rem',
padding: '0 4px',
},
}}
/>
)}
</>
);
})()}
</Box>
{/* Tags - only show first 2 on mobile */}
{/* Tags Row */}
{contact.tags && contact.tags.length > 0 && (
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
{contact.tags.slice(0, 2).map((tag) => (
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', justifyContent: 'flex-start' }}>
{contact.tags.slice(0, 3).map((tag) => (
<Chip
key={tag}
label={tag}
@ -629,18 +576,18 @@ const ContactListPage = () => {
variant="outlined"
sx={{
borderRadius: 1,
backgroundColor: alpha(theme.palette.primary.main, 0.04),
borderColor: alpha(theme.palette.primary.main, 0.12),
color: 'primary.main',
backgroundColor: alpha(theme.palette.grey[500], 0.04),
borderColor: alpha(theme.palette.grey[500], 0.12),
color: 'text.secondary',
fontWeight: 500,
fontSize: '0.65rem',
height: 18
}}
/>
))}
{contact.tags.length > 2 && (
{contact.tags.length > 3 && (
<Chip
label={`+${contact.tags.length - 2}`}
label={`+${contact.tags.length - 3}`}
size="small"
variant="outlined"
sx={{
@ -657,53 +604,185 @@ const ContactListPage = () => {
)}
</Box>
{/* Desktop layout (hidden on mobile) */}
{/* Right Column - Network Status Only (Push to Far Right) */}
<Box sx={{
flexGrow: 1,
display: { xs: 'none', md: 'block' } // Only show on desktop
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-end',
justifyContent: 'center',
gap: 1,
flexShrink: 0,
minWidth: 140,
height: 56,
ml: 'auto'
}}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1, flexWrap: 'wrap' }}>
{contact.groupIds && contact.groupIds.length > 0 && (
<Chip
icon={<Group sx={{ fontSize: 14 }} />}
label={contact.groupIds.length}
{(() => {
const naoStatus = getNaoStatusIndicator(contact);
if (isSelectionMode) {
return (
<Button
variant="contained"
size="small"
onClick={() => handleSelectContact(contact)}
sx={{
borderRadius: 2,
px: 3,
py: 1,
fontWeight: 600,
fontSize: '0.75rem',
height: 28,
minWidth: 80
}}
>
Select
</Button>
);
} else if (contact.naoStatus === 'not_invited') {
return (
<Button
variant="outlined"
size="small"
startIcon={<Send sx={{ fontSize: 14 }} />}
onClick={() => handleInviteToNao(contact)}
sx={{
borderRadius: 2,
px: 2,
py: 1,
fontWeight: 500,
fontSize: '0.75rem',
height: 20,
borderRadius: 1,
backgroundColor: alpha(theme.palette.success.main, 0.04),
borderColor: alpha(theme.palette.success.main, 0.12),
color: 'success.main',
'& .MuiChip-icon': { fontSize: 14 },
height: 28,
minWidth: 80
}}
/>
)}
{/* NAO Status Indicator */}
{(() => {
const naoStatus = getNaoStatusIndicator(contact);
>
Invite
</Button>
);
} else {
return naoStatus ? (
<Chip
icon={naoStatus.icon}
label={naoStatus.label}
size="small"
variant="outlined"
sx={{
fontSize: '0.75rem',
height: 20,
borderRadius: 1,
backgroundColor: naoStatus.bgColor,
borderColor: naoStatus.borderColor,
color: naoStatus.color,
'& .MuiChip-icon': { fontSize: 14 },
fontWeight: 500,
fontSize: '0.75rem',
height: 28,
minWidth: 80,
'& .MuiChip-icon': { fontSize: 14 }
}}
/>
) : null;
}
})()}
{/* Vouch and Praise Indicators with directional arrows - desktop only */}
{contact.groupIds && contact.groupIds.length > 0 && (
<Chip
icon={<Group sx={{ fontSize: 14 }} />}
label={`${contact.groupIds.length} group${contact.groupIds.length > 1 ? 's' : ''}`}
size="small"
variant="outlined"
sx={{
backgroundColor: alpha(theme.palette.primary.main, 0.04),
borderColor: alpha(theme.palette.primary.main, 0.2),
color: 'primary.main',
fontWeight: 500,
fontSize: '0.75rem',
height: 24
}}
/>
)}
</Box>
</Box>
{/* Mobile Layout */}
<Box sx={{ display: { xs: 'flex', md: 'none' }, alignItems: 'flex-start', gap: 2 }}>
{/* Avatar */}
<Box
sx={{
width: 48,
height: 48,
borderRadius: '50%',
backgroundImage: contact.profileImage ? `url(${contact.profileImage})` : 'none',
backgroundSize: 'cover',
backgroundPosition: 'center',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: contact.profileImage ? 'transparent' : 'primary.main',
color: 'white',
fontSize: '1.125rem',
fontWeight: 600,
flexShrink: 0
}}
>
{!contact.profileImage && contact.name.charAt(0)}
</Box>
{/* Main Content */}
<Box sx={{ flex: 1, minWidth: 0 }}>
{/* Name with source icon */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.5 }}>
<Typography
variant="h6"
sx={{
fontWeight: 600,
fontSize: '1rem',
color: 'text.primary'
}}
>
{contact.name}
</Typography>
{getSourceIcon(contact.source)}
</Box>
{/* Job Title & Company */}
<Typography
variant="body2"
color="text.secondary"
sx={{ mb: 0.5, lineHeight: 1.3, fontSize: '0.8rem' }}
>
{contact.position}{contact.company && ` at ${contact.company}`}
</Typography>
{/* Email */}
<Typography
variant="body2"
color="text.secondary"
sx={{
mb: 1,
fontSize: '0.75rem',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap'
}}
>
{contact.email}
</Typography>
{/* Info Chips Row (excluding NAO status) */}
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap', alignItems: 'center', mb: 1 }}>
{/* Groups Count */}
{contact.groupIds && contact.groupIds.length > 0 && (
<Chip
icon={<Group sx={{ fontSize: 12 }} />}
label={`${contact.groupIds.length} group${contact.groupIds.length > 1 ? 's' : ''}`}
size="small"
variant="outlined"
sx={{
backgroundColor: alpha(theme.palette.primary.main, 0.04),
borderColor: alpha(theme.palette.primary.main, 0.2),
color: 'primary.main',
fontWeight: 500,
fontSize: '0.65rem',
height: 20
}}
/>
)}
{/* Vouch & Praise Counts */}
{(() => {
const counts = getVouchPraiseCounts(contact);
const totalVouches = counts.vouchesSent + counts.vouchesReceived;
@ -711,99 +790,49 @@ const ContactListPage = () => {
return (
<>
{totalVouches > 0 && (
<Chip
icon={<VerifiedUser sx={{ fontSize: 14 }} />}
label={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
{counts.vouchesReceived > 0 && (
<>
<ArrowDownward sx={{ fontSize: 10 }} />
<span>{counts.vouchesReceived}</span>
</>
)}
{counts.vouchesSent > 0 && counts.vouchesReceived > 0 && (
<span style={{ fontSize: '0.6rem', opacity: 0.6 }}></span>
)}
{counts.vouchesSent > 0 && (
<>
<ArrowUpward sx={{ fontSize: 10 }} />
<span>{counts.vouchesSent}</span>
</>
)}
{totalVouches === 0 && <span>0</span>}
</Box>
}
icon={<VerifiedUser sx={{ fontSize: 10 }} />}
label={totalVouches}
size="small"
variant="outlined"
title={contact.naoStatus === 'member'
? `Vouches: ${counts.vouchesReceived} received, ${counts.vouchesSent} sent`
: `Vouches: ${counts.vouchesSent} sent (hidden until they join)`}
sx={{
fontSize: '0.75rem',
height: 20,
fontSize: '0.6rem',
height: 18,
borderRadius: 1,
backgroundColor: alpha(theme.palette.primary.main, 0.04),
borderColor: alpha(theme.palette.primary.main, 0.12),
color: 'primary.main',
'& .MuiChip-icon': { fontSize: 14 },
'& .MuiChip-label': {
fontSize: '0.75rem',
padding: '0 4px',
},
'& .MuiChip-icon': { fontSize: 10 },
}}
/>
<Chip
icon={<Favorite sx={{ fontSize: 14 }} />}
label={
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
{counts.praisesReceived > 0 && (
<>
<ArrowDownward sx={{ fontSize: 10 }} />
<span>{counts.praisesReceived}</span>
</>
)}
{counts.praisesSent > 0 && counts.praisesReceived > 0 && (
<span style={{ fontSize: '0.6rem', opacity: 0.6 }}></span>
)}
{counts.praisesSent > 0 && (
<>
<ArrowUpward sx={{ fontSize: 10 }} />
<span>{counts.praisesSent}</span>
</>
)}
{totalPraises === 0 && <span>0</span>}
</Box>
}
{totalPraises > 0 && (
<Chip
icon={<Favorite sx={{ fontSize: 10 }} />}
label={totalPraises}
size="small"
variant="outlined"
title={contact.naoStatus === 'member'
? `Praises: ${counts.praisesReceived} received, ${counts.praisesSent} sent`
: `Praises: ${counts.praisesSent} sent (hidden until they join)`}
sx={{
fontSize: '0.75rem',
height: 20,
fontSize: '0.6rem',
height: 18,
borderRadius: 1,
backgroundColor: alpha('#f8bbd9', 0.3),
borderColor: alpha('#d81b60', 0.3),
color: '#d81b60',
'& .MuiChip-icon': { fontSize: 14 },
'& .MuiChip-label': {
fontSize: '0.75rem',
padding: '0 4px',
},
'& .MuiChip-icon': { fontSize: 10 },
}}
/>
)}
</>
);
})()}
</Box>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
{contact.email}
</Typography>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', mb: 2 }}>
{contact.tags?.map((tag) => (
{/* Tags Row */}
{contact.tags && contact.tags.length > 0 && (
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
{contact.tags.slice(0, 2).map((tag) => (
<Chip
key={tag}
label={tag}
@ -811,18 +840,106 @@ const ContactListPage = () => {
variant="outlined"
sx={{
borderRadius: 1,
backgroundColor: alpha(theme.palette.primary.main, 0.04),
borderColor: alpha(theme.palette.primary.main, 0.12),
color: 'primary.main',
backgroundColor: alpha(theme.palette.grey[500], 0.04),
borderColor: alpha(theme.palette.grey[500], 0.12),
color: 'text.secondary',
fontWeight: 500,
fontSize: '0.6rem',
height: 16
}}
/>
))}
{contact.tags.length > 2 && (
<Chip
label={`+${contact.tags.length - 2}`}
size="small"
variant="outlined"
sx={{
borderRadius: 1,
backgroundColor: alpha(theme.palette.grey[500], 0.04),
borderColor: alpha(theme.palette.grey[500], 0.12),
color: 'text.secondary',
fontSize: '0.6rem',
height: 16
}}
/>
)}
</Box>
)}
</Box>
<Typography variant="caption" color="text.secondary">
Added {formatDate(contact.createdAt)}
</Typography>
{/* Right Column - Status & Action */}
<Box sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-end',
justifyContent: 'center',
gap: 1,
flexShrink: 0,
minWidth: 80,
height: 48
}}>
{(() => {
const naoStatus = getNaoStatusIndicator(contact);
if (isSelectionMode) {
return (
<Button
variant="contained"
size="small"
onClick={() => handleSelectContact(contact)}
sx={{
borderRadius: 2,
px: 2,
py: 0.5,
fontWeight: 600,
fontSize: '0.7rem',
height: 24,
minWidth: 60
}}
>
Select
</Button>
);
} else if (contact.naoStatus === 'not_invited') {
return (
<Button
variant="outlined"
size="small"
startIcon={<Send sx={{ fontSize: 12 }} />}
onClick={() => handleInviteToNao(contact)}
sx={{
borderRadius: 2,
px: 1.5,
py: 0.5,
fontWeight: 500,
fontSize: '0.7rem',
height: 24,
minWidth: 60
}}
>
Invite
</Button>
);
} else {
return naoStatus ? (
<Chip
icon={naoStatus.icon}
label={naoStatus.label}
size="small"
sx={{
backgroundColor: naoStatus.bgColor,
borderColor: naoStatus.borderColor,
color: naoStatus.color,
fontWeight: 500,
fontSize: '0.65rem',
height: 24,
minWidth: 60,
'& .MuiChip-icon': { fontSize: 12 }
}}
/>
) : null;
}
})()}
</Box>
</Box>
</CardContent>

@ -8,11 +8,23 @@ export const dataService = {
try {
const response = await fetch('/contacts.json');
const contactsData = await response.json();
const contacts = contactsData.map((contact: any) => ({
const contacts = contactsData.map((contact: any) => {
const processedContact = {
...contact,
createdAt: new Date(contact.createdAt),
updatedAt: new Date(contact.updatedAt)
}));
};
// Convert optional date fields if they exist
if (contact.joinedAt) {
processedContact.joinedAt = new Date(contact.joinedAt);
}
if (contact.invitedAt) {
processedContact.invitedAt = new Date(contact.invitedAt);
}
return processedContact;
});
resolve(contacts);
} catch (error) {
console.error('Failed to load contacts:', error);
@ -30,11 +42,21 @@ export const dataService = {
const contactsData = await response.json();
const contact = contactsData.find((c: any) => c.id === id);
if (contact) {
resolve({
const processedContact = {
...contact,
createdAt: new Date(contact.createdAt),
updatedAt: new Date(contact.updatedAt)
});
};
// Convert optional date fields if they exist
if (contact.joinedAt) {
processedContact.joinedAt = new Date(contact.joinedAt);
}
if (contact.invitedAt) {
processedContact.invitedAt = new Date(contact.invitedAt);
}
resolve(processedContact);
} else {
resolve(undefined);
}

Loading…
Cancel
Save