Major changes: - Remove Post+ button from desktop left menu and mobile bottom navigation - Add floating action button to Feed page and Group feed tabs - Create PostCreateButton component with popup for post type selection - Add Social Contract page for invite flow with high-trust messaging - Update Group Detail page with expandable feed and better UX - Enhance invitation flow with group-specific context - Update group mock data with appropriate professional logos Features: - Post type selector popup (Post, Offer, Want) with color-coded icons - Context-aware posting (main feed vs group-specific) - Responsive FAB positioning (avoids mobile bottom nav) - Social contract for authentic professional networking - Group-specific invitation experience - Improved navigation structure Technical: - New PostCreateButton component with Material-UI dialog - Enhanced GroupDetailPage with tabs and post functionality - Updated routing for social contract in onboarding flow - Better TypeScript interfaces for group posts and links - Responsive design considerations for mobile and desktop 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>main
parent
5dc7a35fff
commit
50f78fc729
@ -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: <PostAdd />, |
||||
color: theme.palette.primary.main |
||||
}, |
||||
{ |
||||
type: 'offer' as const, |
||||
title: 'Offer', |
||||
description: 'Offer your services, expertise, or resources', |
||||
icon: <LocalOffer />, |
||||
color: theme.palette.success.main |
||||
}, |
||||
{ |
||||
type: 'want' as const, |
||||
title: 'Want', |
||||
description: 'Request help, services, or connections', |
||||
icon: <ShoppingCart />, |
||||
color: theme.palette.warning.main |
||||
} |
||||
]; |
||||
|
||||
return ( |
||||
<> |
||||
<Fab |
||||
color="primary" |
||||
aria-label="create post" |
||||
onClick={handleOpen} |
||||
sx={{ |
||||
position: 'fixed', |
||||
bottom: { xs: 90, md: 24 }, // Higher on mobile to avoid bottom nav
|
||||
right: 24, |
||||
zIndex: 1000, |
||||
}} |
||||
> |
||||
<Add /> |
||||
</Fab> |
||||
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClose} |
||||
maxWidth="sm" |
||||
fullWidth |
||||
PaperProps={{ |
||||
sx: { |
||||
borderRadius: 3, |
||||
p: 1 |
||||
} |
||||
}} |
||||
> |
||||
<DialogTitle sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', pb: 1 }}> |
||||
<Typography variant="h6" component="div"> |
||||
What would you like to create? |
||||
</Typography> |
||||
<IconButton onClick={handleClose} size="small"> |
||||
<Close /> |
||||
</IconButton> |
||||
</DialogTitle> |
||||
|
||||
<DialogContent sx={{ p: 2, pt: 0 }}> |
||||
<List sx={{ p: 0 }}> |
||||
{postTypes.map((postType, index) => ( |
||||
<ListItem key={postType.type} disablePadding sx={{ mb: index < postTypes.length - 1 ? 1 : 0 }}> |
||||
<ListItemButton |
||||
onClick={() => handleCreatePost(postType.type)} |
||||
sx={{ |
||||
borderRadius: 2, |
||||
border: 1, |
||||
borderColor: 'divider', |
||||
p: 2, |
||||
'&:hover': { |
||||
borderColor: postType.color, |
||||
backgroundColor: alpha(postType.color, 0.04), |
||||
} |
||||
}} |
||||
> |
||||
<ListItemIcon sx={{ minWidth: 48 }}> |
||||
<Box |
||||
sx={{ |
||||
display: 'flex', |
||||
alignItems: 'center', |
||||
justifyContent: 'center', |
||||
width: 40, |
||||
height: 40, |
||||
borderRadius: 2, |
||||
backgroundColor: alpha(postType.color, 0.1), |
||||
color: postType.color |
||||
}} |
||||
> |
||||
{postType.icon} |
||||
</Box> |
||||
</ListItemIcon> |
||||
<ListItemText
|
||||
primary={postType.title} |
||||
secondary={postType.description} |
||||
primaryTypographyProps={{ |
||||
fontWeight: 600, |
||||
fontSize: '1rem' |
||||
}} |
||||
secondaryTypographyProps={{ |
||||
fontSize: '0.875rem' |
||||
}} |
||||
/> |
||||
</ListItemButton> |
||||
</ListItem> |
||||
))} |
||||
</List> |
||||
</DialogContent> |
||||
</Dialog> |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
export default PostCreateButton; |
@ -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<Group | null>(null); |
||||
const [posts, setPosts] = useState<GroupPost[]>([]); |
||||
const [links, setLinks] = useState<GroupLink[]>([]); |
||||
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 = () => ( |
||||
<Box sx={{ mt: 3 }}> |
||||
{posts.length === 0 ? ( |
||||
<Card sx={{ textAlign: 'center', py: 8 }}> |
||||
<CardContent> |
||||
<Typography variant="h6" color="text.secondary" gutterBottom> |
||||
No posts yet |
||||
</Typography> |
||||
<Typography variant="body2" color="text.secondary"> |
||||
Be the first to share something with the group! |
||||
</Typography> |
||||
</CardContent> |
||||
</Card> |
||||
) : ( |
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> |
||||
{posts.map((post) => ( |
||||
<Card key={post.id} sx={{ border: 1, borderColor: 'divider' }}> |
||||
<CardContent sx={{ p: 3 }}> |
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}> |
||||
<Avatar src={post.authorAvatar} alt={post.authorName} sx={{ width: 40, height: 40 }}> |
||||
{post.authorName.charAt(0)} |
||||
</Avatar> |
||||
<Box sx={{ flexGrow: 1 }}> |
||||
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}> |
||||
{post.authorName} |
||||
</Typography> |
||||
<Typography variant="caption" color="text.secondary"> |
||||
{formatDate(post.createdAt)} |
||||
</Typography> |
||||
</Box> |
||||
<IconButton size="small"> |
||||
<MoreVert /> |
||||
</IconButton> |
||||
</Box> |
||||
|
||||
<Typography variant="body1" sx={{ mb: 3 }}> |
||||
{post.content} |
||||
</Typography> |
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}> |
||||
<Button |
||||
startIcon={<ThumbUp />} |
||||
size="small" |
||||
variant="text" |
||||
sx={{ color: 'text.secondary' }} |
||||
> |
||||
{post.likes} |
||||
</Button> |
||||
<Button |
||||
startIcon={<Comment />} |
||||
size="small" |
||||
variant="text" |
||||
sx={{ color: 'text.secondary' }} |
||||
> |
||||
{post.comments} |
||||
</Button> |
||||
<Button |
||||
startIcon={<Share />} |
||||
size="small" |
||||
variant="text" |
||||
sx={{ color: 'text.secondary' }} |
||||
> |
||||
Share |
||||
</Button> |
||||
</Box> |
||||
</CardContent> |
||||
</Card> |
||||
))} |
||||
</Box> |
||||
)} |
||||
</Box> |
||||
); |
||||
|
||||
const renderMembersTab = () => ( |
||||
<Box sx={{ mt: 3 }}> |
||||
<Card> |
||||
<CardContent sx={{ p: 3 }}> |
||||
<Typography variant="h6" gutterBottom> |
||||
Members ({group?.memberCount || 0}) |
||||
</Typography> |
||||
<Typography variant="body2" color="text.secondary"> |
||||
Member management coming soon... |
||||
</Typography> |
||||
</CardContent> |
||||
</Card> |
||||
</Box> |
||||
); |
||||
|
||||
const renderChatTab = () => ( |
||||
<Box sx={{ mt: 3 }}> |
||||
<Card> |
||||
<CardContent sx={{ p: 3 }}> |
||||
<Typography variant="h6" gutterBottom> |
||||
Group Chat |
||||
</Typography> |
||||
<Typography variant="body2" color="text.secondary"> |
||||
Real-time group chat functionality coming soon... |
||||
</Typography> |
||||
</CardContent> |
||||
</Card> |
||||
</Box> |
||||
); |
||||
|
||||
const renderFilesTab = () => ( |
||||
<Box sx={{ mt: 3 }}> |
||||
<Card> |
||||
<CardContent sx={{ p: 3 }}> |
||||
<Typography variant="h6" gutterBottom> |
||||
Collaborative Files |
||||
</Typography> |
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}> |
||||
Share documents and spreadsheets with your group |
||||
</Typography> |
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> |
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, p: 2, border: 1, borderColor: 'divider', borderRadius: 2 }}> |
||||
<Description sx={{ fontSize: 40, color: 'primary.main' }} /> |
||||
<Box sx={{ flexGrow: 1 }}> |
||||
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}> |
||||
Collaborative Document Editor |
||||
</Typography> |
||||
<Typography variant="body2" color="text.secondary"> |
||||
Real-time document editing with your group members |
||||
</Typography> |
||||
</Box> |
||||
<Chip label="Coming Soon" size="small" variant="outlined" /> |
||||
</Box> |
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, p: 2, border: 1, borderColor: 'divider', borderRadius: 2 }}> |
||||
<TableChart sx={{ fontSize: 40, color: 'success.main' }} /> |
||||
<Box sx={{ flexGrow: 1 }}> |
||||
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}> |
||||
Collaborative Spreadsheet Editor |
||||
</Typography> |
||||
<Typography variant="body2" color="text.secondary"> |
||||
Work together on spreadsheets and data analysis |
||||
</Typography> |
||||
</Box> |
||||
<Chip label="Coming Soon" size="small" variant="outlined" /> |
||||
</Box> |
||||
</Box> |
||||
</CardContent> |
||||
</Card> |
||||
</Box> |
||||
); |
||||
|
||||
const renderLinksTab = () => ( |
||||
<Box sx={{ mt: 3 }}> |
||||
{links.length === 0 ? ( |
||||
<Card sx={{ textAlign: 'center', py: 8 }}> |
||||
<CardContent> |
||||
<Typography variant="h6" color="text.secondary" gutterBottom> |
||||
No links shared yet |
||||
</Typography> |
||||
<Typography variant="body2" color="text.secondary"> |
||||
Share useful links with your group members |
||||
</Typography> |
||||
</CardContent> |
||||
</Card> |
||||
) : ( |
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> |
||||
{links.map((link) => ( |
||||
<Card key={link.id} sx={{ border: 1, borderColor: 'divider' }}> |
||||
<CardContent sx={{ p: 3 }}> |
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}> |
||||
<Link sx={{ color: 'primary.main' }} /> |
||||
<Box sx={{ flexGrow: 1 }}> |
||||
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}> |
||||
{link.title} |
||||
</Typography> |
||||
<Typography variant="body2" color="text.secondary"> |
||||
{link.description} |
||||
</Typography> |
||||
</Box> |
||||
</Box> |
||||
|
||||
<Typography variant="body2" color="primary.main" sx={{ mb: 2 }}> |
||||
{link.url} |
||||
</Typography> |
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}> |
||||
{link.tags?.map((tag) => ( |
||||
<Chip
|
||||
key={tag}
|
||||
label={tag}
|
||||
size="small"
|
||||
variant="outlined" |
||||
sx={{
|
||||
borderRadius: 1, |
||||
backgroundColor: alpha(theme.palette.primary.main, 0.04), |
||||
borderColor: alpha(theme.palette.primary.main, 0.12), |
||||
color: 'primary.main', |
||||
}} |
||||
/> |
||||
))} |
||||
</Box> |
||||
|
||||
<Typography variant="caption" color="text.secondary"> |
||||
Shared by {link.sharedByName} • {formatDate(link.sharedAt)} |
||||
</Typography> |
||||
</CardContent> |
||||
</Card> |
||||
))} |
||||
</Box> |
||||
)} |
||||
</Box> |
||||
); |
||||
|
||||
if (isLoading) { |
||||
return ( |
||||
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '50vh' }}> |
||||
<Typography variant="h6" color="text.secondary"> |
||||
Loading group... |
||||
</Typography> |
||||
</Box> |
||||
); |
||||
} |
||||
|
||||
if (!group) { |
||||
return ( |
||||
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '50vh' }}> |
||||
<Typography variant="h6" color="text.secondary"> |
||||
Group not found |
||||
</Typography> |
||||
</Box> |
||||
); |
||||
} |
||||
|
||||
return ( |
||||
<Box sx={{ height: '100%' }}> |
||||
{/* Header */} |
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 3 }}> |
||||
<IconButton onClick={handleBack} size="large"> |
||||
<ArrowBack /> |
||||
</IconButton> |
||||
<Avatar |
||||
src={group.image} |
||||
alt={group.name} |
||||
sx={{ width: 64, height: 64, bgcolor: 'primary.main' }} |
||||
> |
||||
{group.name.charAt(0)} |
||||
</Avatar> |
||||
<Box sx={{ flexGrow: 1 }}> |
||||
<Typography variant="h4" component="h1" sx={{ fontWeight: 700 }}> |
||||
{group.name} |
||||
</Typography> |
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mt: 1 }}> |
||||
<Badge badgeContent={group.memberCount} color="primary"> |
||||
<People sx={{ fontSize: 20, color: 'text.secondary' }} /> |
||||
</Badge> |
||||
<Typography variant="body2" color="text.secondary"> |
||||
{group.memberCount} members |
||||
</Typography> |
||||
{group.isPrivate && ( |
||||
<Chip label="Private" size="small" variant="outlined" /> |
||||
)} |
||||
</Box> |
||||
{group.description && ( |
||||
<Typography variant="body1" color="text.secondary" sx={{ mt: 1 }}> |
||||
{group.description} |
||||
</Typography> |
||||
)} |
||||
</Box> |
||||
<Box sx={{ display: 'flex', gap: 1 }}> |
||||
<Button |
||||
variant="contained" |
||||
startIcon={<PersonAdd />} |
||||
onClick={handleInviteToGroup} |
||||
sx={{ borderRadius: 2 }} |
||||
> |
||||
Invite to Group |
||||
</Button> |
||||
</Box> |
||||
</Box> |
||||
|
||||
{/* Navigation Tabs */} |
||||
<Card> |
||||
<Tabs |
||||
value={tabValue} |
||||
onChange={handleTabChange} |
||||
aria-label="group navigation tabs" |
||||
sx={{
|
||||
borderBottom: 1,
|
||||
borderColor: 'divider', |
||||
'& .MuiTab-root': { |
||||
minHeight: 56, |
||||
textTransform: 'none', |
||||
fontWeight: 500, |
||||
} |
||||
}} |
||||
> |
||||
<Tab icon={<RssFeed />} label="Feed" /> |
||||
<Tab icon={<People />} label="Members" /> |
||||
<Tab icon={<Chat />} label="Chat" /> |
||||
<Tab icon={<Folder />} label="Files" /> |
||||
<Tab icon={<Link />} label="Links" /> |
||||
</Tabs> |
||||
|
||||
{/* Tab Content */} |
||||
<Box sx={{ p: 3 }}> |
||||
{tabValue === 0 && renderFeedTab()} |
||||
{tabValue === 1 && renderMembersTab()} |
||||
{tabValue === 2 && renderChatTab()} |
||||
{tabValue === 3 && renderFilesTab()} |
||||
{tabValue === 4 && renderLinksTab()} |
||||
</Box> |
||||
</Card> |
||||
|
||||
{/* Show Post Create Button only on Feed tab */} |
||||
{tabValue === 0 && ( |
||||
<PostCreateButton
|
||||
groupId={groupId}
|
||||
onCreatePost={handleCreatePost} |
||||
/> |
||||
)} |
||||
</Box> |
||||
); |
||||
}; |
||||
|
||||
export default GroupDetailPage; |
@ -0,0 +1,312 @@ |
||||
import { useState, useEffect } from 'react'; |
||||
import { useNavigate, useSearchParams } from 'react-router-dom'; |
||||
import { |
||||
Container, |
||||
Typography, |
||||
Box, |
||||
Paper, |
||||
Button, |
||||
Avatar, |
||||
Chip, |
||||
Dialog, |
||||
DialogTitle, |
||||
DialogContent, |
||||
DialogActions, |
||||
List, |
||||
ListItem, |
||||
ListItemIcon, |
||||
ListItemText, |
||||
Divider, |
||||
alpha, |
||||
useTheme |
||||
} from '@mui/material'; |
||||
import { |
||||
Security, |
||||
Groups, |
||||
Favorite, |
||||
Psychology, |
||||
AccountTree, |
||||
TrendingUp, |
||||
InfoOutlined, |
||||
Close, |
||||
CheckCircle |
||||
} from '@mui/icons-material'; |
||||
import { dataService } from '../services/dataService'; |
||||
import type { Group } from '../types/group'; |
||||
|
||||
const SocialContractPage = () => { |
||||
const [group, setGroup] = useState<Group | null>(null); |
||||
const [isGroupInvite, setIsGroupInvite] = useState(false); |
||||
const [showMoreInfo, setShowMoreInfo] = useState(false); |
||||
const navigate = useNavigate(); |
||||
const [searchParams] = useSearchParams(); |
||||
const theme = useTheme(); |
||||
|
||||
useEffect(() => { |
||||
const loadGroupData = async () => { |
||||
const groupId = searchParams.get('groupId'); |
||||
const inviteId = searchParams.get('invite'); |
||||
|
||||
if (groupId) { |
||||
setIsGroupInvite(true); |
||||
try { |
||||
const groupData = await dataService.getGroup(groupId); |
||||
setGroup(groupData || null); |
||||
} catch (error) { |
||||
console.error('Failed to load group:', error); |
||||
} |
||||
} |
||||
|
||||
// Store invite parameters for later use
|
||||
if (inviteId) { |
||||
sessionStorage.setItem('inviteId', inviteId); |
||||
} |
||||
if (groupId) { |
||||
sessionStorage.setItem('groupId', groupId); |
||||
} |
||||
}; |
||||
|
||||
loadGroupData(); |
||||
}, [searchParams]); |
||||
|
||||
const handleAccept = () => { |
||||
// Store acceptance in session
|
||||
sessionStorage.setItem('socialContractAccepted', 'true'); |
||||
|
||||
// Navigate directly to the appropriate page
|
||||
if (isGroupInvite && group) { |
||||
navigate(`/groups/${group.id}`); |
||||
} else { |
||||
navigate('/contacts'); |
||||
} |
||||
}; |
||||
|
||||
const handleDontLike = () => { |
||||
// Show the "Tell Me More" dialog instead of rejecting
|
||||
setShowMoreInfo(true); |
||||
}; |
||||
|
||||
const handleTellMeMore = () => { |
||||
setShowMoreInfo(true); |
||||
}; |
||||
|
||||
const socialContractPrinciples = [ |
||||
{ |
||||
icon: <Psychology />, |
||||
title: 'Be Your Authentic Self', |
||||
description: 'Share your genuine thoughts, experiences, and perspectives. Authenticity builds trust and meaningful connections.' |
||||
}, |
||||
{ |
||||
icon: <Favorite />, |
||||
title: 'Act with Respect & Kindness', |
||||
description: 'Treat all members with dignity and respect. Disagreements are welcome, but personal attacks are not.' |
||||
}, |
||||
{ |
||||
icon: <Security />, |
||||
title: 'Maintain Confidentiality', |
||||
description: 'What is shared here, stays here. Respect the privacy of discussions and personal information shared by others.' |
||||
}, |
||||
{ |
||||
icon: <TrendingUp />, |
||||
title: 'Contribute Meaningfully', |
||||
description: 'Share valuable insights, ask thoughtful questions, and help others grow. Quality over quantity.' |
||||
}, |
||||
{ |
||||
icon: <AccountTree />, |
||||
title: 'Build Genuine Relationships', |
||||
description: 'Focus on creating real connections, not just expanding your network numbers. Relationships take time and effort.' |
||||
} |
||||
]; |
||||
|
||||
return ( |
||||
<Container maxWidth="md" sx={{ py: 4, minHeight: '100vh', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}> |
||||
<Paper
|
||||
elevation={3}
|
||||
sx={{
|
||||
p: 4,
|
||||
textAlign: 'center', |
||||
background: `linear-gradient(135deg, ${alpha(theme.palette.primary.main, 0.05)} 0%, ${alpha(theme.palette.secondary.main, 0.05)} 100%)`, |
||||
border: 1, |
||||
borderColor: 'divider' |
||||
}} |
||||
> |
||||
{/* Header */} |
||||
<Box sx={{ mb: 4 }}> |
||||
<Typography variant="h3" component="h1" gutterBottom> |
||||
Welcome to NAO |
||||
</Typography> |
||||
|
||||
<Typography variant="h5" color="primary" gutterBottom> |
||||
You're entering a high-trust environment |
||||
</Typography> |
||||
|
||||
<Typography variant="body1" color="text.secondary" sx={{ maxWidth: '600px', mx: 'auto' }}> |
||||
NAO is built on trust, authenticity, and meaningful connections. Before you join{isGroupInvite && group ? ` ${group.name}` : ''}, please read and agree to our social contract. |
||||
</Typography> |
||||
</Box> |
||||
|
||||
{/* Core Principles */} |
||||
<Box sx={{ mb: 4 }}> |
||||
<Typography variant="h6" gutterBottom sx={{ display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 1 }}> |
||||
<CheckCircle color="primary" /> |
||||
Our Core Principles |
||||
</Typography> |
||||
|
||||
<Box sx={{ display: 'grid', gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' }, gap: 2, mt: 3 }}> |
||||
{socialContractPrinciples.slice(0, 4).map((principle, index) => ( |
||||
<Box
|
||||
key={index} |
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
gap: 2,
|
||||
p: 2,
|
||||
borderRadius: 2, |
||||
backgroundColor: alpha(theme.palette.primary.main, 0.04), |
||||
border: 1, |
||||
borderColor: alpha(theme.palette.primary.main, 0.12) |
||||
}} |
||||
> |
||||
<Box sx={{ color: 'primary.main', mt: 0.5 }}> |
||||
{principle.icon} |
||||
</Box> |
||||
<Box sx={{ textAlign: 'left' }}> |
||||
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 0.5 }}> |
||||
{principle.title} |
||||
</Typography> |
||||
<Typography variant="body2" color="text.secondary"> |
||||
{principle.description} |
||||
</Typography> |
||||
</Box> |
||||
</Box> |
||||
))} |
||||
</Box> |
||||
</Box> |
||||
|
||||
{/* Call to Action */} |
||||
<Box sx={{ mb: 3 }}> |
||||
<Typography variant="h6" gutterBottom> |
||||
Ready to join our community? |
||||
</Typography> |
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}> |
||||
By agreeing, you commit to upholding these principles and creating a positive environment for everyone. |
||||
</Typography> |
||||
</Box> |
||||
|
||||
{/* Action Buttons */} |
||||
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'center', flexWrap: 'wrap' }}> |
||||
<Button |
||||
variant="contained" |
||||
size="large" |
||||
onClick={handleAccept} |
||||
sx={{
|
||||
px: 4,
|
||||
py: 1.5, |
||||
borderRadius: 2, |
||||
textTransform: 'none', |
||||
fontSize: '1.1rem' |
||||
}} |
||||
> |
||||
I Agree - Let's Connect! |
||||
</Button> |
||||
|
||||
<Button |
||||
variant="outlined" |
||||
size="large" |
||||
startIcon={<InfoOutlined />} |
||||
onClick={handleTellMeMore} |
||||
sx={{
|
||||
px: 3,
|
||||
py: 1.5, |
||||
borderRadius: 2, |
||||
textTransform: 'none' |
||||
}} |
||||
> |
||||
Tell Me More |
||||
</Button> |
||||
|
||||
<Button |
||||
variant="text" |
||||
size="large" |
||||
startIcon={<Close />} |
||||
onClick={handleDontLike} |
||||
sx={{
|
||||
px: 3,
|
||||
py: 1.5, |
||||
borderRadius: 2, |
||||
textTransform: 'none', |
||||
color: 'text.secondary' |
||||
}} |
||||
> |
||||
I Don't Like the Sound of That |
||||
</Button> |
||||
</Box> |
||||
</Paper> |
||||
|
||||
{/* More Info Dialog */} |
||||
<Dialog
|
||||
open={showMoreInfo}
|
||||
onClose={() => setShowMoreInfo(false)} |
||||
maxWidth="md" |
||||
fullWidth |
||||
> |
||||
<DialogTitle> |
||||
<Typography variant="h5" component="div"> |
||||
About Our High-Trust Environment |
||||
</Typography> |
||||
</DialogTitle> |
||||
<DialogContent> |
||||
<Typography variant="body1" paragraph> |
||||
NAO is more than just a networking platform - it's a community where professionals can be their authentic selves and build genuine relationships. |
||||
</Typography> |
||||
|
||||
<Typography variant="h6" gutterBottom sx={{ mt: 3 }}> |
||||
What makes us different: |
||||
</Typography> |
||||
|
||||
<List> |
||||
{socialContractPrinciples.map((principle, index) => ( |
||||
<ListItem key={index} sx={{ alignItems: 'flex-start' }}> |
||||
<ListItemIcon sx={{ color: 'primary.main', mt: 0.5 }}> |
||||
{principle.icon} |
||||
</ListItemIcon> |
||||
<ListItemText
|
||||
primary={principle.title} |
||||
secondary={principle.description} |
||||
primaryTypographyProps={{ fontWeight: 600 }} |
||||
/> |
||||
</ListItem> |
||||
))} |
||||
</List> |
||||
|
||||
<Divider sx={{ my: 3 }} /> |
||||
|
||||
<Typography variant="h6" gutterBottom> |
||||
Why this matters: |
||||
</Typography> |
||||
|
||||
<Typography variant="body1" paragraph> |
||||
In a world of superficial connections and promotional content, we're creating something different.
|
||||
A space where vulnerability is valued, where you can ask for help without judgment, and where
|
||||
success is measured by the quality of relationships, not just the quantity of connections. |
||||
</Typography> |
||||
|
||||
<Typography variant="body1" paragraph> |
||||
When you join, you're not just adding another network to your list - you're becoming part of
|
||||
a community that will support your professional growth and personal development. |
||||
</Typography> |
||||
</DialogContent> |
||||
<DialogActions> |
||||
<Button onClick={() => setShowMoreInfo(false)} variant="outlined"> |
||||
Close |
||||
</Button> |
||||
<Button onClick={() => { setShowMoreInfo(false); handleAccept(); }} variant="contained"> |
||||
I'm Ready to Join |
||||
</Button> |
||||
</DialogActions> |
||||
</Dialog> |
||||
</Container> |
||||
); |
||||
}; |
||||
|
||||
export default SocialContractPage; |
Loading…
Reference in new issue