Sales Documents App
Current behavior, architecture, authentication, deployment, and document-handling conventions for the A Grade sales workspace.
Sales Documents App
The sales app is an internal A Grade Contracting workspace for creating, editing, printing, and
managing proposals and invoices. It lives in apps/sales and is intended to deploy independently at
sales.agradecontracting.com from the same Bun monorepo as the public website and documentation.
The app is functional today with Supabase authentication and a shared, versioned document store. Client instructions live in the Sales App Guide. The Document Store Plan remains the engineering record for the persistence design.
Current Stack
| Concern | Implementation |
|---|---|
| Application | Vite, React, and TypeScript in apps/sales |
| Package management | Bun workspace from the repository root |
| Shared contracts | packages/shared/src/documents.ts |
| Authentication | Self-hosted Supabase Auth using the public browser key |
| Current persistence | Shared Supabase document store through the Sales API |
| Document output | Responsive editor plus print-ready invoice/proposal preview |
| Deployment target | Coolify/Nixpacks static build at sales.agradecontracting.com |
| API | Bun and Hono at api.agradecontracting.com |
Document Capabilities
The current app supports:
- Invoice and proposal templates.
- Editable sender, customer, project, date, terms, notes, and schedule fields.
- Line items with quantity and unit pricing.
- Optional add-ons.
- Proposal acceptance fields.
- Print-ready document layouts.
- Duplication and non-destructive archiving of shared documents.
- Database-assigned invoice and proposal numbers.
- Optimistic version checks that prevent stale saves from replacing newer work.
JSON import/export is available only in the explicitly selected browser-local development track. It is not rendered and cannot read or write documents while the app is on its production remote track.
Authentication And Access
The app uses a persistent Supabase browser session and supports:
- Password sign-in.
- Password recovery.
- Recovery-session password updates.
- Sign-out.
Public registration is intentionally absent. Access is currently constrained in
packages/shared/src/auth.ts to:
The browser allowlist improves product behavior but is not the authorization boundary. Public signup
remains disabled in Supabase, and the document store authorizes users by their Supabase user IDs
through platform.project_memberships and database policies.
The document-store migration can add a membership only when the matching account already exists in
auth.users. After creating an approved Sales account, run
apps/api/supabase/operations/provision_agrade_sales_users.sql in the Supabase SQL editor. The
operation is idempotent and reports whether each approved account is missing or provisioned.
Auth Redirects
The password-recovery request uses window.location.origin, so the requested return destination is
selected at runtime:
Local development: http://localhost:5173
Production: https://sales.agradecontracting.comBoth origins must be present in the self-hosted Supabase Auth redirect allowlist. SITE_URL remains
the fallback destination, while ADDITIONAL_REDIRECT_URLS contains every application origin or
callback path that GoTrue may honor.
Example shared-instance configuration:
SITE_URL=https://app.iiicoast.tech
ADDITIONAL_REDIRECT_URLS=https://app.iiicoast.tech,http://localhost:5173,https://sales.agradecontracting.comAfter changing the self-hosted Supabase environment, recreate the Auth container and request a new recovery email:
docker compose up -d --no-deps --force-recreate authExisting email links keep the destination embedded when they were generated.
Persistence Tracks
The production app runs on the remote persistence track. It starts with an empty loading state, loads the user's project through the Sales API, and displays only documents returned by the shared store. A remote failure produces a retry screen and never exposes stale browser-local examples.
Browser-local persistence is retained only as an explicitly selected development track. It requires
VITE_DOCUMENT_STORE_MODE=local at build time and a reload. The app does not switch tracks while it
is running, and failed remote writes never fall through into local storage.
An explicitly enabled one-time migration path can read existing browser drafts and submit them through the normal create API. It remains off by default.
Shared Document Model
AGradeDocument is defined in packages/shared/src/documents.ts. It currently includes:
- Document identity, type, number, and date.
- Sender and customer parties.
- Project and section labels.
- Line items and optional add-ons.
- Notes, schedule, acceptance, and footer configuration.
The shared package is the contract boundary. Database rows, API request/response types, runtime validation, and the sales UI should evolve together rather than duplicating document shapes inside individual apps.
Local Development
From the repository root:
bun install
bun devPortless starts the complete workspace at:
Sales: https://sales.agrade
API: https://api.agrade
Web: https://web.agrade
Docs: https://docs.agradeUse bun run dev:plain when Portless is unavailable. The direct Sales and Docs ports are 5173
and 3001 respectively.
Required local browser variables:
VITE_SUPABASE_URL=https://your-self-hosted-supabase.example.com
VITE_SUPABASE_ANON_KEY=your-public-key
VITE_DOCUMENT_STORE_MODE=remoteLocal orchestration supplies or derives the API origin. Never expose the Supabase
secret/service-role key through a VITE_* variable.
Deployment
Deploy the monorepo root as a separate Coolify application for the sales host:
Install command: bun install --frozen-lockfile
Build command: bun run sales:build
Publish path: /apps/sales/distSet VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY, and VITE_DOCUMENT_STORE_MODE=remote as
build-time environment variables. Production defaults to https://api.agradecontracting.com; set
VITE_DOCUMENT_API_URL only when intentionally overriding that origin. The static server must fall
back to index.html for unknown SPA routes.