BACKEND: - Fix JWT authentication in FamiliesGateway * Configure JwtModule with ConfigService in FamiliesModule * Load JWT_SECRET from environment variables * Enable proper token verification for WebSocket connections - Fix circular dependency in TrackingModule * Use forwardRef pattern for FamiliesGateway injection * Make FamiliesGateway optional in TrackingService * Emit WebSocket events when activities are created/updated/deleted FRONTEND: - Create WebSocket service (336 lines) * Socket.IO client with auto-reconnection (exponential backoff 1s → 30s) * Family room join/leave management * Presence tracking (online users per family) * Event handlers for activities, children, members * Connection recovery with auto-rejoin - Create useWebSocket hook (187 lines) * Auto-connect on user authentication * Auto-join user's family room * Connection status tracking * Presence indicators * Hooks: useRealTimeActivities, useRealTimeChildren, useRealTimeFamilyMembers - Expose access token in AuthContext * Add token property to AuthContextType interface * Load token from tokenStorage on initialization * Update token state on login/register/logout * Enable WebSocket authentication - Integrate real-time sync across app * AppShell: Connection status indicator + online count badge * Activities page: Auto-refresh on family activity events * Home page: Auto-refresh daily summary on activity changes * Family page: Real-time member updates - Fix accessibility issues * Remove deprecated legacyBehavior from Link components (Next.js 15) * Fix color contrast in EmailVerificationBanner (WCAG AA) * Add missing aria-labels to IconButtons * Fix React key warnings in family member list DOCUMENTATION: - Update implementation-gaps.md * Mark Real-Time Sync as COMPLETED ✅ * Document WebSocket room management implementation * Document connection recovery and presence indicators * Update summary statistics (49 features completed) FILES CREATED: - maternal-web/hooks/useWebSocket.ts (187 lines) - maternal-web/lib/websocket.ts (336 lines) FILES MODIFIED (14): Backend (4): - families.gateway.ts (JWT verification fix) - families.module.ts (JWT config with ConfigService) - tracking.module.ts (forwardRef for FamiliesModule) - tracking.service.ts (emit WebSocket events) Frontend (9): - lib/auth/AuthContext.tsx (expose access token) - components/layouts/AppShell/AppShell.tsx (connection status + presence) - app/activities/page.tsx (real-time activity updates) - app/page.tsx (real-time daily summary refresh) - app/family/page.tsx (accessibility fixes) - app/(auth)/login/page.tsx (remove legacyBehavior) - components/common/EmailVerificationBanner.tsx (color contrast fix) Documentation (1): - docs/implementation-gaps.md (updated status) IMPACT: ✅ Real-time family collaboration achieved ✅ Activities sync instantly across all family members' devices ✅ Presence tracking shows who's online ✅ Connection recovery handles poor network conditions ✅ Accessibility improvements (WCAG AA compliance) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
119 lines
3.3 KiB
TypeScript
119 lines
3.3 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { Alert, Button, Snackbar, Box } from '@mui/material';
|
|
import { Email } from '@mui/icons-material';
|
|
import { useAuth } from '@/lib/auth/AuthContext';
|
|
import apiClient from '@/lib/api/client';
|
|
|
|
export const EmailVerificationBanner: React.FC = () => {
|
|
const { user } = useAuth();
|
|
const [dismissed, setDismissed] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
const [snackbar, setSnackbar] = useState<{ open: boolean; message: string; severity: 'success' | 'error' }>({
|
|
open: false,
|
|
message: '',
|
|
severity: 'success',
|
|
});
|
|
|
|
// Don't show if user is not logged in, email is verified, or banner was dismissed
|
|
if (!user || user.emailVerified || dismissed) {
|
|
return null;
|
|
}
|
|
|
|
const handleResendVerification = async () => {
|
|
setLoading(true);
|
|
try {
|
|
await apiClient.post('/api/v1/auth/email/send-verification');
|
|
setSnackbar({
|
|
open: true,
|
|
message: 'Verification email sent! Please check your inbox.',
|
|
severity: 'success',
|
|
});
|
|
} catch (error: any) {
|
|
console.error('Failed to resend verification email:', error);
|
|
setSnackbar({
|
|
open: true,
|
|
message: error.response?.data?.message || 'Failed to send verification email. Please try again.',
|
|
severity: 'error',
|
|
});
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleDismiss = () => {
|
|
setDismissed(true);
|
|
// Store dismissal in localStorage to persist across sessions
|
|
localStorage.setItem('emailVerificationBannerDismissed', 'true');
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<Alert
|
|
severity="warning"
|
|
icon={<Email />}
|
|
onClose={handleDismiss}
|
|
sx={{
|
|
borderRadius: 2,
|
|
mb: 2,
|
|
'& .MuiAlert-message': {
|
|
width: '100%',
|
|
color: '#92400E', // Dark brown for better contrast on warning background
|
|
},
|
|
}}
|
|
>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
gap: 2,
|
|
flexWrap: 'wrap',
|
|
}}
|
|
>
|
|
<Box>
|
|
<strong>Verify your email address</strong>
|
|
<br />
|
|
Please check your inbox and click the verification link to access all features.
|
|
</Box>
|
|
<Button
|
|
variant="outlined"
|
|
size="small"
|
|
onClick={handleResendVerification}
|
|
disabled={loading}
|
|
sx={{
|
|
borderRadius: 2,
|
|
textTransform: 'none',
|
|
fontWeight: 600,
|
|
borderColor: '#D97706',
|
|
color: '#92400E',
|
|
'&:hover': {
|
|
borderColor: '#92400E',
|
|
bgcolor: '#FEF3C7',
|
|
},
|
|
}}
|
|
>
|
|
{loading ? 'Sending...' : 'Resend Email'}
|
|
</Button>
|
|
</Box>
|
|
</Alert>
|
|
|
|
<Snackbar
|
|
open={snackbar.open}
|
|
autoHideDuration={6000}
|
|
onClose={() => setSnackbar({ ...snackbar, open: false })}
|
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
|
>
|
|
<Alert
|
|
severity={snackbar.severity}
|
|
onClose={() => setSnackbar({ ...snackbar, open: false })}
|
|
sx={{ borderRadius: 2 }}
|
|
>
|
|
{snackbar.message}
|
|
</Alert>
|
|
</Snackbar>
|
|
</>
|
|
);
|
|
};
|