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 } from 'react';
|
|
|
|
|
import { toast } from 'sonner';
|
|
|
|
|
import { Database, Download, FileSpreadsheet, AlertTriangle, CheckCircle2, XCircle, Loader2 } from 'lucide-react';
|
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
|
import { SectionCard } from './dataShared';
|
|
|
|
|
|
|
|
|
|
const USER_EXPORTS_AVAILABLE = true;
|
|
|
|
|
|
|
|
|
|
function ExportCard({ icon: Icon, title, description, filename, endpoint }) {
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
|
|
|
|
const handleDownload = async () => {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(endpoint, { credentials: 'include' });
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
let data = {};
|
|
|
|
|
try { data = await res.json(); } catch {}
|
|
|
|
|
throw new Error(data.error || `HTTP ${res.status}`);
|
|
|
|
|
}
|
|
|
|
|
const disposition = res.headers.get('Content-Disposition');
|
|
|
|
|
const match = disposition?.match(/filename="?([^"]+)"?/i);
|
|
|
|
|
const name = match ? match[1] : filename;
|
|
|
|
|
const blob = await res.blob();
|
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
|
const a = document.createElement('a');
|
|
|
|
|
a.href = url; a.download = name; a.click();
|
|
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
|
toast.success(`${title} downloaded.`);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
toast.error(err.message || 'Download failed.');
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const disabled = !USER_EXPORTS_AVAILABLE || loading;
|
|
|
|
|
return (
|
|
|
|
|
<div className="px-6 py-5 flex items-start justify-between gap-6">
|
|
|
|
|
<div className="flex items-start gap-4 flex-1 min-w-0">
|
|
|
|
|
<div className="mt-0.5 h-9 w-9 rounded-lg bg-primary/10 flex items-center justify-center shrink-0">
|
|
|
|
|
<Icon className="h-5 w-5 text-primary" />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex-1 min-w-0">
|
|
|
|
|
<div className="flex items-center gap-2 mb-0.5 flex-wrap">
|
|
|
|
|
<p className="text-sm font-medium">{title}</p>
|
|
|
|
|
{!USER_EXPORTS_AVAILABLE && (
|
|
|
|
|
<span className="inline-flex items-center rounded-full bg-muted border border-border px-2 py-0.5 text-[10px] font-medium text-muted-foreground">
|
|
|
|
|
Coming soon
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
<p className="text-xs text-muted-foreground">{description}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="shrink-0 pt-0.5">
|
|
|
|
|
<Button size="sm" variant="outline" disabled={disabled}
|
|
|
|
|
onClick={USER_EXPORTS_AVAILABLE ? handleDownload : undefined}>
|
|
|
|
|
{loading ? <><Loader2 className="h-3.5 w-3.5 mr-1.5 animate-spin" />Downloading…</>
|
|
|
|
|
: <><Download className="h-3.5 w-3.5 mr-1.5" />{USER_EXPORTS_AVAILABLE ? 'Download' : 'Not Available Yet'}</>}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-30 21:52:02 -05:00
|
|
|
export default function DownloadMyDataSection({ cardProps = {} }) {
|
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
|
|
|
return (
|
|
|
|
|
<SectionCard
|
|
|
|
|
title="Download My Data"
|
|
|
|
|
subtitle="Export your bill tracker data for your own records. These exports include only your data — not a full system backup."
|
2026-05-30 21:52:02 -05:00
|
|
|
{...cardProps}
|
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
|
|
|
>
|
|
|
|
|
<ExportCard icon={Database} title="SQLite Data Export"
|
|
|
|
|
description="Download a portable SQLite database containing your bill tracker data."
|
|
|
|
|
filename="bill-tracker-user-export.sqlite" endpoint="/api/export/user-db" />
|
|
|
|
|
<ExportCard icon={FileSpreadsheet} title="Excel Databook"
|
|
|
|
|
description="Download an Excel workbook with sheets for bills, payments, categories, monthly state, and summary data."
|
|
|
|
|
filename="bill-tracker-databook.xlsx" endpoint="/api/export/user-excel" />
|
|
|
|
|
<div className="px-6 py-3 rounded-md bg-amber-50 dark:bg-amber-950/30 border border-amber-200 dark:border-amber-800/40 flex items-start gap-2.5 mx-6 mt-2">
|
|
|
|
|
<AlertTriangle className="h-4 w-4 text-amber-600 dark:text-amber-400 shrink-0 mt-0.5" />
|
|
|
|
|
<p className="text-xs text-amber-700 dark:text-amber-300">Exports may contain sensitive account metadata (website URLs, usernames, account info). Store exported files securely and avoid sharing them unencrypted.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="px-6 py-5 grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
|
|
|
<div className="rounded-lg bg-muted/40 border border-border/60 p-4">
|
|
|
|
|
<p className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-2.5">What's included</p>
|
|
|
|
|
<ul className="space-y-1.5">
|
|
|
|
|
{['Bills','Payments','Categories','Monthly bill state','Monthly starting amounts','History ranges','Notes','Export metadata'].map(i => (
|
|
|
|
|
<li key={i} className="flex items-center gap-2 text-xs text-foreground/80">
|
|
|
|
|
<CheckCircle2 className="h-3.5 w-3.5 text-emerald-500 shrink-0" />{i}
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="rounded-lg bg-muted/40 border border-border/60 p-4">
|
|
|
|
|
<p className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground mb-2.5">What's not included</p>
|
|
|
|
|
<ul className="space-y-1.5">
|
|
|
|
|
{['Passwords','Sessions','Admin settings','Server configuration',"Other users' data",'Full system backup files'].map(i => (
|
|
|
|
|
<li key={i} className="flex items-center gap-2 text-xs text-muted-foreground">
|
style: global readability/theme pass
- Sharpened font stack in index.css, removed softer Georgia digit font for UI text/money
- Tuned dark-mode tokens: clearer foreground, brighter muted text, stronger borders, defined cards
- Updated UI primitives: cards, buttons, inputs, selects, tables, badges
- Cleaned up bills rows, mobile bill rows, tracker dismiss, snowball icons, summary/category/health/analytics money values, import/export status icons
- Reduced fuzzy uppercase label spacing globally
2026-05-28 23:18:14 -05:00
|
|
|
<XCircle className="h-3.5 w-3.5 text-muted-foreground shrink-0" />{i}
|
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
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</SectionCard>
|
|
|
|
|
);
|
|
|
|
|
}
|