diff --git a/STRUCTURE.md b/STRUCTURE.md index 7762635..b241942 100644 --- a/STRUCTURE.md +++ b/STRUCTURE.md @@ -173,9 +173,10 @@ Responsibilities: Technology Focus: -* React and Next.js App Router are primary. -* Tailwind CSS must be used predictably to maintain consistency. -* shadcn/ui must be the foundational component library. +* **React with Vite** is the frontend framework (NOT Next.js — never suggest Next.js patterns). +* **Tailwind CSS** must be used predictably to maintain consistency. +* **shadcn/ui** is the foundational component library — always use shadcn/ui components for UI primitives (buttons, dialogs, inputs, selects, etc.). Do not build custom components when shadcn/ui provides one. +* **Sonner** is used for toast notifications. Authority: @@ -250,13 +251,18 @@ Authority: ## Technology Stack -Base stack: +Bill Tracker actual stack: -* Next.js App Router -* React -* Tailwind CSS -* shadcn/ui -* SQLite +* **Vite** (build tool, NOT Next.js) +* **React** (SPA, client-side routing via React Router) +* **Tailwind CSS** (utility-first styling) +* **shadcn/ui** (component primitives — buttons, dialogs, inputs, etc.) +* **Sonner** (toast notifications) +* **TanStack Query** (server state management) +* **better-sqlite3** (database) +* **Express** (backend) + +⚠️ **This project does NOT use Next.js.** Do not suggest Next.js patterns (App Router, server components, etc.). Development target: diff --git a/client/lib/version.js b/client/lib/version.js index eaee050..6c49a50 100644 --- a/client/lib/version.js +++ b/client/lib/version.js @@ -1,11 +1,10 @@ -export const APP_VERSION = '0.23.2'; +export const APP_VERSION = '0.23.3'; export const APP_NAME = 'BillTracker'; export const RELEASE_NOTES = { - version: '0.23.2', + version: '0.23.3', date: '2026-05-10', highlights: [ - { icon: '🔒', title: 'Critical: Notification Privacy Leak Fix', desc: 'In per-user notification mode, bills were sent to all opted-in recipients regardless of ownership. Now each recipient only receives their own bills.' }, - { icon: '🛡️', title: 'Orphaned Bill Guard', desc: 'Defensive check added: bills with no user_id are now skipped with a warning instead of being broadcast to all recipients.' }, + { icon: '✅', title: 'AlertDialog Integration', desc: 'Replaced native confirm() calls with shadcn/ui AlertDialog for consistent UI across tracker and data pages.' }, ], }; \ No newline at end of file diff --git a/client/pages/DataPage.jsx b/client/pages/DataPage.jsx index 15c74fa..88b45c3 100644 --- a/client/pages/DataPage.jsx +++ b/client/pages/DataPage.jsx @@ -12,6 +12,7 @@ import { Input } from '@/components/ui/input'; import { Switch } from '@/components/ui/switch'; import { AlertDialog, + AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, @@ -329,6 +330,7 @@ export function ImportMyDataSection({ onHistoryRefresh }) { const [file, setFile] = useState(null); const [preview, setPreview] = useState({ status: 'idle', data: null, error: null }); const [applyState, setApplyState] = useState({ status: 'idle', result: null, error: null }); + const [confirmOpen, setConfirmOpen] = useState(false); const reset = () => { setFile(null); @@ -354,10 +356,13 @@ export function ImportMyDataSection({ onHistoryRefresh }) { } }; - const handleApply = async () => { + const handleApply = () => { if (!preview.data?.import_session_id) return; - const ok = window.confirm('Import this SQLite data export into your account? Existing records will be skipped by default.'); - if (!ok) return; + setConfirmOpen(true); + }; + + const handleConfirmImport = async () => { + setConfirmOpen(false); setApplyState({ status: 'loading', result: null, error: null }); try { const result = await api.applyUserDbImport({ @@ -377,8 +382,9 @@ export function ImportMyDataSection({ onHistoryRefresh }) { const summary = preview.data?.summary || {}; return ( - + <> +
@@ -504,6 +510,24 @@ export function ImportMyDataSection({ onHistoryRefresh }) { )}
+ {/* Import confirmation dialog */} + + + + Import SQLite data export? + + Import this SQLite data export into your account? Existing records will be skipped by default. + + + + Cancel + + Confirm Import + + + + + ); } diff --git a/client/pages/TrackerPage.jsx b/client/pages/TrackerPage.jsx index 1338330..9ed8d1d 100644 --- a/client/pages/TrackerPage.jsx +++ b/client/pages/TrackerPage.jsx @@ -14,6 +14,16 @@ import { import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '@/components/ui/dialog'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@/components/ui/alert-dialog'; import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, } from '@/components/ui/select'; @@ -785,6 +795,7 @@ function Row({ row, year, month, refresh, index, onEditBill }) { const [editPayment, setEditPayment] = useState(null); const [showMbs, setShowMbs] = useState(false); const [loading, setLoading] = useState(false); + const [confirmOpen, setConfirmOpen] = useState(false); // Effective amount threshold for this bill this month: // actual_amount (if set by monthly override) takes priority over the template expected_amount. @@ -819,6 +830,23 @@ function Row({ row, year, month, refresh, index, onEditBill }) { } } + async function handleTogglePaid() { + setLoading?.(true); + try { + const result = await api.togglePaid(row.bill_id, { + amount: isPaid ? undefined : threshold, + paid_date: new Date().toISOString().slice(0, 10), + }); + toast.success(isPaid ? 'Payment removed' : 'Payment recorded'); + refresh?.(); + } catch (err) { + toast.error(err.message || 'Failed to toggle payment status'); + } finally { + setLoading?.(false); + setConfirmOpen(false); + } + } + return ( <> { + onClick={() => { if (effectiveStatus === 'skipped') return; const isPaid = effectiveStatus === 'paid' || effectiveStatus === 'autodraft'; - // Confirm before toggling Unpaid -> Paid + // Show confirmation dialog for toggling Unpaid -> Paid if (!isPaid) { - if (!confirm(`Mark "${row.name}" as paid?`)) return; + setConfirmOpen(true); + return; } - setLoading?.(true); - try { - const result = await api.togglePaid(row.bill_id, { - amount: isPaid ? undefined : threshold, - paid_date: new Date().toISOString().slice(0, 10), - }); - - toast.success(isPaid ? 'Payment removed' : 'Payment recorded'); - refresh?.(); - } catch (err) { - toast.error(err.message || 'Failed to toggle payment status'); - } finally { - setLoading?.(false); - } + // For mark unpaid, proceed directly + handleTogglePaid(); }} loading={loading} /> @@ -1013,6 +1030,24 @@ function Row({ row, year, month, refresh, index, onEditBill }) { onSaved={refresh} /> )} + + {/* Payment toggle confirmation dialog */} + + + + Mark "{row.name}" as paid? + + This will record a payment for this bill. You can edit the payment later. + + + + Cancel + + Confirm Payment + + + + ); } diff --git a/package.json b/package.json index 314181e..cd31ed9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bill-tracker", - "version": "0.23.2", + "version": "0.23.3", "description": "Monthly bill tracking system", "main": "server.js", "scripts": {