refactor: component splits, PWA support, CommandPalette
Component Splits:
- AdminPage.jsx: 1,906 -> 82 lines (logic moved to client/components/admin/ — 9 files)
- DataPage.jsx: 3,132 -> 60 lines (logic moved to client/components/data/ — 8 files)
- TrackerPage.jsx: 2,566 -> 2,132 lines (MonthlyStateDialog, StartingAmountsEditDialog, PaymentModal)
PWA:
- vite-plugin-pwa installed with NetworkFirst caching for API routes
- Square PWA icons (192x192, 512x512, apple-touch-icon)
- theme-color, apple meta tags, touch icon in index.html
- Build generates dist/sw.js + Workbox runtime
CommandPalette:
- Navigation commands, Add bill action, month jumps
- Grouped results with empty/filtered states
2026-05-28 20:53:22 -05:00
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import { toast } from 'sonner';
|
|
|
|
|
import { api } from '@/api';
|
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
|
import { Badge } from '@/components/ui/badge';
|
|
|
|
|
import { Label } from '@/components/ui/label';
|
|
|
|
|
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
|
|
|
|
|
import {
|
|
|
|
|
Select, SelectTrigger, SelectValue, SelectContent, SelectItem,
|
|
|
|
|
} from '@/components/ui/select';
|
|
|
|
|
import {
|
|
|
|
|
AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle,
|
|
|
|
|
AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction,
|
|
|
|
|
} from '@/components/ui/alert-dialog';
|
|
|
|
|
|
|
|
|
|
export default function LoginModeCard({ users }) {
|
|
|
|
|
const [modeData, setModeData] = useState(null);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
feat: configurable sync interval, auto-match, encryption note, admin link, SimpleFIN hyperlink
#1 Sync interval in admin UI:
- bankSyncConfigService: reads simplefin_sync_interval_hours from settings
(DB-first, env fallback, default 4h), setSyncIntervalHours() with validation
- bankSyncWorker: live-updates interval from getBankSyncConfig() each tick
- routes/admin: PUT accepts enabled and sync_interval_hours independently
- BankSyncAdminCard: number input (0.5 step, 0.5-168 range), dirty-checks both
#3 Auto-match after background sync:
- matchSuggestionService: autoMatchForUser() auto-applies suggestions ≥80
score (exact amount + date ±1d + name signal), lazy-requires matchTransactionToBill
- bankSyncWorker: calls autoMatchForUser after each successful sync, own try/catch
#4 Encryption note in BankSyncAdminCard below worker status panel
Also: error handling, admin link in tracker sidebar, SimpleFIN bridge hyperlink
2026-05-29 00:28:50 -05:00
|
|
|
const [loadError, setLoadError] = useState('');
|
refactor: component splits, PWA support, CommandPalette
Component Splits:
- AdminPage.jsx: 1,906 -> 82 lines (logic moved to client/components/admin/ — 9 files)
- DataPage.jsx: 3,132 -> 60 lines (logic moved to client/components/data/ — 8 files)
- TrackerPage.jsx: 2,566 -> 2,132 lines (MonthlyStateDialog, StartingAmountsEditDialog, PaymentModal)
PWA:
- vite-plugin-pwa installed with NetworkFirst caching for API routes
- Square PWA icons (192x192, 512x512, apple-touch-icon)
- theme-color, apple meta tags, touch icon in index.html
- Build generates dist/sw.js + Workbox runtime
CommandPalette:
- Navigation commands, Add bill action, month jumps
- Grouped results with empty/filtered states
2026-05-28 20:53:22 -05:00
|
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
|
const [selectedUser, setSelectedUser] = useState('');
|
|
|
|
|
|
|
|
|
|
const [confirmSingle, setConfirmSingle] = useState(false);
|
|
|
|
|
const [pendingUserId, setPendingUserId] = useState(null);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
api.authModeConfig()
|
|
|
|
|
.then(d => { setModeData(d); setSelectedUser(d.default_user_id?.toString() || ''); })
|
feat: configurable sync interval, auto-match, encryption note, admin link, SimpleFIN hyperlink
#1 Sync interval in admin UI:
- bankSyncConfigService: reads simplefin_sync_interval_hours from settings
(DB-first, env fallback, default 4h), setSyncIntervalHours() with validation
- bankSyncWorker: live-updates interval from getBankSyncConfig() each tick
- routes/admin: PUT accepts enabled and sync_interval_hours independently
- BankSyncAdminCard: number input (0.5 step, 0.5-168 range), dirty-checks both
#3 Auto-match after background sync:
- matchSuggestionService: autoMatchForUser() auto-applies suggestions ≥80
score (exact amount + date ±1d + name signal), lazy-requires matchTransactionToBill
- bankSyncWorker: calls autoMatchForUser after each successful sync, own try/catch
#4 Encryption note in BankSyncAdminCard below worker status panel
Also: error handling, admin link in tracker sidebar, SimpleFIN bridge hyperlink
2026-05-29 00:28:50 -05:00
|
|
|
.catch(err => setLoadError(err.message || 'Failed to load login mode config'))
|
refactor: component splits, PWA support, CommandPalette
Component Splits:
- AdminPage.jsx: 1,906 -> 82 lines (logic moved to client/components/admin/ — 9 files)
- DataPage.jsx: 3,132 -> 60 lines (logic moved to client/components/data/ — 8 files)
- TrackerPage.jsx: 2,566 -> 2,132 lines (MonthlyStateDialog, StartingAmountsEditDialog, PaymentModal)
PWA:
- vite-plugin-pwa installed with NetworkFirst caching for API routes
- Square PWA icons (192x192, 512x512, apple-touch-icon)
- theme-color, apple meta tags, touch icon in index.html
- Build generates dist/sw.js + Workbox runtime
CommandPalette:
- Navigation commands, Add bill action, month jumps
- Grouped results with empty/filtered states
2026-05-28 20:53:22 -05:00
|
|
|
.finally(() => setLoading(false));
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const doSetMode = async (mode, userId) => {
|
|
|
|
|
setSaving(true);
|
|
|
|
|
try {
|
|
|
|
|
await api.setAuthMode({
|
|
|
|
|
auth_mode: mode,
|
|
|
|
|
default_user_id: mode === 'single' ? parseInt(userId, 10) : null,
|
|
|
|
|
});
|
|
|
|
|
const d = await api.authModeConfig();
|
|
|
|
|
setModeData(d);
|
|
|
|
|
toast.success(mode === 'single' ? 'Single-user mode enabled.' : 'Login requirement restored.');
|
|
|
|
|
} catch (err) {
|
|
|
|
|
toast.error(err.message || 'Failed to update auth mode.');
|
|
|
|
|
} finally {
|
|
|
|
|
setSaving(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleRequestSingle = () => {
|
|
|
|
|
if (!selectedUser) { toast.error('Select a user first.'); return; }
|
|
|
|
|
setPendingUserId(selectedUser);
|
|
|
|
|
setConfirmSingle(true);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleConfirmSingle = () => {
|
|
|
|
|
setConfirmSingle(false);
|
|
|
|
|
doSetMode('single', pendingUserId);
|
|
|
|
|
};
|
|
|
|
|
|
feat: configurable sync interval, auto-match, encryption note, admin link, SimpleFIN hyperlink
#1 Sync interval in admin UI:
- bankSyncConfigService: reads simplefin_sync_interval_hours from settings
(DB-first, env fallback, default 4h), setSyncIntervalHours() with validation
- bankSyncWorker: live-updates interval from getBankSyncConfig() each tick
- routes/admin: PUT accepts enabled and sync_interval_hours independently
- BankSyncAdminCard: number input (0.5 step, 0.5-168 range), dirty-checks both
#3 Auto-match after background sync:
- matchSuggestionService: autoMatchForUser() auto-applies suggestions ≥80
score (exact amount + date ±1d + name signal), lazy-requires matchTransactionToBill
- bankSyncWorker: calls autoMatchForUser after each successful sync, own try/catch
#4 Encryption note in BankSyncAdminCard below worker status panel
Also: error handling, admin link in tracker sidebar, SimpleFIN bridge hyperlink
2026-05-29 00:28:50 -05:00
|
|
|
if (loading) return <Card><CardContent className="py-8 text-center text-muted-foreground text-sm">Loading…</CardContent></Card>;
|
|
|
|
|
if (loadError) return <Card><CardContent className="py-8 text-center text-sm text-destructive">{loadError}</CardContent></Card>;
|
refactor: component splits, PWA support, CommandPalette
Component Splits:
- AdminPage.jsx: 1,906 -> 82 lines (logic moved to client/components/admin/ — 9 files)
- DataPage.jsx: 3,132 -> 60 lines (logic moved to client/components/data/ — 8 files)
- TrackerPage.jsx: 2,566 -> 2,132 lines (MonthlyStateDialog, StartingAmountsEditDialog, PaymentModal)
PWA:
- vite-plugin-pwa installed with NetworkFirst caching for API routes
- Square PWA icons (192x192, 512x512, apple-touch-icon)
- theme-color, apple meta tags, touch icon in index.html
- Build generates dist/sw.js + Workbox runtime
CommandPalette:
- Navigation commands, Add bill action, month jumps
- Grouped results with empty/filtered states
2026-05-28 20:53:22 -05:00
|
|
|
|
|
|
|
|
const isMulti = !modeData || modeData.auth_mode === 'multi';
|
|
|
|
|
const activeUser = users?.find(u => u.id === modeData?.default_user_id);
|
|
|
|
|
const selectedUsername = users?.find(u => u.id.toString() === selectedUser)?.username ?? selectedUser;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader className="pb-4">
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<CardTitle>Login Mode</CardTitle>
|
|
|
|
|
<Badge className={isMulti ? 'bg-sky-500/15 text-sky-400 border-sky-500/20' : 'bg-amber-500/15 text-amber-400 border-amber-500/20'}>
|
|
|
|
|
{isMulti ? 'Multi-user' : 'Single-user'}
|
|
|
|
|
</Badge>
|
|
|
|
|
</div>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-4">
|
|
|
|
|
{isMulti ? (
|
|
|
|
|
<>
|
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
|
|
|
Single-user mode bypasses the login screen and automatically signs in as the selected user.
|
|
|
|
|
</p>
|
|
|
|
|
<div className="space-y-1.5">
|
|
|
|
|
<Label>Default user</Label>
|
|
|
|
|
<Select value={selectedUser} onValueChange={setSelectedUser}>
|
|
|
|
|
<SelectTrigger>
|
|
|
|
|
<SelectValue placeholder="Select a user…" />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
{(users || []).filter(u => u.role === 'user').map(u => (
|
|
|
|
|
<SelectItem key={u.id} value={u.id.toString()}>{u.username}</SelectItem>
|
|
|
|
|
))}
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</div>
|
|
|
|
|
<Button onClick={handleRequestSingle} disabled={saving} className="w-full">
|
|
|
|
|
{saving ? 'Enabling…' : 'Enable Single-User Mode'}
|
|
|
|
|
</Button>
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
|
|
|
Currently auto-signing in as{' '}
|
|
|
|
|
<span className="font-medium text-foreground">{activeUser?.username ?? '—'}</span>.
|
|
|
|
|
Restoring login requirement will require all users to sign in manually.
|
|
|
|
|
</p>
|
|
|
|
|
<Button variant="outline" onClick={() => doSetMode('multi', null)} disabled={saving} className="w-full">
|
|
|
|
|
{saving ? 'Restoring…' : 'Restore Login Requirement'}
|
|
|
|
|
</Button>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
<AlertDialog open={confirmSingle} onOpenChange={setConfirmSingle}>
|
|
|
|
|
<AlertDialogContent>
|
|
|
|
|
<AlertDialogHeader>
|
|
|
|
|
<AlertDialogTitle>Enable Single-User Mode?</AlertDialogTitle>
|
|
|
|
|
<AlertDialogDescription>
|
|
|
|
|
Anyone who opens the app will be automatically signed in as{' '}
|
|
|
|
|
<span className="font-medium text-foreground">{selectedUsername}</span>.
|
|
|
|
|
The admin login still requires a password.
|
|
|
|
|
</AlertDialogDescription>
|
|
|
|
|
</AlertDialogHeader>
|
|
|
|
|
<AlertDialogFooter>
|
|
|
|
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
|
|
|
<AlertDialogAction onClick={handleConfirmSingle}>
|
|
|
|
|
Enable Single-User Mode
|
|
|
|
|
</AlertDialogAction>
|
|
|
|
|
</AlertDialogFooter>
|
|
|
|
|
</AlertDialogContent>
|
|
|
|
|
</AlertDialog>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|