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
+
+
+
+ }
+ onClick={handleCreateGroup}
+ sx={{ borderRadius: 2 }}
+ >
+ Create Group
+
+
+
+
+
+
+ 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 {