docs: quotation polish + share/download design (beautify, tokenised share link, whatsapp, native share)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>feat/client-detail-redesign
parent
0b340fd85f
commit
8212a9d898
@ -0,0 +1,93 @@
|
|||||||
|
# Quotation Polish + Share/Download — Design
|
||||||
|
|
||||||
|
**Status:** approved in principle (founder, 2026-07-13). Sequenced to run **after** the
|
||||||
|
live-preview build (wf live-preview) lands, so it builds on the finished `templates.ts`
|
||||||
|
rendering + the live preview (every visual change is seen instantly) and never edits the
|
||||||
|
template file concurrently with that build.
|
||||||
|
|
||||||
|
Two threads: (A) make the document **look beautiful**, (B) **share & download** options.
|
||||||
|
|
||||||
|
## A. Beautiful document (templates.ts `documentHtml`)
|
||||||
|
|
||||||
|
Restyle the one shared letterhead template — this automatically improves the PDF, the
|
||||||
|
composer live preview, and the Document Template page (one renderer). A print-quality,
|
||||||
|
professional Indian GST document, not a generic table. Direction (finalised with the
|
||||||
|
frontend-design skill at build time):
|
||||||
|
|
||||||
|
- **Typography:** a clear type hierarchy — company name prominent, a refined document
|
||||||
|
title block (TAX INVOICE / QUOTATION), tabular-figure alignment for all money columns
|
||||||
|
so rupee amounts line up to the paisa. Keep the bundled Segoe/Inter-class stack (renders
|
||||||
|
identically on the office desktops and the Windows server — no web-font fetch in print).
|
||||||
|
- **Layout:** a proper letterhead band (logo left via `template.logo`, company block +
|
||||||
|
GSTIN right), a clean bill-to / ship-to and document-meta row, a well-spaced line table
|
||||||
|
with subtle row rules (not heavy borders), a totals block visually anchored bottom-right
|
||||||
|
with the grand total emphasised, amount-in-words, then terms / declaration / jurisdiction
|
||||||
|
/ signatory / footer from the `template.*` fields.
|
||||||
|
- **Accent:** one restrained accent colour for rules/headers, sourced from a new optional
|
||||||
|
`template.accent` setting (defaults to a professional slate/indigo) so it's brandable
|
||||||
|
from the Document Template page later — no hard-coded brand colour.
|
||||||
|
- **Print discipline:** A4, 14mm margins, avoid row-splitting across page breaks
|
||||||
|
(`break-inside: avoid` on rows), repeat the table header on page 2+. All screen/print
|
||||||
|
parity rules from the live-preview design still hold (one renderer, golden test guards).
|
||||||
|
- **Fidelity:** the existing string-identity golden test continues to guarantee the
|
||||||
|
beautified preview === the beautified PDF.
|
||||||
|
|
||||||
|
## B. Share & download (all three channels + download)
|
||||||
|
|
||||||
|
On **DocumentView** (and optionally right after composing), an action group:
|
||||||
|
|
||||||
|
1. **Download PDF** — button hitting the existing `GET /api/documents/:id/pdf`, served
|
||||||
|
with `Content-Disposition: attachment; filename="QT-26-27-0001.pdf"` (doc-no slugged,
|
||||||
|
`/` → `-`). Trivial; no new endpoint.
|
||||||
|
2. **Shareable view link** (the load-bearing one — needs a secure public route):
|
||||||
|
- New table `document_share (id, document_id, token, created_by, created_at,
|
||||||
|
expires_at NULL, revoked INTEGER DEFAULT 0)` via `CREATE TABLE IF NOT EXISTS`.
|
||||||
|
- `POST /api/documents/:id/share` (requireAuth) → mints a **cryptographically random**
|
||||||
|
token (≥32 bytes, `randomBytes`), default `expires_at` = +30 days (configurable),
|
||||||
|
returns the public URL. Audited.
|
||||||
|
- **Public** `GET /share/:token` (NO auth, the ONLY unauthenticated read route) →
|
||||||
|
validates token exists, not revoked, not expired; renders **only that one
|
||||||
|
document's** PDF (reuse renderPdf) or an HTML view. Serves nothing else — no listing,
|
||||||
|
no client record, no API. Unknown/expired/revoked → a plain "link expired" page.
|
||||||
|
- `POST /api/documents/:id/share/:shareId/revoke` (requireAuth, audited) → sets
|
||||||
|
`revoked=1`. A document's shares are listed on DocumentView so the owner can revoke.
|
||||||
|
- **Security stance (documented, accepted):** anyone holding the link sees that one
|
||||||
|
document's contents (client name + amounts) — the intended trade-off of any share
|
||||||
|
link, mitigated by an unguessable token, optional expiry, and one-click revoke.
|
||||||
|
Rate-limit `/share/:token` lookups to blunt token brute-forcing. No document data in
|
||||||
|
the URL beyond the opaque token.
|
||||||
|
3. **WhatsApp (click-to-chat)** — a `wa.me/<client-phone>?text=<encoded>` link opening
|
||||||
|
WhatsApp with a ready message: greeting + doc type/no + total + **the shareable view
|
||||||
|
link** (so the client taps through to the actual document — this is why the link and
|
||||||
|
WhatsApp compose). Needs the client's phone (already on the client record); no WhatsApp
|
||||||
|
Business API. If no share link exists yet, minting one is offered inline.
|
||||||
|
4. **Native share sheet** — `navigator.share({ title, text, url: shareLink })` where
|
||||||
|
supported (mobile), sharing the view link to any app; where the File share is
|
||||||
|
supported, share the PDF blob directly; desktop/unsupported → falls back to Download.
|
||||||
|
|
||||||
|
## Data / files (build time)
|
||||||
|
|
||||||
|
- `apps/hq/src/db.ts` — `document_share` table (CREATE TABLE IF NOT EXISTS; no migrate).
|
||||||
|
- `apps/hq/src/repos-shares.ts` — mint/validate/revoke/list shares.
|
||||||
|
- `apps/hq/src/api.ts` — `POST /documents/:id/share`, `POST …/share/:shareId/revoke`;
|
||||||
|
`GET /api/documents/:id/pdf` gains the attachment filename.
|
||||||
|
- `apps/hq/src/server.ts` — public `GET /share/:token` mounted OUTSIDE the `/api`
|
||||||
|
auth router, rate-limited; renders via renderPdf.
|
||||||
|
- `apps/hq/src/templates.ts` — the beautification (thread A) + optional
|
||||||
|
`template.accent`.
|
||||||
|
- `apps/hq-web/src/pages/DocumentView.tsx` — the action group (Download / Link / WhatsApp
|
||||||
|
/ Share) + the shares list with revoke.
|
||||||
|
- Tests (TDD, server): token mint is random + unguessable; `/share/:token` serves only
|
||||||
|
the one doc and 404s on unknown/expired/revoked; revoke works; owner-gate/audit;
|
||||||
|
download filename; beautify keeps the golden string-identity test green.
|
||||||
|
|
||||||
|
## Not this round
|
||||||
|
|
||||||
|
Real WhatsApp Business API sending (click-to-chat only); SMS; share analytics
|
||||||
|
(opened/viewed tracking) — a nice later addition on the same `document_share` row.
|
||||||
|
|
||||||
|
## Sequence
|
||||||
|
|
||||||
|
1. Live-preview build (in flight) completes and is verified.
|
||||||
|
2. Plan this doc (writing-plans) → ultracode build → verify. Beauty pass uses the
|
||||||
|
frontend-design skill for the concrete visual system.
|
||||||
Loading…
Reference in New Issue