import React, { useState } from 'react'; import { toast } from 'sonner'; import { Database, Download, FileSpreadsheet, FileJson, AlertTriangle, CheckCircle2, XCircle, Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { cn } from '@/lib/utils'; import { SectionCard } from './dataShared'; // Shared blob download: works whether or not the response sets Content-Disposition // (the JSON payments export doesn't), falling back to the given name. async function downloadFile(endpoint, fallbackName) { const res = await fetch(endpoint, { credentials: 'include' }); if (!res.ok) { let data = {}; try { data = await res.json(); } catch {} throw new Error(data.message || data.error || `HTTP ${res.status}`); } const disposition = res.headers.get('Content-Disposition'); const match = disposition?.match(/filename="?([^"]+)"?/i); const name = match ? match[1] : fallbackName; 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); } function ExportCard({ icon: Icon, title, description, filename, endpoint }) { const [loading, setLoading] = useState(false); const handleDownload = async () => { setLoading(true); try { await downloadFile(endpoint, filename); toast.success(`${title} downloaded.`); } catch (err) { toast.error(err.message || 'Download failed.'); } finally { setLoading(false); } }; return (

{title}

{description}

); } // Filtered payments export: current year by default, or a custom date range, as CSV or JSON. function PaymentsExport() { const [from, setFrom] = useState(''); const [to, setTo] = useState(''); const [format, setFormat] = useState('csv'); const [loading, setLoading] = useState(false); const handle = async () => { if ((from && !to) || (!from && to)) { toast.error('Enter both From and To dates, or leave both empty.'); return; } if (from && to && from > to) { toast.error('“From” must be on or before “To”.'); return; } const params = new URLSearchParams({ format }); if (from && to) { params.set('from', from); params.set('to', to); } setLoading(true); try { await downloadFile(`/api/export?${params.toString()}`, `bills.${format === 'json' ? 'json' : 'csv'}`); toast.success('Payments exported.'); } catch (err) { toast.error(err.message || 'Export failed.'); } finally { setLoading(false); } }; return (

Payments export

Just your payment records — filter by date and choose a format. Leave dates empty for the current year.

setFrom(e.target.value)} className="mt-1 h-9 w-[9.5rem]" />
setTo(e.target.value)} className="mt-1 h-9 w-[9.5rem]" />
Format
{['csv', 'json'].map(f => ( ))}
); } export default function DownloadMyDataSection({ cardProps = {} }) { return (

Exports may contain sensitive account metadata (website URLs, usernames, account info). Store exported files securely and avoid sharing them unencrypted.

What's included

    {['Bills','Payments','Categories','Monthly bill state','Monthly starting amounts','History ranges','Notes','Export metadata'].map(i => (
  • {i}
  • ))}

What's not included

    {['Passwords','Sessions','Admin settings','Server configuration',"Other users' data",'Full system backup files'].map(i => (
  • {i}
  • ))}
); }