Compare commits

...

5 Commits

Author SHA1 Message Date
Christopher Maujean 495ac246b2 one more ci adjustment 2 months ago
Christopher Maujean 9e7e47051a more build errors 2 months ago
Christopher Maujean 6950da1a93 more lint 2 months ago
Christopher Maujean 9ddacb722a more tsc errors fixed 2 months ago
Christopher Maujean 291e196525 remove build:ldo from our ci process 2 months ago
  1. 4
      .gitlab-ci.yml
  2. 3
      src/App.tsx
  3. 13
      src/components/onboarding/ConnectAccountsStep.tsx
  4. 34
      src/pages/GroupDetailPage.tsx
  5. 16
      src/pages/GroupJoinPage.tsx
  6. 24
      src/pages/NotificationsPage.tsx
  7. 2
      src/pages/OnboardingPage.tsx
  8. 69
      src/services/dataService.ts
  9. 1
      src/types/group.ts

@ -31,7 +31,6 @@ build:
<<: *node_template <<: *node_template
stage: build stage: build
script: script:
- bun run build:ldo
- bun run build - bun run build
artifacts: artifacts:
paths: paths:
@ -45,9 +44,8 @@ pages:
<<: *node_template <<: *node_template
stage: deploy stage: deploy
script: script:
- bun run build:ldo
- bun run build - bun run build
- mkdir public - mkdir -p public
- cp -r dist/* public/ - cp -r dist/* public/
artifacts: artifacts:
paths: paths:

@ -3,6 +3,7 @@ import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline'; import CssBaseline from '@mui/material/CssBaseline';
import { OnboardingProvider } from '@/contexts/OnboardingContext'; import { OnboardingProvider } from '@/contexts/OnboardingContext';
import { BrowserNGLdoProvider, useNextGraphAuth } from '@/lib/nextgraph'; import { BrowserNGLdoProvider, useNextGraphAuth } from '@/lib/nextgraph';
import type { NextGraphAuth } from '@/types/nextgraph';
import DashboardLayout from '@/components/layout/DashboardLayout'; import DashboardLayout from '@/components/layout/DashboardLayout';
import SocialContractPage from '@/pages/SocialContractPage'; import SocialContractPage from '@/pages/SocialContractPage';
import GroupJoinPage from '@/pages/GroupJoinPage'; import GroupJoinPage from '@/pages/GroupJoinPage';
@ -26,7 +27,7 @@ import { isNextGraphEnabled } from '@/utils/featureFlags';
const theme = createAppTheme('light'); const theme = createAppTheme('light');
const NextGraphAppContent = () => { const NextGraphAppContent = () => {
const nextGraphAuth = useNextGraphAuth(); const nextGraphAuth = useNextGraphAuth() as unknown as NextGraphAuth | undefined;
const { session, login, logout } = nextGraphAuth || {}; const { session, login, logout } = nextGraphAuth || {};
console.log('NextGraph Auth:', nextGraphAuth); console.log('NextGraph Auth:', nextGraphAuth);

@ -28,13 +28,14 @@ import {
Security Security
} from '@mui/icons-material'; } from '@mui/icons-material';
import { useOnboarding } from '@/hooks/useOnboarding'; import { useOnboarding } from '@/hooks/useOnboarding';
import type { ConnectedAccount } from '@/types/onboarding';
const ConnectAccountsStep = () => { const ConnectAccountsStep = () => {
const { state, connectAccount, disconnectAccount } = useOnboarding(); const { state, connectAccount, disconnectAccount } = useOnboarding();
const [connectingAccount, setConnectingAccount] = useState<string | null>(null); const [connectingAccount, setConnectingAccount] = useState<string | null>(null);
const [connectionDialog, setConnectionDialog] = useState<{ const [connectionDialog, setConnectionDialog] = useState<{
open: boolean; open: boolean;
account: { id: string; type: string; isConnected: boolean; [key: string]: unknown; } | null; account: ConnectedAccount | null;
action: 'connect' | 'disconnect'; action: 'connect' | 'disconnect';
}>({ }>({
open: false, open: false,
@ -72,7 +73,7 @@ const ConnectAccountsStep = () => {
} }
}; };
const handleConnectionToggle = (account: { id: string; type: string; isConnected: boolean; [key: string]: unknown; }) => { const handleConnectionToggle = (account: ConnectedAccount) => {
if (account.isConnected) { if (account.isConnected) {
setConnectionDialog({ setConnectionDialog({
open: true, open: true,
@ -90,6 +91,8 @@ const ConnectAccountsStep = () => {
const handleConfirmConnection = async () => { const handleConfirmConnection = async () => {
const { account, action } = connectionDialog; const { account, action } = connectionDialog;
if (!account) return;
setConnectingAccount(account.id); setConnectingAccount(account.id);
try { try {
@ -136,7 +139,7 @@ const ConnectAccountsStep = () => {
</Box> </Box>
<Grid container spacing={3}> <Grid container spacing={3}>
{state.connectedAccounts.map((account) => ( {state.connectedAccounts.map((account: ConnectedAccount) => (
<Grid size={{ xs: 12, sm: 6 }} key={account.id}> <Grid size={{ xs: 12, sm: 6 }} key={account.id}>
<Card <Card
sx={{ sx={{
@ -218,14 +221,14 @@ const ConnectAccountsStep = () => {
fullWidth fullWidth
> >
<DialogTitle> <DialogTitle>
{connectionDialog.action === 'connect' ? 'Connect' : 'Disconnect'} {connectionDialog.account?.name} {connectionDialog.action === 'connect' ? 'Connect' : 'Disconnect'} {connectionDialog.account?.name || 'Account'}
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
{connectingAccount ? ( {connectingAccount ? (
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', py: 3 }}> <Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', py: 3 }}>
<CircularProgress size={60} sx={{ mb: 2 }} /> <CircularProgress size={60} sx={{ mb: 2 }} />
<Typography variant="body1"> <Typography variant="body1">
{connectionDialog.action === 'connect' ? 'Connecting' : 'Disconnecting'} your {connectionDialog.account?.name} account... {connectionDialog.action === 'connect' ? 'Connecting' : 'Disconnecting'} your {connectionDialog.account?.name || 'account'}...
</Typography> </Typography>
</Box> </Box>
) : ( ) : (

@ -680,7 +680,7 @@ const GroupDetailPage = () => {
// Filter posts based on selected filters // Filter posts based on selected filters
const filteredPosts = posts.filter((post) => { const filteredPosts = posts.filter((post) => {
const personMatch = selectedPersonFilter === 'all' || post.authorName === selectedPersonFilter; const personMatch = selectedPersonFilter === 'all' || post.authorName === selectedPersonFilter;
const topicMatch = selectedTopicFilter === 'all' || post.topic === selectedTopicFilter; const topicMatch = selectedTopicFilter === 'all' || true;
return personMatch && topicMatch; return personMatch && topicMatch;
}); });
@ -766,8 +766,8 @@ const GroupDetailPage = () => {
<Box <Box
sx={{ sx={{
display: 'grid', display: 'grid',
gridTemplateColumns: post.images.length === 1 ? '1fr' : gridTemplateColumns: (post.images?.length ?? 0) === 1 ? '1fr' :
post.images.length === 2 ? '1fr 1fr' : (post.images?.length ?? 0) === 2 ? '1fr 1fr' :
'repeat(3, 1fr)', 'repeat(3, 1fr)',
gap: 1, gap: 1,
borderRadius: 2, borderRadius: 2,
@ -782,9 +782,9 @@ const GroupDetailPage = () => {
alt={`Post image ${index + 1}`} alt={`Post image ${index + 1}`}
sx={{ sx={{
width: '100%', width: '100%',
height: post.images.length === 1 ? 300 : 200, height: (post.images?.length ?? 0) === 1 ? 300 : 200,
objectFit: 'cover', objectFit: 'cover',
borderRadius: post.images.length === 1 ? 2 : 1, borderRadius: (post.images?.length ?? 0) === 1 ? 2 : 1,
cursor: 'pointer', cursor: 'pointer',
transition: 'transform 0.2s ease-in-out', transition: 'transform 0.2s ease-in-out',
'&:hover': { '&:hover': {
@ -1276,9 +1276,9 @@ const GroupDetailPage = () => {
const renderNetworkView = (members: Array<{ id: string; name: string; avatar?: string; [key: string]: unknown; }>) => { const renderNetworkView = (members: Array<{ id: string; name: string; avatar?: string; position: { x: number; y: number }; [key: string]: unknown; }>) => {
// Shared position calculation function for perfect alignment // Shared position calculation function for perfect alignment
const getNodePosition = (member: { id: string; name: string; [key: string]: unknown; }) => { const getNodePosition = (member: { id: string; name: string; position: { x: number; y: number }; [key: string]: unknown; }) => {
const centerX = 400; // Center of 800px viewBox const centerX = 400; // Center of 800px viewBox
const centerY = 400; // Center of 800px viewBox const centerY = 400; // Center of 800px viewBox
const scale = 1.8; // Increased scale for better visibility const scale = 1.8; // Increased scale for better visibility
@ -1314,7 +1314,7 @@ const GroupDetailPage = () => {
> >
{/* Connection lines between members */} {/* Connection lines between members */}
{members.map(member => {members.map(member =>
member.connections (member.connections as string[] | undefined)
?.map((connId: string) => { ?.map((connId: string) => {
const connectedMember = members.find(m => m.id === connId); const connectedMember = members.find(m => m.id === connId);
if (!connectedMember) return null; if (!connectedMember) return null;
@ -1341,7 +1341,7 @@ const GroupDetailPage = () => {
opacity = 0.9; opacity = 0.9;
} else if (isCenterConnection) { } else if (isCenterConnection) {
// Strong connections to center node // Strong connections to center node
strength = Math.max(member.relationshipStrength, connectedMember.relationshipStrength); strength = Math.max(member.relationshipStrength as number, connectedMember.relationshipStrength as number);
strokeColor = theme.palette.primary.main; strokeColor = theme.palette.primary.main;
opacity = strength; opacity = strength;
} else { } else {
@ -1402,11 +1402,11 @@ const GroupDetailPage = () => {
border: `${member.id === 'oli-sb' ? 4 : 3}px solid ${ border: `${member.id === 'oli-sb' ? 4 : 3}px solid ${
member.id === 'oli-sb' member.id === 'oli-sb'
? theme.palette.primary.main ? theme.palette.primary.main
: alpha(theme.palette.primary.main, member.relationshipStrength) : alpha(theme.palette.primary.main, member.relationshipStrength as number)
}`, }`,
boxShadow: member.id === 'oli-sb' boxShadow: member.id === 'oli-sb'
? `0 0 20px ${alpha(theme.palette.primary.main, 0.4)}` ? `0 0 20px ${alpha(theme.palette.primary.main, 0.4)}`
: `0 0 ${member.relationshipStrength * 15}px ${alpha(theme.palette.primary.main, 0.3)}`, : `0 0 ${(member.relationshipStrength as number) * 15}px ${alpha(theme.palette.primary.main, 0.3)}`,
backgroundColor: member.id === 'oli-sb' ? alpha(theme.palette.primary.main, 0.1) : 'white', backgroundColor: member.id === 'oli-sb' ? alpha(theme.palette.primary.main, 0.1) : 'white',
backgroundImage: member.avatar ? `url(${member.avatar})` : 'none', backgroundImage: member.avatar ? `url(${member.avatar})` : 'none',
backgroundSize: member.avatar ? getContactPhotoStyles(member.name).backgroundSize : 'cover', backgroundSize: member.avatar ? getContactPhotoStyles(member.name).backgroundSize : 'cover',
@ -1419,7 +1419,7 @@ const GroupDetailPage = () => {
color: member.id === 'oli-sb' ? theme.palette.primary.main : theme.palette.text.primary color: member.id === 'oli-sb' ? theme.palette.primary.main : theme.palette.text.primary
}} }}
> >
{!member.avatar && member.initials} {!member.avatar && (member.initials as string)}
</div> </div>
{/* Name label */} {/* Name label */}
@ -1456,7 +1456,7 @@ const GroupDetailPage = () => {
gap: '2px' gap: '2px'
}} }}
> >
{member.vouches} {member.vouches as number}
</div> </div>
<div <div
style={{ style={{
@ -1471,7 +1471,7 @@ const GroupDetailPage = () => {
gap: '2px' gap: '2px'
}} }}
> >
{member.praises} {member.praises as number}
</div> </div>
</div> </div>
)} )}
@ -1486,7 +1486,7 @@ const GroupDetailPage = () => {
}; };
const renderMapView = (members: Array<{ id: string; name: string; location?: string; [key: string]: unknown; }>, compact?: boolean) => { const renderMapView = (members: Array<{ id: string; name: string; location?: { lat: number; lng: number; visible: boolean }; [key: string]: unknown; }>, compact?: boolean) => {
const visibleMembers = members.filter(m => m.location?.visible); const visibleMembers = members.filter(m => m.location?.visible);
return ( return (
@ -1554,7 +1554,7 @@ const GroupDetailPage = () => {
}} }}
> >
<Avatar <Avatar
src={member.avatar} src={member.avatar as string}
sx={{ sx={{
width: compact ? 32 : 40, width: compact ? 32 : 40,
height: compact ? 32 : 40, height: compact ? 32 : 40,
@ -1569,7 +1569,7 @@ const GroupDetailPage = () => {
backgroundPosition: member.avatar ? getContactPhotoStyles(member.name).backgroundPosition : 'center', backgroundPosition: member.avatar ? getContactPhotoStyles(member.name).backgroundPosition : 'center',
}} }}
> >
{member.initials} {member.initials as string}
</Avatar> </Avatar>
{/* Name tooltip - smaller in compact mode */} {/* Name tooltip - smaller in compact mode */}

@ -238,12 +238,12 @@ const GroupJoinPage = () => {
cursor: 'pointer', cursor: 'pointer',
transition: 'all 0.2s ease-in-out', transition: 'all 0.2s ease-in-out',
border: 2, border: 2,
borderColor: selectedProfileCard === customProfileCard.name ? customProfileCard.color : 'divider', borderColor: selectedProfileCard === customProfileCard.name ? (customProfileCard.color as string) : 'divider',
backgroundColor: selectedProfileCard === customProfileCard.name backgroundColor: selectedProfileCard === customProfileCard.name
? alpha(customProfileCard.color || theme.palette.primary.main, 0.08) ? alpha((customProfileCard.color as string) || theme.palette.primary.main, 0.08)
: 'background.paper', : 'background.paper',
'&:hover': { '&:hover': {
borderColor: customProfileCard.color, borderColor: (customProfileCard.color as string),
transform: 'translateY(-2px)', transform: 'translateY(-2px)',
boxShadow: theme.shadows[4], boxShadow: theme.shadows[4],
}, },
@ -252,7 +252,7 @@ const GroupJoinPage = () => {
<CardContent sx={{ p: 3, textAlign: 'center', position: 'relative' }}> <CardContent sx={{ p: 3, textAlign: 'center', position: 'relative' }}>
<Avatar <Avatar
sx={{ sx={{
bgcolor: customProfileCard.color || theme.palette.primary.main, bgcolor: (customProfileCard.color as string) || theme.palette.primary.main,
width: 48, width: 48,
height: 48, height: 48,
mx: 'auto', mx: 'auto',
@ -260,21 +260,21 @@ const GroupJoinPage = () => {
'& .MuiSvgIcon-root': { fontSize: 28 } '& .MuiSvgIcon-root': { fontSize: 28 }
}} }}
> >
{getProfileCardIcon(customProfileCard.icon || 'PersonOutline')} {getProfileCardIcon((customProfileCard.icon as string) || 'PersonOutline')}
</Avatar> </Avatar>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 1 }}> <Typography variant="h6" sx={{ fontWeight: 600, mb: 1 }}>
{customProfileCard.name} {customProfileCard.name as string}
</Typography> </Typography>
<Typography variant="body2" color="text.secondary" sx={{ fontSize: '0.875rem' }}> <Typography variant="body2" color="text.secondary" sx={{ fontSize: '0.875rem' }}>
{customProfileCard.description} {customProfileCard.description as string}
</Typography> </Typography>
{selectedProfileCard === customProfileCard.name && ( {selectedProfileCard === customProfileCard.name && (
<CheckCircle <CheckCircle
sx={{ sx={{
color: customProfileCard.color, color: (customProfileCard.color as string),
mt: 1, mt: 1,
fontSize: 20 fontSize: 20
}} }}

@ -81,9 +81,9 @@ const NotificationsPage = () => {
} }
}; };
const handleAcceptVouch = async (notificationId: string, vouchId: string) => { const handleAcceptVouch = async (notificationId: string) => {
try { try {
await notificationService.acceptVouch(notificationId, vouchId); await notificationService.acceptVouch(notificationId);
setNotifications(prev => setNotifications(prev =>
prev.map(n => n.id === notificationId ? { ...n, status: 'accepted', isActionable: true } : n) prev.map(n => n.id === notificationId ? { ...n, status: 'accepted', isActionable: true } : n)
); );
@ -96,9 +96,9 @@ const NotificationsPage = () => {
} }
}; };
const handleRejectVouch = async (notificationId: string, vouchId: string) => { const handleRejectVouch = async (notificationId: string) => {
try { try {
await notificationService.rejectVouch(notificationId, vouchId); await notificationService.rejectVouch(notificationId);
setNotifications(prev => setNotifications(prev =>
prev.map(n => n.id === notificationId ? { ...n, status: 'rejected', isActionable: false } : n) prev.map(n => n.id === notificationId ? { ...n, status: 'rejected', isActionable: false } : n)
); );
@ -111,9 +111,9 @@ const NotificationsPage = () => {
} }
}; };
const handleAcceptPraise = async (notificationId: string, praiseId: string) => { const handleAcceptPraise = async (notificationId: string) => {
try { try {
await notificationService.acceptPraise(notificationId, praiseId); await notificationService.acceptPraise(notificationId);
setNotifications(prev => setNotifications(prev =>
prev.map(n => n.id === notificationId ? { ...n, status: 'accepted', isActionable: true } : n) prev.map(n => n.id === notificationId ? { ...n, status: 'accepted', isActionable: true } : n)
); );
@ -126,9 +126,9 @@ const NotificationsPage = () => {
} }
}; };
const handleRejectPraise = async (notificationId: string, praiseId: string) => { const handleRejectPraise = async (notificationId: string) => {
try { try {
await notificationService.rejectPraise(notificationId, praiseId); await notificationService.rejectPraise(notificationId);
setNotifications(prev => setNotifications(prev =>
prev.map(n => n.id === notificationId ? { ...n, status: 'rejected', isActionable: false } : n) prev.map(n => n.id === notificationId ? { ...n, status: 'rejected', isActionable: false } : n)
); );
@ -328,7 +328,7 @@ const NotificationsPage = () => {
<Button <Button
size="small" size="small"
variant="outlined" variant="outlined"
onClick={() => handleRejectVouch(notification.id, notification.metadata?.vouchId || '')} onClick={() => handleRejectVouch(notification.id)}
sx={{ sx={{
minWidth: 60, minWidth: 60,
fontSize: '0.75rem', fontSize: '0.75rem',
@ -340,7 +340,7 @@ const NotificationsPage = () => {
<Button <Button
size="small" size="small"
variant="contained" variant="contained"
onClick={() => handleAcceptVouch(notification.id, notification.metadata?.vouchId || '')} onClick={() => handleAcceptVouch(notification.id)}
sx={{ sx={{
minWidth: 60, minWidth: 60,
fontSize: '0.75rem', fontSize: '0.75rem',
@ -356,7 +356,7 @@ const NotificationsPage = () => {
<Button <Button
size="small" size="small"
variant="outlined" variant="outlined"
onClick={() => handleRejectPraise(notification.id, notification.metadata?.praiseId || '')} onClick={() => handleRejectPraise(notification.id)}
sx={{ sx={{
minWidth: 60, minWidth: 60,
fontSize: '0.75rem', fontSize: '0.75rem',
@ -368,7 +368,7 @@ const NotificationsPage = () => {
<Button <Button
size="small" size="small"
variant="contained" variant="contained"
onClick={() => handleAcceptPraise(notification.id, notification.metadata?.praiseId || '')} onClick={() => handleAcceptPraise(notification.id)}
sx={{ sx={{
minWidth: 60, minWidth: 60,
fontSize: '0.75rem', fontSize: '0.75rem',

@ -11,7 +11,7 @@ import {
LinearProgress LinearProgress
} from '@mui/material'; } from '@mui/material';
import { ArrowBack, ArrowForward } from '@mui/icons-material'; import { ArrowBack, ArrowForward } from '@mui/icons-material';
import { useOnboarding } from '@/contexts/OnboardingContext'; import { useOnboarding } from '@/hooks/useOnboarding';
import BasicInfoStep from '@/components/onboarding/BasicInfoStep'; import BasicInfoStep from '@/components/onboarding/BasicInfoStep';
import ConnectAccountsStep from '@/components/onboarding/ConnectAccountsStep'; import ConnectAccountsStep from '@/components/onboarding/ConnectAccountsStep';
import { dataService } from '@/services/dataService'; import { dataService } from '@/services/dataService';

@ -1,6 +1,20 @@
import type { Contact, ImportSource } from '@/types/contact'; import type { Contact, ImportSource } from '@/types/contact';
import type { Group } from '@/types/group'; import type { Group } from '@/types/group';
// Raw data types from JSON files (with string dates)
interface RawContact extends Omit<Contact, 'createdAt' | 'updatedAt' | 'joinedAt' | 'invitedAt'> {
createdAt: string;
updatedAt: string;
joinedAt?: string;
invitedAt?: string;
}
interface RawGroup extends Omit<Group, 'createdAt' | 'updatedAt' | 'latestPostAt'> {
createdAt: string;
updatedAt: string;
latestPostAt?: string;
}
export const dataService = { export const dataService = {
async getContacts(): Promise<Contact[]> { async getContacts(): Promise<Contact[]> {
return new Promise((resolve) => { return new Promise((resolve) => {
@ -8,21 +22,16 @@ export const dataService = {
try { try {
const response = await fetch('/contacts.json'); const response = await fetch('/contacts.json');
const contactsData = await response.json(); const contactsData = await response.json();
const contacts = contactsData.map((contact: Contact & { createdAt: string; updatedAt: string; joinedAt?: string; invitedAt?: string; }) => { const contacts = contactsData.map((contact: RawContact) => {
const processedContact = { const { createdAt, updatedAt, joinedAt, invitedAt, ...contactData } = contact;
...contact, const processedContact: Contact = {
createdAt: new Date(contact.createdAt), ...contactData,
updatedAt: new Date(contact.updatedAt) createdAt: new Date(createdAt),
updatedAt: new Date(updatedAt),
joinedAt: joinedAt ? new Date(joinedAt) : undefined,
invitedAt: invitedAt ? new Date(invitedAt) : undefined
}; };
// Convert optional date fields if they exist
if (contact.joinedAt) {
processedContact.joinedAt = new Date(contact.joinedAt);
}
if (contact.invitedAt) {
processedContact.invitedAt = new Date(contact.invitedAt);
}
return processedContact; return processedContact;
}); });
resolve(contacts); resolve(contacts);
@ -111,18 +120,15 @@ export const dataService = {
try { try {
const response = await fetch('/groups.json'); const response = await fetch('/groups.json');
const groupsData = await response.json(); const groupsData = await response.json();
const groups = groupsData.map((group: Group & { createdAt: string; updatedAt: string; latestPostAt?: string; }) => { const groups = groupsData.map((group: RawGroup) => {
const processedGroup = { const { createdAt, updatedAt, latestPostAt, ...groupData } = group;
...(group as unknown as Group), const processedGroup: Group = {
createdAt: new Date(group.createdAt), ...groupData,
updatedAt: new Date(group.updatedAt) createdAt: new Date(createdAt),
updatedAt: new Date(updatedAt),
latestPostAt: latestPostAt ? new Date(latestPostAt) : undefined
}; };
// Convert optional date fields if they exist
if (group.latestPostAt) {
processedGroup.latestPostAt = new Date(group.latestPostAt);
}
return processedGroup; return processedGroup;
}); });
resolve(groups); resolve(groups);
@ -172,19 +178,16 @@ export const dataService = {
const response = await fetch('/groups.json'); const response = await fetch('/groups.json');
const groupsData = await response.json(); const groupsData = await response.json();
const userGroups = groupsData const userGroups = groupsData
.filter((group: Record<string, unknown>) => (group.memberIds as string[]).includes(userId)) .filter((group: RawGroup) => group.memberIds.includes(userId))
.map((group: Record<string, unknown>) => { .map((group: RawGroup) => {
const { createdAt, updatedAt, latestPostAt, ...groupData } = group;
const processedGroup: Group = { const processedGroup: Group = {
...(group as unknown as Group), ...groupData,
createdAt: new Date(group.createdAt as string), createdAt: new Date(createdAt),
updatedAt: new Date(group.updatedAt as string) updatedAt: new Date(updatedAt),
latestPostAt: latestPostAt ? new Date(latestPostAt) : undefined
}; };
// Convert optional date fields if they exist
if (group.latestPostAt) {
processedGroup.latestPostAt = new Date(group.latestPostAt as string);
}
return processedGroup; return processedGroup;
}); });
resolve(userGroups); resolve(userGroups);

@ -35,6 +35,7 @@ export interface GroupPost {
likes: number; likes: number;
comments: number; comments: number;
attachments?: string[]; attachments?: string[];
images?: string[];
} }
export interface GroupLink { export interface GroupLink {

Loading…
Cancel
Save