Files
maternal-app/maternal-web/components/layouts/AppShell/AppShell.tsx
Andrei f083e3ed94
Some checks failed
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
fix: Remove photo URL TextField completely and add debug logging
- Completely removed TextField from PhotoUpload component (not just hidden)
- Added console logging to AppShell to debug user photo updates
- Rebuilt frontend to ensure changes are deployed

The TextField was showing base64 strings even with display:none due to
Next.js caching. Complete removal ensures clean UI.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-04 09:20:59 +00:00

220 lines
6.5 KiB
TypeScript

'use client';
import { useState } from 'react';
import {
Box,
Container,
Chip,
Tooltip,
IconButton,
Menu,
MenuItem,
ListItemIcon,
ListItemText,
Avatar,
Divider,
} from '@mui/material';
import { MobileNav } from '../MobileNav/MobileNav';
import { TabBar } from '../TabBar/TabBar';
import { useMediaQuery } from '@/hooks/useMediaQuery';
import { ReactNode } from 'react';
import { useWebSocket } from '@/hooks/useWebSocket';
import { Wifi, WifiOff, People, AccountCircle, Settings, ChildCare, Group, Logout } from '@mui/icons-material';
import { useTranslation } from '@/hooks/useTranslation';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/lib/auth/AuthContext';
interface AppShellProps {
children: ReactNode;
}
export const AppShell = ({ children }: AppShellProps) => {
const { t } = useTranslation('common');
const router = useRouter();
const { user, logout } = useAuth();
const isMobile = useMediaQuery('(max-width: 768px)');
const isTablet = useMediaQuery('(max-width: 1024px)');
const { isConnected, presence } = useWebSocket();
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
// Debug: Log user photo changes
React.useEffect(() => {
console.log('👤 User updated in AppShell:', {
name: user?.name,
hasPhoto: !!user?.photoUrl,
photoPreview: user?.photoUrl?.substring(0, 50)
});
}, [user?.photoUrl]);
const handleMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleMenuClose = () => {
setAnchorEl(null);
};
const handleNavigate = (path: string) => {
handleMenuClose();
router.push(path);
};
const handleLogout = async () => {
handleMenuClose();
await logout();
router.push('/login');
};
return (
<Box sx={{
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
bgcolor: 'background.default',
pb: '64px', // Space for tab bar on both mobile and desktop
}}>
{/* Header Bar - Both Mobile and Desktop */}
<Box
sx={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
height: 48,
bgcolor: 'background.paper',
borderBottom: '1px solid',
borderColor: 'divider',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
px: 2,
zIndex: 1200,
boxShadow: 1,
}}
>
{/* Left Side - Family Members Online Indicator */}
<Box>
{isConnected && presence.count > 1 && (
<Tooltip title={t('connection.familyMembersOnline', { count: presence.count })}>
<Chip
icon={<People />}
label={presence.count}
size="small"
color="primary"
sx={{
fontWeight: 600,
}}
/>
</Tooltip>
)}
</Box>
{/* Right Side - User Menu Button with Status Indicator */}
<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}
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>
<Menu
id="user-menu"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleMenuClose}
onClick={handleMenuClose}
transformOrigin={{ horizontal: 'right', vertical: 'top' }}
anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}
sx={{
mt: 1,
}}
>
<MenuItem onClick={() => handleNavigate('/settings')}>
<ListItemIcon>
<Settings fontSize="small" />
</ListItemIcon>
<ListItemText>{t('navigation.settings')}</ListItemText>
</MenuItem>
<MenuItem onClick={() => handleNavigate('/children')}>
<ListItemIcon>
<ChildCare fontSize="small" />
</ListItemIcon>
<ListItemText>{t('navigation.children')}</ListItemText>
</MenuItem>
<MenuItem onClick={() => handleNavigate('/family')}>
<ListItemIcon>
<Group fontSize="small" />
</ListItemIcon>
<ListItemText>{t('navigation.family')}</ListItemText>
</MenuItem>
<Divider />
<MenuItem onClick={handleLogout}>
<ListItemIcon>
<Logout fontSize="small" color="error" />
</ListItemIcon>
<ListItemText>{t('navigation.logout')}</ListItemText>
</MenuItem>
</Menu>
</Box>
<Container
maxWidth={isTablet ? 'md' : 'lg'}
sx={{
flex: 1,
px: { xs: 2, md: 3 },
py: 3,
pt: '64px', // Add top padding for header bar on both mobile and desktop
}}
>
{children}
</Container>
<TabBar />
</Box>
);
};