This is my full-stack demonstration project for a 3D parts ordering workflow, built as part of a technical assignment. It showcases a multistep wizard interface, file uploads, material configuration, payment options, and admin question management.
- Frontend (Next.js + Vercel) https://saeki-web.vercel.app
- Admin Questions Dashboard UI https://saeki-web.vercel.app/admin/questions
- API (Express + Render) (no default /GET, so the page will show nothing) https://saeki.onrender.com
- Drag & drop or click-to-select IGES/.STEP files.
- Server-side storage: Express forwards files to BunnyCDN (via its REST API).
- Upload feedback: Buttons disable/turn green on success, errors shown inline.
- Materials fetched from
/materialsvia a customuseMaterialshook. - Selector UI: Grid of cards—click to choose—no dropdowns or external libs.
-
Three steps:
- UploadStep—file uploader
- ConfigureStep—choose material per part
- CheckoutStep—customer info + payment
-
Stepper bar remains sticky, shows completed (teal), active (coral), upcoming (gray).
-
Validation centralized in
lib/validation.ts(unit-tested).
-
CustomerInfoSection collects name/email/company.
-
PaymentSection toggles between:
- Credit Card (number, holder, CVV)
- Purchase Order (PDF upload)
-
Confirmation modal before final submission.
- ChatWidget on the Thank-You page polls
/orders/:id/questionsevery 8 s. - Admin UI at
/admin/questionsto view/respond (no auth). - Persistence via PostgreSQL under the hood.
/
├─ packages/
│ ├─ api/ ← Express + PostgreSQL
│ └─ web/ ← Next.js + React + Tailwind
├─ pnpm-workspace.yaml
└─ README.md
-
Clone & bootstrap
git clone https://github.com/your-org/saeki.git cd saeki pnpm install -
Backend (
packages/api)cd packages/api pnpm install # create .env with DATABASE_URL, BUNNY_API_KEY, BUNNY_STORAGE_ZONE, BUNNY_PULL_ZONE pnpm run dev
-
Frontend (
packages/web)cd ../web pnpm install # create .env with NEXT_PUBLIC_API_URL=http://localhost:4000 pnpm run dev
-
Browse
- Frontend: http://localhost:3000
- API health: http://localhost:4000/
A quick “smoke” test that runs through the core happy path (upload → configure → checkout → confirm → thank‑you) using Playwright. Don't forget that you need to have web and api up and running locally.
cd packages/web
pnpm exec playwright test # headless smoke E2E suite
# or run with UI to observe the browser:
pnpm exec playwright test --headedTip: This suite exercises the full UI flow in a real browser. It should pass within 30 seconds on a healthy local or CI environment.
These cover smaller slices:
- Unit tests for pure functions (e.g.
validateOrderlogic). - Component tests for UI pieces (e.g.
Stepperrenders correct classes and labels).
cd packages/web
pnpm test # runs all Jest suites (unit + component)
pnpm test:watch # rerun on file changesThese tests live in packages/api and verify your Express routes and business logic end-to-end (in memory):
- Integration tests for each REST endpoint
- e.g.
POST /ordersreturns400on invalid payload
- located under
src/__tests__/*.test.ts
- e.g.
Run them with:
cd packages/api
pnpm test # runs Jest against your API code
pnpm test --watch # rerun on changes- Frontend: Vercel (auto-deploy from GitHub,
packages/web). - Backend: Render (auto-deploy from GitHub,
packages/api, build=pnpm run build, start=pnpm start). - Env vars set in respective dashboards for API URLs, BunnyCDN keys, CORS origins.
- Local state with React hooks; in prod you might swap to a state machine.
- Separation of concerns: Each wizard step is its own component under
components/wizardSteps/. - TypeScript end-to-end: Frontend, backend, and tests.
- TailwindCSS for a rapid, responsive UI.
- Error handling surfaces both client-side validation and backend errors distinctly.
Since I’m using third-party services, if you’d like to run everything locally, please contact me so I can share the .env variables in a more secure way than just dropping them on GitHub! :)
The free-tier Render server probably will sleep, so the very first request can take ~10–50 s.
{ "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint", // Jest component & unit tests "test": "jest --config jest.config.ts", "test:watch": "jest --watch --config jest.config.ts", // Playwright smoke E2E test "e2e": "playwright test", "e2e:headed": "playwright test --headed" } }