This commit implements comprehensive timezone and time format customization:
## Backend Changes
- Added timeFormat field ('12h' | '24h') to user preferences JSONB in user entity
- Timezone field already existed in user entity, now actively used
- Backend ready to accept timezone on registration
## Frontend Components (2 new)
- TimeZoneSelector: Dropdown with timezones grouped by region (Americas, Europe, Asia, Pacific, Africa)
* Auto-detect button to detect browser timezone
* Save functionality with success/error feedback
* Integrated into Settings > Preferences section
- TimeFormatSelector: Radio buttons to choose 12h vs 24h format
* Live preview showing current time in selected format
* Save functionality with user feedback
* Integrated into Settings > Preferences section
## Timezone Auto-Detection
- Register function now auto-detects user's timezone via Intl.DateTimeFormat()
- Detected timezone sent to backend during registration
- Timezone stored in user profile for persistent preference
## Enhanced useLocalizedDate Hook
- Added useAuth integration to access user timezone and timeFormat preferences
- Installed and integrated date-fns-tz for timezone conversion
- New format() function with timezone support via useTimezone option
- New formatTime() function respecting user's 12h/24h preference
- New formatDateTime() function combining date, time, and timezone
- All formatting now respects user's:
* Language (existing: en, es, fr, pt-BR, zh-CN)
* Timezone (user-selected or auto-detected)
* Time format (12h with AM/PM or 24h)
## Settings Page Updates
- Added TimeZoneSelector to Preferences card
- Added TimeFormatSelector to Preferences card
- Visual separators (Dividers) between preference sections
- Settings now show: Language | Units | Timezone | Time Format
## Translations
- Enhanced settings.json with timezone and time format keys:
* preferences.timezone, autoDetectTimezone, timezoneUpdated
* preferences.12hour, 24hour, timeFormatUpdated
## User Experience Flow
1. User registers → timezone auto-detected and saved
2. User can change timezone in Settings > Preferences > Time Zone
3. User can change time format in Settings > Preferences > Time Format
4. All dates/times throughout app respect these preferences
5. Changes persist across sessions
Files changed: 10 files
New dependencies: date-fns-tz
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
128 lines
3.5 KiB
TypeScript
128 lines
3.5 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import {
|
|
Box,
|
|
FormControl,
|
|
RadioGroup,
|
|
FormControlLabel,
|
|
Radio,
|
|
Button,
|
|
CircularProgress,
|
|
Alert,
|
|
Typography,
|
|
} from '@mui/material';
|
|
import { Save, Schedule } from '@mui/icons-material';
|
|
import { useAuth } from '@/lib/auth/AuthContext';
|
|
import { usersApi } from '@/lib/api/users';
|
|
import { useTranslation } from '@/hooks/useTranslation';
|
|
|
|
export function TimeFormatSelector() {
|
|
const { user, refreshUser } = useAuth();
|
|
const { t } = useTranslation('settings');
|
|
const [timeFormat, setTimeFormat] = useState<'12h' | '24h'>(
|
|
user?.preferences?.timeFormat || '12h'
|
|
);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
|
|
|
const handleSave = async () => {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
setSuccessMessage(null);
|
|
|
|
try {
|
|
await usersApi.updateProfile({
|
|
preferences: {
|
|
...user?.preferences,
|
|
timeFormat,
|
|
},
|
|
});
|
|
await refreshUser();
|
|
setSuccessMessage('Time format updated successfully');
|
|
} catch (err: any) {
|
|
console.error('Failed to update time format:', err);
|
|
setError(err.response?.data?.message || 'Failed to update time format');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const currentTime = new Date();
|
|
const preview12h = currentTime.toLocaleTimeString('en-US', {
|
|
hour: 'numeric',
|
|
minute: '2-digit',
|
|
hour12: true,
|
|
});
|
|
const preview24h = currentTime.toLocaleTimeString('en-US', {
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
hour12: false,
|
|
});
|
|
|
|
return (
|
|
<Box>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
|
|
<Schedule color="action" />
|
|
<Typography variant="subtitle1" fontWeight="600">
|
|
Time Format
|
|
</Typography>
|
|
</Box>
|
|
|
|
{error && (
|
|
<Alert severity="error" sx={{ mb: 2 }} onClose={() => setError(null)}>
|
|
{error}
|
|
</Alert>
|
|
)}
|
|
|
|
{successMessage && (
|
|
<Alert severity="success" sx={{ mb: 2 }} onClose={() => setSuccessMessage(null)}>
|
|
{successMessage}
|
|
</Alert>
|
|
)}
|
|
|
|
<FormControl component="fieldset" sx={{ mb: 2 }}>
|
|
<RadioGroup
|
|
value={timeFormat}
|
|
onChange={(e) => setTimeFormat(e.target.value as '12h' | '24h')}
|
|
>
|
|
<FormControlLabel
|
|
value="12h"
|
|
control={<Radio disabled={isLoading} />}
|
|
label={
|
|
<Box>
|
|
<Typography variant="body1">12-hour format</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
Example: {preview12h}
|
|
</Typography>
|
|
</Box>
|
|
}
|
|
/>
|
|
<FormControlLabel
|
|
value="24h"
|
|
control={<Radio disabled={isLoading} />}
|
|
label={
|
|
<Box>
|
|
<Typography variant="body1">24-hour format</Typography>
|
|
<Typography variant="body2" color="text.secondary">
|
|
Example: {preview24h}
|
|
</Typography>
|
|
</Box>
|
|
}
|
|
/>
|
|
</RadioGroup>
|
|
</FormControl>
|
|
|
|
<Button
|
|
variant="contained"
|
|
startIcon={isLoading ? <CircularProgress size={20} color="inherit" /> : <Save />}
|
|
onClick={handleSave}
|
|
disabled={isLoading || timeFormat === user?.preferences?.timeFormat}
|
|
>
|
|
{isLoading ? 'Saving...' : 'Save'}
|
|
</Button>
|
|
</Box>
|
|
);
|
|
}
|