Implement contact selection for group invitations

- Added selection mode support to ContactListPage
- Added Select buttons for each contact when in selection mode
- Modified header and UI to show selection context
- Disabled normal contact click behavior in selection mode
- Implemented return navigation with selected contact data
- Fixed invite form button visibility issue with proper spacing

Flow: Group Invite → Select from Network → Contact List → Select Contact → Back to Group Invite (prefilled)

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

Co-Authored-By: Claude <noreply@anthropic.com>
main
Claude Code Assistant 2 months ago
parent 9a2170ec86
commit 06c41e8397
  1. 4
      src/components/invite/InviteForm.tsx
  2. 105
      src/pages/ContactListPage.tsx

@ -141,9 +141,9 @@ const InviteForm: React.FC<InviteFormProps> = ({
</Box> </Box>
</DialogTitle> </DialogTitle>
<DialogContent sx={{ p: 3 }}> <DialogContent sx={{ p: 3, pt: 2 }}>
{/* Network Selection Option */} {/* Network Selection Option */}
<Box sx={{ mb: 3, textAlign: 'center' }}> <Box sx={{ mb: 3, mt: 2, textAlign: 'center' }}>
<Button <Button
variant="outlined" variant="outlined"
startIcon={<ContactPage />} startIcon={<ContactPage />}

@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate, useSearchParams } from 'react-router-dom';
import { import {
Typography, Typography,
Box, Box,
@ -42,6 +42,12 @@ const ContactListPage = () => {
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const theme = useTheme(); const theme = useTheme();
const navigate = useNavigate(); const navigate = useNavigate();
const [searchParams] = useSearchParams();
// Check if we're in selection mode for group invitations
const isSelectionMode = searchParams.get('mode') === 'select';
const returnTo = searchParams.get('returnTo');
const groupId = searchParams.get('groupId');
useEffect(() => { useEffect(() => {
const loadContacts = async () => { const loadContacts = async () => {
@ -70,9 +76,20 @@ const ContactListPage = () => {
}, [searchQuery, contacts]); }, [searchQuery, contacts]);
const handleContactClick = (contactId: string) => { const handleContactClick = (contactId: string) => {
if (isSelectionMode) {
// Don't navigate in selection mode, let the select button handle it
return;
}
navigate(`/contacts/${contactId}`); navigate(`/contacts/${contactId}`);
}; };
const handleSelectContact = (contact: Contact) => {
if (returnTo === 'group-invite' && groupId) {
// Navigate back to group page with selected contact data
navigate(`/groups/${groupId}?selectedContactName=${encodeURIComponent(contact.name)}&selectedContactEmail=${encodeURIComponent(contact.email)}`);
}
};
const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => { const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => {
setTabValue(newValue); setTabValue(newValue);
}; };
@ -109,35 +126,42 @@ const ContactListPage = () => {
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Box> <Box>
<Typography variant="h4" component="h1" sx={{ fontWeight: 700, mb: 1 }}> <Typography variant="h4" component="h1" sx={{ fontWeight: 700, mb: 1 }}>
Contacts {isSelectionMode ? 'Select Contact to Invite' : 'Contacts'}
</Typography> </Typography>
{isSelectionMode && (
<Typography variant="body2" color="text.secondary">
Choose a contact from your network to invite to the group
</Typography>
)}
</Box> </Box>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}> {!isSelectionMode && (
<Button <Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
variant="outlined" <Button
startIcon={<CloudDownload />} variant="outlined"
onClick={() => navigate('/import')} startIcon={<CloudDownload />}
sx={{ borderRadius: 2 }} onClick={() => navigate('/import')}
> sx={{ borderRadius: 2 }}
Import >
</Button> Import
<Button </Button>
variant="outlined" <Button
startIcon={<QrCode />} variant="outlined"
onClick={handleInvite} startIcon={<QrCode />}
sx={{ borderRadius: 2 }} onClick={handleInvite}
> sx={{ borderRadius: 2 }}
Invite >
</Button> Invite
<Button </Button>
variant="contained" <Button
startIcon={<Add />} variant="contained"
onClick={handleAddContact} startIcon={<Add />}
sx={{ borderRadius: 2 }} onClick={handleAddContact}
> sx={{ borderRadius: 2 }}
Add Contacts >
</Button> Add Contacts
</Box> </Button>
</Box>
)}
</Box> </Box>
<Card sx={{ mb: 3 }}> <Card sx={{ mb: 3 }}>
@ -209,15 +233,15 @@ const ContactListPage = () => {
<Card <Card
onClick={() => handleContactClick(contact.id)} onClick={() => handleContactClick(contact.id)}
sx={{ sx={{
cursor: 'pointer', cursor: isSelectionMode ? 'default' : 'pointer',
transition: 'all 0.2s ease-in-out', transition: 'all 0.2s ease-in-out',
border: 1, border: 1,
borderColor: 'divider', borderColor: 'divider',
'&:hover': { '&:hover': !isSelectionMode ? {
borderColor: 'primary.main', borderColor: 'primary.main',
boxShadow: theme.shadows[4], boxShadow: theme.shadows[4],
transform: 'translateY(-2px)', transform: 'translateY(-2px)',
}, } : {},
}} }}
> >
<CardContent sx={{ p: 3 }}> <CardContent sx={{ p: 3 }}>
@ -336,6 +360,25 @@ const ContactListPage = () => {
Added {formatDate(contact.createdAt)} Added {formatDate(contact.createdAt)}
</Typography> </Typography>
</Box> </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>
)}
</Box> </Box>
</CardContent> </CardContent>
</Card> </Card>

Loading…
Cancel
Save