Initial commit: Voiced invoice desktop app

Offline-first invoicing app for Arun P, built by Bongbetic.

- Tauri v2 (Rust) + React/TypeScript + IBM Carbon Design System UI
- SQLite (rusqlite) storage with a persistent invoice-number series
- Guided first-run setup, pre-filled vendor/bank details
- Fixed-amount or rate x quantity line items (per sec/min/hour/session/unit)
- GST as CGST+SGST or IGST, Indian amount-in-words
- Optional signature, with a computer-generated note as fallback
- Print-ready PDF export via @react-pdf/renderer
- Windows cross-compile via cargo-xwin (NSIS installer)

Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
This commit is contained in:
2026-10-03 23:07:51 +05:30
co-authored by CommandCodeBot
commit 3af9f3f66f
125 changed files with 13660 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
import { useCallback, useEffect, useState } from "react";
import { InlineNotification, Loading, Theme } from "@carbon/react";
import { api } from "./lib/api";
import type { Settings } from "./lib/types";
import { AppShell, type AppView } from "./components/AppShell";
import Onboarding from "./views/Onboarding";
import NewInvoice from "./views/NewInvoice";
import InvoiceHistory from "./views/InvoiceHistory";
import SeriesSettings from "./views/SeriesSettings";
import AppSettings from "./views/AppSettings";
export default function App() {
const [settings, setSettings] = useState<Settings | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [view, setView] = useState<AppView>("new");
const refreshSettings = useCallback(async () => {
try {
const next = await api.getSettings();
setSettings(next);
setError(null);
} catch (e) {
setError(String(e));
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
void refreshSettings();
}, [refreshSettings]);
if (loading) {
return (
<Theme theme="g10">
<div className="voiced-empty">
<Loading description="Loading Voiced" withOverlay={false} small />
</div>
</Theme>
);
}
if (error) {
return (
<Theme theme="g10">
<div className="voiced-page">
<InlineNotification
kind="error"
lowContrast
title="Could not start Voiced"
subtitle={error}
hideCloseButton
/>
</div>
</Theme>
);
}
if (!settings) return null;
const theme = settings.theme === "g100" ? "g100" : "g10";
if (!settings.onboarded) {
return (
<Theme theme={theme}>
<Onboarding
initial={settings}
onDone={(next) => {
setSettings(next);
setView("new");
}}
/>
</Theme>
);
}
const toggleTheme = async () => {
const next: Settings = { ...settings, theme: settings.theme === "g100" ? "g10" : "g100" };
setSettings(next);
try {
await api.saveSettings(next);
} catch (e) {
setError(String(e));
}
};
return (
<Theme theme={theme}>
<AppShell view={view} onNavigate={setView} theme={settings.theme} onToggleTheme={toggleTheme}>
{view === "new" && <NewInvoice settings={settings} onSettingsChange={refreshSettings} />}
{view === "invoices" && <InvoiceHistory settings={settings} />}
{view === "series" && <SeriesSettings />}
{view === "settings" && <AppSettings settings={settings} onSaved={refreshSettings} />}
</AppShell>
</Theme>
);
}