From 052b383c0f61621ca9338a16e4cae4c473f24261 Mon Sep 17 00:00:00 2001 From: Claude Code Assistant Date: Mon, 28 Jul 2025 08:18:28 +0100 Subject: [PATCH] Remove Card wrappers and implement full-width layout with Group Info page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major layout changes: - Remove Card wrappers and padding from all main pages for full-width content - Add proper spacing between tab bars and content (24px) - Fix mobile layout issues and button positioning Header improvements: - Replace avatar dropdown with direct Person icon link to My Account - Move Settings and Logout to My Account page as new tab and bottom button - Clean up unused menu components and imports New Group Info functionality: - Create GroupInfoPage with full group description, member list, and invite button - Make Info icon in GroupDetailPage navigate to new info page - Maintain all existing invite functionality (type name/email or select from network) - Add proper routing and contact selection flow Layout fixes: - Remove horizontal padding from main content areas - Fix vertical alignment issues in group headers on mobile - Add right margin to prevent buttons from touching screen edge - Simplify map view by removing duplicate frames and titles Code cleanup: - Remove unused imports (Paper, People, Container, formatDate) - Remove unused functions (handleInviteToGroup, renderFeedContent, renderActivityView) - Fix TypeScript errors and ensure clean build - Remove @ts-nocheck and improve type safety 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- public/groups.json | 46 +- src/App.tsx | 2 + .../account/PersonhoodCredentials.tsx | 22 +- src/components/layout/DashboardLayout.tsx | 130 ++--- src/pages/AccountPage.tsx | 74 ++- src/pages/ContactListPage.tsx | 20 +- src/pages/FeedPage.tsx | 4 +- src/pages/GroupDetailPage.tsx | 401 ++------------- src/pages/GroupInfoPage.tsx | 469 ++++++++++++++++++ src/pages/GroupPage.tsx | 269 ++++++++-- src/pages/MessagesPage.tsx | 49 +- src/pages/NotificationsPage.tsx | 6 +- src/services/dataService.ts | 49 +- src/theme/theme.ts | 43 +- src/types/group.ts | 4 + 15 files changed, 1025 insertions(+), 563 deletions(-) create mode 100644 src/pages/GroupInfoPage.tsx diff --git a/public/groups.json b/public/groups.json index 5b58db5..641789e 100644 --- a/public/groups.json +++ b/public/groups.json @@ -10,7 +10,11 @@ "updatedAt": "2024-12-15T18:00:00Z", "isPrivate": false, "tags": ["nao", "genesis", "governance", "culture", "legal", "architecture"], - "image": "/naog1-butterfly-logo.svg" + "image": "/naog1-butterfly-logo.svg", + "latestPost": "Just uploaded the updated governance framework for review. Looking forward to everyone's feedback on the new proposal distribution mechanism.", + "latestPostAt": "2024-12-15T16:30:00Z", + "latestPostAuthor": "Oliver Sylvester-Bradley", + "unreadCount": 3 }, { "id": "8", @@ -23,7 +27,11 @@ "updatedAt": "2024-12-20T14:30:00Z", "isPrivate": true, "tags": ["community", "gardening", "sustainability", "local", "education", "environment"], - "image": "/community-garden-logo.svg" + "image": "/community-garden-logo.svg", + "latestPost": "Winter planning meeting this Saturday at 10am! We'll be discussing the greenhouse expansion and seed ordering for spring planting. 🌱", + "latestPostAt": "2024-12-20T12:15:00Z", + "latestPostAuthor": "Tree Willard", + "unreadCount": 7 }, { "id": "1", @@ -36,7 +44,10 @@ "updatedAt": "2023-10-15T14:30:00Z", "isPrivate": false, "tags": ["react", "frontend", "development"], - "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/react/react-original.svg" + "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/react/react-original.svg", + "latestPost": "Has anyone tried the new React 19 features yet? The compiler changes look really promising for performance optimization.", + "latestPostAt": "2023-10-15T12:45:00Z", + "latestPostAuthor": "Alex Lion Yes!" }, { "id": "2", @@ -49,7 +60,11 @@ "updatedAt": "2023-10-20T16:45:00Z", "isPrivate": false, "tags": ["product", "management", "strategy"], - "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/trello/trello-plain.svg" + "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/trello/trello-plain.svg", + "latestPost": "Sharing a great framework for prioritizing feature requests based on user impact and development effort. Link in comments!", + "latestPostAt": "2023-10-20T14:20:00Z", + "latestPostAuthor": "Ruben Daniels", + "unreadCount": 2 }, { "id": "3", @@ -62,7 +77,11 @@ "updatedAt": "2023-09-25T13:15:00Z", "isPrivate": false, "tags": ["design", "ux", "ui"], - "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/figma/figma-original.svg" + "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/figma/figma-original.svg", + "latestPost": "Looking for feedback on this mobile-first navigation pattern. Does the bottom tab bar feel intuitive for enterprise users?", + "latestPostAt": "2023-09-25T11:30:00Z", + "latestPostAuthor": "Ariana Bahrami", + "unreadCount": 1 }, { "id": "4", @@ -75,7 +94,10 @@ "updatedAt": "2023-08-30T10:20:00Z", "isPrivate": true, "tags": ["engineering", "leadership", "management"], - "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/github/github-original.svg" + "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/github/github-original.svg", + "latestPost": "Q3 performance reviews are coming up. Any recommendations for structuring technical growth conversations with senior engineers?", + "latestPostAt": "2023-08-30T09:45:00Z", + "latestPostAuthor": "Brad de Graf" }, { "id": "5", @@ -88,7 +110,11 @@ "updatedAt": "2023-07-20T09:30:00Z", "isPrivate": false, "tags": ["business", "strategy", "consulting"], - "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/linkedin/linkedin-original.svg" + "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/linkedin/linkedin-original.svg", + "latestPost": "Interesting case study on how a SaaS company pivoted their pricing model and increased LTV by 40%. Worth a read for B2B strategies.", + "latestPostAt": "2023-07-20T08:15:00Z", + "latestPostAuthor": "David Thomson", + "unreadCount": 4 }, { "id": "6", @@ -101,6 +127,10 @@ "updatedAt": "2023-06-25T15:45:00Z", "isPrivate": false, "tags": ["marketing", "digital", "growth"], - "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/google/google-original.svg" + "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/google/google-original.svg", + "latestPost": "The latest iOS privacy updates are affecting our attribution models. Anyone found good workarounds for measuring campaign effectiveness?", + "latestPostAt": "2023-06-25T13:30:00Z", + "latestPostAuthor": "Kevin Triplett", + "unreadCount": 5 } ] \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 95bede7..fe14e05 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,6 +10,7 @@ import ContactListPage from './pages/ContactListPage'; import ContactViewPage from './pages/ContactViewPage'; import GroupPage from './pages/GroupPage'; import GroupDetailPage from './pages/GroupDetailPage'; +import GroupInfoPage from './pages/GroupInfoPage'; import InvitationPage from './pages/InvitationPage'; import OnboardingPage from './pages/OnboardingPage'; import FeedPage from './pages/FeedPage'; @@ -44,6 +45,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/account/PersonhoodCredentials.tsx b/src/components/account/PersonhoodCredentials.tsx index f00ab62..a945236 100644 --- a/src/components/account/PersonhoodCredentials.tsx +++ b/src/components/account/PersonhoodCredentials.tsx @@ -42,12 +42,14 @@ interface PersonhoodCredentialsProps { credentials: PersonhoodCredentials; onGenerateQR: () => void; onRefreshCredentials: () => void; + userName?: string; } const PersonhoodCredentialsComponent = ({ credentials, onGenerateQR, - onRefreshCredentials + onRefreshCredentials, + userName = "User" }: PersonhoodCredentialsProps) => { const theme = useTheme(); const [showQRDialog, setShowQRDialog] = useState(false); @@ -225,10 +227,14 @@ const PersonhoodCredentialsComponent = ({ {/* QR Code Dialog */} setShowQRDialog(false)} maxWidth="sm" fullWidth> - - - My Verification QR Code - setShowQRDialog(false)} size="small"> + + + {userName} + setShowQRDialog(false)} + size="small" + sx={{ position: 'absolute', right: 0, top: '50%', transform: 'translateY(-50%)' }} + > @@ -249,7 +255,7 @@ const PersonhoodCredentialsComponent = ({ Scan to Verify My Personhood - Ask trusted contacts to scan this QR code to verify that you are a real human being. Each verification strengthens your credibility score. + Ask trusted contacts to scan this QR code to verify that you are a real human being. Each verification strengthens your identity. } @@ -259,12 +265,12 @@ const PersonhoodCredentialsComponent = ({ /> - - + diff --git a/src/components/layout/DashboardLayout.tsx b/src/components/layout/DashboardLayout.tsx index 4c2c21d..5abf344 100644 --- a/src/components/layout/DashboardLayout.tsx +++ b/src/components/layout/DashboardLayout.tsx @@ -8,7 +8,6 @@ import { Toolbar, List, Typography, - Divider, IconButton, ListItem, ListItemButton, @@ -16,15 +15,10 @@ import { ListItemText, useTheme, useMediaQuery, - Avatar, - Menu, - MenuItem, Badge, Collapse, } from '@mui/material'; import { - Settings, - Logout, SearchRounded, Groups, Chat, @@ -38,6 +32,7 @@ import { Business, Work, People, + AutoAwesome, } from '@mui/icons-material'; import BottomNavigation from '../navigation/BottomNavigation'; import { notificationService } from '../../services/notificationService'; @@ -69,7 +64,6 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); const [mobileOpen, setMobileOpen] = useState(false); - const [profileMenuAnchor, setProfileMenuAnchor] = useState(null); const [expandedItems, setExpandedItems] = useState>(new Set(['Network'])); const [notificationSummary, setNotificationSummary] = useState({ total: 0, @@ -116,13 +110,6 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => { setMobileOpen(!mobileOpen); }; - const handleProfileMenuOpen = (event: React.MouseEvent) => { - setProfileMenuAnchor(event.currentTarget); - }; - - const handleProfileMenuClose = () => { - setProfileMenuAnchor(null); - }; const handleNavigation = (path: string) => { navigate(path); @@ -203,15 +190,20 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => { }} selected={isActive || isParentOfActive} sx={{ - mx: 1, - ml: level > 0 ? 3 : 1, - borderRadius: 2, + mx: 0, + ml: 0, + pl: level > 0 ? 4 : 3, + borderRadius: 0, minHeight: 48, + borderRight: isActive || isParentOfActive ? 3 : 0, + borderRightColor: 'primary.main', '&.Mui-selected': { backgroundColor: 'primary.main', color: 'primary.contrastText', - ml: level > 0 ? 3 : 1, - mr: 2, + ml: 0, + pl: level > 0 ? 4 : 3, + mr: 0, + borderRight: 0, '&:hover': { backgroundColor: 'primary.dark', }, @@ -256,13 +248,21 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => { const drawerContent = ( - + NAO - + {navItems.map((item) => renderNavItem(item))} @@ -347,10 +347,21 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => { borderBottom: 1, borderColor: 'divider', boxShadow: 'none', + borderRadius: '0 !important', + borderTopLeftRadius: '0 !important', + borderTopRightRadius: '0 !important', + borderBottomLeftRadius: '0 !important', + borderBottomRightRadius: '0 !important', zIndex: theme.zIndex.drawer + 1, }} > - + { + { + // AI Assistant functionality + console.log('AI Assistant clicked'); + }} + sx={{ color: 'primary.main' }} + > + + @@ -382,18 +404,11 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => { navigate('/account')} color="inherit" > - - U - + @@ -459,10 +474,12 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => { > { - - { handleProfileMenuClose(); navigate('/account'); }}> - My Profile - - - Settings - - - - Logout - - {/* Bottom Navigation for Mobile */} {isMobile && } diff --git a/src/pages/AccountPage.tsx b/src/pages/AccountPage.tsx index 1dfac51..c8785d5 100644 --- a/src/pages/AccountPage.tsx +++ b/src/pages/AccountPage.tsx @@ -2,7 +2,6 @@ import { useState, useEffect } from 'react'; import { Typography, Box, - Paper, Tabs, Tab, Grid, @@ -28,6 +27,8 @@ import { Bookmarks, Language, Timeline, + Settings, + Logout, } from '@mui/icons-material'; import { DEFAULT_RCARDS, DEFAULT_PRIVACY_SETTINGS } from '../types/notification'; import type { RCardWithPrivacy } from '../types/notification'; @@ -47,7 +48,7 @@ interface TabPanelProps { const TabPanel = ({ children, value, index }: TabPanelProps) => { return ( ); }; @@ -246,15 +247,16 @@ const AccountPage = () => { width: '100%', maxWidth: { xs: '100vw', md: '100%' }, overflow: 'hidden', - boxSizing: 'border-box' + boxSizing: 'border-box', + p: { xs: '10px', md: 0 }, + mx: { xs: 0, md: 'auto' } }}> {/* Header */} { {/* Navigation Tabs */} - @@ -306,11 +307,12 @@ const AccountPage = () => { } label="My Webpage" /> } label="My Stream" /> } label="My Bookmarks" /> + } label="Settings" /> {/* Profile Tab */} - + @@ -379,6 +381,7 @@ const AccountPage = () => { @@ -387,7 +390,7 @@ const AccountPage = () => { {/* My Cards Tab */} - + {/* rCard List */} @@ -495,7 +498,7 @@ const AccountPage = () => { {/* My Webpage Tab */} - + @@ -514,18 +517,59 @@ const AccountPage = () => { {/* My Stream Tab */} - + {/* My Bookmarks Tab */} - + - + + {/* Settings Tab */} + + + + + + Settings + + + Account settings and preferences + + + Coming soon... + + + + + + + + {/* Logout Button - Always visible at bottom */} + + + {/* rCard Management Dialog */} { if (returnTo === 'group-invite' && groupId) { // Navigate back to group page with selected contact data navigate(`/groups/${groupId}?selectedContactName=${encodeURIComponent(contact.name)}&selectedContactEmail=${encodeURIComponent(contact.email)}`); + } else if (returnTo === 'group-info' && groupId) { + // Navigate back to group info page with selected contact data + navigate(`/groups/${groupId}/info?selectedContactName=${encodeURIComponent(contact.name)}&selectedContactEmail=${encodeURIComponent(contact.email)}`); } }; @@ -356,11 +359,11 @@ const ContactListPage = () => { }}> { component="h1" sx={{ fontWeight: 700, - mb: 1, + mb: { xs: 0, md: 0 }, fontSize: { xs: '1.5rem', md: '2.125rem' }, overflow: 'hidden', textOverflow: 'ellipsis', @@ -453,13 +456,12 @@ const ContactListPage = () => { )} - { {tabValue === 0 && ( - + { )} )} - + ); }; diff --git a/src/pages/FeedPage.tsx b/src/pages/FeedPage.tsx index 558ed27..252fb34 100644 --- a/src/pages/FeedPage.tsx +++ b/src/pages/FeedPage.tsx @@ -12,7 +12,7 @@ const FeedPage = () => { mx: { xs: 0, md: 'auto' } }}> { component="h1" sx={{ fontWeight: 700, - mb: 1, + mb: { xs: 0, md: 0 }, fontSize: { xs: '1.5rem', md: '2.125rem' }, overflow: 'hidden', textOverflow: 'ellipsis', diff --git a/src/pages/GroupDetailPage.tsx b/src/pages/GroupDetailPage.tsx index 953a870..790d1e9 100644 --- a/src/pages/GroupDetailPage.tsx +++ b/src/pages/GroupDetailPage.tsx @@ -1,4 +1,3 @@ -// @ts-nocheck import { useState, useEffect } from 'react'; import { useParams, useNavigate, useSearchParams } from 'react-router-dom'; import { getContactPhotoStyles } from '../utils/photoStyles'; @@ -13,7 +12,6 @@ import { IconButton, Button, Chip, - Badge, alpha, useTheme, Dialog, @@ -28,7 +26,6 @@ import { } from '@mui/material'; import { ArrowBack, - People, Chat, Folder, Link, @@ -38,13 +35,13 @@ import { MoreVert, Description, TableChart, - PersonAdd, AutoAwesome, AccountTree, TrendingUp, LocationOn, ExpandMore, ExpandLess, + Info, } from '@mui/icons-material'; import { dataService } from '../services/dataService'; import type { Group, GroupPost, GroupLink } from '../types/group'; @@ -280,9 +277,6 @@ const GroupDetailPage = () => { navigate('/groups'); }; - const handleInviteToGroup = () => { - setShowInviteForm(true); - }; const handleInviteSubmit = (inviteData: InviteFormData) => { console.log('Sending invite:', inviteData); @@ -386,78 +380,6 @@ const GroupDetailPage = () => { }).format(date); }; - const renderFeedTab = () => ( - - {posts.length === 0 ? ( - - - - No posts yet - - - Be the first to share something with the group! - - - - ) : ( - - {posts.map((post) => ( - - - - - {post.authorName.charAt(0)} - - - - {post.authorName} - - - {formatDate(post.createdAt)} - - - - - - - - - {post.content} - - - - - - - - - - ))} - - )} - - ); // Real NAO member data with relationships, activities, and locations const getMockMembers = () => [ @@ -741,8 +663,6 @@ const GroupDetailPage = () => { }; const renderActivityTab = () => { - const members = getMockMembers(); - // Get all unique people and topics for filters const allPeople = ['all', ...Array.from(new Set(posts.map((p: any) => p.authorName)))]; const allTopics = ['all', ...Array.from(new Set(posts.map((p: any) => p.topic).filter(Boolean)))]; @@ -994,89 +914,9 @@ const GroupDetailPage = () => { const renderMapTab = () => { const members = getMockMembers(); - return ( - - - - Member Locations - - - - {renderMapView(members)} - - - - ); + return renderMapView(members); }; - const renderFeedContent = () => ( - posts.length === 0 ? ( - - - No posts yet - - - Be the first to share something with the group! - - - ) : ( - - {posts.map((post) => ( - - - - - {post.authorName.charAt(0)} - - - - {post.authorName} - - - {formatDate(post.createdAt)} - - - - - - - - - {post.content} - - - - - - - - - - ))} - - ) - ); const renderNetworkView = (members: any[]) => { // Shared position calculation function for perfect alignment @@ -1284,112 +1124,12 @@ const GroupDetailPage = () => { ); }; - const renderActivityView = (members: any[]) => { - // Get all unique topics and their activity leaders - const topicAnalysis: any = {}; - - members.forEach(member => { - member.activities?.forEach((activity: any) => { - if (!topicAnalysis[activity.topic]) { - topicAnalysis[activity.topic] = []; - } - topicAnalysis[activity.topic].push({ - member, - count: activity.count, - lastActive: activity.lastActive - }); - }); - }); - - // Sort topics by total activity - const sortedTopics = Object.entries(topicAnalysis) - .map(([topic, activities]: [string, any]) => ({ - topic, - activities: activities.sort((a: any, b: any) => b.count - a.count), - totalActivity: activities.reduce((sum: number, a: any) => sum + a.count, 0) - })) - .sort((a, b) => b.totalActivity - a.totalActivity); - - return ( - - - - Topic Activity Leaders - - - - {sortedTopics.map(({ topic, activities, totalActivity }) => ( - - - - - {topic} - - - - - - {activities.slice(0, 3).map((activity: any, index: number) => ( - - - #{index + 1} - - - - {activity.member.initials} - - - - - {activity.member.name} - - - Last active: {activity.lastActive} - - - - - - {activity.count} posts - - - - - ))} - - - - ))} - - - ); - }; const renderMapView = (members: any[]) => { const visibleMembers = members.filter(m => m.location?.visible); return ( - - - Anyville Community Garden Network - - {/* World map view */} { maxWidth: { xs: '100vw', md: '100%' }, overflow: tabValue === 0 ? 'hidden' : 'hidden', // Prevent scrolling for network tab boxSizing: 'border-box', - p: { xs: '10px', md: 0 }, // Normal padding + pt: { xs: 1.5, md: 2 }, // Reduced top padding + pb: 0, // No bottom padding mx: { xs: 0, md: 'auto' } }}> {/* Header - Hidden for network tab to maximize space */} { src={group.image} alt={group.name} sx={{ - width: { xs: 48, md: 64 }, - height: { xs: 48, md: 64 }, + width: { xs: 40, md: 56 }, + height: { xs: 40, md: 56 }, bgcolor: 'white', border: 1, borderColor: 'primary.main', @@ -1735,41 +1476,6 @@ const GroupDetailPage = () => { > {group.name} - - - - - - {group.memberCount} members - - {group.isPrivate && ( - - )} - - {group.description && ( - - {group.description} - - )} {/* Desktop buttons */} { alignItems: 'flex-start', flexShrink: 0 }}> - - + + - {/* Mobile buttons row */} - - - - {/* Navigation Tabs */} - { {/* Tab Content */} { {tabValue === 4 && renderFilesTab()} {tabValue === 5 && renderLinksTab()} - + {/* Show Post Create Button only on Activity tab */} {tabValue === 1 && ( diff --git a/src/pages/GroupInfoPage.tsx b/src/pages/GroupInfoPage.tsx new file mode 100644 index 0000000..b05155b --- /dev/null +++ b/src/pages/GroupInfoPage.tsx @@ -0,0 +1,469 @@ +import { useState, useEffect } from 'react'; +import { useParams, useNavigate, useSearchParams } from 'react-router-dom'; +import { getContactPhotoStyles } from '../utils/photoStyles'; +import { + Typography, + Box, + Avatar, + Button, + Card, + CardContent, + IconButton, + Chip, + List, + ListItem, + ListItemAvatar, + ListItemText, + alpha, + useTheme, +} from '@mui/material'; +import { + ArrowBack, + PersonAdd, + Public, + Lock, +} from '@mui/icons-material'; +import { dataService } from '../services/dataService'; +import type { Group } from '../types/group'; +import InviteForm, { type InviteFormData } from '../components/invite/InviteForm'; + +// Real NAO member data +const getMockMembers = () => [ + { + id: 'oli-sb', + name: 'Oliver Sylvester-Bradley', + avatar: '/images/Oli.jpg', + role: 'Admin', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 365), // 1 year ago + }, + { + id: 'ruben-daniels', + name: 'Ruben Daniels', + avatar: '/images/Ruben.jpg', + role: 'Member', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 300), // 300 days ago + }, + { + id: 'margeigh-novotny', + name: 'Margeigh Novotny', + avatar: '/images/Margeigh.jpg', + role: 'Member', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 280), // 280 days ago + }, + { + id: 'alex-lion', + name: 'Alex Lion Yes!', + avatar: '/images/Alex.jpg', + role: 'Member', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 250), // 250 days ago + }, + { + id: 'day-waterbury', + name: 'Day Waterbury', + avatar: '/images/Day.jpg', + role: 'Member', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 200), // 200 days ago + }, + { + id: 'kevin-triplett', + name: 'Kevin Triplett', + avatar: '/images/Kevin.jpg', + role: 'Member', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 180), // 180 days ago + }, + { + id: 'tim-bansemer', + name: 'Tim Bansemer', + avatar: '/images/Tim.jpg', + role: 'Member', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 150), // 150 days ago + }, + { + id: 'aza-mafi', + name: 'Aza Mafi', + avatar: '/images/Aza.jpg', + role: 'Member', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 120), // 120 days ago + }, + { + id: 'duke-dorje', + name: 'Duke Dorje', + avatar: '/images/Duke.jpg', + role: 'Member', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 100), // 100 days ago + }, + { + id: 'david-thomson', + name: 'David Thomson', + avatar: '/images/David.jpg', + role: 'Member', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 80), // 80 days ago + }, + { + id: 'samuel-gbafa', + name: 'Samuel Gbafa', + avatar: '/images/Sam.jpg', + role: 'Member', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 60), // 60 days ago + }, + { + id: 'meena-seshamani', + name: 'Meena Seshamani', + avatar: '/images/Meena.jpg', + role: 'Member', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 40), // 40 days ago + }, + { + id: 'niko-bonnieure', + name: 'Niko Bonnieure', + avatar: '/images/Niko.jpg', + role: 'Member', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 30), // 30 days ago + }, + { + id: 'tree-willard', + name: 'Tree Willard', + avatar: '/images/Tree.jpg', + role: 'Member', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 20), // 20 days ago + }, + { + id: 'stephane-bancel', + name: 'Stephane Bancel', + avatar: '/images/Stephane.jpg', + role: 'Member', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 15), // 15 days ago + }, + { + id: 'joscha-raue', + name: 'Joscha Raue', + avatar: '/images/Joscha.jpg', + role: 'Member', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 10), // 10 days ago + }, + { + id: 'drummond-reed', + name: 'Drummond Reed', + avatar: '/images/Drummond.jpg', + role: 'Member', + joinedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5), // 5 days ago + }, +]; + +const GroupInfoPage = () => { + const { groupId } = useParams<{ groupId: string }>(); + const navigate = useNavigate(); + const [searchParams, setSearchParams] = useSearchParams(); + const theme = useTheme(); + + const [group, setGroup] = useState(null); + const [members] = useState(getMockMembers()); + const [isLoading, setIsLoading] = useState(true); + const [showInviteForm, setShowInviteForm] = useState(false); + const [selectedContact, setSelectedContact] = useState<{name: string; email: string} | undefined>(); + + useEffect(() => { + const loadGroupData = async () => { + if (!groupId) return; + + setIsLoading(true); + try { + const groupData = await dataService.getGroup(groupId); + setGroup(groupData || null); + + // Handle returning from contact selection + const selectedContactName = searchParams.get('selectedContactName'); + const selectedContactEmail = searchParams.get('selectedContactEmail'); + if (selectedContactName && selectedContactEmail) { + setSelectedContact({ + name: selectedContactName, + email: selectedContactEmail + }); + setShowInviteForm(true); + + // Clean up selection parameters + const newSearchParams = new URLSearchParams(searchParams); + newSearchParams.delete('selectedContactName'); + newSearchParams.delete('selectedContactEmail'); + setSearchParams(newSearchParams); + } + } catch (error) { + console.error('Failed to load group data:', error); + } finally { + setIsLoading(false); + } + }; + + loadGroupData(); + }, [groupId, searchParams, setSearchParams]); + + const handleBack = () => { + navigate(`/groups/${groupId}`); + }; + + const handleInviteToGroup = () => { + setShowInviteForm(true); + }; + + const handleInviteSubmit = (inviteData: InviteFormData) => { + console.log('Sending invite:', inviteData); + // TODO: Generate personalized invitation link and send email + // For now, navigate to invite page with the data + const inviteParams = new URLSearchParams({ + groupId: groupId!, + inviteeName: inviteData.inviteeName, + inviterName: inviteData.inviterName, + relationshipType: inviteData.relationshipType, + }); + + setShowInviteForm(false); + navigate(`/invite?${inviteParams.toString()}`); + }; + + const handleSelectFromNetwork = () => { + // Navigate to contacts page with selection mode and return context + setShowInviteForm(false); + navigate(`/contacts?mode=select&returnTo=group-info&groupId=${groupId}`); + }; + + const formatDate = (date: Date) => { + return new Intl.DateTimeFormat('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }).format(date); + }; + + const getPrivacyIcon = (isPrivate: boolean) => { + return isPrivate ? ( + + ) : ( + + ); + }; + + if (isLoading) { + return ( + + + Loading group... + + + ); + } + + if (!group) { + return ( + + + Group not found + + + ); + } + + return ( + + {/* Header */} + + + + + + + {group.name.charAt(0)} + + + + + + {group.name} + + {getPrivacyIcon(group.isPrivate)} + + + + + + + + {/* Content */} + + {/* Description Card */} + + + + About this group + + + {group.description} + + + {group.tags && group.tags.length > 0 && ( + + {group.tags.map((tag) => ( + + ))} + + )} + + + + {/* Members List */} + + + + + Members ({members.length}) + + + + + {members.map((member, index) => ( + + + + {!member.avatar && member.name.split(' ').map(n => n[0]).join('')} + + + + + {member.name} + + {member.role === 'Admin' && ( + + )} + + } + secondary={ + + Joined {formatDate(member.joinedAt)} + + } + /> + + ))} + + + + + + {/* Invite Form */} + {group && ( + { + setShowInviteForm(false); + setSelectedContact(undefined); + }} + onSubmit={handleInviteSubmit} + onSelectFromNetwork={handleSelectFromNetwork} + group={group} + prefilledContact={selectedContact} + /> + )} + + ); +}; + +export default GroupInfoPage; \ No newline at end of file diff --git a/src/pages/GroupPage.tsx b/src/pages/GroupPage.tsx index 9f5acc9..b31913a 100644 --- a/src/pages/GroupPage.tsx +++ b/src/pages/GroupPage.tsx @@ -76,14 +76,6 @@ const GroupPage = () => { ); }; - const formatDate = (date: Date) => { - return new Intl.DateTimeFormat('en-US', { - year: 'numeric', - month: 'short', - day: 'numeric' - }).format(date); - }; - return ( { display: 'flex', justifyContent: 'space-between', alignItems: 'center', - mb: { xs: 2, md: 3 }, + mb: { xs: 1, md: 1 }, width: '100%', overflow: 'hidden', minWidth: 0 @@ -109,7 +101,7 @@ const GroupPage = () => { component="h1" sx={{ fontWeight: 700, - mb: 1, + mb: { xs: 0, md: 0 }, fontSize: { xs: '1.5rem', md: '2.125rem' }, overflow: 'hidden', textOverflow: 'ellipsis', @@ -126,8 +118,9 @@ const GroupPage = () => { onClick={handleCreateGroup} sx={{ borderRadius: 2, - fontSize: { xs: '0.75rem', md: '0.875rem' }, - px: { xs: 1, md: 2 } + fontSize: { xs: '0.7rem', md: '0.875rem' }, + px: { xs: 0.75, md: 2 }, + py: { xs: 0.5, md: 0.75 } }} > Create Group @@ -135,16 +128,155 @@ const GroupPage = () => { - + setSearchQuery(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + }} + sx={{ + mb: 2, + '& .MuiInputBase-root': { + height: 40 + } + }} + /> + + {isLoading ? ( + + + Loading 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, index) => ( + handleGroupClick(group.id)} + sx={{ + cursor: 'pointer', + p: 2, + borderBottom: index === filteredGroups.length - 1 ? 'none' : '1px solid', + borderColor: 'divider', + '&:active': { + backgroundColor: alpha(theme.palette.primary.main, 0.08), + }, + }} + > + + + + + + + + + + {group.name} + + + + + {group.memberCount} + + + {getPrivacyIcon(group.isPrivate)} + + + + {group.unreadCount && group.unreadCount > 0 && ( + + + + )} + + + + {group.latestPost && ( + + {group.latestPostAuthor && `${group.latestPostAuthor.split(' ')[0]}: `}{group.latestPost} + + )} + + + + ))} + + )} + + + {/* Desktop: Keep original card layout */} + - { - - - {group.name} - - {getPrivacyIcon(group.isPrivate)} - - - - - - - - members - + + + + {group.name} + + + + + {group.memberCount} + + + {getPrivacyIcon(group.isPrivate)} + + + {group.unreadCount && group.unreadCount > 0 && ( + + + + )} - + {group.description} - {group.tags?.map((tag) => ( + {group.tags?.slice(0, 3).map((tag) => ( { ))} - - Created {formatDate(group.createdAt)} - + {group.latestPost && ( + + + Latest post: + + + {group.latestPostAuthor && `${group.latestPostAuthor.split(' ')[0]}: `}{group.latestPost} + + + )} ))} )} - - + ); diff --git a/src/pages/MessagesPage.tsx b/src/pages/MessagesPage.tsx index 9f87be3..66503b8 100644 --- a/src/pages/MessagesPage.tsx +++ b/src/pages/MessagesPage.tsx @@ -1,17 +1,46 @@ -import { Box, Typography, Container } from '@mui/material'; +import { Box, Typography } from '@mui/material'; const MessagesPage = () => { return ( - - - - Messages - - - Your messages and conversations will appear here. - + + + + + Messages + + - + + Your messages and conversations will appear here. + + ); }; diff --git a/src/pages/NotificationsPage.tsx b/src/pages/NotificationsPage.tsx index 5b17ba2..6144bf3 100644 --- a/src/pages/NotificationsPage.tsx +++ b/src/pages/NotificationsPage.tsx @@ -184,7 +184,7 @@ const NotificationsPage = () => { display: 'flex', justifyContent: 'space-between', alignItems: 'center', - mb: { xs: 2, md: 3 }, + mb: { xs: 1, md: 1 }, width: '100%', overflow: 'hidden', minWidth: 0 @@ -195,7 +195,7 @@ const NotificationsPage = () => { component="h1" sx={{ fontWeight: 700, - mb: 1, + mb: { xs: 0, md: 0 }, fontSize: { xs: '1.5rem', md: '2.125rem' }, overflow: 'hidden', textOverflow: 'ellipsis', @@ -233,7 +233,7 @@ const NotificationsPage = () => { maxWidth: { xs: 'calc(100vw - 0px)', md: '100%' }, overflow: 'hidden', mx: { xs: '-10px', md: 0 }, - borderRadius: { xs: 0, md: 1 }, + borderRadius: 0, boxSizing: 'border-box' }}> diff --git a/src/services/dataService.ts b/src/services/dataService.ts index 6a1b707..8bef7f0 100644 --- a/src/services/dataService.ts +++ b/src/services/dataService.ts @@ -111,11 +111,20 @@ export const dataService = { 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) - })); + const groups = groupsData.map((group: any) => { + const processedGroup = { + ...group, + createdAt: new Date(group.createdAt), + updatedAt: new Date(group.updatedAt) + }; + + // Convert optional date fields if they exist + if (group.latestPostAt) { + processedGroup.latestPostAt = new Date(group.latestPostAt); + } + + return processedGroup; + }); resolve(groups); } catch (error) { console.error('Failed to load groups:', error); @@ -133,11 +142,18 @@ export const dataService = { const groupsData = await response.json(); const group = groupsData.find((g: any) => g.id === id); if (group) { - resolve({ + const processedGroup = { ...group, createdAt: new Date(group.createdAt), updatedAt: new Date(group.updatedAt) - }); + }; + + // Convert optional date fields if they exist + if (group.latestPostAt) { + processedGroup.latestPostAt = new Date(group.latestPostAt); + } + + resolve(processedGroup); } else { resolve(undefined); } @@ -157,11 +173,20 @@ export const dataService = { 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) - })); + .map((group: any) => { + const processedGroup = { + ...group, + createdAt: new Date(group.createdAt), + updatedAt: new Date(group.updatedAt) + }; + + // Convert optional date fields if they exist + if (group.latestPostAt) { + processedGroup.latestPostAt = new Date(group.latestPostAt); + } + + return processedGroup; + }); resolve(userGroups); } catch (error) { console.error('Failed to load user groups:', error); diff --git a/src/theme/theme.ts b/src/theme/theme.ts index 29ea538..f2fcb0a 100644 --- a/src/theme/theme.ts +++ b/src/theme/theme.ts @@ -326,8 +326,36 @@ export const createAppTheme = (mode: PaletteMode) => { root: { backgroundColor: isDark ? '#1e293b' : '#ffffff', color: isDark ? '#e2e8f0' : '#334155', - boxShadow: 'none', + boxShadow: 'none !important', + borderRadius: '0 !important', borderBottom: `1px solid ${isDark ? alpha('#e2e8f0', 0.08) : alpha('#334155', 0.08)}`, + height: 64, + minHeight: 64, + '&::before': { + borderRadius: '0 !important', + }, + '&::after': { + borderRadius: '0 !important', + }, + '& > *': { + borderRadius: '0 !important', + }, + '&.MuiPaper-elevation': { + boxShadow: 'none !important', + }, + '&.MuiPaper-elevation4': { + boxShadow: 'none !important', + }, + }, + }, + }, + MuiToolbar: { + styleOverrides: { + root: { + minHeight: '64px !important', + height: '64px !important', + paddingTop: '0 !important', + paddingBottom: '0 !important', }, }, }, @@ -335,15 +363,22 @@ export const createAppTheme = (mode: PaletteMode) => { styleOverrides: { paper: { backgroundColor: isDark ? '#0f172a' : '#ffffff', - borderRight: `1px solid ${isDark ? alpha('#e2e8f0', 0.08) : alpha('#334155', 0.08)}`, + borderRadius: 0, + border: 'none', + }, + docked: { + '& .MuiDrawer-paper': { + border: 'none', + borderRight: 'none', + }, }, }, }, MuiListItem: { styleOverrides: { root: { - borderRadius: 8, - margin: '2px 8px', + borderRadius: 0, + margin: 0, '&:hover': { backgroundColor: isDark ? alpha('#e2e8f0', 0.04) : alpha('#334155', 0.04), }, diff --git a/src/types/group.ts b/src/types/group.ts index 2eac60f..e48ad50 100644 --- a/src/types/group.ts +++ b/src/types/group.ts @@ -10,6 +10,10 @@ export interface Group { isPrivate: boolean; tags?: string[]; image?: string; + latestPost?: string; + latestPostAuthor?: string; + latestPostAt?: Date; + unreadCount?: number; } export interface GroupMember {