invoice-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev 333971bf96d2
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone b3b80918bc912dda8c53a48d
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
src/main.tsx
// Invoice's entry. "/" keeps the static landing and mounts the editor and "Your invoices" into it;
// "/i/<id>" (the client's view) and "/i/<id>/admin" (the owner's) hide the landing. The invoice page
// and the Bounded client load only on those routes, so the landing stays light.
import { lazy, Suspense, useEffect, type ReactNode } from "react";
import { createRoot } from "react-dom/client";
import { usePath } from "./lib/router";
import { Toaster } from "./ui";
import { NewInvoice } from "./NewInvoice";
import { YourInvoices } from "./Mine";
import { PageSkeleton } from "./Skeleton";
import "./styles.css";

const InvoicePage = lazy(() => import("./InvoicePage").then((m) => ({ default: m.InvoicePage })));
const LANDING_TITLE = document.title;
const invoiceRoute = (path: string) => path.match(/^\/i\/([A-Za-z0-9]{8,32})(\/admin)?\/?$/);

/** Landing pieces render only on the landing, so an invoice page does not load the editor behind it. */
function OnLanding({ children }: { children: ReactNode }) {
  return invoiceRoute(usePath()) ? null : <>{children}</>;
}

function App() {
  const path = usePath();
  const inv = invoiceRoute(path);
  document.body.classList.toggle("app-mode", Boolean(inv));
  useEffect(() => { if (!inv) document.title = LANDING_TITLE; }, [Boolean(inv)]);
  return (
    <>
      {inv ? (
        <Suspense fallback={<PageSkeleton />}>
          <InvoicePage key={path} id={inv[1]} admin={Boolean(inv[2])} />
        </Suspense>
      ) : null}
      <Toaster />
    </>
  );
}

createRoot(document.getElementById("app")!).render(<App />);
const editorSlot = document.getElementById("editor");
if (editorSlot) createRoot(editorSlot).render(<OnLanding><NewInvoice /></OnLanding>);
const mineSlot = document.getElementById("mine");
if (mineSlot) createRoot(mineSlot).render(<OnLanding><YourInvoices /></OnLanding>);

Change history

Quality pass: phone-readable invoice, estimates, email client, saved clients, CSV export, openapi.json
333971bf96 · 5 hours ago
Bounded source sync
088b53bddb · 1 day ago
Bounded source sync
3f3e9ff8f9 · 1 day ago
Bounded source sync
209bec4267 · 1 day ago
Bounded source sync
11f3d98cda · 1 day ago
Bounded source sync
722948ebc3 · 1 day ago
Bounded source sync
3e9d04b2c0 · 1 day ago