added groups

main
Samuel Gbafa 2 months ago
parent 3194f76839
commit 4434609ec9
  1. 5
      .claude/commands/fix-issue.md
  2. 6
      data/contacts.json
  3. 80
      data/groups.json
  4. 80
      public/groups.json
  5. 2
      src/App.tsx
  6. 2
      src/components/layout/DashboardLayout.tsx
  7. 22
      src/pages/ContactListPage.tsx
  8. 52
      src/pages/ContactViewPage.tsx
  9. 243
      src/pages/GroupPage.tsx
  10. 67
      src/services/dataService.ts
  11. 1
      src/types/contact.ts
  12. 20
      src/types/group.ts
  13. 1
      src/types/onboarding.ts

@ -0,0 +1,5 @@
This command is being called by someone who is not a developer. Your job is to take steps to accomplish the stated goal.
ultrathink about the root cause of the following problem. Use subagents to explore if needed. Create a plan to address the root cause. Follow this use plan and then fix it.
Optional context: #$ARGUMENTS

@ -11,6 +11,7 @@
"linkedinUrl": "https://linkedin.com/in/johnsmith", "linkedinUrl": "https://linkedin.com/in/johnsmith",
"notes": "Met at React conference 2023", "notes": "Met at React conference 2023",
"tags": ["developer", "react", "frontend"], "tags": ["developer", "react", "frontend"],
"groupIds": ["1", "4"],
"createdAt": "2023-10-01T10:00:00Z", "createdAt": "2023-10-01T10:00:00Z",
"updatedAt": "2023-10-15T14:30:00Z" "updatedAt": "2023-10-15T14:30:00Z"
}, },
@ -26,6 +27,7 @@
"linkedinUrl": "https://linkedin.com/in/sarahjohnson", "linkedinUrl": "https://linkedin.com/in/sarahjohnson",
"notes": "Potential collaboration on new project", "notes": "Potential collaboration on new project",
"tags": ["product", "startup", "management"], "tags": ["product", "startup", "management"],
"groupIds": ["2", "5", "6"],
"createdAt": "2023-09-15T09:00:00Z", "createdAt": "2023-09-15T09:00:00Z",
"updatedAt": "2023-10-20T16:45:00Z" "updatedAt": "2023-10-20T16:45:00Z"
}, },
@ -40,6 +42,7 @@
"profileImage": "https://i.pravatar.cc/150?img=3", "profileImage": "https://i.pravatar.cc/150?img=3",
"notes": "Design consultant for mobile app", "notes": "Design consultant for mobile app",
"tags": ["design", "ux", "mobile"], "tags": ["design", "ux", "mobile"],
"groupIds": ["1", "3"],
"createdAt": "2023-08-20T11:30:00Z", "createdAt": "2023-08-20T11:30:00Z",
"updatedAt": "2023-09-10T13:15:00Z" "updatedAt": "2023-09-10T13:15:00Z"
}, },
@ -55,6 +58,7 @@
"linkedinUrl": "https://linkedin.com/in/emilyrodriguez", "linkedinUrl": "https://linkedin.com/in/emilyrodriguez",
"notes": "Former colleague from previous company", "notes": "Former colleague from previous company",
"tags": ["engineering", "management", "scaling"], "tags": ["engineering", "management", "scaling"],
"groupIds": ["2", "4"],
"createdAt": "2023-07-10T08:00:00Z", "createdAt": "2023-07-10T08:00:00Z",
"updatedAt": "2023-08-25T10:20:00Z" "updatedAt": "2023-08-25T10:20:00Z"
}, },
@ -69,6 +73,7 @@
"profileImage": "https://i.pravatar.cc/150?img=5", "profileImage": "https://i.pravatar.cc/150?img=5",
"notes": "Helped with business strategy", "notes": "Helped with business strategy",
"tags": ["consulting", "strategy", "business"], "tags": ["consulting", "strategy", "business"],
"groupIds": ["5", "6"],
"createdAt": "2023-06-05T14:00:00Z", "createdAt": "2023-06-05T14:00:00Z",
"updatedAt": "2023-07-15T09:30:00Z" "updatedAt": "2023-07-15T09:30:00Z"
}, },
@ -84,6 +89,7 @@
"linkedinUrl": "https://linkedin.com/in/lisathompson", "linkedinUrl": "https://linkedin.com/in/lisathompson",
"notes": "Great at digital marketing strategies", "notes": "Great at digital marketing strategies",
"tags": ["marketing", "digital", "strategy"], "tags": ["marketing", "digital", "strategy"],
"groupIds": ["3", "5", "6"],
"createdAt": "2023-05-12T12:00:00Z", "createdAt": "2023-05-12T12:00:00Z",
"updatedAt": "2023-06-20T15:45:00Z" "updatedAt": "2023-06-20T15:45:00Z"
} }

@ -0,0 +1,80 @@
[
{
"id": "1",
"name": "React Developers",
"description": "A group for React developers to share knowledge and collaborate on projects",
"memberCount": 15,
"memberIds": ["1", "2", "3"],
"createdBy": "1",
"createdAt": "2023-08-01T10:00:00Z",
"updatedAt": "2023-10-15T14:30:00Z",
"isPrivate": false,
"tags": ["react", "frontend", "development"],
"image": "https://i.pravatar.cc/200?img=react"
},
{
"id": "2",
"name": "Product Management Circle",
"description": "Product managers discussing strategies, tools, and best practices",
"memberCount": 8,
"memberIds": ["2", "4"],
"createdBy": "2",
"createdAt": "2023-09-10T09:00:00Z",
"updatedAt": "2023-10-20T16:45:00Z",
"isPrivate": false,
"tags": ["product", "management", "strategy"],
"image": "https://i.pravatar.cc/200?img=product"
},
{
"id": "3",
"name": "UX Design Collective",
"description": "Designers sharing insights, resources, and feedback on user experience",
"memberCount": 12,
"memberIds": ["3", "6"],
"createdBy": "3",
"createdAt": "2023-07-15T11:30:00Z",
"updatedAt": "2023-09-25T13:15:00Z",
"isPrivate": false,
"tags": ["design", "ux", "ui"],
"image": "https://i.pravatar.cc/200?img=design"
},
{
"id": "4",
"name": "Engineering Leadership",
"description": "Engineering managers and tech leads discussing team management and technical strategy",
"memberCount": 6,
"memberIds": ["4", "1"],
"createdBy": "4",
"createdAt": "2023-06-20T08:00:00Z",
"updatedAt": "2023-08-30T10:20:00Z",
"isPrivate": true,
"tags": ["engineering", "leadership", "management"],
"image": "https://i.pravatar.cc/200?img=leadership"
},
{
"id": "5",
"name": "Business Strategy Network",
"description": "Business consultants and strategists sharing market insights and methodologies",
"memberCount": 10,
"memberIds": ["5", "2", "6"],
"createdBy": "5",
"createdAt": "2023-05-05T14:00:00Z",
"updatedAt": "2023-07-20T09:30:00Z",
"isPrivate": false,
"tags": ["business", "strategy", "consulting"],
"image": "https://i.pravatar.cc/200?img=business"
},
{
"id": "6",
"name": "Digital Marketing Hub",
"description": "Marketing professionals sharing campaigns, tools, and growth strategies",
"memberCount": 20,
"memberIds": ["6", "2", "5"],
"createdBy": "6",
"createdAt": "2023-04-10T12:00:00Z",
"updatedAt": "2023-06-25T15:45:00Z",
"isPrivate": false,
"tags": ["marketing", "digital", "growth"],
"image": "https://i.pravatar.cc/200?img=marketing"
}
]

@ -0,0 +1,80 @@
[
{
"id": "1",
"name": "React Developers",
"description": "A group for React developers to share knowledge and collaborate on projects",
"memberCount": 15,
"memberIds": ["1", "2", "3"],
"createdBy": "1",
"createdAt": "2023-08-01T10:00:00Z",
"updatedAt": "2023-10-15T14:30:00Z",
"isPrivate": false,
"tags": ["react", "frontend", "development"],
"image": "https://i.pravatar.cc/200?img=react"
},
{
"id": "2",
"name": "Product Management Circle",
"description": "Product managers discussing strategies, tools, and best practices",
"memberCount": 8,
"memberIds": ["2", "4"],
"createdBy": "2",
"createdAt": "2023-09-10T09:00:00Z",
"updatedAt": "2023-10-20T16:45:00Z",
"isPrivate": false,
"tags": ["product", "management", "strategy"],
"image": "https://i.pravatar.cc/200?img=product"
},
{
"id": "3",
"name": "UX Design Collective",
"description": "Designers sharing insights, resources, and feedback on user experience",
"memberCount": 12,
"memberIds": ["3", "6"],
"createdBy": "3",
"createdAt": "2023-07-15T11:30:00Z",
"updatedAt": "2023-09-25T13:15:00Z",
"isPrivate": false,
"tags": ["design", "ux", "ui"],
"image": "https://i.pravatar.cc/200?img=design"
},
{
"id": "4",
"name": "Engineering Leadership",
"description": "Engineering managers and tech leads discussing team management and technical strategy",
"memberCount": 6,
"memberIds": ["4", "1"],
"createdBy": "4",
"createdAt": "2023-06-20T08:00:00Z",
"updatedAt": "2023-08-30T10:20:00Z",
"isPrivate": true,
"tags": ["engineering", "leadership", "management"],
"image": "https://i.pravatar.cc/200?img=leadership"
},
{
"id": "5",
"name": "Business Strategy Network",
"description": "Business consultants and strategists sharing market insights and methodologies",
"memberCount": 10,
"memberIds": ["5", "2", "6"],
"createdBy": "5",
"createdAt": "2023-05-05T14:00:00Z",
"updatedAt": "2023-07-20T09:30:00Z",
"isPrivate": false,
"tags": ["business", "strategy", "consulting"],
"image": "https://i.pravatar.cc/200?img=business"
},
{
"id": "6",
"name": "Digital Marketing Hub",
"description": "Marketing professionals sharing campaigns, tools, and growth strategies",
"memberCount": 20,
"memberIds": ["6", "2", "5"],
"createdBy": "6",
"createdAt": "2023-04-10T12:00:00Z",
"updatedAt": "2023-06-25T15:45:00Z",
"isPrivate": false,
"tags": ["marketing", "digital", "growth"],
"image": "https://i.pravatar.cc/200?img=marketing"
}
]

@ -6,6 +6,7 @@ import DashboardLayout from './components/layout/DashboardLayout';
import ImportPage from './pages/ImportPage'; import ImportPage from './pages/ImportPage';
import ContactListPage from './pages/ContactListPage'; import ContactListPage from './pages/ContactListPage';
import ContactViewPage from './pages/ContactViewPage'; import ContactViewPage from './pages/ContactViewPage';
import GroupPage from './pages/GroupPage';
import InvitationPage from './pages/InvitationPage'; import InvitationPage from './pages/InvitationPage';
import OnboardingPage from './pages/OnboardingPage'; import OnboardingPage from './pages/OnboardingPage';
import { createAppTheme } from './theme/theme'; import { createAppTheme } from './theme/theme';
@ -23,6 +24,7 @@ function App() {
<Route path="/" element={<ImportPage />} /> <Route path="/" element={<ImportPage />} />
<Route path="/contacts" element={<ContactListPage />} /> <Route path="/contacts" element={<ContactListPage />} />
<Route path="/contacts/:id" element={<ContactViewPage />} /> <Route path="/contacts/:id" element={<ContactViewPage />} />
<Route path="/groups" element={<GroupPage />} />
<Route path="/invite" element={<InvitationPage />} /> <Route path="/invite" element={<InvitationPage />} />
<Route path="/onboarding" element={<OnboardingPage />} /> <Route path="/onboarding" element={<OnboardingPage />} />
</Routes> </Routes>

@ -31,6 +31,7 @@ import {
Logout, Logout,
NotificationsNone, NotificationsNone,
SearchRounded, SearchRounded,
Group,
} from '@mui/icons-material'; } from '@mui/icons-material';
const drawerWidth = 280; const drawerWidth = 280;
@ -56,6 +57,7 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => {
const navItems: NavItem[] = [ const navItems: NavItem[] = [
{ text: 'Contacts', icon: <People />, path: '/contacts' }, { text: 'Contacts', icon: <People />, path: '/contacts' },
{ text: 'Groups', icon: <Group />, path: '/groups' },
{ text: 'Import', icon: <CloudDownload />, path: '/' }, { text: 'Import', icon: <CloudDownload />, path: '/' },
{ text: 'Invite', icon: <QrCode />, path: '/invite' }, { text: 'Invite', icon: <QrCode />, path: '/invite' },
{ text: 'Join Network', icon: <PersonAdd />, path: '/onboarding' }, { text: 'Join Network', icon: <PersonAdd />, path: '/onboarding' },

@ -26,7 +26,8 @@ import {
LinkedIn, LinkedIn,
Phone, Phone,
Email, Email,
QrCode QrCode,
Group
} from '@mui/icons-material'; } from '@mui/icons-material';
import { dataService } from '../services/dataService'; import { dataService } from '../services/dataService';
import type { Contact } from '../types/contact'; import type { Contact } from '../types/contact';
@ -228,6 +229,25 @@ const ContactListPage = () => {
{contact.name} {contact.name}
</Typography> </Typography>
{getSourceIcon(contact.source)} {getSourceIcon(contact.source)}
{contact.groupIds && contact.groupIds.length > 0 && (
<Chip
icon={<Group sx={{ fontSize: 14 }} />}
label={contact.groupIds.length}
size="small"
variant="outlined"
sx={{
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,
},
}}
/>
)}
</Box> </Box>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}> <Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>

@ -26,14 +26,17 @@ import {
Delete, Delete,
Person, Person,
Schedule, Schedule,
Source Source,
Group
} from '@mui/icons-material'; } from '@mui/icons-material';
import { dataService } from '../services/dataService'; import { dataService } from '../services/dataService';
import type { Contact } from '../types/contact'; import type { Contact } from '../types/contact';
import type { Group as GroupType } from '../types/group';
const ContactViewPage = () => { const ContactViewPage = () => {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const [contact, setContact] = useState<Contact | null>(null); const [contact, setContact] = useState<Contact | null>(null);
const [contactGroups, setContactGroups] = useState<GroupType[]>([]);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const navigate = useNavigate(); const navigate = useNavigate();
@ -48,9 +51,20 @@ const ContactViewPage = () => {
setIsLoading(true); setIsLoading(true);
try { try {
const contactData = await dataService.getContact(id); const [contactData, allGroups] = await Promise.all([
dataService.getContact(id),
dataService.getGroups()
]);
if (contactData) { if (contactData) {
setContact(contactData); setContact(contactData);
// Filter groups that the contact belongs to
const contactGroupIds = contactData.groupIds || [];
const userGroups = allGroups.filter(group =>
contactGroupIds.includes(group.id)
);
setContactGroups(userGroups);
} else { } else {
setError('Contact not found'); setError('Contact not found');
} }
@ -347,6 +361,40 @@ const ContactViewPage = () => {
</Typography> </Typography>
</Box> </Box>
)} )}
{contactGroups.length > 0 && (
<Box sx={{ mt: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<Group sx={{ mr: 2, color: 'text.secondary' }} />
<Box>
<Typography variant="body2" color="text.secondary">
Groups
</Typography>
<Typography variant="body1">
Member of {contactGroups.length} group{contactGroups.length > 1 ? 's' : ''}
</Typography>
</Box>
</Box>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
{contactGroups.map((group) => (
<Chip
key={group.id}
label={group.name}
size="small"
variant="outlined"
clickable
onClick={() => navigate(`/groups/${group.id}`)}
sx={{
borderRadius: 1,
'&:hover': {
backgroundColor: 'action.hover',
},
}}
/>
))}
</Box>
</Box>
)}
</CardContent> </CardContent>
</Card> </Card>
</Grid> </Grid>

@ -0,0 +1,243 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Typography,
Box,
Avatar,
Chip,
TextField,
InputAdornment,
Button,
Card,
CardContent,
Grid,
alpha,
useTheme,
Badge
} from '@mui/material';
import {
Search,
Add,
Group,
Public,
Lock,
People
} from '@mui/icons-material';
import { dataService } from '../services/dataService';
import type { Group as GroupType } from '../types/group';
const GroupPage = () => {
const [groups, setGroups] = useState<GroupType[]>([]);
const [filteredGroups, setFilteredGroups] = useState<GroupType[]>([]);
const [searchQuery, setSearchQuery] = useState('');
const [isLoading, setIsLoading] = useState(true);
const theme = useTheme();
const navigate = useNavigate();
useEffect(() => {
const loadGroups = async () => {
setIsLoading(true);
try {
const groupsData = await dataService.getGroups();
setGroups(groupsData);
setFilteredGroups(groupsData);
} catch (error) {
console.error('Failed to load groups:', error);
} finally {
setIsLoading(false);
}
};
loadGroups();
}, []);
useEffect(() => {
const filtered = groups.filter(group =>
group.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
group.description?.toLowerCase().includes(searchQuery.toLowerCase()) ||
group.tags?.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase()))
);
setFilteredGroups(filtered);
}, [searchQuery, groups]);
const handleGroupClick = (groupId: string) => {
navigate(`/groups/${groupId}`);
};
const handleCreateGroup = () => {
// TODO: Navigate to create group page
console.log('Create group functionality coming soon');
};
const getPrivacyIcon = (isPrivate: boolean) => {
return isPrivate ? (
<Lock sx={{ fontSize: 16, color: '#ff9800' }} />
) : (
<Public sx={{ fontSize: 16, color: '#4caf50' }} />
);
};
const formatDate = (date: Date) => {
return new Intl.DateTimeFormat('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
}).format(date);
};
return (
<Box sx={{ height: '100%' }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
<Box>
<Typography variant="h4" component="h1" sx={{ fontWeight: 700, mb: 1 }}>
Groups
</Typography>
<Typography variant="body1" color="text.secondary">
Explore and manage your {filteredGroups.length} groups
</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button
variant="contained"
startIcon={<Add />}
onClick={handleCreateGroup}
sx={{ borderRadius: 2 }}
>
Create Group
</Button>
</Box>
</Box>
<Card sx={{ mb: 3 }}>
<Box sx={{ p: 3 }}>
<TextField
fullWidth
placeholder="Search groups..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Search />
</InputAdornment>
),
}}
sx={{ mb: 3 }}
/>
{isLoading ? (
<Box sx={{ textAlign: 'center', py: 8 }}>
<Typography variant="h6" color="text.secondary" gutterBottom>
Loading groups...
</Typography>
<Typography variant="body2" color="text.secondary">
Please wait while we fetch your groups
</Typography>
</Box>
) : filteredGroups.length === 0 ? (
<Box sx={{ textAlign: 'center', py: 8 }}>
<Typography variant="h6" color="text.secondary" gutterBottom>
{searchQuery ? 'No groups found' : 'No groups yet'}
</Typography>
<Typography variant="body2" color="text.secondary">
{searchQuery ? 'Try adjusting your search terms.' : 'Create your first group to get started!'}
</Typography>
</Box>
) : (
<Grid container spacing={2}>
{filteredGroups.map((group) => (
<Grid size={{ xs: 12, md: 6, lg: 4 }} key={group.id}>
<Card
onClick={() => handleGroupClick(group.id)}
sx={{
cursor: 'pointer',
transition: 'all 0.2s ease-in-out',
border: 1,
borderColor: 'divider',
height: '100%',
'&:hover': {
borderColor: 'primary.main',
boxShadow: theme.shadows[4],
transform: 'translateY(-2px)',
},
}}
>
<CardContent sx={{ p: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
<Avatar
src={group.image}
alt={group.name}
sx={{ width: 48, height: 48, bgcolor: 'primary.main' }}
>
<Group />
</Avatar>
<Box sx={{ flexGrow: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1 }}>
<Typography variant="h6" component="div" sx={{ fontWeight: 600 }}>
{group.name}
</Typography>
{getPrivacyIcon(group.isPrivate)}
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Badge
badgeContent={group.memberCount}
color="primary"
sx={{
'& .MuiBadge-badge': {
position: 'static',
transform: 'none',
fontSize: '0.75rem',
minWidth: 'auto',
height: 20,
borderRadius: 1
}
}}
>
<People sx={{ fontSize: 16, color: 'text.secondary' }} />
</Badge>
<Typography variant="body2" color="text.secondary">
members
</Typography>
</Box>
</Box>
</Box>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2, minHeight: 40 }}>
{group.description}
</Typography>
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap', mb: 2 }}>
{group.tags?.map((tag) => (
<Chip
key={tag}
label={tag}
size="small"
variant="outlined"
sx={{
borderRadius: 1,
backgroundColor: alpha(theme.palette.primary.main, 0.04),
borderColor: alpha(theme.palette.primary.main, 0.12),
color: 'primary.main',
fontWeight: 500,
}}
/>
))}
</Box>
<Typography variant="caption" color="text.secondary">
Created {formatDate(group.createdAt)}
</Typography>
</CardContent>
</Card>
</Grid>
))}
</Grid>
)}
</Box>
</Card>
</Box>
);
};
export default GroupPage;

@ -1,4 +1,5 @@
import type { Contact, ImportSource } from '../types/contact'; import type { Contact, ImportSource } from '../types/contact';
import type { Group } from '../types/group';
export const dataService = { export const dataService = {
async getContacts(): Promise<Contact[]> { async getContacts(): Promise<Contact[]> {
@ -80,5 +81,71 @@ export const dataService = {
} }
}, 2000); }, 2000);
}); });
},
async getGroups(): Promise<Group[]> {
return new Promise(async (resolve) => {
setTimeout(async () => {
try {
const response = await fetch('/groups.json');
const groupsData = await response.json();
const groups = groupsData.map((group: any) => ({
...group,
createdAt: new Date(group.createdAt),
updatedAt: new Date(group.updatedAt)
}));
resolve(groups);
} catch (error) {
console.error('Failed to load groups:', error);
resolve([]);
}
}, 500);
});
},
async getGroup(id: string): Promise<Group | undefined> {
return new Promise(async (resolve) => {
setTimeout(async () => {
try {
const response = await fetch('/groups.json');
const groupsData = await response.json();
const group = groupsData.find((g: any) => g.id === id);
if (group) {
resolve({
...group,
createdAt: new Date(group.createdAt),
updatedAt: new Date(group.updatedAt)
});
} else {
resolve(undefined);
}
} catch (error) {
console.error('Failed to load group:', error);
resolve(undefined);
}
}, 300);
});
},
async getGroupsForUser(userId: string): Promise<Group[]> {
return new Promise(async (resolve) => {
setTimeout(async () => {
try {
const response = await fetch('/groups.json');
const groupsData = await response.json();
const userGroups = groupsData
.filter((group: any) => group.memberIds.includes(userId))
.map((group: any) => ({
...group,
createdAt: new Date(group.createdAt),
updatedAt: new Date(group.updatedAt)
}));
resolve(userGroups);
} catch (error) {
console.error('Failed to load user groups:', error);
resolve([]);
}
}, 400);
});
} }
}; };

@ -10,6 +10,7 @@ export interface Contact {
linkedinUrl?: string; linkedinUrl?: string;
notes?: string; notes?: string;
tags?: string[]; tags?: string[];
groupIds?: string[];
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
} }

@ -0,0 +1,20 @@
export interface Group {
id: string;
name: string;
description?: string;
memberCount: number;
memberIds: string[];
createdBy: string;
createdAt: Date;
updatedAt: Date;
isPrivate: boolean;
tags?: string[];
image?: string;
}
export interface GroupMember {
userId: string;
groupId: string;
joinedAt: Date;
role: 'admin' | 'member' | 'moderator';
}

@ -6,6 +6,7 @@ export interface UserProfile {
company?: string; company?: string;
position?: string; position?: string;
bio?: string; bio?: string;
groupIds?: string[];
} }
export interface ConnectedAccount { export interface ConnectedAccount {

Loading…
Cancel
Save