WFDASHDRIVER-APP/app/settings/page.tsx
SJCoetz b738bc7495 feat: initialize project with Next.js, TypeScript, and Tailwind CSS
- Added package.json with dependencies for Next.js, React, and Tailwind CSS.
- Created postcss.config.js for Tailwind CSS and autoprefixer integration.
- Added SVG icon for the application in public/icons/icon.svg.
- Created manifest.json for PWA support with app details and icon.
- Configured Tailwind CSS in tailwind.config.js with custom colors and animations.
- Set up TypeScript configuration in tsconfig.json for strict type checking and module resolution.
2026-05-24 21:51:29 +02:00

362 lines
13 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useAuth } from '@/components/AuthProvider';
import BottomNav from '@/components/BottomNav';
import { getSettings, saveSettings, logout } from '@/lib/pocketbase';
import { AppSettings, DEFAULT_SETTINGS } from '@/lib/types';
import { cn } from '@/lib/utils';
import {
Settings,
Clock,
Crosshair,
Moon,
Type,
WifiOff,
LogOut,
ChevronRight,
Save,
RotateCcw,
Info,
} from 'lucide-react';
const INTERVAL_OPTIONS = [
{ value: 30, label: '30 seconds' },
{ value: 60, label: '1 minute' },
{ value: 120, label: '2 minutes' },
{ value: 300, label: '5 minutes' },
{ value: 600, label: '10 minutes' },
];
const ACCURACY_OPTIONS = [
{ value: 10, label: '10m (High)' },
{ value: 50, label: '50m (Balanced)' },
{ value: 100, label: '100m (Battery)' },
{ value: 500, label: '500m (Low)' },
];
export default function SettingsPage() {
const router = useRouter();
const { isLoggedIn, user, isLoading: authLoading } = useAuth();
const [settings, setSettings] = useState<AppSettings>(DEFAULT_SETTINGS);
const [saved, setSaved] = useState(false);
useEffect(() => {
if (!authLoading && !isLoggedIn) {
router.push('/login/');
}
}, [isLoggedIn, authLoading, router]);
useEffect(() => {
setSettings(getSettings());
}, []);
const updateSetting = <K extends keyof AppSettings>(key: K, value: AppSettings[K]) => {
const updated = { ...settings, [key]: value };
setSettings(updated);
saveSettings({ [key]: value });
setSaved(true);
setTimeout(() => setSaved(false), 2000);
};
const resetSettings = () => {
if (confirm('Reset all settings to defaults?')) {
setSettings(DEFAULT_SETTINGS);
saveSettings(DEFAULT_SETTINGS);
setSaved(true);
setTimeout(() => setSaved(false), 2000);
}
};
if (authLoading) {
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="w-8 h-8 border-2 border-orange-500 border-t-transparent rounded-full animate-spin" />
</div>
);
}
if (!isLoggedIn) {
return null;
}
return (
<div className="min-h-screen bg-gray-50 pb-20">
{/* Header */}
<header className="bg-gray-900 text-white safe-top">
<div className="px-4 py-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-orange-500 rounded-xl flex items-center justify-center">
<Settings size={20} className="text-white" />
</div>
<div>
<h1 className="text-lg font-bold">Settings</h1>
<p className="text-xs text-gray-400">
{user?.name || user?.email}
</p>
</div>
</div>
</div>
</header>
<main className="p-4 space-y-6">
{/* Location Settings */}
<section>
<div className="flex items-center gap-2 mb-3 px-1">
<Crosshair size={16} className="text-orange-500" />
<h2 className="text-sm font-semibold text-gray-700 uppercase tracking-wide">
Location Tracking
</h2>
</div>
<div className="bg-white rounded-xl card-shadow overflow-hidden">
{/* Update Interval */}
<div className="p-4 border-b border-gray-100">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<Clock size={16} className="text-gray-400" />
<span className="text-sm font-medium text-gray-700">
Update Interval
</span>
</div>
<span className="text-xs text-orange-600 font-medium bg-orange-50 px-2 py-1 rounded-full">
{settings.locationInterval}s
</span>
</div>
<div className="grid grid-cols-3 gap-2">
{INTERVAL_OPTIONS.map((option) => (
<button
key={option.value}
onClick={() => updateSetting('locationInterval', option.value)}
className={cn(
'py-2 px-2 rounded-lg text-xs font-medium transition-all',
'touch-manipulation',
settings.locationInterval === option.value
? 'bg-orange-500 text-white shadow-sm'
: 'bg-gray-50 text-gray-600 hover:bg-gray-100'
)}
>
{option.label}
</button>
))}
</div>
<p className="text-[10px] text-gray-400 mt-2">
How often your location is sent to the server
</p>
</div>
{/* Minimum Accuracy */}
<div className="p-4 border-b border-gray-100">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<Crosshair size={16} className="text-gray-400" />
<span className="text-sm font-medium text-gray-700">
Minimum Accuracy
</span>
</div>
<span className="text-xs text-orange-600 font-medium bg-orange-50 px-2 py-1 rounded-full">
{settings.minimumAccuracy}m
</span>
</div>
<div className="grid grid-cols-2 gap-2">
{ACCURACY_OPTIONS.map((option) => (
<button
key={option.value}
onClick={() => updateSetting('minimumAccuracy', option.value)}
className={cn(
'py-2 px-2 rounded-lg text-xs font-medium transition-all',
'touch-manipulation',
settings.minimumAccuracy === option.value
? 'bg-orange-500 text-white shadow-sm'
: 'bg-gray-50 text-gray-600 hover:bg-gray-100'
)}
>
{option.label}
</button>
))}
</div>
<p className="text-[10px] text-gray-400 mt-2">
Skip location updates with worse accuracy
</p>
</div>
{/* High Accuracy Toggle */}
<div className="p-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Crosshair size={16} className="text-gray-400" />
<div>
<span className="text-sm font-medium text-gray-700 block">
High Accuracy Mode
</span>
<span className="text-[10px] text-gray-400">
Uses GPS for better precision
</span>
</div>
</div>
<button
onClick={() =>
updateSetting('enableHighAccuracy', !settings.enableHighAccuracy)
}
className={cn(
'w-12 h-7 rounded-full transition-colors relative',
settings.enableHighAccuracy ? 'bg-orange-500' : 'bg-gray-300'
)}
>
<div
className={cn(
'absolute top-0.5 w-6 h-6 bg-white rounded-full shadow-md transition-transform',
settings.enableHighAccuracy ? 'translate-x-5' : 'translate-x-0.5'
)}
/>
</button>
</div>
</div>
</div>
</section>
{/* Display Settings */}
<section>
<div className="flex items-center gap-2 mb-3 px-1">
<Type size={16} className="text-orange-500" />
<h2 className="text-sm font-semibold text-gray-700 uppercase tracking-wide">
Display
</h2>
</div>
<div className="bg-white rounded-xl card-shadow overflow-hidden">
{/* Font Size */}
<div className="p-4 border-b border-gray-100">
<div className="flex items-center gap-2 mb-3">
<Type size={16} className="text-gray-400" />
<span className="text-sm font-medium text-gray-700">Font Size</span>
</div>
<div className="flex gap-2">
{(['small', 'normal', 'large'] as const).map((size) => (
<button
key={size}
onClick={() => updateSetting('fontSize', size)}
className={cn(
'flex-1 py-2.5 rounded-lg text-sm font-medium transition-all',
'touch-manipulation capitalize',
settings.fontSize === size
? 'bg-orange-500 text-white shadow-sm'
: 'bg-gray-50 text-gray-600 hover:bg-gray-100'
)}
>
{size === 'small' && (
<span className="text-xs">A</span>
)}
{size === 'normal' && (
<span className="text-sm">A</span>
)}
{size === 'large' && (
<span className="text-lg">A</span>
)}
</button>
))}
</div>
</div>
{/* Offline Mode */}
<div className="p-4">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<WifiOff size={16} className="text-gray-400" />
<div>
<span className="text-sm font-medium text-gray-700 block">
Offline Queue
</span>
<span className="text-[10px] text-gray-400">
Queue actions when offline
</span>
</div>
</div>
<button
onClick={() =>
updateSetting('offlineMode', !settings.offlineMode)
}
className={cn(
'w-12 h-7 rounded-full transition-colors relative',
settings.offlineMode ? 'bg-orange-500' : 'bg-gray-300'
)}
>
<div
className={cn(
'absolute top-0.5 w-6 h-6 bg-white rounded-full shadow-md transition-transform',
settings.offlineMode ? 'translate-x-5' : 'translate-x-0.5'
)}
/>
</button>
</div>
</div>
</div>
</section>
{/* About */}
<section>
<div className="flex items-center gap-2 mb-3 px-1">
<Info size={16} className="text-orange-500" />
<h2 className="text-sm font-semibold text-gray-700 uppercase tracking-wide">
About
</h2>
</div>
<div className="bg-white rounded-xl card-shadow overflow-hidden">
<div className="p-4 text-center">
<h3 className="text-lg font-bold text-gray-800">1Driver</h3>
<p className="text-xs text-gray-400 mt-1">Version 1.0.0</p>
<p className="text-xs text-gray-400 mt-1">
Collections Management App
</p>
</div>
</div>
</section>
{/* Actions */}
<div className="space-y-3">
<button
onClick={resetSettings}
className={cn(
'w-full py-3 px-4 rounded-xl text-sm font-medium',
'bg-gray-200 text-gray-700 active:bg-gray-300',
'touch-manipulation flex items-center justify-center gap-2'
)}
>
<RotateCcw size={16} />
Reset to Defaults
</button>
<button
onClick={() => {
if (confirm('Are you sure you want to sign out?')) {
logout();
}
}}
className={cn(
'w-full py-3.5 px-4 rounded-xl text-white font-semibold text-base',
'bg-red-500 active:bg-red-600 transition-colors',
'touch-manipulation flex items-center justify-center gap-2'
)}
>
<LogOut size={18} />
Sign Out
</button>
</div>
</main>
{/* Saved Toast */}
{saved && (
<div className="fixed top-4 left-4 right-4 z-[60] animate-fade-in">
<div className="px-4 py-3 rounded-xl shadow-lg text-sm font-medium text-white text-center bg-green-500 flex items-center justify-center gap-2">
<Save size={16} />
Settings saved
</div>
</div>
)}
<BottomNav />
</div>
);
}