diff --git a/public/groups.json b/public/groups.json
index 10cac92..51b69a9 100644
--- a/public/groups.json
+++ b/public/groups.json
@@ -10,7 +10,7 @@
"updatedAt": "2023-10-15T14:30:00Z",
"isPrivate": false,
"tags": ["react", "frontend", "development"],
- "image": "https://i.pravatar.cc/200?img=react"
+ "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/react/react-original.svg"
},
{
"id": "2",
@@ -23,7 +23,7 @@
"updatedAt": "2023-10-20T16:45:00Z",
"isPrivate": false,
"tags": ["product", "management", "strategy"],
- "image": "https://i.pravatar.cc/200?img=product"
+ "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/trello/trello-plain.svg"
},
{
"id": "3",
@@ -36,7 +36,7 @@
"updatedAt": "2023-09-25T13:15:00Z",
"isPrivate": false,
"tags": ["design", "ux", "ui"],
- "image": "https://i.pravatar.cc/200?img=design"
+ "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/figma/figma-original.svg"
},
{
"id": "4",
@@ -49,7 +49,7 @@
"updatedAt": "2023-08-30T10:20:00Z",
"isPrivate": true,
"tags": ["engineering", "leadership", "management"],
- "image": "https://i.pravatar.cc/200?img=leadership"
+ "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/github/github-original.svg"
},
{
"id": "5",
@@ -62,7 +62,7 @@
"updatedAt": "2023-07-20T09:30:00Z",
"isPrivate": false,
"tags": ["business", "strategy", "consulting"],
- "image": "https://i.pravatar.cc/200?img=business"
+ "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/linkedin/linkedin-original.svg"
},
{
"id": "6",
@@ -75,6 +75,6 @@
"updatedAt": "2023-06-25T15:45:00Z",
"isPrivate": false,
"tags": ["marketing", "digital", "growth"],
- "image": "https://i.pravatar.cc/200?img=marketing"
+ "image": "https://cdn.jsdelivr.net/gh/devicons/devicon/icons/google/google-original.svg"
}
]
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
index 77db9fb..18c65d9 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -3,10 +3,12 @@ import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { OnboardingProvider } from './context/OnboardingContext';
import DashboardLayout from './components/layout/DashboardLayout';
+import SocialContractPage from './pages/SocialContractPage';
import ImportPage from './pages/ImportPage';
import ContactListPage from './pages/ContactListPage';
import ContactViewPage from './pages/ContactViewPage';
import GroupPage from './pages/GroupPage';
+import GroupDetailPage from './pages/GroupDetailPage';
import InvitationPage from './pages/InvitationPage';
import OnboardingPage from './pages/OnboardingPage';
import FeedPage from './pages/FeedPage';
@@ -23,21 +25,30 @@ function App() {
-
-
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
- } />
-
-
+
+ {/* Social Contract page - outside DashboardLayout */}
+ } />
+
+ {/* Main app routes - inside DashboardLayout */}
+
+
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+ } />
+
diff --git a/src/components/PostCreateButton.tsx b/src/components/PostCreateButton.tsx
new file mode 100644
index 0000000..e6bad76
--- /dev/null
+++ b/src/components/PostCreateButton.tsx
@@ -0,0 +1,173 @@
+import { useState } from 'react';
+import {
+ Fab,
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ List,
+ ListItem,
+ ListItemButton,
+ ListItemIcon,
+ ListItemText,
+ Typography,
+ Box,
+ IconButton,
+ useTheme,
+ alpha
+} from '@mui/material';
+import {
+ Add,
+ PostAdd,
+ LocalOffer,
+ ShoppingCart,
+ Close
+} from '@mui/icons-material';
+
+interface PostCreateButtonProps {
+ groupId?: string;
+ onCreatePost?: (type: 'post' | 'offer' | 'want', groupId?: string) => void;
+}
+
+const PostCreateButton = ({ groupId, onCreatePost }: PostCreateButtonProps) => {
+ const [open, setOpen] = useState(false);
+ const theme = useTheme();
+
+ const handleOpen = () => {
+ setOpen(true);
+ };
+
+ const handleClose = () => {
+ setOpen(false);
+ };
+
+ const handleCreatePost = (type: 'post' | 'offer' | 'want') => {
+ if (onCreatePost) {
+ onCreatePost(type, groupId);
+ } else {
+ // Default behavior - navigate to posts page with type parameter
+ const searchParams = new URLSearchParams();
+ searchParams.append('type', type);
+ if (groupId) {
+ searchParams.append('groupId', groupId);
+ }
+ window.location.href = `/posts?${searchParams.toString()}`;
+ }
+ handleClose();
+ };
+
+ const postTypes = [
+ {
+ type: 'post' as const,
+ title: 'Post',
+ description: 'Share an update, thought, or announcement',
+ icon: ,
+ color: theme.palette.primary.main
+ },
+ {
+ type: 'offer' as const,
+ title: 'Offer',
+ description: 'Offer your services, expertise, or resources',
+ icon: ,
+ color: theme.palette.success.main
+ },
+ {
+ type: 'want' as const,
+ title: 'Want',
+ description: 'Request help, services, or connections',
+ icon: ,
+ color: theme.palette.warning.main
+ }
+ ];
+
+ return (
+ <>
+
+
+
+
+
+ >
+ );
+};
+
+export default PostCreateButton;
\ No newline at end of file
diff --git a/src/components/layout/DashboardLayout.tsx b/src/components/layout/DashboardLayout.tsx
index 0483f24..74bf625 100644
--- a/src/components/layout/DashboardLayout.tsx
+++ b/src/components/layout/DashboardLayout.tsx
@@ -65,7 +65,6 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => {
{ text: 'Feed', icon: , path: '/feed' },
{ text: 'Network', icon: , path: '/contacts' },
{ text: 'Groups', icon: , path: '/groups' },
- { text: 'Post', icon: , path: '/posts' },
{ text: 'Chat', icon: , path: '/messages' },
];
diff --git a/src/components/navigation/BottomNavigation.tsx b/src/components/navigation/BottomNavigation.tsx
index 4593c6b..1e18313 100644
--- a/src/components/navigation/BottomNavigation.tsx
+++ b/src/components/navigation/BottomNavigation.tsx
@@ -20,7 +20,6 @@ const BottomNavigation = () => {
{ label: 'Feed', icon: , path: '/feed' },
{ label: 'Network', icon: , path: '/contacts' },
{ label: 'Groups', icon: , path: '/groups' },
- { label: 'Post', icon: , path: '/posts' },
{ label: 'Chat', icon: , path: '/messages' },
];
diff --git a/src/pages/FeedPage.tsx b/src/pages/FeedPage.tsx
index 67e51bb..ed6fd3c 100644
--- a/src/pages/FeedPage.tsx
+++ b/src/pages/FeedPage.tsx
@@ -1,17 +1,27 @@
import { Box, Typography, Container } from '@mui/material';
+import PostCreateButton from '../components/PostCreateButton';
const FeedPage = () => {
+ const handleCreatePost = (type: 'post' | 'offer' | 'want') => {
+ console.log(`Creating ${type} in main feed`);
+ // TODO: Implement post creation logic
+ };
+
return (
-
-
-
- Feed
-
-
- Your personalized network feed will appear here.
-
-
-
+ <>
+
+
+
+ Feed
+
+
+ Your personalized network feed will appear here.
+
+
+
+
+
+ >
);
};
diff --git a/src/pages/GroupDetailPage.tsx b/src/pages/GroupDetailPage.tsx
new file mode 100644
index 0000000..b38d888
--- /dev/null
+++ b/src/pages/GroupDetailPage.tsx
@@ -0,0 +1,462 @@
+import { useState, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import {
+ Typography,
+ Box,
+ Tabs,
+ Tab,
+ Avatar,
+ Card,
+ CardContent,
+ IconButton,
+ Button,
+ Chip,
+ Divider,
+ Badge,
+ alpha,
+ useTheme
+} from '@mui/material';
+import {
+ ArrowBack,
+ RssFeed,
+ People,
+ Chat,
+ Folder,
+ Link,
+ ThumbUp,
+ Comment,
+ Share,
+ MoreVert,
+ FolderShared,
+ Description,
+ TableChart,
+ PersonAdd
+} from '@mui/icons-material';
+import { dataService } from '../services/dataService';
+import type { Group, GroupPost, GroupLink } from '../types/group';
+import PostCreateButton from '../components/PostCreateButton';
+
+const GroupDetailPage = () => {
+ const { groupId } = useParams<{ groupId: string }>();
+ const navigate = useNavigate();
+ const theme = useTheme();
+
+ const [group, setGroup] = useState(null);
+ const [posts, setPosts] = useState([]);
+ const [links, setLinks] = useState([]);
+ const [tabValue, setTabValue] = useState(0);
+ const [isLoading, setIsLoading] = useState(true);
+
+ useEffect(() => {
+ const loadGroupData = async () => {
+ if (!groupId) return;
+
+ setIsLoading(true);
+ try {
+ const groupData = await dataService.getGroup(groupId);
+ setGroup(groupData || null);
+
+ // Mock data for posts and links until we have real data
+ const mockPosts: GroupPost[] = [
+ {
+ id: '1',
+ groupId: groupId,
+ authorId: 'user1',
+ authorName: 'John Doe',
+ authorAvatar: undefined,
+ content: 'Welcome to our group! Looking forward to collaborating with everyone.',
+ createdAt: new Date(Date.now() - 1000 * 60 * 30), // 30 minutes ago
+ updatedAt: new Date(Date.now() - 1000 * 60 * 30),
+ likes: 5,
+ comments: 2,
+ },
+ {
+ id: '2',
+ groupId: groupId,
+ authorId: 'user2',
+ authorName: 'Jane Smith',
+ authorAvatar: undefined,
+ content: 'Just shared some useful resources in our Files section. Check them out!',
+ createdAt: new Date(Date.now() - 1000 * 60 * 60 * 2), // 2 hours ago
+ updatedAt: new Date(Date.now() - 1000 * 60 * 60 * 2),
+ likes: 3,
+ comments: 1,
+ }
+ ];
+
+ const mockLinks: GroupLink[] = [
+ {
+ id: '1',
+ groupId: groupId,
+ title: 'Industry Best Practices Guide',
+ url: 'https://example.com/guide',
+ description: 'Comprehensive guide on industry best practices',
+ sharedBy: 'user1',
+ sharedByName: 'John Doe',
+ sharedAt: new Date(Date.now() - 1000 * 60 * 60 * 24), // 1 day ago
+ tags: ['guide', 'best-practices']
+ }
+ ];
+
+ setPosts(mockPosts);
+ setLinks(mockLinks);
+ } catch (error) {
+ console.error('Failed to load group data:', error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ loadGroupData();
+ }, [groupId]);
+
+ const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => {
+ setTabValue(newValue);
+ };
+
+ const handleBack = () => {
+ navigate('/groups');
+ };
+
+ const handleInviteToGroup = () => {
+ // TODO: Implement invite to group functionality
+ console.log('Inviting to group:', group?.name);
+ // This could navigate to an invite page or open a modal
+ navigate(`/invite?groupId=${groupId}`);
+ };
+
+ const handleCreatePost = (type: 'post' | 'offer' | 'want', groupId?: string) => {
+ console.log(`Creating ${type} in group ${groupId || 'unknown'}`);
+ // TODO: Implement group post creation logic
+ };
+
+ const formatDate = (date: Date) => {
+ return new Intl.DateTimeFormat('en-US', {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit'
+ }).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}
+
+
+
+ }
+ size="small"
+ variant="text"
+ sx={{ color: 'text.secondary' }}
+ >
+ {post.likes}
+
+ }
+ size="small"
+ variant="text"
+ sx={{ color: 'text.secondary' }}
+ >
+ {post.comments}
+
+ }
+ size="small"
+ variant="text"
+ sx={{ color: 'text.secondary' }}
+ >
+ Share
+
+
+
+
+ ))}
+
+ )}
+
+ );
+
+ const renderMembersTab = () => (
+
+
+
+
+ Members ({group?.memberCount || 0})
+
+
+ Member management coming soon...
+
+
+
+
+ );
+
+ const renderChatTab = () => (
+
+
+
+
+ Group Chat
+
+
+ Real-time group chat functionality coming soon...
+
+
+
+
+ );
+
+ const renderFilesTab = () => (
+
+
+
+
+ Collaborative Files
+
+
+ Share documents and spreadsheets with your group
+
+
+
+
+
+
+
+ Collaborative Document Editor
+
+
+ Real-time document editing with your group members
+
+
+
+
+
+
+
+
+
+ Collaborative Spreadsheet Editor
+
+
+ Work together on spreadsheets and data analysis
+
+
+
+
+
+
+
+
+ );
+
+ const renderLinksTab = () => (
+
+ {links.length === 0 ? (
+
+
+
+ No links shared yet
+
+
+ Share useful links with your group members
+
+
+
+ ) : (
+
+ {links.map((link) => (
+
+
+
+
+
+
+ {link.title}
+
+
+ {link.description}
+
+
+
+
+
+ {link.url}
+
+
+
+ {link.tags?.map((tag) => (
+
+ ))}
+
+
+
+ Shared by {link.sharedByName} • {formatDate(link.sharedAt)}
+
+
+
+ ))}
+
+ )}
+
+ );
+
+ if (isLoading) {
+ return (
+
+
+ Loading group...
+
+
+ );
+ }
+
+ if (!group) {
+ return (
+
+
+ Group not found
+
+
+ );
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
+
+
+ {group.name.charAt(0)}
+
+
+
+ {group.name}
+
+
+
+
+
+
+ {group.memberCount} members
+
+ {group.isPrivate && (
+
+ )}
+
+ {group.description && (
+
+ {group.description}
+
+ )}
+
+
+ }
+ onClick={handleInviteToGroup}
+ sx={{ borderRadius: 2 }}
+ >
+ Invite to Group
+
+
+
+
+ {/* Navigation Tabs */}
+
+
+ } label="Feed" />
+ } label="Members" />
+ } label="Chat" />
+ } label="Files" />
+ } label="Links" />
+
+
+ {/* Tab Content */}
+
+ {tabValue === 0 && renderFeedTab()}
+ {tabValue === 1 && renderMembersTab()}
+ {tabValue === 2 && renderChatTab()}
+ {tabValue === 3 && renderFilesTab()}
+ {tabValue === 4 && renderLinksTab()}
+
+
+
+ {/* Show Post Create Button only on Feed tab */}
+ {tabValue === 0 && (
+
+ )}
+
+ );
+};
+
+export default GroupDetailPage;
\ No newline at end of file
diff --git a/src/pages/InvitationPage.tsx b/src/pages/InvitationPage.tsx
index 82a3480..4743568 100644
--- a/src/pages/InvitationPage.tsx
+++ b/src/pages/InvitationPage.tsx
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react';
-import { useNavigate } from 'react-router-dom';
+import { useNavigate, useSearchParams } from 'react-router-dom';
import {
Container,
Typography,
@@ -14,7 +14,9 @@ import {
Snackbar,
Alert,
TextField,
- InputAdornment
+ InputAdornment,
+ Avatar,
+ Chip
} from '@mui/material';
import {
Share,
@@ -23,26 +25,47 @@ import {
Message,
WhatsApp,
GetApp,
- Refresh
+ Refresh,
+ ArrowBack,
+ Groups
} from '@mui/icons-material';
import { QRCodeSVG } from 'qrcode.react';
+import { dataService } from '../services/dataService';
+import type { Group } from '../types/group';
const InvitationPage = () => {
const [invitationUrl, setInvitationUrl] = useState('');
const [copySuccess, setCopySuccess] = useState(false);
const [invitationId, setInvitationId] = useState('');
+ const [group, setGroup] = useState(null);
+ const [isGroupInvite, setIsGroupInvite] = useState(false);
const navigate = useNavigate();
+ const [searchParams] = useSearchParams();
useEffect(() => {
- const generateInvitation = () => {
+ const loadGroupAndGenerateInvitation = async () => {
+ const groupId = searchParams.get('groupId');
+
+ if (groupId) {
+ setIsGroupInvite(true);
+ try {
+ const groupData = await dataService.getGroup(groupId);
+ setGroup(groupData || null);
+ } catch (error) {
+ console.error('Failed to load group:', error);
+ }
+ }
+
const id = Math.random().toString(36).substring(2, 15);
setInvitationId(id);
- const url = `${window.location.origin}/onboarding?invite=${id}`;
+ const url = groupId
+ ? `${window.location.origin}/onboarding?invite=${id}&groupId=${groupId}`
+ : `${window.location.origin}/onboarding?invite=${id}`;
setInvitationUrl(url);
};
- generateInvitation();
- }, []);
+ loadGroupAndGenerateInvitation();
+ }, [searchParams]);
const handleCopyToClipboard = async () => {
try {
@@ -56,9 +79,14 @@ const InvitationPage = () => {
const handleShare = async () => {
if (navigator.share) {
try {
+ const title = isGroupInvite ? `Join ${group?.name}` : 'Join My Network';
+ const text = isGroupInvite
+ ? `Join our ${group?.name} group to collaborate and stay connected!`
+ : 'Join my personal network to stay connected!';
+
await navigator.share({
- title: 'Join My Network',
- text: 'Join my personal network to stay connected!',
+ title,
+ text,
url: invitationUrl,
});
} catch (err) {
@@ -70,24 +98,26 @@ const InvitationPage = () => {
};
const handleEmailShare = () => {
- const subject = encodeURIComponent('Join My Network');
- const body = encodeURIComponent(
- `I'd like to add you to my personal network. Please use this link to join: ${invitationUrl}`
- );
+ const subject = isGroupInvite
+ ? encodeURIComponent(`Join ${group?.name}`)
+ : encodeURIComponent('Join My Network');
+ const body = isGroupInvite
+ ? encodeURIComponent(`I'd like to invite you to join our ${group?.name} group. Please use this link to join: ${invitationUrl}`)
+ : encodeURIComponent(`I'd like to add you to my personal network. Please use this link to join: ${invitationUrl}`);
window.open(`mailto:?subject=${subject}&body=${body}`);
};
const handleWhatsAppShare = () => {
- const text = encodeURIComponent(
- `Join my personal network: ${invitationUrl}`
- );
+ const text = isGroupInvite
+ ? encodeURIComponent(`Join our ${group?.name} group: ${invitationUrl}`)
+ : encodeURIComponent(`Join my personal network: ${invitationUrl}`);
window.open(`https://wa.me/?text=${text}`);
};
const handleSMSShare = () => {
- const text = encodeURIComponent(
- `Join my personal network: ${invitationUrl}`
- );
+ const text = isGroupInvite
+ ? encodeURIComponent(`Join our ${group?.name} group: ${invitationUrl}`)
+ : encodeURIComponent(`Join my personal network: ${invitationUrl}`);
window.open(`sms:?body=${text}`);
};
@@ -116,9 +146,12 @@ const InvitationPage = () => {
};
const handleNewInvitation = () => {
+ const groupId = searchParams.get('groupId');
const id = Math.random().toString(36).substring(2, 15);
setInvitationId(id);
- const url = `${window.location.origin}/onboarding?invite=${id}`;
+ const url = groupId
+ ? `${window.location.origin}/onboarding?invite=${id}&groupId=${groupId}`
+ : `${window.location.origin}/onboarding?invite=${id}`;
setInvitationUrl(url);
};
@@ -126,14 +159,59 @@ const InvitationPage = () => {
navigate('/contacts');
};
+ const handleBack = () => {
+ if (isGroupInvite && group) {
+ navigate(`/groups/${group.id}`);
+ } else {
+ navigate('/contacts');
+ }
+ };
+
return (
-
-
- Invite to Your Network
+ {/* Back Button */}
+
+
+
+
+
+ {isGroupInvite ? `Back to ${group?.name}` : 'Back to Contacts'}
+
+
+ {/* Header */}
+
+ {isGroupInvite && group && (
+
+
+
+
+
+
+ Invite to {group.name}
+
+ {group.isPrivate && (
+
+ )}
+
+
+ )}
+
+ {!isGroupInvite && (
+
+ Invite to Your Network
+
+ )}
+
- Share your personal network invitation
+ {isGroupInvite
+ ? `Share your ${group?.name} group invitation`
+ : 'Share your personal network invitation'
+ }
@@ -155,7 +233,10 @@ const InvitationPage = () => {
/>
- Scan to join your network
+ {isGroupInvite
+ ? `Scan to join ${group?.name}`
+ : 'Scan to join your network'
+ }
+
+ }
+ onClick={handleDontLike}
+ sx={{
+ px: 3,
+ py: 1.5,
+ borderRadius: 2,
+ textTransform: 'none',
+ color: 'text.secondary'
+ }}
+ >
+ I Don't Like the Sound of That
+
+
+
+
+ {/* More Info Dialog */}
+
+
+ );
+};
+
+export default SocialContractPage;
\ No newline at end of file
diff --git a/src/types/group.ts b/src/types/group.ts
index e078af0..2eac60f 100644
--- a/src/types/group.ts
+++ b/src/types/group.ts
@@ -17,4 +17,30 @@ export interface GroupMember {
groupId: string;
joinedAt: Date;
role: 'admin' | 'member' | 'moderator';
+}
+
+export interface GroupPost {
+ id: string;
+ groupId: string;
+ authorId: string;
+ authorName: string;
+ authorAvatar?: string;
+ content: string;
+ createdAt: Date;
+ updatedAt: Date;
+ likes: number;
+ comments: number;
+ attachments?: string[];
+}
+
+export interface GroupLink {
+ id: string;
+ groupId: string;
+ title: string;
+ url: string;
+ description?: string;
+ sharedBy: string;
+ sharedByName: string;
+ sharedAt: Date;
+ tags?: string[];
}
\ No newline at end of file