Add NAO membership status indicators and invite functionality to contacts

FEATURES:
- NAO membership status indicators with distinct visual styling:
  * Green "NAO Member" chip for existing members
  * Orange "Invited" chip for pending invitations
  * No indicator for contacts not yet invited
- Invite buttons for non-members to easily send NAO invitations
- Smart button placement that doesn't interfere with selection mode

VISUAL DESIGN:
- Color-coded status chips with icons (CheckCircle, Schedule)
- Consistent styling with existing contact chips
- Invite button with Send icon for clear action indication
- Proper spacing and alignment in contact cards

DATA STRUCTURE:
- Extended Contact interface with naoStatus, invitedAt, joinedAt fields
- Sample data includes different membership states for testing
- Auto-navigation to invitation page with contact details pre-filled

FUNCTIONALITY:
- Invite button navigates to /invite with contact name and email pre-filled
- Status indicators help users quickly identify network coverage
- Maintains existing selection mode functionality for group invitations

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

Co-Authored-By: Claude <noreply@anthropic.com>
main
Claude Code Assistant 2 months ago
parent 7c31fe221a
commit d71bc03451
  1. 7
      public/contacts.json
  2. 118
      src/pages/ContactListPage.tsx
  3. 3
      src/types/contact.ts

@ -11,6 +11,7 @@
"linkedinUrl": "https://linkedin.com/in/johnsmith",
"notes": "Met at React conference 2023",
"tags": ["developer", "react", "frontend"],
"naoStatus": "not_invited",
"createdAt": "2023-10-01T10:00:00Z",
"updatedAt": "2023-10-15T14:30:00Z"
},
@ -26,6 +27,8 @@
"linkedinUrl": "https://linkedin.com/in/sarahjohnson",
"notes": "Potential collaboration on new project",
"tags": ["product", "startup", "management"],
"naoStatus": "member",
"joinedAt": "2023-08-15T10:00:00Z",
"createdAt": "2023-09-15T09:00:00Z",
"updatedAt": "2023-10-20T16:45:00Z"
},
@ -40,6 +43,8 @@
"profileImage": "https://i.pravatar.cc/150?img=3",
"notes": "Design consultant for mobile app",
"tags": ["design", "ux", "mobile"],
"naoStatus": "invited",
"invitedAt": "2023-12-01T15:30:00Z",
"createdAt": "2023-08-20T11:30:00Z",
"updatedAt": "2023-09-10T13:15:00Z"
},
@ -55,6 +60,8 @@
"linkedinUrl": "https://linkedin.com/in/emilyrodriguez",
"notes": "Former colleague from previous company",
"tags": ["engineering", "management", "scaling"],
"naoStatus": "member",
"joinedAt": "2023-07-20T12:00:00Z",
"createdAt": "2023-07-10T08:00:00Z",
"updatedAt": "2023-08-25T10:20:00Z"
},

@ -29,7 +29,10 @@ import {
Group,
CloudDownload,
VerifiedUser,
Favorite
Favorite,
CheckCircle,
Schedule,
Send
} from '@mui/icons-material';
import { dataService } from '../services/dataService';
import type { Contact } from '../types/contact';
@ -121,6 +124,34 @@ const ContactListPage = () => {
}).format(date);
};
const getNaoStatusIndicator = (contact: Contact) => {
switch (contact.naoStatus) {
case 'member':
return {
icon: <CheckCircle sx={{ fontSize: 16 }} />,
label: 'NAO Member',
color: theme.palette.success.main,
bgColor: alpha(theme.palette.success.main, 0.08),
borderColor: alpha(theme.palette.success.main, 0.2)
};
case 'invited':
return {
icon: <Schedule sx={{ fontSize: 16 }} />,
label: 'Invited',
color: theme.palette.warning.main,
bgColor: alpha(theme.palette.warning.main, 0.08),
borderColor: alpha(theme.palette.warning.main, 0.2)
};
default:
return null;
}
};
const handleInviteToNao = (contact: Contact) => {
// Navigate to invitation page with contact pre-filled
navigate(`/invite?inviteeName=${encodeURIComponent(contact.name)}&inviteeEmail=${encodeURIComponent(contact.email)}`);
};
return (
<Box sx={{ height: '100%' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
@ -293,6 +324,31 @@ const ContactListPage = () => {
}}
/>
)}
{/* NAO Status Indicator */}
{(() => {
const naoStatus = getNaoStatusIndicator(contact);
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,
},
}}
/>
) : null;
})()}
{/* Vouch and Praise Indicators */}
<Chip
icon={<VerifiedUser sx={{ fontSize: 14 }} />}
@ -361,24 +417,48 @@ const ContactListPage = () => {
</Typography>
</Box>
{/* Select button for selection mode */}
{isSelectionMode && (
<Button
variant="contained"
onClick={(e) => {
e.stopPropagation();
handleSelectContact(contact);
}}
sx={{
borderRadius: 2,
textTransform: 'none',
alignSelf: 'center',
minWidth: 80
}}
>
Select
</Button>
)}
{/* Action buttons */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, alignSelf: 'center' }}>
{/* Select button for selection mode */}
{isSelectionMode && (
<Button
variant="contained"
onClick={(e) => {
e.stopPropagation();
handleSelectContact(contact);
}}
sx={{
borderRadius: 2,
textTransform: 'none',
minWidth: 80
}}
>
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: 80,
fontSize: '0.875rem',
py: 0.5,
px: 1.5
}}
>
Invite
</Button>
)}
</Box>
</Box>
</CardContent>
</Card>

@ -11,6 +11,9 @@ export interface Contact {
notes?: string;
tags?: string[];
groupIds?: string[];
naoStatus?: 'member' | 'invited' | 'not_invited';
invitedAt?: Date;
joinedAt?: Date;
createdAt: Date;
updatedAt: Date;
}

Loading…
Cancel
Save