fix: walkthrough pages respect theme + mobile UX improvements
Some checks are pending
Deploy to Production / deploy (push) Waiting to run
Test / backend-test (push) Waiting to run
Test / frontend-test (push) Waiting to run

- Use CSS variables (bg-primary, text-primary, etc.) instead of hardcoded colors
- Both pages now properly follow light/dark theme preference
- Larger touch targets (44px+) for tablet/phone use
- Simplified controls and cleaner visual hierarchy
- Toggle checkboxes replaced with large tap-friendly buttons
- Slider with step=5 for easier touch adjustment
This commit is contained in:
fullsizemalt 2025-12-19 21:13:26 -08:00
parent 77e7382504
commit 8dbcd51246
2 changed files with 364 additions and 574 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,9 @@
import { useState, useEffect } from 'react';
import { Link } from 'react-router-dom';
import { Save, CheckSquare, Settings, Camera, Loader2, ArrowLeft, BookOpen, FileText, Droplets, Sprout, Bug } from 'lucide-react';
import { Save, CheckSquare, Camera, Loader2, ArrowLeft, Droplets, Sprout, Bug } from 'lucide-react';
import { settingsApi, WalkthroughSettings, PhotoRequirement } from '../lib/settingsApi';
import { documentsApi, Document } from '../lib/documentsApi';
import { Card } from '../components/ui/card';
import { useToast } from '../context/ToastContext';
import { cn } from '../lib/utils';
import { motion } from 'framer-motion';
const PHOTO_OPTIONS: { label: string; value: PhotoRequirement }[] = [
{ label: 'Always Required', value: 'REQUIRED' },
@ -20,7 +17,6 @@ export default function WalkthroughSettingsPage() {
const [settings, setSettings] = useState<WalkthroughSettings | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [checklists, setChecklists] = useState<Document[]>([]);
useEffect(() => {
loadSettings();
@ -29,12 +25,8 @@ export default function WalkthroughSettingsPage() {
const loadSettings = async () => {
setIsLoading(true);
try {
const [data, docs] = await Promise.all([
settingsApi.getWalkthrough(),
documentsApi.getDocuments({ type: 'CHECKLIST', status: 'APPROVED' }).catch(() => [])
]);
const data = await settingsApi.getWalkthrough();
setSettings(data);
setChecklists(docs);
} catch (e) {
console.error(e);
addToast('Failed to load settings', 'error');
@ -61,73 +53,27 @@ export default function WalkthroughSettingsPage() {
if (isLoading || !settings) {
return (
<div className="min-h-screen bg-[#0B0E14] flex items-center justify-center">
<div className="min-h-screen bg-primary flex items-center justify-center">
<div className="text-center">
<Loader2 className="animate-spin text-emerald-500 mx-auto mb-4" size={32} />
<p className="text-slate-400 text-sm">Loading settings...</p>
<Loader2 className="animate-spin text-accent mx-auto mb-4" size={32} />
<p className="text-tertiary text-sm">Loading settings...</p>
</div>
</div>
);
}
// Toggle switch component
const Toggle = ({ checked, onChange, label, description, icon: Icon }: {
checked: boolean;
onChange: () => void;
label: string;
description?: string;
icon?: typeof Droplets;
}) => (
<label className="flex items-center justify-between p-4 rounded-xl hover:bg-slate-800/30 transition-colors cursor-pointer">
<div className="flex items-center gap-4">
{Icon && (
<div className={cn(
"w-10 h-10 rounded-xl flex items-center justify-center",
checked ? "bg-emerald-500/20 border border-emerald-500/30" : "bg-slate-800 border border-slate-700"
)}>
<Icon size={18} className={checked ? "text-emerald-500" : "text-slate-500"} />
</div>
)}
<div>
<span className="text-sm font-bold text-white">{label}</span>
{description && <p className="text-xs text-slate-500 mt-0.5">{description}</p>}
</div>
</div>
<button
type="button"
role="switch"
aria-checked={checked}
onClick={onChange}
className={cn(
"relative w-12 h-7 rounded-full transition-colors duration-200",
checked ? 'bg-emerald-600' : 'bg-slate-700'
)}
>
<motion.span
animate={{ x: checked ? 22 : 2 }}
transition={{ type: "spring", stiffness: 500, damping: 30 }}
className="absolute top-1 w-5 h-5 bg-white rounded-full shadow-lg"
/>
</button>
</label>
);
return (
<div className="min-h-screen bg-[#0B0E14] pb-20">
<div className="min-h-screen bg-primary pb-20">
{/* Header */}
<div className="sticky top-0 z-20 bg-[#0B0E14]/95 backdrop-blur-xl border-b border-slate-800/50">
<div className="max-w-2xl mx-auto px-4 py-4">
<div className="flex items-center gap-4">
<Link to="/settings" className="p-2 rounded-lg hover:bg-slate-800 transition-colors text-slate-400">
<div className="sticky top-0 z-20 bg-elevated border-b border-subtle">
<div className="max-w-2xl mx-auto px-4 py-3">
<div className="flex items-center gap-3">
<Link to="/settings" className="p-2 -ml-2 rounded-lg hover:bg-tertiary text-tertiary">
<ArrowLeft size={20} />
</Link>
<div>
<h1 className="text-lg font-bold text-white uppercase italic tracking-wide">
Walkthrough Config
</h1>
<p className="text-xs text-slate-500 mt-0.5">
Configure daily checklist requirements
</p>
<h1 className="text-base font-semibold text-primary">Walkthrough Settings</h1>
<p className="text-xs text-tertiary">Configure daily checklist</p>
</div>
</div>
</div>
@ -136,61 +82,51 @@ export default function WalkthroughSettingsPage() {
<div className="max-w-2xl mx-auto px-4 py-6">
<form onSubmit={handleUpdate} className="space-y-6">
{/* Enabled Modules */}
<Card className="bg-[#13171F] border-slate-800 overflow-hidden">
<div className="p-4 border-b border-slate-800 flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-emerald-500/20 flex items-center justify-center">
<CheckSquare size={16} className="text-emerald-500" />
<div className="card">
<div className="p-4 border-b border-subtle flex items-center gap-3">
<CheckSquare size={16} className="text-accent" />
<span className="text-sm font-medium text-primary">Enabled Modules</span>
</div>
<div>
<h3 className="text-sm font-bold text-white uppercase tracking-wide">Enabled Modules</h3>
<p className="text-xs text-slate-500">Choose which checks to include</p>
</div>
</div>
<div className="divide-y divide-slate-800/50">
<div className="divide-y divide-subtle">
<Toggle
checked={settings.enableReservoirs}
onChange={() => setSettings({ ...settings, enableReservoirs: !settings.enableReservoirs })}
label="Reservoir Checks"
description="Tank levels and nutrient monitoring"
description="Tank levels and nutrients"
icon={Droplets}
/>
<Toggle
checked={settings.enableIrrigation}
onChange={() => setSettings({ ...settings, enableIrrigation: !settings.enableIrrigation })}
label="Irrigation Checks"
description="Dripper counts and flow verification"
description="Dripper counts and flow"
icon={Sprout}
/>
<Toggle
checked={settings.enablePlantHealth}
onChange={() => setSettings({ ...settings, enablePlantHealth: !settings.enablePlantHealth })}
label="Plant Health Checks"
description="Visual inspections and pest monitoring"
description="Visual inspections"
icon={Bug}
/>
</div>
</Card>
</div>
{/* Photo Requirements */}
<Card className="bg-[#13171F] border-slate-800 overflow-hidden">
<div className="p-4 border-b border-slate-800 flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-blue-500/20 flex items-center justify-center">
<Camera size={16} className="text-blue-500" />
</div>
<div>
<h3 className="text-sm font-bold text-white uppercase tracking-wide">Photo Requirements</h3>
<p className="text-xs text-slate-500">When to require photo documentation</p>
</div>
<div className="card">
<div className="p-4 border-b border-subtle flex items-center gap-3">
<Camera size={16} className="text-accent" />
<span className="text-sm font-medium text-primary">Photo Requirements</span>
</div>
<div className="p-4 space-y-4">
<div>
<label className="block text-xs font-bold text-slate-400 uppercase tracking-widest mb-2">
<label className="block text-xs font-medium text-tertiary uppercase tracking-wider mb-2">
Reservoirs
</label>
<select
value={settings.reservoirPhotos}
onChange={e => setSettings({ ...settings, reservoirPhotos: e.target.value as PhotoRequirement })}
className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-sm text-white focus:border-emerald-500/50 focus:outline-none"
className="input w-full py-3"
>
{PHOTO_OPTIONS.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
@ -198,13 +134,13 @@ export default function WalkthroughSettingsPage() {
</select>
</div>
<div>
<label className="block text-xs font-bold text-slate-400 uppercase tracking-widest mb-2">
<label className="block text-xs font-medium text-tertiary uppercase tracking-wider mb-2">
Irrigation
</label>
<select
value={settings.irrigationPhotos}
onChange={e => setSettings({ ...settings, irrigationPhotos: e.target.value as PhotoRequirement })}
className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-sm text-white focus:border-emerald-500/50 focus:outline-none"
className="input w-full py-3"
>
{PHOTO_OPTIONS.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
@ -212,13 +148,13 @@ export default function WalkthroughSettingsPage() {
</select>
</div>
<div>
<label className="block text-xs font-bold text-slate-400 uppercase tracking-widest mb-2">
<label className="block text-xs font-medium text-tertiary uppercase tracking-wider mb-2">
Plant Health
</label>
<select
value={settings.plantHealthPhotos}
onChange={e => setSettings({ ...settings, plantHealthPhotos: e.target.value as PhotoRequirement })}
className="w-full px-4 py-3 bg-slate-800 border border-slate-700 rounded-xl text-sm text-white focus:border-emerald-500/50 focus:outline-none"
className="input w-full py-3"
>
{PHOTO_OPTIONS.map(opt => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
@ -226,55 +162,17 @@ export default function WalkthroughSettingsPage() {
</select>
</div>
</div>
</Card>
{/* Linked SOPs */}
{checklists.length > 0 && (
<Card className="bg-[#13171F] border-slate-800 overflow-hidden">
<div className="p-4 border-b border-slate-800 flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-purple-500/20 flex items-center justify-center">
<BookOpen size={16} className="text-purple-500" />
</div>
<div>
<h3 className="text-sm font-bold text-white uppercase tracking-wide">Linked SOPs</h3>
<p className="text-xs text-slate-500">Related checklist documents</p>
</div>
</div>
<div className="p-4 space-y-2">
{checklists.map(doc => (
<div key={doc.id} className="flex items-center gap-3 p-3 bg-slate-800/50 rounded-xl">
<FileText size={16} className="text-slate-400" />
<div className="flex-1">
<span className="text-sm text-white">{doc.title}</span>
<span className="text-xs text-slate-500 ml-2">v{doc.version}</span>
</div>
<Link
to={`/documents?id=${doc.id}`}
className="text-xs text-emerald-500 font-bold uppercase tracking-wider hover:text-emerald-400"
>
View
</Link>
</div>
))}
</div>
</Card>
)}
<button
type="submit"
disabled={isSaving}
className="w-full bg-emerald-600 hover:bg-emerald-700 text-white py-4 rounded-xl font-bold uppercase tracking-widest text-sm transition-all shadow-xl shadow-emerald-500/20 disabled:opacity-50 flex items-center justify-center gap-2"
className="btn btn-primary w-full py-4 text-base"
>
{isSaving ? (
<>
<Loader2 size={16} className="animate-spin" />
Saving...
</>
<><Loader2 size={18} className="animate-spin" /> Saving...</>
) : (
<>
<Save size={16} />
Save Configuration
</>
<><Save size={18} /> Save Settings</>
)}
</button>
</form>
@ -282,3 +180,48 @@ export default function WalkthroughSettingsPage() {
</div>
);
}
// Toggle component
function Toggle({ checked, onChange, label, description, icon: Icon }: {
checked: boolean;
onChange: () => void;
label: string;
description?: string;
icon?: typeof Droplets;
}) {
return (
<label className="flex items-center justify-between p-4 hover:bg-tertiary transition-colors cursor-pointer active:bg-secondary">
<div className="flex items-center gap-3">
{Icon && (
<div className={cn(
"w-10 h-10 rounded-lg flex items-center justify-center",
checked ? "bg-accent-muted text-accent" : "bg-tertiary text-tertiary"
)}>
<Icon size={18} />
</div>
)}
<div>
<span className="text-sm font-medium text-primary">{label}</span>
{description && <p className="text-xs text-tertiary">{description}</p>}
</div>
</div>
<button
type="button"
role="switch"
aria-checked={checked}
onClick={onChange}
className={cn(
"relative w-12 h-7 rounded-full transition-colors",
checked ? 'bg-accent' : 'bg-tertiary'
)}
>
<span
className={cn(
"absolute top-1 w-5 h-5 bg-white rounded-full shadow transition-transform",
checked ? 'translate-x-6' : 'translate-x-1'
)}
/>
</button>
</label>
);
}