feat: Add notification bell UI component to header
Some checks failed
ParentFlow CI/CD Pipeline / Backend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Frontend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Security Scanning (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-app/maternal-app-backend dockerfile:Dockerfile.production name:backend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-web dockerfile:Dockerfile.production name:frontend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Development (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
Some checks failed
ParentFlow CI/CD Pipeline / Backend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Frontend Tests (push) Has been cancelled
ParentFlow CI/CD Pipeline / Security Scanning (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-app/maternal-app-backend dockerfile:Dockerfile.production name:backend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Build Docker Images (map[context:maternal-web dockerfile:Dockerfile.production name:frontend]) (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Development (push) Has been cancelled
ParentFlow CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
Implements Phase 1 of notification bell feature with: - NotificationBell component with badge counter (99+ max) - useNotifications hook with 30s polling and caching - Notifications API client for backend integration - Integration into AppShell header next to user avatar - Responsive dropdown (400px desktop, full-width mobile) - Empty state, loading, and error handling - Optimistic UI updates for mark as read - Animated badge with pulse effect - Icon mapping for notification types 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,7 @@ import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useAuth } from '@/lib/auth/AuthContext';
|
||||
import Link from 'next/link';
|
||||
import { NotificationBell } from '@/components/notifications/NotificationBell';
|
||||
|
||||
interface AppShellProps {
|
||||
children: ReactNode;
|
||||
@@ -149,59 +150,62 @@ export const AppShell = ({ children }: AppShellProps) => {
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Right Side - User Menu Button with Status Indicator */}
|
||||
<Box sx={{ width: 80, display: 'flex', justifyContent: 'flex-end' }}>
|
||||
<Tooltip title={isConnected ? t('connection.syncActive') : t('connection.syncDisconnected')}>
|
||||
<IconButton
|
||||
onClick={handleMenuOpen}
|
||||
size="medium"
|
||||
aria-label="user menu"
|
||||
aria-controls={anchorEl ? 'user-menu' : undefined}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={anchorEl ? 'true' : undefined}
|
||||
sx={{
|
||||
minWidth: 44,
|
||||
minHeight: 44,
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
src={user?.photoUrl || undefined}
|
||||
alt={user?.name ? `${user.name}'s profile photo` : 'User profile photo'}
|
||||
key={user?.photoUrl || 'no-photo'} // Force re-render when photoUrl changes
|
||||
{/* Right Side - Notifications & User Menu */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<NotificationBell />
|
||||
|
||||
<Tooltip title={isConnected ? t('connection.syncActive') : t('connection.syncDisconnected')}>
|
||||
<IconButton
|
||||
onClick={handleMenuOpen}
|
||||
size="medium"
|
||||
aria-label="user menu"
|
||||
aria-controls={anchorEl ? 'user-menu' : undefined}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={anchorEl ? 'true' : undefined}
|
||||
sx={{
|
||||
width: 36,
|
||||
height: 36,
|
||||
bgcolor: 'primary.main',
|
||||
fontSize: '0.875rem',
|
||||
}}
|
||||
imgProps={{
|
||||
onError: (e: any) => {
|
||||
console.error('Avatar image failed to load:', user?.photoUrl?.substring(0, 50));
|
||||
e.target.style.display = 'none'; // Hide broken image, show fallback
|
||||
}
|
||||
minWidth: 44,
|
||||
minHeight: 44,
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{user?.name?.charAt(0).toUpperCase() || 'U'}
|
||||
</Avatar>
|
||||
{/* Status Dot Indicator */}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: 2,
|
||||
right: 2,
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: '50%',
|
||||
bgcolor: isConnected ? '#4caf50' : '#9e9e9e',
|
||||
border: '2px solid',
|
||||
borderColor: 'background.paper',
|
||||
boxShadow: 1,
|
||||
}}
|
||||
aria-label={isConnected ? 'Online' : 'Offline'}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Avatar
|
||||
src={user?.photoUrl || undefined}
|
||||
alt={user?.name ? `${user.name}'s profile photo` : 'User profile photo'}
|
||||
key={user?.photoUrl || 'no-photo'} // Force re-render when photoUrl changes
|
||||
sx={{
|
||||
width: 36,
|
||||
height: 36,
|
||||
bgcolor: 'primary.main',
|
||||
fontSize: '0.875rem',
|
||||
}}
|
||||
imgProps={{
|
||||
onError: (e: any) => {
|
||||
console.error('Avatar image failed to load:', user?.photoUrl?.substring(0, 50));
|
||||
e.target.style.display = 'none'; // Hide broken image, show fallback
|
||||
}
|
||||
}}
|
||||
>
|
||||
{user?.name?.charAt(0).toUpperCase() || 'U'}
|
||||
</Avatar>
|
||||
{/* Status Dot Indicator */}
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: 2,
|
||||
right: 2,
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: '50%',
|
||||
bgcolor: isConnected ? '#4caf50' : '#9e9e9e',
|
||||
border: '2px solid',
|
||||
borderColor: 'background.paper',
|
||||
boxShadow: 1,
|
||||
}}
|
||||
aria-label={isConnected ? 'Online' : 'Offline'}
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
|
||||
<Menu
|
||||
id="user-menu"
|
||||
@@ -248,7 +252,6 @@ export const AppShell = ({ children }: AppShellProps) => {
|
||||
<ListItemText>{t('navigation.logout')}</ListItemText>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Container
|
||||
|
||||
Reference in New Issue
Block a user