feat: Add collapsible sections and mobile grid layout
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

- Convert Active Sessions and Trusted Devices to collapsible Accordion components
- Display count badge in collapsed state
- Show loading state in accordion header
- Implement 2-card grid layout on mobile (xs=6)
- Responsive card sizing and spacing
- Centered layout on mobile, horizontal on desktop
- Hide full birthdate on mobile, show age only

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-04 08:08:24 +00:00
parent ec3f0264a0
commit 426b5a309e
5 changed files with 79 additions and 43 deletions

View File

@@ -214,9 +214,9 @@ export default function ChildrenPage() {
</Grid>
</Grid>
) : (
<Grid container spacing={3}>
<Grid container spacing={{ xs: 2, sm: 3 }}>
{children.map((child, index) => (
<Grid item xs={12} sm={6} key={child.id}>
<Grid item xs={6} sm={6} md={4} key={child.id}>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
@@ -225,43 +225,43 @@ export default function ChildrenPage() {
<Paper
elevation={0}
sx={{
p: 3,
p: { xs: 2, sm: 3 },
borderRadius: 3,
bgcolor: 'background.paper',
border: 1,
borderColor: 'divider'
}}
>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
<Box sx={{ display: 'flex', flexDirection: { xs: 'column', sm: 'row' }, alignItems: { xs: 'center', sm: 'flex-start' }, gap: { xs: 1, sm: 2 } }}>
<Avatar
src={child.photoUrl}
sx={{
width: 64,
height: 64,
width: { xs: 48, sm: 64 },
height: { xs: 48, sm: 64 },
bgcolor: 'primary.light',
fontSize: 24
fontSize: { xs: 20, sm: 24 }
}}
>
{child.name[0]}
</Avatar>
<Box sx={{ flexGrow: 1 }}>
<Typography variant="h6" fontWeight="600">{child.name}</Typography>
<Typography variant="body2" color="text.secondary">
<Box sx={{ flexGrow: 1, textAlign: { xs: 'center', sm: 'left' }, width: '100%' }}>
<Typography variant="h6" fontWeight="600" sx={{ fontSize: { xs: '1rem', sm: '1.25rem' } }}>{child.name}</Typography>
<Typography variant="body2" color="text.secondary" sx={{ fontSize: { xs: '0.75rem', sm: '0.875rem' } }}>
{t(`gender.${child.gender}`)}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mt: 1 }}>
<Box sx={{ display: { xs: 'none', sm: 'flex' }, alignItems: 'center', gap: 1, mt: 1 }}>
<CalendarToday fontSize="small" color="action" />
<Typography variant="caption">
{formatDate(child.birthDate, 'PPP')}
</Typography>
</Box>
<Typography variant="caption" color="primary.main">
<Typography variant="caption" color="primary.main" sx={{ display: 'block', mt: 0.5 }}>
{t('age')}: {calculateAge(child.birthDate)}
</Typography>
</Box>
</Box>
<Box sx={{ display: 'flex', gap: 1, mt: 2 }}>
<Box sx={{ display: 'flex', gap: 1, mt: 2, justifyContent: { xs: 'center', sm: 'flex-start' } }}>
<IconButton size="small" color="primary" onClick={() => handleEditChild(child)}>
<Edit />
</IconButton>

View File

@@ -266,10 +266,9 @@ export default function SettingsPage() {
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.3 }}
style={{ marginBottom: '24px' }}
>
<Box sx={{ mb: 3 }}>
<SessionsManagement />
</Box>
</motion.div>
{/* Device Trust Management */}
@@ -277,10 +276,9 @@ export default function SettingsPage() {
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.33 }}
style={{ marginBottom: '24px' }}
>
<Box sx={{ mb: 3 }}>
<DeviceTrustManagement />
</Box>
</motion.div>
{/* Biometric Authentication */}

View File

@@ -22,6 +22,9 @@ import {
Divider,
ToggleButton,
ToggleButtonGroup,
Accordion,
AccordionSummary,
AccordionDetails,
} from '@mui/material';
import {
Devices,
@@ -32,6 +35,7 @@ import {
CheckCircle,
Shield,
ShieldOutlined,
ExpandMore,
} from '@mui/icons-material';
import { devicesApi, type DeviceInfo } from '@/lib/api/devices';
import { motion } from 'framer-motion';
@@ -143,26 +147,41 @@ export function DeviceTrustManagement() {
if (isLoading) {
return (
<Card>
<CardContent sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
<CircularProgress />
</CardContent>
</Card>
<Accordion>
<AccordionSummary expandIcon={<ExpandMore />}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, width: '100%' }}>
<Shield color="primary" />
<Typography variant="h6" fontWeight="600">
Trusted Devices
</Typography>
<CircularProgress size={20} sx={{ ml: 'auto' }} />
</Box>
</AccordionSummary>
</Accordion>
);
}
return (
<>
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<Accordion>
<AccordionSummary
expandIcon={<ExpandMore />}
sx={{
'& .MuiAccordionSummary-content': {
margin: '12px 0',
},
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, width: '100%' }}>
<Shield color="primary" />
<Typography variant="h6" fontWeight="600">
Device Trust Management
Trusted Devices
</Typography>
<Chip label={`${devices.length} devices`} size="small" sx={{ ml: 'auto' }} />
<Chip label={`${devices.length}`} size="small" sx={{ ml: 'auto', mr: 1 }} />
</Box>
</AccordionSummary>
<AccordionDetails>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
Manage which devices are trusted to access your account. Untrusted devices will require additional verification.
</Typography>
@@ -278,8 +297,8 @@ export function DeviceTrustManagement() {
)}
</>
)}
</CardContent>
</Card>
</AccordionDetails>
</Accordion>
{/* Remove Device Dialog */}
<Dialog open={removeDialogOpen} onClose={() => setRemoveDialogOpen(false)} maxWidth="sm" fullWidth>

View File

@@ -20,6 +20,9 @@ import {
ListItemSecondaryAction,
IconButton,
Divider,
Accordion,
AccordionSummary,
AccordionDetails,
} from '@mui/material';
import {
Devices,
@@ -28,6 +31,7 @@ import {
Tablet,
Delete,
CheckCircle,
ExpandMore,
} from '@mui/icons-material';
import { sessionsApi, type SessionInfo } from '@/lib/api/sessions';
import { motion } from 'framer-motion';
@@ -116,26 +120,41 @@ export function SessionsManagement() {
if (isLoading) {
return (
<Card>
<CardContent sx={{ display: 'flex', justifyContent: 'center', py: 4 }}>
<CircularProgress />
</CardContent>
</Card>
<Accordion>
<AccordionSummary expandIcon={<ExpandMore />}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, width: '100%' }}>
<Devices color="primary" />
<Typography variant="h6" fontWeight="600">
Active Sessions
</Typography>
<CircularProgress size={20} sx={{ ml: 'auto' }} />
</Box>
</AccordionSummary>
</Accordion>
);
}
return (
<>
<Card>
<CardContent>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
<Accordion>
<AccordionSummary
expandIcon={<ExpandMore />}
sx={{
'& .MuiAccordionSummary-content': {
margin: '12px 0',
},
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, width: '100%' }}>
<Devices color="primary" />
<Typography variant="h6" fontWeight="600">
Active Sessions
</Typography>
<Chip label={`${sessions.length} active`} size="small" sx={{ ml: 'auto' }} />
<Chip label={`${sessions.length}`} size="small" sx={{ ml: 'auto', mr: 1 }} />
</Box>
</AccordionSummary>
<AccordionDetails>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
Manage your active sessions across different devices. You can revoke access from any device.
</Typography>
@@ -220,8 +239,8 @@ export function SessionsManagement() {
)}
</>
)}
</CardContent>
</Card>
</AccordionDetails>
</Accordion>
{/* Revoke Session Dialog */}
<Dialog open={revokeDialogOpen} onClose={() => setRevokeDialogOpen(false)} maxWidth="sm" fullWidth>

File diff suppressed because one or more lines are too long