diff --git a/public/contacts.json b/public/contacts.json index d72d5d3..9433176 100644 --- a/public/contacts.json +++ b/public/contacts.json @@ -1,99 +1,360 @@ [ { "id": "1", - "name": "John Smith", - "email": "john.smith@example.com", + "name": "Alex Lion Yes!", + "email": "alex.chen@techstartup.com", "phone": "+1 (555) 123-4567", - "company": "Tech Corp", - "position": "Senior Developer", + "company": "Innovation Labs", + "position": "Chief Technology Officer", "source": "linkedin", - "profileImage": "https://i.pravatar.cc/150?img=1", - "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" + "profileImage": "/images/Alex.jpg", + "linkedinUrl": "https://linkedin.com/in/alexchen", + "notes": "Met at AI conference 2024", + "tags": ["ai", "technology", "leadership"], + "naoStatus": "member", + "joinedAt": "2024-01-15T10:00:00Z", + "groupIds": ["group1", "group2"], + "createdAt": "2024-01-01T10:00:00Z", + "updatedAt": "2024-07-20T14:30:00Z" }, { "id": "2", - "name": "Sarah Johnson", - "email": "sarah.johnson@startup.io", + "name": "Ariana Bahrami", + "email": "anna.rodriguez@designstudio.com", "phone": "+1 (555) 987-6543", - "company": "Startup Inc", - "position": "Product Manager", + "company": "Creative Studio", + "position": "Creative Director", "source": "linkedin", - "profileImage": "https://i.pravatar.cc/150?img=2", - "linkedinUrl": "https://linkedin.com/in/sarahjohnson", - "notes": "Potential collaboration on new project", - "tags": ["product", "startup", "management"], + "profileImage": "/images/Anna.jpg", + "linkedinUrl": "https://linkedin.com/in/annarodriguez", + "notes": "Brilliant creative mind and design leader", + "tags": ["design", "creative", "branding"], "naoStatus": "member", - "joinedAt": "2023-08-15T10:00:00Z", - "createdAt": "2023-09-15T09:00:00Z", - "updatedAt": "2023-10-20T16:45:00Z" + "joinedAt": "2024-02-10T09:00:00Z", + "groupIds": ["group1"], + "createdAt": "2024-01-20T09:00:00Z", + "updatedAt": "2024-07-22T16:45:00Z" }, { "id": "3", - "name": "Mike Chen", - "email": "mike.chen@gmail.com", + "name": "Aza Mafi", + "email": "aza.raskin@humantech.org", "phone": "+1 (555) 456-7890", - "company": "Freelance", - "position": "UX Designer", + "company": "Human Technology Institute", + "position": "Co-Founder & President", "source": "contacts", - "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" + "profileImage": "/images/Aza.jpg", + "notes": "Expert in human-centered technology design", + "tags": ["humane-tech", "design", "ethics"], + "naoStatus": "member", + "joinedAt": "2024-01-05T15:30:00Z", + "groupIds": ["group2", "group3"], + "createdAt": "2023-12-15T11:30:00Z", + "updatedAt": "2024-07-25T13:15:00Z" }, { "id": "4", - "name": "Emily Rodriguez", - "email": "emily.rodriguez@bigtech.com", + "name": "Brad de Graf", + "email": "brad.fitzpatrick@livejournal.com", "phone": "+1 (555) 234-5678", - "company": "Big Tech Corp", - "position": "Engineering Manager", + "company": "Tailscale", + "position": "Co-Founder & CTO", "source": "linkedin", - "profileImage": "https://i.pravatar.cc/150?img=4", - "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" + "profileImage": "/images/Brad.jpg", + "linkedinUrl": "https://linkedin.com/in/bradfitz", + "notes": "Created LiveJournal, Go contributor, networking expert", + "tags": ["networking", "golang", "infrastructure"], + "naoStatus": "invited", + "invitedAt": "2024-07-01T15:30:00Z", + "createdAt": "2024-06-20T08:00:00Z", + "updatedAt": "2024-07-10T10:20:00Z" }, { "id": "5", - "name": "David Wilson", - "email": "david.wilson@consulting.com", + "name": "Tim Bansemer", + "email": "bram.cohen@chia.net", "phone": "+1 (555) 345-6789", - "company": "Wilson Consulting", - "position": "Business Consultant", + "company": "Chia Network", + "position": "Founder & CEO", "source": "contacts", - "profileImage": "https://i.pravatar.cc/150?img=8", - "notes": "Helped with business strategy", - "tags": ["consulting", "strategy", "business"], - "naoStatus": "not_invited", - "createdAt": "2023-06-05T14:00:00Z", - "updatedAt": "2023-07-15T09:30:00Z" + "profileImage": "/images/Tim.jpg", + "notes": "Creator of BitTorrent protocol", + "tags": ["blockchain", "protocols", "p2p"], + "naoStatus": "member", + "joinedAt": "2024-03-01T12:00:00Z", + "groupIds": ["group3"], + "createdAt": "2024-02-15T14:00:00Z", + "updatedAt": "2024-05-20T09:30:00Z" }, { "id": "6", - "name": "Lisa Thompson", - "email": "lisa.thompson@marketing.co", + "name": "David Thomson", + "email": "david@theproductioncoard.com", "phone": "+1 (555) 567-8901", - "company": "Marketing Co", - "position": "Marketing Director", + "company": "The Production Board", + "position": "Founder & CEO", "source": "linkedin", - "profileImage": "https://i.pravatar.cc/150?img=9", - "linkedinUrl": "https://linkedin.com/in/lisathompson", - "notes": "Great at digital marketing strategies", - "tags": ["marketing", "digital", "strategy"], + "profileImage": "/images/David.jpg", + "linkedinUrl": "https://linkedin.com/in/dfriedberg", + "notes": "Entrepreneur focused on industrial transformation", + "tags": ["entrepreneur", "climate", "agriculture"], "naoStatus": "not_invited", - "createdAt": "2023-05-12T12:00:00Z", - "updatedAt": "2023-06-20T15:45:00Z" + "createdAt": "2024-04-12T12:00:00Z", + "updatedAt": "2024-06-20T15:45:00Z" + }, + { + "id": "7", + "name": "Day Waterbury", + "email": "day.waterbury@socialimpact.org", + "phone": "+1 (555) 678-9012", + "company": "Social Impact Ventures", + "position": "Managing Partner", + "source": "contacts", + "profileImage": "/images/Day.jpg", + "notes": "Focused on social entrepreneurship and impact investing", + "tags": ["social-impact", "investing", "ventures"], + "naoStatus": "member", + "joinedAt": "2024-02-20T11:00:00Z", + "groupIds": ["group1", "group3"], + "createdAt": "2024-02-01T10:30:00Z", + "updatedAt": "2024-07-15T14:20:00Z" + }, + { + "id": "8", + "name": "Drummond Reed", + "email": "drummond.reed@gen.xyz", + "phone": "+1 (555) 789-0123", + "company": "Gen Digital", + "position": "Chief Trust Officer", + "source": "linkedin", + "profileImage": "/images/Drummond.jpg", + "linkedinUrl": "https://linkedin.com/in/drummondre", + "notes": "Identity and trust infrastructure expert", + "tags": ["identity", "trust", "digital-credentials"], + "naoStatus": "member", + "joinedAt": "2024-01-25T09:15:00Z", + "groupIds": ["group2"], + "createdAt": "2024-01-10T13:45:00Z", + "updatedAt": "2024-06-30T11:25:00Z" + }, + { + "id": "9", + "name": "Duke Dorje", + "email": "duke.stump@blockchainvc.com", + "phone": "+1 (555) 890-1234", + "company": "Blockchain Ventures", + "position": "Investment Partner", + "source": "contacts", + "profileImage": "/images/Duke.jpg", + "notes": "Early-stage blockchain and crypto investor", + "tags": ["blockchain", "crypto", "investing"], + "naoStatus": "invited", + "invitedAt": "2024-06-15T14:20:00Z", + "createdAt": "2024-05-30T16:10:00Z", + "updatedAt": "2024-07-05T12:40:00Z" + }, + { + "id": "10", + "name": "Frederic Boyer", + "email": "frederic.laloux@reinventorgs.com", + "phone": "+1 (555) 901-2345", + "company": "Reinventing Organizations", + "position": "Author & Organizational Advisor", + "source": "linkedin", + "profileImage": "/images/Frederic.jpg", + "linkedinUrl": "https://linkedin.com/in/fredericlaloux", + "notes": "Author of 'Reinventing Organizations', organizational transformation expert", + "tags": ["organizations", "transformation", "author"], + "naoStatus": "not_invited", + "createdAt": "2024-03-20T15:30:00Z", + "updatedAt": "2024-05-15T10:15:00Z" + }, + { + "id": "11", + "name": "Joscha Raue", + "email": "joscha.bach@cognitiveai.org", + "phone": "+1 (555) 012-3456", + "company": "Cognitive AI Research", + "position": "Principal AI Researcher", + "source": "contacts", + "profileImage": "/images/Joscha.jpg", + "notes": "Cognitive scientist and AI researcher, expert in artificial general intelligence", + "tags": ["ai", "cognition", "research"], + "naoStatus": "member", + "joinedAt": "2024-01-30T13:45:00Z", + "groupIds": ["group2", "group3"], + "createdAt": "2024-01-15T11:20:00Z", + "updatedAt": "2024-07-18T09:50:00Z" + }, + { + "id": "12", + "name": "Kevin Triplett", + "email": "kevin.kelly@wiredmagazine.com", + "phone": "+1 (555) 123-4567", + "company": "Wired Magazine", + "position": "Senior Maverick", + "source": "linkedin", + "profileImage": "/images/Kevin.jpg", + "linkedinUrl": "https://linkedin.com/in/kevinkelly", + "notes": "Co-founder of Wired, technology philosopher and author", + "tags": ["technology", "futurism", "writing"], + "naoStatus": "member", + "joinedAt": "2023-12-20T10:30:00Z", + "groupIds": ["group1", "group2"], + "createdAt": "2023-12-01T14:15:00Z", + "updatedAt": "2024-07-12T16:20:00Z" + }, + { + "id": "13", + "name": "Kristina Lillieneke", + "email": "kristina.shen@gravityvc.com", + "phone": "+1 (555) 234-5678", + "company": "Gravity Ventures", + "position": "Founding Partner", + "source": "contacts", + "profileImage": "/images/Kristina.jpg", + "notes": "Early-stage venture capitalist focused on deep tech", + "tags": ["venture-capital", "deep-tech", "startups"], + "naoStatus": "not_invited", + "createdAt": "2024-04-05T12:30:00Z", + "updatedAt": "2024-06-15T14:45:00Z" + }, + { + "id": "14", + "name": "Margeigh Novotny", + "email": "margeigh.novosad@sustainabletech.org", + "phone": "+1 (555) 345-6789", + "company": "Sustainable Technology Institute", + "position": "Executive Director", + "source": "linkedin", + "profileImage": "/images/Margeigh.jpg", + "linkedinUrl": "https://linkedin.com/in/margeighnovosad", + "notes": "Leader in sustainable technology and environmental innovation", + "tags": ["sustainability", "environment", "technology"], + "naoStatus": "member", + "joinedAt": "2024-06-01T14:20:00Z", + "groupIds": ["group1", "group3"], + "createdAt": "2024-06-25T09:40:00Z", + "updatedAt": "2024-07-20T13:30:00Z" + }, + { + "id": "15", + "name": "Oliver Sylvester-Bradley", + "email": "meena.seshamani@healthpolicy.gov", + "phone": "+1 (555) 456-7890", + "company": "Center for Medicare & Medicaid Services", + "position": "Deputy Administrator", + "source": "contacts", + "profileImage": "/images/Oli.jpg", + "notes": "Healthcare policy expert and Medicare/Medicaid leader", + "tags": ["healthcare", "policy", "medicare"], + "naoStatus": "member", + "joinedAt": "2024-03-15T15:20:00Z", + "groupIds": ["group1"], + "createdAt": "2024-03-01T10:45:00Z", + "updatedAt": "2024-07-08T12:15:00Z" + }, + { + "id": "17", + "name": "Ruben Daniels", + "email": "ruben.harris@careerkarmaco.com", + "phone": "+1 (555) 678-9012", + "company": "Career Karma", + "position": "Co-Founder & CEO", + "source": "linkedin", + "profileImage": "/images/Ruben.jpg", + "linkedinUrl": "https://linkedin.com/in/rubenharris", + "notes": "Entrepreneur focused on career development and education technology", + "tags": ["education", "career-development", "entrepreneur"], + "naoStatus": "member", + "joinedAt": "2024-04-15T16:30:00Z", + "groupIds": ["group1", "group2"], + "createdAt": "2024-05-10T14:20:00Z", + "updatedAt": "2024-07-01T11:35:00Z" + }, + { + "id": "18", + "name": "Samuel Gbafa", + "email": "sam.altman@openai.com", + "phone": "+1 (555) 789-0123", + "company": "OpenAI", + "position": "CEO", + "source": "linkedin", + "profileImage": "/images/Sam.jpg", + "linkedinUrl": "https://linkedin.com/in/samaltman", + "notes": "Leading the development of artificial general intelligence", + "tags": ["ai", "openai", "leadership"], + "naoStatus": "invited", + "invitedAt": "2024-06-20T16:45:00Z", + "createdAt": "2024-06-01T13:25:00Z", + "updatedAt": "2024-07-15T14:50:00Z" + }, + { + "id": "20", + "name": "Niko Bonnieure", + "email": "niko@nextgraph.org", + "phone": "+1 (555) 901-2345", + "company": "NextGraph", + "position": "Founder & Lead Developer", + "source": "contacts", + "profileImage": "/images/Niko.jpg", + "notes": "Building decentralized graph database technology", + "tags": ["decentralized", "database", "p2p"], + "naoStatus": "member", + "joinedAt": "2024-02-01T11:15:00Z", + "groupIds": ["group2"], + "createdAt": "2024-01-15T16:25:00Z", + "updatedAt": "2024-07-25T18:30:00Z" + }, + { + "id": "21", + "name": "Tree Willard", + "email": "tree.willard@foresttech.org", + "phone": "+1 (555) 012-3456", + "company": "Forest Technology Institute", + "position": "Environmental Systems Engineer", + "source": "contacts", + "profileImage": "/images/Tree.jpg", + "notes": "Expert in sustainable forest management and environmental technology", + "tags": ["environment", "forestry", "sustainability"], + "naoStatus": "member", + "joinedAt": "2024-03-10T14:00:00Z", + "groupIds": ["group1", "group3"], + "createdAt": "2024-02-25T12:00:00Z", + "updatedAt": "2024-07-25T19:00:00Z" + }, + { + "id": "22", + "name": "Meena Seshamani", + "email": "meena.seshamani@healthpolicy.gov", + "phone": "+1 (555) 456-7890", + "company": "Center for Medicare & Medicaid Services", + "position": "Deputy Administrator", + "source": "contacts", + "profileImage": "/images/Meena.jpg", + "notes": "Healthcare policy expert and Medicare/Medicaid leader", + "tags": ["healthcare", "policy", "medicare"], + "naoStatus": "member", + "joinedAt": "2024-03-15T15:20:00Z", + "groupIds": ["group1"], + "createdAt": "2024-03-01T10:45:00Z", + "updatedAt": "2024-07-08T12:15:00Z" + }, + { + "id": "23", + "name": "Stephane Bancel", + "email": "stephane.bancel@modernatx.com", + "phone": "+1 (555) 890-1234", + "company": "Moderna", + "position": "Chief Executive Officer", + "source": "contacts", + "profileImage": "/images/Stephane.jpg", + "notes": "Biotech leader, revolutionized mRNA vaccine technology", + "tags": ["biotech", "mrna", "vaccines"], + "naoStatus": "member", + "joinedAt": "2024-04-10T12:30:00Z", + "groupIds": ["group3"], + "createdAt": "2024-03-25T15:10:00Z", + "updatedAt": "2024-07-22T10:40:00Z" } ] \ No newline at end of file diff --git a/public/images/Alex.jpg b/public/images/Alex.jpg new file mode 100644 index 0000000..dabda4c Binary files /dev/null and b/public/images/Alex.jpg differ diff --git a/public/images/Anna.jpg b/public/images/Anna.jpg new file mode 100644 index 0000000..a9229b6 Binary files /dev/null and b/public/images/Anna.jpg differ diff --git a/public/images/Aza.jpg b/public/images/Aza.jpg new file mode 100644 index 0000000..9526290 Binary files /dev/null and b/public/images/Aza.jpg differ diff --git a/public/images/Brad.jpg b/public/images/Brad.jpg new file mode 100644 index 0000000..e3326ad Binary files /dev/null and b/public/images/Brad.jpg differ diff --git a/public/images/Bram.jpg b/public/images/Bram.jpg new file mode 100644 index 0000000..195c8b0 Binary files /dev/null and b/public/images/Bram.jpg differ diff --git a/public/images/David.jpg b/public/images/David.jpg new file mode 100644 index 0000000..cca9432 Binary files /dev/null and b/public/images/David.jpg differ diff --git a/public/images/Day.jpg b/public/images/Day.jpg new file mode 100644 index 0000000..2e45225 Binary files /dev/null and b/public/images/Day.jpg differ diff --git a/public/images/Drummond.jpg b/public/images/Drummond.jpg new file mode 100644 index 0000000..be2dbb9 Binary files /dev/null and b/public/images/Drummond.jpg differ diff --git a/public/images/Duke.jpg b/public/images/Duke.jpg new file mode 100644 index 0000000..f16b004 Binary files /dev/null and b/public/images/Duke.jpg differ diff --git a/public/images/Frederic.jpg b/public/images/Frederic.jpg new file mode 100644 index 0000000..d8bd936 Binary files /dev/null and b/public/images/Frederic.jpg differ diff --git a/public/images/Joscha.jpg b/public/images/Joscha.jpg new file mode 100644 index 0000000..1160398 Binary files /dev/null and b/public/images/Joscha.jpg differ diff --git a/public/images/Kevin.jpg b/public/images/Kevin.jpg new file mode 100644 index 0000000..f0c93f5 Binary files /dev/null and b/public/images/Kevin.jpg differ diff --git a/public/images/Kristina.jpg b/public/images/Kristina.jpg new file mode 100644 index 0000000..9865132 Binary files /dev/null and b/public/images/Kristina.jpg differ diff --git a/public/images/Margeigh.jpg b/public/images/Margeigh.jpg new file mode 100644 index 0000000..ad7c71d Binary files /dev/null and b/public/images/Margeigh.jpg differ diff --git a/public/images/Meena.jpg b/public/images/Meena.jpg new file mode 100644 index 0000000..c5a3bbe Binary files /dev/null and b/public/images/Meena.jpg differ diff --git a/public/images/Niko.jpg b/public/images/Niko.jpg new file mode 100644 index 0000000..72a1643 Binary files /dev/null and b/public/images/Niko.jpg differ diff --git a/public/images/Oli.jpg b/public/images/Oli.jpg new file mode 100644 index 0000000..aa06d21 Binary files /dev/null and b/public/images/Oli.jpg differ diff --git a/public/images/Ruben.jpg b/public/images/Ruben.jpg new file mode 100644 index 0000000..10b53d8 Binary files /dev/null and b/public/images/Ruben.jpg differ diff --git a/public/images/Sam.jpg b/public/images/Sam.jpg new file mode 100644 index 0000000..2b11c52 Binary files /dev/null and b/public/images/Sam.jpg differ diff --git a/public/images/Stephane.jpg b/public/images/Stephane.jpg new file mode 100644 index 0000000..cc048fe Binary files /dev/null and b/public/images/Stephane.jpg differ diff --git a/public/images/Tim.jpg b/public/images/Tim.jpg new file mode 100644 index 0000000..d35f6e3 Binary files /dev/null and b/public/images/Tim.jpg differ diff --git a/public/images/Tree.jpg b/public/images/Tree.jpg new file mode 100644 index 0000000..3bab09b Binary files /dev/null and b/public/images/Tree.jpg differ diff --git a/public/images/world-map.png b/public/images/world-map.png new file mode 100644 index 0000000..ab7a2c0 Binary files /dev/null and b/public/images/world-map.png differ diff --git a/src/components/NetworkGraph.tsx b/src/components/NetworkGraph.tsx new file mode 100644 index 0000000..cdca81d --- /dev/null +++ b/src/components/NetworkGraph.tsx @@ -0,0 +1,419 @@ +import { useState, useEffect, useRef } from 'react'; +import { Box, Typography, Card, CardContent } from '@mui/material'; +import { alpha, useTheme } from '@mui/material/styles'; +import type { Contact } from '../types/contact'; +import { getContactPhotoStyles } from '../utils/photoStyles'; + +interface NetworkNode extends Contact { + x: number; + y: number; + vx: number; + vy: number; + fx?: number; + fy?: number; +} + +interface NetworkLink { + source: string; + target: string; + strength: number; + type: 'vouch' | 'praise' | 'group'; +} + +interface NetworkGraphProps { + contacts: Contact[]; + width?: number; + height?: number; +} + +const NetworkGraph = ({ contacts, width, height }: NetworkGraphProps) => { + const theme = useTheme(); + const svgRef = useRef(null); + const containerRef = useRef(null); + const [nodes, setNodes] = useState([]); + const [links, setLinks] = useState([]); + const [hoveredNode, setHoveredNode] = useState(null); + const [draggedNode, setDraggedNode] = useState(null); + const [isDragging, setIsDragging] = useState(false); + const [dimensions, setDimensions] = useState({ width: 800, height: 600 }); + + // Update dimensions when container resizes + useEffect(() => { + const updateDimensions = () => { + if (containerRef.current) { + const rect = containerRef.current.getBoundingClientRect(); + setDimensions({ + width: width || rect.width || 800, + height: height || rect.height || 600 + }); + } + }; + + updateDimensions(); + window.addEventListener('resize', updateDimensions); + return () => window.removeEventListener('resize', updateDimensions); + }, [width, height]); + + // Initialize network data + useEffect(() => { + if (!contacts.length) return; + + // Create nodes from contacts + const networkNodes: NetworkNode[] = contacts.map((contact) => ({ + ...contact, + x: Math.random() * (dimensions.width - 100) + 50, + y: Math.random() * (dimensions.height - 100) + 50, + vx: 0, + vy: 0, + })); + + // Create links based on relationships + const networkLinks: NetworkLink[] = []; + + // Add links for shared groups + contacts.forEach((contact, i) => { + contacts.forEach((otherContact, j) => { + if (i >= j) return; // Avoid duplicates + + const sharedGroups = contact.groupIds?.filter(id => + otherContact.groupIds?.includes(id) + ) || []; + + if (sharedGroups.length > 0) { + networkLinks.push({ + source: contact.id, + target: otherContact.id, + strength: sharedGroups.length, + type: 'group' + }); + } + }); + }); + + // Add links for NAO members (representing vouch/praise relationships) + const naoMembers = contacts.filter(c => c.naoStatus === 'member'); + naoMembers.forEach((contact, i) => { + naoMembers.forEach((otherContact, j) => { + if (i >= j) return; + + // Create stronger links between NAO members + const existingLink = networkLinks.find(l => + (l.source === contact.id && l.target === otherContact.id) || + (l.source === otherContact.id && l.target === contact.id) + ); + + if (existingLink) { + existingLink.strength += 2; + existingLink.type = 'vouch'; + } else { + networkLinks.push({ + source: contact.id, + target: otherContact.id, + strength: 1, + type: 'vouch' + }); + } + }); + }); + + setNodes(networkNodes); + setLinks(networkLinks); + }, [contacts, dimensions.width, dimensions.height]); + + // Simple force simulation using requestAnimationFrame + useEffect(() => { + if (!nodes.length || !links.length) return; + + let animationId: number; + const alpha = 0.1; + const centerForce = 0.02; + const linkForce = 0.1; + const repelForce = 100; + const centerX = dimensions.width / 2; + const centerY = dimensions.height / 2; + + const simulate = () => { + setNodes(prevNodes => { + const newNodes = prevNodes.map(node => ({ ...node })); + + // Apply forces + newNodes.forEach(node => { + if (node.id === draggedNode) return; // Don't apply forces to dragged node + + // Center force + node.vx += (centerX - node.x) * centerForce; + node.vy += (centerY - node.y) * centerForce; + + // Link force + links.forEach(link => { + const sourceNode = newNodes.find(n => n.id === link.source); + const targetNode = newNodes.find(n => n.id === link.target); + + if (sourceNode && targetNode) { + const dx = targetNode.x - sourceNode.x; + const dy = targetNode.y - sourceNode.y; + const distance = Math.sqrt(dx * dx + dy * dy) || 1; + const targetDistance = 80 + (link.strength * 20); + const force = (distance - targetDistance) * linkForce * link.strength; + + const fx = (dx / distance) * force; + const fy = (dy / distance) * force; + + if (sourceNode.id !== draggedNode) { + sourceNode.vx += fx; + sourceNode.vy += fy; + } + if (targetNode.id !== draggedNode) { + targetNode.vx -= fx; + targetNode.vy -= fy; + } + } + }); + + // Repel force between nodes + newNodes.forEach(otherNode => { + if (node.id === otherNode.id) return; + + const dx = otherNode.x - node.x; + const dy = otherNode.y - node.y; + const distance = Math.sqrt(dx * dx + dy * dy) || 1; + + if (distance < 120) { + const force = repelForce / (distance * distance); + const fx = (dx / distance) * force; + const fy = (dy / distance) * force; + + if (node.id !== draggedNode) { + node.vx -= fx; + node.vy -= fy; + } + } + }); + + // Apply velocity with damping + node.vx *= 0.9; + node.vy *= 0.9; + + // Update position + node.x += node.vx * alpha; + node.y += node.vy * alpha; + + // Keep nodes within bounds + const radius = 20; + node.x = Math.max(radius, Math.min(dimensions.width - radius, node.x)); + node.y = Math.max(radius, Math.min(dimensions.height - radius, node.y)); + }); + + return newNodes; + }); + + animationId = requestAnimationFrame(simulate); + }; + + animationId = requestAnimationFrame(simulate); + + return () => { + cancelAnimationFrame(animationId); + }; + }, [nodes.length, links, dimensions.width, dimensions.height, draggedNode]); + + const handleMouseDown = (nodeId: string, event: React.MouseEvent) => { + event.preventDefault(); + setDraggedNode(nodeId); + setIsDragging(true); + }; + + const handleMouseMove = (event: React.MouseEvent) => { + if (!isDragging || !draggedNode || !svgRef.current) return; + + const rect = svgRef.current.getBoundingClientRect(); + const x = event.clientX - rect.left; + const y = event.clientY - rect.top; + + setNodes(prevNodes => + prevNodes.map(node => + node.id === draggedNode + ? { ...node, x, y, vx: 0, vy: 0 } + : node + ) + ); + }; + + const handleMouseUp = () => { + setDraggedNode(null); + setIsDragging(false); + }; + + const getNodeColor = (node: NetworkNode) => { + switch (node.naoStatus) { + case 'member': + return theme.palette.success.main; + case 'invited': + return theme.palette.warning.main; + default: + return theme.palette.primary.main; + } + }; + + const getLinkColor = (link: NetworkLink) => { + switch (link.type) { + case 'vouch': + return theme.palette.success.main; + case 'praise': + return '#d81b60'; + default: + return alpha(theme.palette.primary.main, 0.3); + } + }; + + const getNodeRadius = (node: NetworkNode) => { + const baseRadius = 20; + const connections = links.filter(l => l.source === node.id || l.target === node.id).length; + return baseRadius + Math.sqrt(connections) * 3; + }; + + return ( + + + {/* Links */} + + {links.map((link, index) => { + const sourceNode = nodes.find(n => n.id === link.source); + const targetNode = nodes.find(n => n.id === link.target); + + if (!sourceNode || !targetNode) return null; + + return ( + + ); + })} + + + {/* Nodes */} + + {nodes.map((node) => ( + + handleMouseDown(node.id, e)} + onMouseEnter={() => setHoveredNode(node.id)} + onMouseLeave={() => setHoveredNode(null)} + /> + + {/* Profile image if available */} + {node.profileImage && ( + +
+ + )} + + {/* Node label */} + + {node.name.split(' ')[0]} + + + ))} + + + + {/* Tooltip */} + {hoveredNode && ( + + {(() => { + const node = nodes.find(n => n.id === hoveredNode); + if (!node) return null; + + const connections = links.filter(l => l.source === node.id || l.target === node.id).length; + + return ( + + + + {node.name} + + + {node.position} {node.company && `at ${node.company}`} + + + {connections} connection{connections !== 1 ? 's' : ''} + + + Status: {node.naoStatus === 'member' ? 'NAO Member' : + node.naoStatus === 'invited' ? 'Invited' : 'Not Invited'} + + + + ); + })()} + + )} + + ); +}; + +export default NetworkGraph; \ No newline at end of file diff --git a/src/pages/ContactListPage.tsx b/src/pages/ContactListPage.tsx index 94e657c..f9c5037 100644 --- a/src/pages/ContactListPage.tsx +++ b/src/pages/ContactListPage.tsx @@ -16,6 +16,7 @@ import { alpha, useTheme } from '@mui/material'; +import NetworkGraph from '../components/NetworkGraph'; import { List as ListIcon, Hub, @@ -38,6 +39,7 @@ import { } from '@mui/icons-material'; import { dataService } from '../services/dataService'; import type { Contact } from '../types/contact'; +import { getContactPhotoStyles } from '../utils/photoStyles'; const ContactListPage = () => { const [contacts, setContacts] = useState([]); @@ -153,7 +155,28 @@ const ContactListPage = () => { if (contact.naoStatus === 'member') { // For NAO members, show sent/received counts - if (contact.name === 'Sarah Johnson') { + if (contact.name === 'Ruben Daniels') { + return { + vouchesSent: 3, // What I sent to Ruben (includes exchanges with Margeigh) + vouchesReceived: 4, // What Ruben sent to me (includes exchanges with Margeigh) + praisesSent: 2, // What I sent to Ruben (includes exchanges with Margeigh) + praisesReceived: 3 // What Ruben sent to me (includes exchanges with Margeigh) + }; + } else if (contact.name === 'Margeigh Novotny') { + return { + vouchesSent: 2, // What I sent to Margeigh (includes exchanges with Ruben) + vouchesReceived: 3, // What Margeigh sent to me (includes exchanges with Ruben) + praisesSent: 4, // What I sent to Margeigh (includes exchanges with Ruben) + praisesReceived: 2 // What Margeigh sent to me (includes exchanges with Ruben) + }; + } else if (contact.name === 'Oliver Sylvester-Bradley') { + return { + vouchesSent: 4, // What I sent to Oliver + vouchesReceived: 2, // What Oliver sent to me + praisesSent: 2, // What I sent to Oliver + praisesReceived: 3 // What Oliver sent to me + }; + } else if (contact.name === 'Sarah Johnson') { return { vouchesSent: 1, // What I sent to Sarah vouchesReceived: 2, // What Sarah sent to me @@ -324,11 +347,7 @@ const ContactListPage = () => { }} > } label="List" /> - - - } label="Network" disabled /> - - + } label="Network" /> } label="Map" disabled /> @@ -336,7 +355,8 @@ const ContactListPage = () => { - + {tabValue === 0 && ( + { height: 56, borderRadius: '50%', backgroundImage: contact.profileImage ? `url(${contact.profileImage})` : 'none', - backgroundSize: 'cover', - backgroundPosition: 'center', + backgroundSize: contact.profileImage ? getContactPhotoStyles(contact.name).backgroundSize : 'cover', + backgroundPosition: contact.profileImage ? getContactPhotoStyles(contact.name).backgroundPosition : 'center center', display: 'flex', alignItems: 'center', justifyContent: 'center', @@ -706,8 +726,8 @@ const ContactListPage = () => { height: 48, borderRadius: '50%', backgroundImage: contact.profileImage ? `url(${contact.profileImage})` : 'none', - backgroundSize: 'cover', - backgroundPosition: 'center', + backgroundSize: contact.name === 'Tree Willard' ? '120%' : contact.name === 'Niko Bonnieure' ? '100%' : contact.name === 'Tim Bansemer' ? '220%' : contact.name === 'Duke Dorje' ? '200%' : contact.name === 'Kevin Triplett' ? '220%' : contact.name === 'Kristina Lillieneke' ? '220%' : contact.name === 'Oliver Sylvester-Bradley' ? '220%' : contact.name === 'David Thomson' ? '220%' : contact.name === 'Samuel Gbafa' ? '280%' : contact.name === 'Meena Seshamani' ? '280%' : '180%', + backgroundPosition: contact.name === 'Alex Lion Yes!' ? '70% 70%' : contact.name === 'Aza Mafi' ? 'center 80%' : contact.name === 'Day Waterbury' ? 'center 60%' : contact.name === 'Duke Dorje' ? '60% 65%' : contact.name === 'Frederic Boyer' ? 'center 60%' : contact.name === 'Joscha Raue' ? '60% 65%' : contact.name === 'Kevin Triplett' ? '40% 60%' : contact.name === 'Kristina Lillieneke' ? 'center 60%' : contact.name === 'Margeigh Novotny' ? 'center 70%' : contact.name === 'Oliver Sylvester-Bradley' ? 'center 55%' : contact.name === 'Samuel Gbafa' ? '60% 60%' : contact.name === 'Meena Seshamani' ? '60% 60%' : 'center center', display: 'flex', alignItems: 'center', justifyContent: 'center', @@ -948,7 +968,60 @@ const ContactListPage = () => { ))} )} - + + )} + + {tabValue === 1 && ( + + {isLoading ? ( + + + + Loading network... + + + Building your contact network visualization + + + + ) : contacts.length === 0 ? ( + + + + No contacts to visualize + + + Import some contacts to see your network! + + + + ) : ( + + + + )} + + )} ); diff --git a/src/pages/ContactViewPage.tsx b/src/pages/ContactViewPage.tsx index 796180d..6b3041e 100644 --- a/src/pages/ContactViewPage.tsx +++ b/src/pages/ContactViewPage.tsx @@ -125,6 +125,71 @@ const ContactViewPage = () => { } }; + const getContactPhotoStyles = (contact: Contact) => { + // Apply the same custom photo positioning and zoom levels from ContactListPage + let backgroundSize = '180%'; // default + let backgroundPosition = 'center center'; // default + + switch (contact.name) { + case 'Tree Willard': + backgroundSize = '120%'; + break; + case 'Niko Bonnieure': + backgroundSize = '100%'; + break; + case 'Tim Bansemer': + backgroundSize = '220%'; + break; + case 'Duke Dorje': + backgroundSize = '200%'; + backgroundPosition = '60% 65%'; + break; + case 'Kevin Triplett': + backgroundSize = '220%'; + backgroundPosition = '40% 60%'; + break; + case 'Kristina Lillieneke': + backgroundSize = '220%'; + backgroundPosition = 'center 60%'; + break; + case 'Oliver Sylvester-Bradley': + backgroundSize = '220%'; + backgroundPosition = 'center 55%'; + break; + case 'David Thomson': + backgroundSize = '220%'; + break; + case 'Samuel Gbafa': + backgroundSize = '280%'; + backgroundPosition = '60% 60%'; + break; + case 'Meena Seshamani': + backgroundSize = '280%'; + backgroundPosition = '60% 60%'; + break; + case 'Alex Lion Yes!': + backgroundPosition = '70% 70%'; + break; + case 'Aza Mafi': + backgroundPosition = 'center 80%'; + break; + case 'Day Waterbury': + backgroundPosition = 'center 60%'; + break; + case 'Frederic Boyer': + backgroundPosition = 'center 60%'; + break; + case 'Joscha Raue': + backgroundPosition = '60% 65%'; + break; + case 'Margeigh Novotny': + backgroundPosition = 'center 70%'; + break; + } + + return { backgroundSize, backgroundPosition }; + }; + const formatDate = (date: Date) => { return new Intl.DateTimeFormat('en-US', { year: 'numeric', @@ -229,8 +294,8 @@ const ContactViewPage = () => { height: { xs: 100, sm: 120 }, borderRadius: '50%', backgroundImage: contact.profileImage ? `url(${contact.profileImage})` : 'none', - backgroundSize: 'cover', - backgroundPosition: 'center center', + backgroundSize: contact.profileImage ? getContactPhotoStyles(contact).backgroundSize : 'cover', + backgroundPosition: contact.profileImage ? getContactPhotoStyles(contact).backgroundPosition : 'center center', backgroundRepeat: 'no-repeat', display: 'flex', alignItems: 'center', diff --git a/src/pages/GroupDetailPage.tsx b/src/pages/GroupDetailPage.tsx index 9aa4816..953a870 100644 --- a/src/pages/GroupDetailPage.tsx +++ b/src/pages/GroupDetailPage.tsx @@ -1,6 +1,7 @@ // @ts-nocheck import { useState, useEffect } from 'react'; import { useParams, useNavigate, useSearchParams } from 'react-router-dom'; +import { getContactPhotoStyles } from '../utils/photoStyles'; import { Typography, Box, @@ -162,10 +163,10 @@ const GroupDetailPage = () => { { id: '1', groupId: groupId, - authorId: 'sarah-j', - authorName: 'Sarah Johnson', - authorAvatar: 'https://i.pravatar.cc/150?img=2', - content: 'Great turnout at today\'s garden workday! We managed to plant 3 new beds with tomatoes, peppers, and herbs. The community spirit was amazing - had over 20 volunteers show up. Next week we\'ll be focusing on the composting area setup.', + authorId: 'ruben-daniels', + authorName: 'Ruben Daniels', + authorAvatar: '/images/Ruben.jpg', + content: 'Excited to share some insights from our recent community building research! The data shows that peer-to-peer learning increases engagement by 300%. Looking forward to implementing these findings in our next workshop series.', topic: 'Garden Planning', images: [ 'https://images.unsplash.com/photo-1416879595882-3373a0480b5b?w=400', @@ -179,10 +180,10 @@ const GroupDetailPage = () => { { id: '2', groupId: groupId, - authorId: 'mike-c', - authorName: 'Mike Chen', - authorAvatar: 'https://i.pravatar.cc/150?img=3', - content: 'Quick reminder: please bring your own tools to tomorrow\'s session. We\'ll have some extras but not enough for everyone.', + authorId: 'oliver-sb', + authorName: 'Oliver Sylvester-Bradley', + authorAvatar: '/images/Oli.jpg', + content: 'Just finished reviewing the latest networking protocols for our upcoming NAO infrastructure upgrade. The decentralized approach we\'re implementing should improve connection reliability by 40%. Technical details in the documents section.', topic: 'Tool Sharing', createdAt: new Date(Date.now() - 1000 * 60 * 60), // 1 hour ago updatedAt: new Date(Date.now() - 1000 * 60 * 60), @@ -192,10 +193,10 @@ const GroupDetailPage = () => { { id: '3', groupId: groupId, - authorId: 'emily-r', - authorName: 'Emily Rodriguez', - authorAvatar: 'https://i.pravatar.cc/150?img=4', - content: 'I\'ve been researching the best composting methods for our community garden and wanted to share some findings. After reviewing multiple academic papers and speaking with local agricultural extension services, here are the key recommendations I\'ve compiled:\n\n1. Three-bin system works best for our volume\n2. Carbon to nitrogen ratio should be 30:1\n3. Regular turning every 2-3 weeks\n4. Moisture content around 50-60%\n5. Temperature monitoring is crucial\n\nI\'ve also been in contact with the city\'s waste management department about getting bulk brown materials delivered. They\'re willing to drop off wood chips and dried leaves monthly at no cost to our group. This could save us significant money on soil amendments.\n\nWhat are everyone\'s thoughts on implementing this system? I\'m happy to lead the composting committee if there\'s interest.', + authorId: 'margeigh-novotny', + authorName: 'Margeigh Novotny', + authorAvatar: '/images/Margeigh.jpg', + content: 'Leading a deep dive into sustainable technology frameworks for our next quarter. After extensive research into environmental innovation patterns, here are the key insights I\'ve compiled:\n\n1. Circular economy models show 40% better resource efficiency\n2. Renewable energy integration reduces operational costs by 60%\n3. Smart monitoring systems optimize performance\n4. Community engagement drives adoption rates\n5. Long-term impact measurement is essential\n\nI\'ve also been working with several cleantech startups on implementation strategies. They\'re offering pilot program partnerships that could significantly accelerate our sustainability goals.\n\nWhat are everyone\'s thoughts on this roadmap? I\'m excited to lead the sustainability working group if there\'s interest.', topic: 'Composting', isLong: true, images: ['https://images.unsplash.com/photo-1611273426858-450d8e3c9fce?w=400'], @@ -207,10 +208,10 @@ const GroupDetailPage = () => { { id: '4', groupId: groupId, - authorId: 'lisa-t', - authorName: 'Lisa Thompson', - authorAvatar: 'https://i.pravatar.cc/150?img=9', - content: 'Amazing harvest festival photos! Thanks to everyone who made it such a success. 🌽🥕🍅', + authorId: 'alex-lion', + authorName: 'Alex Lion Yes!', + authorAvatar: '/images/Alex.jpg', + content: 'Amazing progress on our AI innovation labs this quarter! Our latest models are showing incredible performance improvements. Thanks to everyone who contributed to making this breakthrough possible. 🚀🤖✨', topic: 'Community Events', images: [ 'https://images.unsplash.com/photo-1500937386664-56d1dfef3854?w=400', @@ -225,10 +226,10 @@ const GroupDetailPage = () => { { id: '5', groupId: groupId, - authorId: 'david-w', - authorName: 'David Wilson', - authorAvatar: 'https://i.pravatar.cc/150?img=8', - content: 'Update on our fundraising efforts: We\'ve raised $2,400 towards our $5,000 goal for the new greenhouse! Big thanks to everyone who\'s contributed. We\'re planning a bake sale for next weekend to help us get closer to the target.', + authorId: 'day-waterbury', + authorName: 'Day Waterbury', + authorAvatar: '/images/Day.jpg', + content: 'Update on our social impact funding efforts: We\'ve raised $240K towards our $500K goal for the new community development program! Big thanks to everyone who\'s contributed. We\'re planning an impact showcase event next weekend to help us get closer to the target.', topic: 'Fundraising', createdAt: new Date(Date.now() - 1000 * 60 * 60 * 6), // 6 hours ago updatedAt: new Date(Date.now() - 1000 * 60 * 60 * 6), @@ -458,103 +459,279 @@ const GroupDetailPage = () => { ); - // Mock member data with relationships, activities, and locations + // Real NAO member data with relationships, activities, and locations const getMockMembers = () => [ { - id: 'current-user', - name: 'You', + id: 'oli-sb', + name: 'Oliver Sylvester-Bradley', initials: 'OS', - avatar: null, + avatar: '/images/Oli.jpg', relationshipStrength: 1.0, - position: { x: 0, y: 0 }, // Center + position: { x: 0, y: 0 }, // Center node activities: [ - { topic: 'Community Events', count: 15, lastActive: '2 hours ago' }, - { topic: 'Sustainability', count: 8, lastActive: '1 day ago' } + { topic: 'NAO Genesis', count: 25, lastActive: '1 hour ago' }, + { topic: 'Network Building', count: 18, lastActive: '3 hours ago' } ], location: { lat: 40.7128, lng: -74.0060, visible: true }, - vouches: 12, - praises: 18, - connections: ['sarah-j', 'mike-c', 'lisa-t', 'david-w'] + vouches: 15, + praises: 22, + connections: ['ruben-daniels', 'margeigh-novotny', 'alex-lion', 'day-waterbury', 'kevin-triplett', 'tim-bansemer'] }, { - id: 'sarah-j', - name: 'Sarah Johnson', - initials: 'SJ', - avatar: 'https://i.pravatar.cc/150?img=2', - relationshipStrength: 0.9, + id: 'ruben-daniels', + name: 'Ruben Daniels', + initials: 'RD', + avatar: '/images/Ruben.jpg', + relationshipStrength: 0.95, position: { x: -120, y: -80 }, activities: [ - { topic: 'Garden Planning', count: 22, lastActive: '30 minutes ago' }, - { topic: 'Community Events', count: 12, lastActive: '3 hours ago' } + { topic: 'Career Development', count: 20, lastActive: '45 minutes ago' }, + { topic: 'Education Tech', count: 15, lastActive: '2 hours ago' } ], location: { lat: 40.7158, lng: -74.0090, visible: true }, - vouches: 8, - praises: 14, - connections: ['current-user', 'mike-c', 'emily-r'] + vouches: 12, + praises: 18, + connections: ['oli-sb', 'margeigh-novotny', 'alex-lion', 'kevin-triplett'] }, { - id: 'mike-c', - name: 'Mike Chen', - initials: 'MC', - avatar: 'https://i.pravatar.cc/150?img=3', - relationshipStrength: 0.7, - position: { x: 100, y: -100 }, + id: 'margeigh-novotny', + name: 'Margeigh Novotny', + initials: 'MN', + avatar: '/images/Margeigh.jpg', + relationshipStrength: 0.95, + position: { x: 120, y: -80 }, activities: [ - { topic: 'Tool Sharing', count: 18, lastActive: '1 hour ago' }, - { topic: 'Organic Methods', count: 9, lastActive: '2 days ago' } + { topic: 'Sustainable Tech', count: 22, lastActive: '1 hour ago' }, + { topic: 'Environmental Innovation', count: 16, lastActive: '4 hours ago' } ], location: { lat: 40.7098, lng: -74.0030, visible: true }, - vouches: 6, - praises: 11, - connections: ['current-user', 'sarah-j', 'david-w'] + vouches: 11, + praises: 19, + connections: ['oli-sb', 'ruben-daniels', 'tree-willard', 'day-waterbury'] }, { - id: 'emily-r', - name: 'Emily Rodriguez', - initials: 'ER', - avatar: 'https://i.pravatar.cc/150?img=4', + id: 'alex-lion', + name: 'Alex Lion Yes!', + initials: 'AL', + avatar: '/images/Alex.jpg', relationshipStrength: 0.8, position: { x: -80, y: 120 }, activities: [ - { topic: 'Composting', count: 14, lastActive: '4 hours ago' }, - { topic: 'Pest Control', count: 7, lastActive: '1 day ago' } + { topic: 'AI Technology', count: 28, lastActive: '2 hours ago' }, + { topic: 'Innovation Labs', count: 12, lastActive: '1 day ago' } ], location: { lat: 40.7098, lng: -74.0090, visible: true }, - vouches: 9, - praises: 13, - connections: ['current-user', 'sarah-j', 'lisa-t'] + vouches: 14, + praises: 16, + connections: ['oli-sb', 'ruben-daniels', 'aza-mafi', 'joscha-raue'] }, { - id: 'david-w', - name: 'David Wilson', + id: 'day-waterbury', + name: 'Day Waterbury', initials: 'DW', - avatar: 'https://i.pravatar.cc/150?img=8', - relationshipStrength: 0.5, + avatar: '/images/Day.jpg', + relationshipStrength: 0.75, position: { x: 140, y: 90 }, activities: [ - { topic: 'Fundraising', count: 11, lastActive: '6 hours ago' }, - { topic: 'Community Outreach', count: 5, lastActive: '3 days ago' } + { topic: 'Social Impact', count: 18, lastActive: '3 hours ago' }, + { topic: 'Impact Investing', count: 10, lastActive: '1 day ago' } ], - location: { lat: 40.7068, lng: -74.0040, visible: false }, // Privacy setting - vouches: 4, - praises: 7, - connections: ['current-user', 'mike-c'] + location: { lat: 40.7068, lng: -74.0040, visible: true }, + vouches: 9, + praises: 13, + connections: ['oli-sb', 'margeigh-novotny', 'tree-willard'] }, { - id: 'lisa-t', - name: 'Lisa Thompson', - initials: 'LT', - avatar: 'https://i.pravatar.cc/150?img=9', - relationshipStrength: 0.6, + id: 'kevin-triplett', + name: 'Kevin Triplett', + initials: 'KT', + avatar: '/images/Kevin.jpg', + relationshipStrength: 0.85, position: { x: -140, y: 60 }, activities: [ - { topic: 'Social Media', count: 25, lastActive: '15 minutes ago' }, - { topic: 'Event Photography', count: 8, lastActive: '2 hours ago' } + { topic: 'Technology Philosophy', count: 24, lastActive: '4 hours ago' }, + { topic: 'Future Vision', count: 11, lastActive: '6 hours ago' } ], location: { lat: 40.7138, lng: -74.0070, visible: true }, + vouches: 16, + praises: 20, + connections: ['oli-sb', 'ruben-daniels', 'aza-mafi'] + }, + { + id: 'tim-bansemer', + name: 'Tim Bansemer', + initials: 'TB', + avatar: '/images/Tim.jpg', + relationshipStrength: 0.7, + position: { x: 0, y: -140 }, + activities: [ + { topic: 'Blockchain Protocols', count: 16, lastActive: '5 hours ago' }, + { topic: 'P2P Networks', count: 8, lastActive: '2 days ago' } + ], + location: { lat: 40.7200, lng: -74.0060, visible: true }, + vouches: 8, + praises: 12, + connections: ['oli-sb', 'niko-bonnieure'] + }, + { + id: 'aza-mafi', + name: 'Aza Mafi', + initials: 'AM', + avatar: '/images/Aza.jpg', + relationshipStrength: 0.8, + position: { x: -180, y: -20 }, + activities: [ + { topic: 'Humane Technology', count: 19, lastActive: '3 hours ago' }, + { topic: 'Design Ethics', count: 13, lastActive: '1 day ago' } + ], + location: { lat: 40.7180, lng: -74.0120, visible: true }, + vouches: 13, + praises: 17, + connections: ['alex-lion', 'kevin-triplett', 'joscha-raue'] + }, + { + id: 'duke-dorje', + name: 'Duke Dorje', + initials: 'DD', + avatar: '/images/Duke.jpg', + relationshipStrength: 0.6, + position: { x: 180, y: -20 }, + activities: [ + { topic: 'Blockchain Investing', count: 14, lastActive: '8 hours ago' }, + { topic: 'Crypto Strategy', count: 7, lastActive: '3 days ago' } + ], + location: { lat: 40.7080, lng: -74.0000, visible: false }, // Privacy setting + vouches: 6, + praises: 9, + connections: ['tim-bansemer'] + }, + { + id: 'david-thomson', + name: 'David Thomson', + initials: 'DT', + avatar: '/images/David.jpg', + relationshipStrength: 0.65, + position: { x: 80, y: 160 }, + activities: [ + { topic: 'Industrial Transformation', count: 12, lastActive: '6 hours ago' }, + { topic: 'Climate Solutions', count: 9, lastActive: '2 days ago' } + ], + location: { lat: 40.7050, lng: -74.0020, visible: true }, vouches: 7, + praises: 11, + connections: ['day-waterbury', 'tree-willard'] + }, + { + id: 'samuel-gbafa', + name: 'Samuel Gbafa', + initials: 'SG', + avatar: '/images/Sam.jpg', + relationshipStrength: 0.9, + position: { x: -60, y: -160 }, + activities: [ + { topic: 'AI Leadership', count: 32, lastActive: '1 hour ago' }, + { topic: 'OpenAI Development', count: 18, lastActive: '4 hours ago' } + ], + location: { lat: 40.7220, lng: -74.0100, visible: true }, + vouches: 20, + praises: 25, + connections: ['alex-lion', 'joscha-raue'] + }, + { + id: 'meena-seshamani', + name: 'Meena Seshamani', + initials: 'MS', + avatar: '/images/Meena.jpg', + relationshipStrength: 0.7, + position: { x: -120, y: 140 }, + activities: [ + { topic: 'Healthcare Policy', count: 15, lastActive: '5 hours ago' }, + { topic: 'Medicare Innovation', count: 8, lastActive: '1 day ago' } + ], + location: { lat: 40.7040, lng: -74.0110, visible: true }, + vouches: 10, + praises: 14, + connections: ['day-waterbury'] + }, + { + id: 'niko-bonnieure', + name: 'Niko Bonnieure', + initials: 'NB', + avatar: '/images/Niko.jpg', + relationshipStrength: 0.75, + position: { x: 60, y: -160 }, + activities: [ + { topic: 'Decentralized Systems', count: 17, lastActive: '2 hours ago' }, + { topic: 'Graph Databases', count: 11, lastActive: '8 hours ago' } + ], + location: { lat: 40.7210, lng: -74.0040, visible: true }, + vouches: 9, + praises: 13, + connections: ['tim-bansemer', 'drummond-reed'] + }, + { + id: 'tree-willard', + name: 'Tree Willard', + initials: 'TW', + avatar: '/images/Tree.jpg', + relationshipStrength: 0.8, + position: { x: 160, y: 40 }, + activities: [ + { topic: 'Environmental Systems', count: 20, lastActive: '4 hours ago' }, + { topic: 'Forest Technology', count: 12, lastActive: '1 day ago' } + ], + location: { lat: 40.7090, lng: -74.0010, visible: true }, + vouches: 11, praises: 16, - connections: ['current-user', 'emily-r'] + connections: ['margeigh-novotny', 'day-waterbury', 'david-thomson'] + }, + { + id: 'stephane-bancel', + name: 'Stephane Bancel', + initials: 'SB', + avatar: '/images/Stephane.jpg', + relationshipStrength: 0.7, + position: { x: -160, y: 120 }, + activities: [ + { topic: 'Biotech Innovation', count: 21, lastActive: '6 hours ago' }, + { topic: 'mRNA Technology', count: 14, lastActive: '2 days ago' } + ], + location: { lat: 40.7020, lng: -74.0130, visible: true }, + vouches: 12, + praises: 18, + connections: ['meena-seshamani'] + }, + { + id: 'joscha-raue', + name: 'Joscha Raue', + initials: 'JR', + avatar: '/images/Joscha.jpg', + relationshipStrength: 0.8, + position: { x: -100, y: -140 }, + activities: [ + { topic: 'Cognitive AI', count: 23, lastActive: '3 hours ago' }, + { topic: 'AGI Research', count: 16, lastActive: '7 hours ago' } + ], + location: { lat: 40.7190, lng: -74.0080, visible: true }, + vouches: 14, + praises: 19, + connections: ['alex-lion', 'aza-mafi', 'samuel-gbafa'] + }, + { + id: 'drummond-reed', + name: 'Drummond Reed', + initials: 'DR', + avatar: '/images/Drummond.jpg', + relationshipStrength: 0.75, + position: { x: 100, y: -120 }, + activities: [ + { topic: 'Digital Identity', count: 18, lastActive: '4 hours ago' }, + { topic: 'Trust Infrastructure', count: 10, lastActive: '1 day ago' } + ], + location: { lat: 40.7170, lng: -74.0050, visible: true }, + vouches: 8, + praises: 12, + connections: ['niko-bonnieure'] } ]; @@ -904,8 +1081,8 @@ const GroupDetailPage = () => { const renderNetworkView = (members: any[]) => { // Shared position calculation function for perfect alignment const getNodePosition = (member: any) => { - const centerX = 400; // Center of 800px viewBox - const centerY = 240; // Center of 680px viewBox, positioned to minimize top space + const centerX = 290; // Moved 40px right from previous position (250 + 40 = 290) + const centerY = 375; // Moved 25px up from previous position (400 - 25 = 375) const scale = 1.8; // Increased scale for better visibility const x = centerX + member.position.x * scale; @@ -918,7 +1095,7 @@ const GroupDetailPage = () => { { {/* SVG Network Graph */} {/* Connection lines between members */} {members.map(member => member.connections - ?.filter((connId: string) => connId !== 'current-user') - .map((connId: string) => { + ?.map((connId: string) => { const connectedMember = members.find(m => m.id === connId); if (!connectedMember) return null; @@ -950,10 +1126,29 @@ const GroupDetailPage = () => { const endX = endPos.x; const endY = endPos.y; - const isCurrentUserConnection = member.id === 'current-user' || connId === 'current-user'; - const strength = isCurrentUserConnection - ? Math.max(member.relationshipStrength, connectedMember.relationshipStrength) - : 0.3; // Faint lines between other members + // Define core triangle members + const coreMembers = ['oli-sb', 'ruben-daniels', 'margeigh-novotny']; + const isCoreConnection = coreMembers.includes(member.id) && coreMembers.includes(connId); + const isCenterConnection = member.id === 'oli-sb' || connId === 'oli-sb'; + + let strength, strokeColor, opacity; + + if (isCoreConnection) { + // Strong blue connections for core triangle + strength = 1.0; + strokeColor = theme.palette.primary.main; + opacity = 0.9; + } else if (isCenterConnection) { + // Strong connections to center node + strength = Math.max(member.relationshipStrength, connectedMember.relationshipStrength); + strokeColor = theme.palette.primary.main; + opacity = strength; + } else { + // Weaker connections between other members + strength = 0.4; + strokeColor = theme.palette.grey[400]; + opacity = 0.4; + } return ( { y1={startY} x2={endX} y2={endY} - stroke={isCurrentUserConnection ? theme.palette.primary.main : theme.palette.grey[400]} - strokeWidth={strength * 4} - opacity={isCurrentUserConnection ? strength : 0.3} + stroke={strokeColor} + strokeWidth={strength * 5} + opacity={opacity} /> ); }) @@ -1003,24 +1198,24 @@ const GroupDetailPage = () => { width: '60px', height: '60px', borderRadius: '50%', - border: `${member.id === 'current-user' ? 4 : 3}px solid ${ - member.id === 'current-user' + border: `${member.id === 'oli-sb' ? 4 : 3}px solid ${ + member.id === 'oli-sb' ? theme.palette.primary.main : alpha(theme.palette.primary.main, member.relationshipStrength) }`, - boxShadow: member.id === 'current-user' + boxShadow: member.id === 'oli-sb' ? `0 0 20px ${alpha(theme.palette.primary.main, 0.4)}` : `0 0 ${member.relationshipStrength * 15}px ${alpha(theme.palette.primary.main, 0.3)}`, - backgroundColor: member.id === 'current-user' ? theme.palette.primary.main : 'white', + backgroundColor: member.id === 'oli-sb' ? alpha(theme.palette.primary.main, 0.1) : 'white', backgroundImage: member.avatar ? `url(${member.avatar})` : 'none', - backgroundSize: 'cover', - backgroundPosition: 'center', + backgroundSize: member.avatar ? getContactPhotoStyles(member.name).backgroundSize : 'cover', + backgroundPosition: member.avatar ? getContactPhotoStyles(member.name).backgroundPosition : 'center', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '1.2rem', fontWeight: 600, - color: member.id === 'current-user' ? 'white' : theme.palette.text.primary + color: member.id === 'oli-sb' ? theme.palette.primary.main : theme.palette.text.primary }} > {!member.avatar && member.initials} @@ -1029,8 +1224,8 @@ const GroupDetailPage = () => { {/* Name label */}
{ {member.name}
- {/* Activity indicators for current user */} - {member.id === 'current-user' && ( + {/* Activity indicators for center user */} + {member.id === 'oli-sb' && (
{ Anyville Community Garden Network - {/* Mock map view */} + {/* World map view */} - {/* Garden plots and features */} - - - - {/* Member locations */} {visibleMembers.map((member, index) => { - // Convert lat/lng to approximate positions on our mock map - const x = 50 + (member.location.lng + 74.0060) * 2000; - const y = 50 + (40.7128 - member.location.lat) * 2000; + // Use percentage-based positioning to make it responsive and lock to background + const positions = [ + { x: 8, y: 25 }, // Western US/Canada + { x: 18, y: 20 }, // Eastern US/Canada + { x: 15, y: 40 }, // Mexico/Central America + { x: 22, y: 55 }, // Colombia/Venezuela + { x: 25, y: 70 }, // Brazil + { x: 20, y: 85 }, // Argentina/Chile + { x: 12, y: 95 }, // Southern Chile + { x: 45, y: 15 }, // Iceland/Greenland + { x: 50, y: 25 }, // UK/Ireland + { x: 55, y: 20 }, // Scandinavia + { x: 58, y: 30 }, // Central Europe + { x: 62, y: 35 }, // Eastern Europe/Russia West + { x: 70, y: 25 }, // Russia Central + { x: 85, y: 20 }, // Russia East/Siberia + { x: 52, y: 45 }, // North Africa + { x: 48, y: 55 }, // West Africa + { x: 58, y: 60 }, // East Africa + { x: 55, y: 75 }, // South Africa + { x: 65, y: 40 }, // Middle East + { x: 72, y: 45 }, // India + { x: 80, y: 30 }, // China + { x: 88, y: 35 }, // Japan/Korea + { x: 82, y: 50 }, // Southeast Asia + { x: 85, y: 65 }, // Indonesia + { x: 90, y: 80 }, // Australia + { x: 95, y: 90 }, // New Zealand + ]; + + const position = positions[index % positions.length]; + const x = `${position.x + (Math.random() - 0.5) * 5}%`; // Percentage-based with small randomness + const y = `${position.y + (Math.random() - 0.5) * 5}%`; return ( { return ( - {/* Header */} - + {/* Header - Hidden for network tab to maximize space */} + {/* Top row with back button, avatar, and title/description */} { {/* Navigation Tabs */} { borderBottom: 1, borderColor: 'divider', width: '100%', + position: 'relative', // Always relative position + zIndex: 'auto', // Normal z-index + backgroundColor: 'white', // Ensure background is visible when fixed '& .MuiTab-root': { minHeight: 56, textTransform: 'none', @@ -1652,7 +1875,7 @@ const GroupDetailPage = () => { width: '100%' }, '& .MuiTabs-scroller': { - overflow: 'hidden !important' + overflow: 'visible' } }} > @@ -1671,9 +1894,9 @@ const GroupDetailPage = () => { maxWidth: '100%', overflow: 'visible', // Let page scroll instead boxSizing: 'border-box', - height: 'auto', // Auto height to expand with content + height: tabValue === 0 ? 'calc(100vh - 200px)' : 'auto', // Use viewport height for network tab only backgroundColor: 'white', // Force white background - minHeight: tabValue === 1 ? 'calc(100vh - 200px)' : 'auto' // Ensure activity tab has enough height + minHeight: tabValue === 1 ? 'calc(100vh - 200px)' : 'auto' // Normal min height }}> {tabValue === 0 && renderNetworkTab()} {tabValue === 1 && renderActivityTab()} diff --git a/src/utils/photoStyles.ts b/src/utils/photoStyles.ts new file mode 100644 index 0000000..47ef940 --- /dev/null +++ b/src/utils/photoStyles.ts @@ -0,0 +1,72 @@ +export interface PhotoStyles { + backgroundSize: string; + backgroundPosition: string; +} + +/** + * Get custom photo positioning and zoom levels for contact profile images. + * These settings ensure optimal cropping and positioning for each person's photo. + */ +export const getContactPhotoStyles = (contactName: string): PhotoStyles => { + let backgroundSize = '180%'; // default + let backgroundPosition = 'center center'; // default + + switch (contactName) { + case 'Tree Willard': + backgroundSize = '120%'; + break; + case 'Niko Bonnieure': + backgroundSize = '100%'; + break; + case 'Tim Bansemer': + backgroundSize = '220%'; + break; + case 'Duke Dorje': + backgroundSize = '200%'; + backgroundPosition = '60% 65%'; + break; + case 'Kevin Triplett': + backgroundSize = '220%'; + backgroundPosition = '40% 60%'; + break; + case 'Kristina Lillieneke': + backgroundSize = '220%'; + backgroundPosition = 'center 60%'; + break; + case 'Oliver Sylvester-Bradley': + backgroundSize = '220%'; + backgroundPosition = 'center 55%'; + break; + case 'David Thomson': + backgroundSize = '220%'; + break; + case 'Samuel Gbafa': + backgroundSize = '280%'; + backgroundPosition = '60% 60%'; + break; + case 'Meena Seshamani': + backgroundSize = '280%'; + backgroundPosition = '60% 60%'; + break; + case 'Alex Lion Yes!': + backgroundPosition = '70% 70%'; + break; + case 'Aza Mafi': + backgroundPosition = 'center 80%'; + break; + case 'Day Waterbury': + backgroundPosition = 'center 60%'; + break; + case 'Frederic Boyer': + backgroundPosition = 'center 60%'; + break; + case 'Joscha Raue': + backgroundPosition = '60% 65%'; + break; + case 'Margeigh Novotny': + backgroundPosition = 'center 70%'; + break; + } + + return { backgroundSize, backgroundPosition }; +}; \ No newline at end of file