Files
maternal-app/maternal-web/app/settings/page.tsx
andupetcu ac440ddb85 Implement comprehensive tracking system and analytics dashboard
- Add Feeding Tracker with 3 feeding types (breast, bottle, solid)
  - Built-in timer for breastfeeding sessions
  - Recent feeding history with delete functionality
  - Form validation and child selection

- Add Sleep Tracker with duration tracking
  - Start/end time inputs with "Now" quick buttons
  - Sleep quality and location tracking
  - Ongoing sleep support with real-time duration
  - Recent sleep activities list

- Add Diaper Tracker with comprehensive monitoring
  - 4 diaper types (wet, dirty, both, dry)
  - Multiple condition selectors
  - Rash monitoring with severity levels
  - Color-coded visual indicators

- Add Insights/Analytics Dashboard
  - Summary statistics cards (feedings, sleep, diapers)
  - Interactive charts using Recharts (bar, line, pie)
  - Date range filtering (7/30/90 days)
  - Activity timeline and distribution
  - Recent activities list

- Add Settings page with backend integration
  - Profile update functionality with API integration
  - Form validation and error handling
  - Loading states and success notifications
  - Notification and appearance preferences

- Add Users API service for profile management

All pages include:
- Full CRUD operations with backend APIs
- Loading states and error handling
- Form validation and user feedback
- Framer Motion animations
- Material-UI design system
- Responsive layouts

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-30 22:36:28 +03:00

227 lines
7.1 KiB
TypeScript

'use client';
import { Box, Typography, Card, CardContent, TextField, Button, Divider, Switch, FormControlLabel, Alert, CircularProgress, Snackbar } from '@mui/material';
import { Save, Logout } from '@mui/icons-material';
import { useAuth } from '@/lib/auth/AuthContext';
import { useState } from 'react';
import { AppShell } from '@/components/layouts/AppShell/AppShell';
import { ProtectedRoute } from '@/components/common/ProtectedRoute';
import { usersApi } from '@/lib/api/users';
import { motion } from 'framer-motion';
export default function SettingsPage() {
const { user, logout, refreshUser } = useAuth();
const [name, setName] = useState(user?.name || '');
const [settings, setSettings] = useState({
notifications: true,
emailUpdates: false,
darkMode: false,
});
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [successMessage, setSuccessMessage] = useState<string | null>(null);
const [nameError, setNameError] = useState<string | null>(null);
const handleSave = async () => {
// Validate name
if (!name || name.trim() === '') {
setNameError('Name cannot be empty');
return;
}
setIsLoading(true);
setError(null);
setNameError(null);
try {
await usersApi.updateProfile({ name: name.trim() });
await refreshUser();
setSuccessMessage('Profile updated successfully!');
} catch (err: any) {
console.error('Failed to update profile:', err);
setError(err.response?.data?.message || 'Failed to update profile. Please try again.');
} finally {
setIsLoading(false);
}
};
const handleLogout = async () => {
await logout();
};
return (
<ProtectedRoute>
<AppShell>
<Box sx={{ maxWidth: 'md', mx: 'auto' }}>
<Typography variant="h4" fontWeight="600" gutterBottom>
Settings
</Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 4 }}>
Manage your account settings and preferences
</Typography>
{/* Error Alert */}
{error && (
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<Alert severity="error" sx={{ mb: 3 }} onClose={() => setError(null)}>
{error}
</Alert>
</motion.div>
)}
{/* Profile Settings */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4 }}
>
<Card sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" fontWeight="600" gutterBottom>
Profile Information
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 2 }}>
<TextField
label="Name"
value={name}
onChange={(e) => {
setName(e.target.value);
if (nameError) setNameError(null);
}}
fullWidth
error={!!nameError}
helperText={nameError}
disabled={isLoading}
/>
<TextField
label="Email"
value={user?.email || ''}
fullWidth
disabled
helperText="Email cannot be changed"
/>
<Button
variant="contained"
startIcon={isLoading ? <CircularProgress size={20} color="inherit" /> : <Save />}
onClick={handleSave}
disabled={isLoading}
sx={{ alignSelf: 'flex-start' }}
>
{isLoading ? 'Saving...' : 'Save Changes'}
</Button>
</Box>
</CardContent>
</Card>
</motion.div>
{/* Notification Settings */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.1 }}
>
<Card sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" fontWeight="600" gutterBottom>
Notifications
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Settings are stored locally (backend integration coming soon)
</Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mt: 2 }}>
<FormControlLabel
control={
<Switch
checked={settings.notifications}
onChange={(e) => setSettings({ ...settings, notifications: e.target.checked })}
/>
}
label="Push Notifications"
/>
<FormControlLabel
control={
<Switch
checked={settings.emailUpdates}
onChange={(e) => setSettings({ ...settings, emailUpdates: e.target.checked })}
/>
}
label="Email Updates"
/>
</Box>
</CardContent>
</Card>
</motion.div>
{/* Appearance Settings */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.2 }}
>
<Card sx={{ mb: 3 }}>
<CardContent>
<Typography variant="h6" fontWeight="600" gutterBottom>
Appearance
</Typography>
<Box sx={{ mt: 2 }}>
<FormControlLabel
control={
<Switch
checked={settings.darkMode}
onChange={(e) => setSettings({ ...settings, darkMode: e.target.checked })}
/>
}
label="Dark Mode (Coming Soon)"
disabled
/>
</Box>
</CardContent>
</Card>
</motion.div>
{/* Account Actions */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, delay: 0.3 }}
>
<Card>
<CardContent>
<Typography variant="h6" fontWeight="600" gutterBottom>
Account Actions
</Typography>
<Divider sx={{ my: 2 }} />
<Button
variant="outlined"
color="error"
startIcon={<Logout />}
onClick={handleLogout}
fullWidth
>
Logout
</Button>
</CardContent>
</Card>
</motion.div>
{/* Success Snackbar */}
<Snackbar
open={!!successMessage}
autoHideDuration={4000}
onClose={() => setSuccessMessage(null)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
>
<Alert onClose={() => setSuccessMessage(null)} severity="success" sx={{ width: '100%' }}>
{successMessage}
</Alert>
</Snackbar>
</Box>
</AppShell>
</ProtectedRoute>
);
}