diff --git a/.claude/commands/fix-issue.md b/.claude/commands/fix-issue.md new file mode 100644 index 0000000..0a4eb91 --- /dev/null +++ b/.claude/commands/fix-issue.md @@ -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 diff --git a/data/contacts.json b/data/contacts.json index 63297dc..5bf60dd 100644 --- a/data/contacts.json +++ b/data/contacts.json @@ -11,6 +11,7 @@ "linkedinUrl": "https://linkedin.com/in/johnsmith", "notes": "Met at React conference 2023", "tags": ["developer", "react", "frontend"], + "groupIds": ["1", "4"], "createdAt": "2023-10-01T10:00:00Z", "updatedAt": "2023-10-15T14:30:00Z" }, @@ -26,6 +27,7 @@ "linkedinUrl": "https://linkedin.com/in/sarahjohnson", "notes": "Potential collaboration on new project", "tags": ["product", "startup", "management"], + "groupIds": ["2", "5", "6"], "createdAt": "2023-09-15T09:00:00Z", "updatedAt": "2023-10-20T16:45:00Z" }, @@ -40,6 +42,7 @@ "profileImage": "https://i.pravatar.cc/150?img=3", "notes": "Design consultant for mobile app", "tags": ["design", "ux", "mobile"], + "groupIds": ["1", "3"], "createdAt": "2023-08-20T11:30:00Z", "updatedAt": "2023-09-10T13:15:00Z" }, @@ -55,6 +58,7 @@ "linkedinUrl": "https://linkedin.com/in/emilyrodriguez", "notes": "Former colleague from previous company", "tags": ["engineering", "management", "scaling"], + "groupIds": ["2", "4"], "createdAt": "2023-07-10T08:00:00Z", "updatedAt": "2023-08-25T10:20:00Z" }, @@ -69,6 +73,7 @@ "profileImage": "https://i.pravatar.cc/150?img=5", "notes": "Helped with business strategy", "tags": ["consulting", "strategy", "business"], + "groupIds": ["5", "6"], "createdAt": "2023-06-05T14:00:00Z", "updatedAt": "2023-07-15T09:30:00Z" }, @@ -84,6 +89,7 @@ "linkedinUrl": "https://linkedin.com/in/lisathompson", "notes": "Great at digital marketing strategies", "tags": ["marketing", "digital", "strategy"], + "groupIds": ["3", "5", "6"], "createdAt": "2023-05-12T12:00:00Z", "updatedAt": "2023-06-20T15:45:00Z" } diff --git a/data/groups.json b/data/groups.json new file mode 100644 index 0000000..10cac92 --- /dev/null +++ b/data/groups.json @@ -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" + } +] \ No newline at end of file diff --git a/public/groups.json b/public/groups.json new file mode 100644 index 0000000..10cac92 --- /dev/null +++ b/public/groups.json @@ -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" + } +] \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 64a77a6..bd18138 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ import DashboardLayout from './components/layout/DashboardLayout'; import ImportPage from './pages/ImportPage'; import ContactListPage from './pages/ContactListPage'; import ContactViewPage from './pages/ContactViewPage'; +import GroupPage from './pages/GroupPage'; import InvitationPage from './pages/InvitationPage'; import OnboardingPage from './pages/OnboardingPage'; import { createAppTheme } from './theme/theme'; @@ -23,6 +24,7 @@ function App() { } /> } /> } /> + } /> } /> } /> diff --git a/src/components/layout/DashboardLayout.tsx b/src/components/layout/DashboardLayout.tsx index 897d82a..c2267fa 100644 --- a/src/components/layout/DashboardLayout.tsx +++ b/src/components/layout/DashboardLayout.tsx @@ -31,6 +31,7 @@ import { Logout, NotificationsNone, SearchRounded, + Group, } from '@mui/icons-material'; const drawerWidth = 280; @@ -56,6 +57,7 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => { const navItems: NavItem[] = [ { text: 'Contacts', icon: , path: '/contacts' }, + { text: 'Groups', icon: , path: '/groups' }, { text: 'Import', icon: , path: '/' }, { text: 'Invite', icon: , path: '/invite' }, { text: 'Join Network', icon: , path: '/onboarding' }, diff --git a/src/pages/ContactListPage.tsx b/src/pages/ContactListPage.tsx index b09c5fb..94edcf4 100644 --- a/src/pages/ContactListPage.tsx +++ b/src/pages/ContactListPage.tsx @@ -26,7 +26,8 @@ import { LinkedIn, Phone, Email, - QrCode + QrCode, + Group } from '@mui/icons-material'; import { dataService } from '../services/dataService'; import type { Contact } from '../types/contact'; @@ -228,6 +229,25 @@ const ContactListPage = () => { {contact.name} {getSourceIcon(contact.source)} + {contact.groupIds && contact.groupIds.length > 0 && ( + } + 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, + }, + }} + /> + )} diff --git a/src/pages/ContactViewPage.tsx b/src/pages/ContactViewPage.tsx index 614587f..5465de3 100644 --- a/src/pages/ContactViewPage.tsx +++ b/src/pages/ContactViewPage.tsx @@ -26,14 +26,17 @@ import { Delete, Person, Schedule, - Source + Source, + Group } from '@mui/icons-material'; import { dataService } from '../services/dataService'; import type { Contact } from '../types/contact'; +import type { Group as GroupType } from '../types/group'; const ContactViewPage = () => { const { id } = useParams<{ id: string }>(); const [contact, setContact] = useState(null); + const [contactGroups, setContactGroups] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const navigate = useNavigate(); @@ -48,9 +51,20 @@ const ContactViewPage = () => { setIsLoading(true); try { - const contactData = await dataService.getContact(id); + const [contactData, allGroups] = await Promise.all([ + dataService.getContact(id), + dataService.getGroups() + ]); + if (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 { setError('Contact not found'); } @@ -347,6 +361,40 @@ const ContactViewPage = () => { )} + + {contactGroups.length > 0 && ( + + + + + + Groups + + + Member of {contactGroups.length} group{contactGroups.length > 1 ? 's' : ''} + + + + + {contactGroups.map((group) => ( + navigate(`/groups/${group.id}`)} + sx={{ + borderRadius: 1, + '&:hover': { + backgroundColor: 'action.hover', + }, + }} + /> + ))} + + + )} diff --git a/src/pages/GroupPage.tsx b/src/pages/GroupPage.tsx new file mode 100644 index 0000000..02b8e31 --- /dev/null +++ b/src/pages/GroupPage.tsx @@ -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([]); + const [filteredGroups, setFilteredGroups] = useState([]); + 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 ? ( + + ) : ( + + ); + }; + + const formatDate = (date: Date) => { + return new Intl.DateTimeFormat('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }).format(date); + }; + + return ( + + + + + Groups + + + Explore and manage your {filteredGroups.length} groups + + + + + + + + + + setSearchQuery(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + sx={{ mb: 3 }} + /> + + {isLoading ? ( + + + Loading groups... + + + Please wait while we fetch your groups + + + ) : filteredGroups.length === 0 ? ( + + + {searchQuery ? 'No groups found' : 'No groups yet'} + + + {searchQuery ? 'Try adjusting your search terms.' : 'Create your first group to get started!'} + + + ) : ( + + {filteredGroups.map((group) => ( + + 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)', + }, + }} + > + + + + + + + + + + {group.name} + + {getPrivacyIcon(group.isPrivate)} + + + + + + + + members + + + + + + + {group.description} + + + + {group.tags?.map((tag) => ( + + ))} + + + + Created {formatDate(group.createdAt)} + + + + + ))} + + )} + + + + ); +}; + +export default GroupPage; \ No newline at end of file diff --git a/src/services/dataService.ts b/src/services/dataService.ts index 911ffc3..2fca703 100644 --- a/src/services/dataService.ts +++ b/src/services/dataService.ts @@ -1,4 +1,5 @@ import type { Contact, ImportSource } from '../types/contact'; +import type { Group } from '../types/group'; export const dataService = { async getContacts(): Promise { @@ -80,5 +81,71 @@ export const dataService = { } }, 2000); }); + }, + + async getGroups(): Promise { + 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 { + 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 { + 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); + }); } }; \ No newline at end of file diff --git a/src/types/contact.ts b/src/types/contact.ts index 35cfa82..9aaf70f 100644 --- a/src/types/contact.ts +++ b/src/types/contact.ts @@ -10,6 +10,7 @@ export interface Contact { linkedinUrl?: string; notes?: string; tags?: string[]; + groupIds?: string[]; createdAt: Date; updatedAt: Date; } diff --git a/src/types/group.ts b/src/types/group.ts new file mode 100644 index 0000000..e078af0 --- /dev/null +++ b/src/types/group.ts @@ -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'; +} \ No newline at end of file diff --git a/src/types/onboarding.ts b/src/types/onboarding.ts index 1ada877..2bf02ce 100644 --- a/src/types/onboarding.ts +++ b/src/types/onboarding.ts @@ -6,6 +6,7 @@ export interface UserProfile { company?: string; position?: string; bio?: string; + groupIds?: string[]; } export interface ConnectedAccount {