Getting Started
SignQuote is a Vite + React 18 + TypeScript + Tailwind single-page app. There is
no backend, no environment variables, and no network calls at runtime — pricing runs
in the browser and all state lives in localStorage. That makes it trivial to run: clone,
install, npm run dev.
Prerequisites
- Node 18 or newer. The repo was developed and tested on Node 25; any current LTS works.
- npm (ships with Node). No other global tooling is required.
No .env file, API keys, or services to provision. If npm install succeeds, the app will run.
Run it
Install dependencies
npm installStart the dev server
npm run devThis runs vite and prints a local URL (typically http://localhost:5173). The page
hot-reloads on save.
Run the tests
npm testThis runs vitest run. The suite is the pricing-engine acceptance fixtures in
src/pricing/engine.test.ts, which pin the worked examples and acceptance criteria
(AC-1…AC-8) against the seeded constants. See Acceptance & Testing.
Build for production
npm run buildThe build script is tsc --noEmit && vite build — it type-checks the whole project first,
then produces the optimized bundle. Preview that build locally with:
npm run previewThe four scripts come straight from package.json:
| Script | Command | What it does |
|---|---|---|
npm run dev | vite | Dev server with hot reload |
npm run build | tsc --noEmit && vite build | Type-check, then production bundle |
npm run preview | vite preview | Serve the built bundle locally |
npm test | vitest run | Run the pricing-engine fixtures once |
Vitest is configured in vite.config.ts with environment: 'node' and
include: ['src/**/*.test.ts'], so npm test only picks up the engine test file.
First-run behavior
The app is never empty. On first load, loadState in src/storage.ts finds no saved
state under the localStorage key signquote.v1 and calls seedState, which writes the two
example quotes from buildSeedState in src/data/defaults.ts:
| # | Customer | Family | Config |
|---|---|---|---|
| 1001 | Maria Torres / Cafe Luna | Channel letters | SEED_CAFE_CONFIG — "CAFE", 4 letters, 18”, front-lit |
| 1002 | David Chen / Chen & Associates LLP | Dimensional letters | SEED_LAW_OFFICE_CONFIG — "LAW OFFICE", 8 letters, 12”, cast aluminum |
These two seeds are the same configs the test suite pins as AC-1 and AC-2 — the prices you see in the app match the prices the tests assert.
State persists across reloads because AppProvider (src/context/AppContext.tsx) saves
to localStorage on every change. Anything you create or edit survives a refresh. Clearing the
browser’s site data wipes everything and the seeds are rebuilt on the next load — acceptable
for a demo.
To restore the original seeds at any time, use Reset demo data on the Settings screen
(src/screens/settings/DemoDataCard.tsx). It calls resetDemoData, which reseeds state and
returns you to the quotes list — restoring the two seeded quotes (#1001 and #1002) and resetting
all Table B settings (constants, LED products, price grid, shop info) to their defaults.
Reset is destructive: quotes you created are deleted along with any constant edits. The
Settings card asks for a window.confirm before proceeding.
Project layout
The app source lives under src/. The two files to read first are the pricing engine and the
seeded defaults — they define the whole behavior; the rest is UI around them.
- engine.ts
- engine.test.ts
- defaults.ts
- constantMeta.ts
- types.ts
- storage.ts
| Path | What it is |
|---|---|
src/pricing/engine.ts | The pure pricing engine: computePricing(config, settings). No I/O, no React — see Pricing Engine. |
src/pricing/engine.test.ts | Vitest fixtures pinning the acceptance criteria against the seeded constants. |
src/data/defaults.ts | Table B cost constants (DEFAULT_SETTINGS), the LED products and dimensional price grid, new-quote defaults, and the two seed quotes. |
src/data/constantMeta.ts | Display metadata (CONSTANT_GROUPS) that drives how constants are grouped and labeled on the Settings screen. |
src/types.ts | All shared types — SignConfig, Constants, Settings, Quote, PricingResult, and friends. |
src/storage.ts | localStorage load/save/seed under the signquote.v1 key. |
src/context/AppContext.tsx | App-wide state and the action API (createQuote, updateQuoteConfig, resetDemoData, …) plus the current view. |
src/screens/QuotesList.tsx | The quotes list — the salesperson’s home screen. |
src/screens/QuoteEditor.tsx + editor/ | The guided quote form: category cards, field controls, and the live price panel. |
src/screens/SettingsScreen.tsx + settings/ | The owner’s Table B editor: constant groups, LED products, the price grid, shop info, a pricing preview, and demo-data reset. |
src/pdf/ | The branded PDF: QuotePDF (the document), PdfPreviewModal (in-app preview), and downloadQuotePdf (the download action), built with @react-pdf/renderer. |
src/ui/ | Small UI helpers: format.ts (currency/number formatting) and labels.ts (human-readable labels for config enums). |
The docs site
These docs are a separate Nextra v3 site living in docs/ within the same repo. They are
bilingual (English + Russian) — use the language switcher in the navbar. To run them:
cd docs && npm install && npm run devThat serves the docs at http://localhost:3030 (the same URL the app’s Docs button points to).
To build and serve a production version:
npm run build && npm run startThe docs use Nextra’s i18n routing, which needs a Node server — so this is a normal next build
(not a static export). Host it on Vercel or any Node platform.
Where to read next
- Architecture — how the pieces fit: the two-table model, state flow, and the role boundary.
- Pricing Engine —
computePricingformulas, the line items, and the seeded numbers behind the worked examples.