Files
maternal-app/maternal-web/components/layouts/TabBar/TabBar.tsx
Andrei d0b78181a3
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: Comprehensive authentication and UI fixes
Authentication & Token Management:
- Add deviceId to token refresh flow (backend requires both refreshToken and deviceId)
- Fix React Strict Mode token clearing race condition with retry logic
- Improve AuthContext to handle all token state combinations properly
- Store deviceId in localStorage alongside tokens

UI/UX Improvements:
- Remove deprecated legacyBehavior from Next.js Link components
- Update primary theme color to WCAG AA compliant #7c3aed
- Fix nested button error in TabBar voice navigation
- Fix invalid Tabs value error in DynamicChildDashboard

Multi-Child Dashboard:
- Load all children into Redux store properly
- Fetch metrics for all children, not just selected one
- Remove mock data to prevent unauthorized API calls

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-05 16:10:11 +00:00

121 lines
3.7 KiB
TypeScript

'use client';
import { usePathname, useRouter } from 'next/navigation';
import { useState } from 'react';
import { BottomNavigation, BottomNavigationAction, Paper, Fab, Box, IconButton } from '@mui/material';
import {
Home,
Timeline,
Insights,
Chat,
Mic,
} from '@mui/icons-material';
import { useTranslation } from '@/hooks/useTranslation';
export const TabBar = () => {
const { t } = useTranslation('common');
const router = useRouter();
const pathname = usePathname();
const [voiceOpen, setVoiceOpen] = useState(false);
const tabs = [
{ label: t('navigation.home'), icon: <Home />, value: '/' },
{ label: t('navigation.track'), icon: <Timeline />, value: '/track' },
{ label: '', icon: null, value: 'voice' }, // Placeholder for center button
{ label: t('navigation.insights'), icon: <Insights />, value: '/insights' },
{ label: t('navigation.aiChat'), icon: <Chat />, value: '/ai-assistant' },
];
return (
<>
<Paper
component="nav"
aria-label="Primary navigation"
sx={{
position: 'fixed',
bottom: { xs: 0, md: 20 },
left: { xs: 0, md: '50%' },
right: { xs: 0, md: 'auto' },
transform: { xs: 'none', md: 'translateX(-50%)' },
width: { xs: '100%', md: '30%' },
zIndex: 1000,
borderRadius: { xs: 0, md: 2 },
}}
elevation={3}
>
<BottomNavigation
value={pathname}
onChange={(event, newValue) => {
if (newValue !== 'voice') {
router.push(newValue);
}
}}
showLabels
sx={{
height: 64,
borderRadius: { xs: 0, md: 2 },
'& .MuiBottomNavigationAction-root': {
minWidth: 60,
'&.Mui-selected': {
color: 'primary.main',
},
},
}}
>
{tabs.map((tab) => {
if (tab.value === 'voice') {
// Center voice button - using BottomNavigationAction as wrapper to avoid prop warnings
return (
<BottomNavigationAction
key="voice-placeholder"
label=""
value="voice"
showLabel={false}
onClick={(e) => {
e.stopPropagation();
const voiceButton = document.querySelector('[aria-label="voice input"]') as HTMLButtonElement;
if (voiceButton) {
voiceButton.click();
}
}}
icon={(
<Box
aria-label="voice command"
sx={{
bgcolor: '#FF69B4',
color: 'white',
width: 48,
height: 48,
'&:hover': {
bgcolor: '#FF1493',
},
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '50%',
}}
>
<Mic />
</Box>
)}
sx={{ minWidth: 80 }}
/>
);
}
return (
<BottomNavigationAction
key={tab.value}
label={tab.label}
icon={tab.icon}
value={tab.value}
/>
);
})}
</BottomNavigation>
</Paper>
{/* Voice Command Floating Button is now centralized in layout.tsx - removed duplicate */}
</>
);
};