Rebalance settings page columns and inline lock-after input

Left column: Profile, Appearance, Calendar, Dashboard, Weather (prefs & display)
Right column: Security, Authentication, Integrations (security & services)

Also inlines the "Lock after [input] minutes" onto a single row.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-02-25 17:14:24 +08:00
parent 7d6ac4d257
commit ca1cd14ed1

View File

@ -220,7 +220,7 @@ export default function SettingsPage() {
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
<div className="grid gap-6 lg:grid-cols-2"> <div className="grid gap-6 lg:grid-cols-2">
{/* ── Left column: Profile, Appearance, Weather ── */} {/* ── Left column: Profile, Appearance, Calendar, Dashboard, Weather ── */}
<div className="space-y-6"> <div className="space-y-6">
{/* Profile */} {/* Profile */}
@ -300,140 +300,6 @@ export default function SettingsPage() {
</CardContent> </CardContent>
</Card> </Card>
{/* Weather */}
<Card>
<CardHeader>
<div className="flex items-center gap-3">
<div className="p-1.5 rounded-md bg-amber-500/10">
<Cloud className="h-4 w-4 text-amber-400" aria-hidden="true" />
</div>
<div>
<CardTitle>Weather</CardTitle>
<CardDescription>Configure the weather widget on your dashboard</CardDescription>
</div>
</div>
</CardHeader>
<CardContent>
<div className="space-y-2">
<Label>Location</Label>
{hasLocation ? (
<div className="flex items-center gap-2">
<span className="inline-flex items-center gap-2 rounded-md border border-accent/30 bg-accent/10 px-3 py-1.5 text-sm text-foreground">
<MapPin className="h-3.5 w-3.5 text-accent" />
{settings?.weather_city || `${settings?.weather_lat}, ${settings?.weather_lon}`}
</span>
<button
type="button"
onClick={handleLocationClear}
className="inline-flex items-center justify-center rounded-md h-7 w-7 text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
title="Clear location"
aria-label="Clear weather location"
>
<X className="h-4 w-4" />
</button>
</div>
) : (
<div ref={searchRef} className="relative">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
<Input
type="text"
placeholder="Search for a city..."
value={locationQuery}
onChange={(e) => handleLocationInputChange(e.target.value)}
onFocus={() => { if (locationResults.length > 0) setShowDropdown(true); }}
className="pl-9 pr-9"
/>
{isSearching && (
<Loader2 className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground animate-spin" />
)}
</div>
{showDropdown && (
<div className="absolute z-50 mt-1 w-full rounded-md border bg-popover shadow-lg overflow-hidden">
{locationResults.map((loc, i) => (
<button
key={`${loc.lat}-${loc.lon}-${i}`}
type="button"
onClick={() => handleLocationSelect(loc)}
className="flex items-center gap-2.5 w-full px-3 py-2.5 text-sm text-left hover:bg-accent/10 transition-colors"
>
<MapPin className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
<span>
<span className="text-foreground font-medium">{loc.name}</span>
{(loc.state || loc.country) && (
<span className="text-muted-foreground">
{loc.state ? `, ${loc.state}` : ''}{loc.country ? `, ${loc.country}` : ''}
</span>
)}
</span>
</button>
))}
</div>
)}
</div>
)}
<p className="text-sm text-muted-foreground">
Search and select your city for accurate weather data on the dashboard.
</p>
</div>
</CardContent>
</Card>
</div>
{/* ── Right column: Security, Authentication, Calendar, Dashboard, Integrations ── */}
<div className="space-y-6">
{/* Security (auto-lock) */}
<Card>
<CardHeader>
<div className="flex items-center gap-3">
<div className="p-1.5 rounded-md bg-emerald-500/10">
<Shield className="h-4 w-4 text-emerald-400" aria-hidden="true" />
</div>
<div>
<CardTitle>Security</CardTitle>
<CardDescription>Configure screen lock behavior</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>Auto-lock</Label>
<p className="text-sm text-muted-foreground">
Automatically lock the screen after idle time
</p>
</div>
<Switch
checked={autoLockEnabled}
onCheckedChange={handleAutoLockToggle}
/>
</div>
<div className="space-y-2">
<Label htmlFor="auto_lock_minutes">Lock after</Label>
<div className="flex gap-3 items-center">
<Input
id="auto_lock_minutes"
type="number"
min="1"
max="60"
value={autoLockMinutes}
onChange={(e) => setAutoLockMinutes(e.target.value === '' ? '' : parseInt(e.target.value) || '')}
onBlur={handleAutoLockMinutesSave}
onKeyDown={(e) => { if (e.key === 'Enter') handleAutoLockMinutesSave(); }}
className="w-24"
disabled={!autoLockEnabled || isUpdating}
/>
<span className="text-sm text-muted-foreground">minutes</span>
</div>
</div>
</CardContent>
</Card>
{/* Authentication (TOTP + password change) */}
<TotpSetupSection />
{/* Calendar */} {/* Calendar */}
<Card> <Card>
<CardHeader> <CardHeader>
@ -529,6 +395,138 @@ export default function SettingsPage() {
</CardContent> </CardContent>
</Card> </Card>
{/* Weather */}
<Card>
<CardHeader>
<div className="flex items-center gap-3">
<div className="p-1.5 rounded-md bg-amber-500/10">
<Cloud className="h-4 w-4 text-amber-400" aria-hidden="true" />
</div>
<div>
<CardTitle>Weather</CardTitle>
<CardDescription>Configure the weather widget on your dashboard</CardDescription>
</div>
</div>
</CardHeader>
<CardContent>
<div className="space-y-2">
<Label>Location</Label>
{hasLocation ? (
<div className="flex items-center gap-2">
<span className="inline-flex items-center gap-2 rounded-md border border-accent/30 bg-accent/10 px-3 py-1.5 text-sm text-foreground">
<MapPin className="h-3.5 w-3.5 text-accent" />
{settings?.weather_city || `${settings?.weather_lat}, ${settings?.weather_lon}`}
</span>
<button
type="button"
onClick={handleLocationClear}
className="inline-flex items-center justify-center rounded-md h-7 w-7 text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
title="Clear location"
aria-label="Clear weather location"
>
<X className="h-4 w-4" />
</button>
</div>
) : (
<div ref={searchRef} className="relative">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
<Input
type="text"
placeholder="Search for a city..."
value={locationQuery}
onChange={(e) => handleLocationInputChange(e.target.value)}
onFocus={() => { if (locationResults.length > 0) setShowDropdown(true); }}
className="pl-9 pr-9"
/>
{isSearching && (
<Loader2 className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground animate-spin" />
)}
</div>
{showDropdown && (
<div className="absolute z-50 mt-1 w-full rounded-md border bg-popover shadow-lg overflow-hidden">
{locationResults.map((loc, i) => (
<button
key={`${loc.lat}-${loc.lon}-${i}`}
type="button"
onClick={() => handleLocationSelect(loc)}
className="flex items-center gap-2.5 w-full px-3 py-2.5 text-sm text-left hover:bg-accent/10 transition-colors"
>
<MapPin className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
<span>
<span className="text-foreground font-medium">{loc.name}</span>
{(loc.state || loc.country) && (
<span className="text-muted-foreground">
{loc.state ? `, ${loc.state}` : ''}{loc.country ? `, ${loc.country}` : ''}
</span>
)}
</span>
</button>
))}
</div>
)}
</div>
)}
<p className="text-sm text-muted-foreground">
Search and select your city for accurate weather data on the dashboard.
</p>
</div>
</CardContent>
</Card>
</div>
{/* ── Right column: Security, Authentication, Integrations ── */}
<div className="space-y-6">
{/* Security (auto-lock) */}
<Card>
<CardHeader>
<div className="flex items-center gap-3">
<div className="p-1.5 rounded-md bg-emerald-500/10">
<Shield className="h-4 w-4 text-emerald-400" aria-hidden="true" />
</div>
<div>
<CardTitle>Security</CardTitle>
<CardDescription>Configure screen lock behavior</CardDescription>
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label>Auto-lock</Label>
<p className="text-sm text-muted-foreground">
Automatically lock the screen after idle time
</p>
</div>
<Switch
checked={autoLockEnabled}
onCheckedChange={handleAutoLockToggle}
/>
</div>
<div className="flex items-center gap-3">
<Label htmlFor="auto_lock_minutes" className="shrink-0">Lock after</Label>
<Input
id="auto_lock_minutes"
type="number"
min="1"
max="60"
value={autoLockMinutes}
onChange={(e) => setAutoLockMinutes(e.target.value === '' ? '' : parseInt(e.target.value) || '')}
onBlur={handleAutoLockMinutesSave}
onKeyDown={(e) => { if (e.key === 'Enter') handleAutoLockMinutesSave(); }}
className="w-20"
disabled={!autoLockEnabled || isUpdating}
/>
<span className="text-sm text-muted-foreground shrink-0">minutes</span>
</div>
</CardContent>
</Card>
{/* Authentication (TOTP + password change) */}
<TotpSetupSection />
{/* Integrations (ntfy push notifications) */} {/* Integrations (ntfy push notifications) */}
<NtfySettingsSection settings={settings} updateSettings={updateSettings} /> <NtfySettingsSection settings={settings} updateSettings={updateSettings} />