Full design · Apps
Invoicing
Invoicing for Australian sole traders and small studios.
Live preview. Click around, it works.
About this template
Invoicing for Australian sole traders and small studios. A dashboard with what is outstanding, overdue and paid this month, a weekly cash flow chart and a list of what needs attention. Invoices with search, status filters, sorting, bulk actions and CSV export. An editor with quantity, rate, discount and GST on or off per line, live totals worked out in whole cents, payment terms, a tax invoice check and an A4 preview that prints exactly. A client payment page with a demo card checkout and bank details. Clients with balances, history and printable statements. Quotes that turn into invoices, recurring invoices and automatic overdue reminders on a visible schedule, and settings for your ABN, logo, numbering, terms and bank details. Owner and bookkeeper roles, and a GST self test.
What the agents are asked
The first message sent when you use this template. You can edit the plan before anything is built.
Adapt this invoicing app to my business. Keep every screen (Home, Invoices, the invoice editor and its A4 preview, the client payment page, Clients and statements, Quotes, Recurring and reminders, Settings) and keep the money rules exactly as they are: amounts in whole cents, GST worked out by the tax engine in script.js, and the self test at #/selftest passing. Change the brand name, colours, business details, ABN, bank details, invoice numbering, payment terms and the sample clients and line items to match my business, and keep all tax wording as general information rather than advice. Where I want real accounts, emailed invoices, card payments or a server schedule, wire in authentication, the tables in schema.sql, Stripe Checkout, an email service and a daily scheduled function as the README describes, never putting a secret key in the browser.
Invoicing for Australian sole traders and small studios: invoices and quotes with GST worked out in whole cents, a payment page for clients, recurring invoices, automatic overdue reminders, statements, and owner and bookkeeper roles. Everything runs in the browser in demo mode, and the data layer is shaped like schema.sql, so moving to a real database is a change in one place.
The brand (Sumwise), the studio (Offcut Studio, Noor Haddad) and all clients are fictional. Every sample row is marked Sample and "Remove sample data" deletes them in one step.
Tax wording is general information, not advice. The app explains how it works GST out; it does not tell anyone how to run their tax affairs.
Identity
- Colours: Ink
#121417, Paper#F4F4F1, Kingfisher#0A6E78. Status chips are tints. Light and dark themes; the invoice itself is always white paper. - Type: Schibsted Grotesk (display, numbers that matter), Instrument Sans (text), Red Hat Mono (invoice numbers, BSB, account).
- Motion: one staggered entrance on first load,
view()scroll reveals with an IntersectionObserver fallback, aclip-pathreveal on photographs, view transitions between screens, 160 ms hovers. The orchestrated moment: when an invoice is paid (the demo checkout or "Record payment" in full), the amount due counts down to $0.00 and one kingfisher rule draws under "Paid in full". All of it respects reduced motion, and there is an in app Motion setting.
Art direction
One shoot, three photographs, all lit through a half open timber venetian blind so the stripes echo ruled paper: early afternoon, cool neutral 5000K, 50 mm at f/2.8, fine grain, deep blue black shadows, natural skin, no text or logos.
Photo list
| Name | Ratio | Used on | Fallback colour |
|---|---|---|---|
studio-desk | 2:3 | Sign in, sticky left column | --fallback-desk |
desk-still | 2:1 | Empty states and the first run welcome | --fallback-still |
owner-noor | 1:1 | The owner's avatar: greeting, sidebar, menu | --sunk |
Prompts are in media.json. Images are referenced as /template-media/invoice-generator/<name>.webp.
Roles and demo mode
"Viewing as: Owner / Bookkeeper" sits in the top bar (in the demo strip on a phone). The bookkeeper sees everything and can create invoices, record payments, send reminders and edit clients, but Settings and reminder rules are read only, and they cannot delete clients or remove sample data. The same rules are in can in script.js and in the policies in schema.sql.
Sign in is a demo: any email and password work, or pick a role.
Screens
- Sign in (
#/signin): the studio desk photograph, email and password, or one click into either role. - Home (
#/home): outstanding with a not yet due and overdue split, overdue, paid this month against the same point last month, GST this BAS quarter on invoices issued and on payments received, a weekly cash flow chart (received, and due by due date; hover or focus a week, or switch to a table), "Needs attention" with a working action per row, and the next 14 days of the schedule. A three step getting started card for a first visit. - Invoices (
#/invoices): status tabs with counts (draft, sent, viewed, part paid, overdue, paid, void), search across number, client, lines and amount, client and date range filters (including this and last financial year), sortable columns, bulk actions (mark as sent, send reminder, record as paid, export CSV, delete drafts) with Undo. Keyboard:/search,JKmove,Xselect,Enteropen. On a phone rows become cards. - Invoice editor (
#/invoice/<id>): client, dates and terms (due on receipt, 7, 14, 30 days, end of next month, or a date), reference, GST mode (rates exclude GST, include GST, or no GST), lines with quantity, rate, discount %, a GST switch per line, drag orAltarrow reordering and suggestions from past lines, live totals, a note, a tax invoice check, payments with reversal, and activity. The A4 preview on the right is the print output. Drafts autosave; a new invoice nobody typed into is removed when you leave and its number is given back. Paid, voided and part paid invoices lock their amounts. - Client view (
#/pay/<id>): what the client opens from the email. The invoice, the amount due, a demo card checkout labelled "Demo checkout. Connect Stripe in project settings." (4242 4242 4242 4242 approves, 4000 0000 0000 0002 declines), bank details with copy buttons, and print or save as PDF. Opening it marks a sent invoice as viewed. - Clients (
#/clients,#/client/<id>): balance, overdue, billed this financial year, usual days to pay, notes and history. Archive, or delete if nothing was billed. - Statement (
#/statement/<id>): an A4 statement of account for any period with opening balance, running balance and aging buckets. Prints on its own. - Quotes (
#/quotes,#/quote/<id>): the same editor; send, mark accepted or declined, and turn an accepted quote into a draft invoice in one click. - Recurring and reminders (
#/schedule): a 30 day ribbon and agenda of every recurring invoice and reminder the schedule will send, recurring invoices with pause and edit, reminder rules (before due, on the due date, 3, 7, 14 or 30 days after), a reminder log, and a demo clock that moves time forward a day or a week and runs the schedule for each day it passes. - Settings (
#/settings): business details, ABN with a format check only (11 digits and the ATO checksum; it does not confirm the ABN is active), logo upload with a preview, GST registration and default mode, numbering with a preview, default terms, bank details and whether they show, automatic runs, appearance, export everything as JSON, remove or reload the sample, reset. - Self test (
#/selftest): 24 money, GST and date checks run in the page and printed with PASS or FAIL.
Also: a command palette (Ctrl K), ? for every shortcut, notifications for views, payments, reminders and recurring invoices, empty, loading and error states, and a warning when the browser will not keep changes.
How GST is worked out
All amounts are integer cents; quantities are integer hundredths and discounts integer basis points, so there is no floating point anywhere. Per line: gross = round(qty_h × unit_cents / 100), discount = round(gross × bp / 10000), net = gross − discount. GST is worked out once per invoice on the total of the lines marked GST and rounded to the nearest cent, half up: taxable / 10 when rates exclude GST, taxable / 11 when they include it, and nothing when the business is not registered. Each invoice keeps its own mode, so changing a setting never rewrites an invoice a client has already seen. The self test covers each rule.
Data model
store in script.js holds the tables in schema.sql: businesses, members, clients, invoices, invoice_lines, payments, quotes, quote_lines, recurring, recurring_lines, reminders, invoice_events. Paid, part paid, viewed and overdue are derived from payments and dates, never stored. Payments, reminders and events are append only; a reversed payment is a new negative row. The seed in schema.sql is generated from SAMPLE in script.js.
Moving from localStorage to Supabase
Every read and write goes through store (section 4 of script.js). Replace its methods with calls to your database client, keeping the same names:
const store = {
db: emptyDb(),
async load() {
for (const t of TABLES) {
const { data, error } = await supabase.from(t).select('*');
if (error) throw error;
this.db[t] = data;
}
},
async insert(t, row) {
const { data, error } = await supabase.from(t).insert(row).select().single();
if (error) throw error;
this.db[t].push(data);
return data;
},
// update, remove: the same pattern. Payments, reminders and events: insert only.
};Take numbers from sw_next_number(business, 'invoice') rather than nextNumber() so two people never get the same one. The client view reads sw_public_invoice(share_token) and calls sw_mark_viewed(share_token); the link becomes #/pay/<share_token>.
How the first person gets in
members.user_id starts null and nothing sets it on its own (LESSONS.md L60): after sign in, call claim_invite() (no arguments; it matches the caller's own confirmed email against an unlinked members row). The seeded business has nobody signed in yet, so the very first person to sign up with a confirmed email and call start_as_owner('Their Name') becomes its owner: this only ever succeeds once, while no real (non sample) linked owner exists; anyone after that gets "This business already has an owner". The owner then adds a bookkeeper by inserting a members row with business_id, name, email and role = 'bookkeeper' (the same way the sample members row is seeded), and that person signs in and calls claim_invite() themselves. In Supabase Authentication settings, Confirm email must be turned on, or nova_confirmed_email() never returns an address and nobody can claim or start anything. This template is demo only today (store still talks to localStorage only, see the section above for wiring it to a real backend); nova_confirmed_email(), claim_invite() and start_as_owner(p_name) are ready in schema.sql for whoever does that.
Server side functions you will need
- Send an invoice, quote, reminder or statement: an Edge Function that emails the client the private link through your email service. The email key stays on the server.
- Card payments: an Edge Function that creates a Stripe Checkout Session for the balance, and a webhook that inserts the
paymentsrow and apaymentevent when Stripe confirms. Never mark an invoice paid from the browser. - The schedule on a server: run the logic of
runSchedule()once a day (a scheduled Edge Function or pg_cron): issue recurring invoices whosenext_issue_datehas come, then send each open invoice's latest due reminder and log older ones as skipped. The unique(invoice_id, kind)onremindersstops a reminder going twice. - Logo upload: store the file in a storage bucket and save its URL in
businesses.logo_url(the demo keeps a small data URL in the browser).
What is demo or simulated
Sign in, sending email, the card checkout, reminders and recurring runs, and the demo clock. Each says so where it happens.
Files
index.html (shell and icon sprite), styles.css (tokens, components, screens, the invoice document, motion, print), script.js (one module, sections listed at the top), schema.sql, media.json, template.json.
Accessibility
Labelled inputs, visible focus, keyboard access for every action (including the chart and line reordering), a table view of the chart, live regions for saves and totals, colour never used alone for status, and reduced motion honoured.