AV
HomeAboutProjectBlog

© 2026 Ave syah Shina. All rights reserved.

  1. Home
  2. Projects
  3. Upsell Dashboard

Upsell Dashboard

January 1, 20246 min read
View Project
Next JsTypescriptNode JsExpress JsMaterial UIMongo DB
1 / 1

Overview

Upsell Dashboard (upsell-dashboard) is the merchant-facing administration and point-of-sale console for Upsell, an Icelandic restaurant upsell and sales platform. Restaurant owners and staff use it to manage their menu, process and track orders, run promotions and loyalty programs, configure table layouts, and operate kitchen display screens — all scoped to a chosen store through the store-picker entry point at upsell.is.

The dashboard is a Next.js 14 (Pages Router) + React 18 single-page application styled with Material UI v6. It is one package inside a large Nx monorepo that also hosts the platform's backend APIs (the legacy upsell-api Express service, the modern dashboard-api Cloudflare Worker, and a family of domain-specific workers such as orders-api, products-api, and promotions-api). The frontend reaches those services through a mix of Axios/REST and generated, type-safe Hono RPC clients.

Because the dashboard predates the platform's migration toward serverless Cloudflare Workers, it still reads and writes legacy MongoDB collections through the Express API, while newer features progressively move to PostgreSQL (Drizzle) behind domain workers. The sections below cover the dashboard's role in that wider platform, the monorepo layout, the MongoDB data model, and the author's personal contributions.

Tech Stack & Rationale

Layer

Technology

Why

Frontend framework

Next.js 14 (Pages Router), React 18

Server-rendered dashboard shell with per-store dynamic routes; Pages Router keeps the legacy Minimal UI template intact

Language

TypeScript (strict)

Mandatory across the monorepo; typed Prismic-free REST/API payloads

UI & styling

Material UI v6 (MUI), Emotion

Rich data-table, form, and date-picker primitives for a dense admin UI; Emotion enables the Minimal UI theming layer

State management

Redux Toolkit + redux-persist, SWR, React Context

RTK for legacy entity slices; SWR for newer cached data fetching; Context for cross-cutting concerns (auth, API clients, print queue)

Data fetching

Axios, SWR, Hono RPC clients (@upsell/dashboard-api-client)

Axios for the legacy REST API; generated type-safe clients for the dashboard-api worker

Auth

PropelAuth

Hosted multi-tenant auth with orgs, roles, and access tokens wired through a React provider

Feature flags

GrowthBook (legacy), Reflag

Progressive rollout and remote config; Reflag is the active replacement

Backend APIs

Express (upsell-api), Cloudflare Workers + Hono (dashboard-api, domain workers)

Legacy REST monolith + modern domain-driven serverless layer

Databases

MongoDB (Mongoose, legacy), PostgreSQL (Drizzle, new)

Existing merchant data lives in Mongo; new features target Postgres during an ongoing migration

Observability

Sentry, Mixpanel, OpenTelemetry

Error tracking, product analytics, and distributed tracing

Build & orchestration

Nx, pnpm

Monorepo task graph, affected builds, and deployment targets

Architecture

The dashboard follows a store-scoped, multi-tenant request flow: after authentication, every page lives under a dynamic /[storeId] route, and all data reads/writes resolve against the selected store's organization (orgId).

The flow runs: Store picker (/ selects a store) → /[storeId] page (dynamic route) → API clients (Axios · Hono RPC · SWR) → Backend (Express · Cloudflare Workers) → Data (MongoDB · PostgreSQL). Authentication (PropelAuth) supplies the org and access token that scope every request.

  1. Store picker — the root / page lists the merchant's stores; selecting one navigates into a /[storeId] route and passes a redirect back from legacy URLs (handled by middleware.ts, which maps old /dashboard/* and /app/dashboard/* paths to the store picker).
  2. Authentication — the AuthProvider (PropelAuth) supplies the user session, the active organization (orgId), and an access token. usePropelAuthContext exposes currentOrgName and propelActiveOrgId.
  3. Store-scoped route — each page under /[storeId] (orders, restaurant, customers, inventory, loyalty, reports, POS, TV, settings, team management, and more) renders through the dashboard layout, which provides the sidebar/navbar shell and guards (guards/role components) against unauthorized access.
  4. Data access — new pages use the DashboardApiProvider context, which builds generated type-safe Hono RPC clients (customersClient, ordersClient, productsClient, etc.) bound to NEXT_PUBLIC_NEW_API_ENDPOINT. Legacy pages still use Redux Toolkit thunks + Axios against upsell-api, with SWR used for cache-friendly reads.
  5. Backend — the dashboard-api Cloudflare Worker orchestrates the domain workers (orders-api, products-api, promotions-api, store-api, restaurant-api, etc.) and never touches the database directly. The legacy upsell-api Express service remains in maintenance mode for MongoDB-backed reads/writes.
  6. Data — legacy collections live in MongoDB (Mongoose); new features are written to PostgreSQL through Drizzle ORM, with migrations tracked in the monorepo's migrations/ directory.

Folder Structure

monorepo/
├── apps/ # Application projects
│ ├── upsell-dashboard/ # Admin dashboard (Next.js) — this project
│ ├── upsell-api/ # Legacy Express API (MongoDB)
│ ├── dashboard-api/ # Dashboard backend (Cloudflare Worker)
│ ├── orders-api/ # Order processing (Cloudflare Worker)
│ ├── products-api/ # Product management (Cloudflare Worker)
│ ├── restaurant-api/ # Restaurant data (Cloudflare Worker)
│ ├── store-api/ # Store management (Cloudflare Worker)
│ ├── pos-api/ # POS integration (Cloudflare Worker)
│ ├── promotions-api/ # Promotions engine (Cloudflare Worker)
│ ├── integrations-api/ # Third-party integrations (Cloudflare Worker)
│ ├── upsell-widget-web/ # Customer-facing ordering widget
│ └── ... # schedulers, TV, and utility services
├── libraries/ # Shared libraries
│ ├── upsell-kit/ # Core order/payment business logic
│ ├── dashboard-api/ # Hono RPC clients + router for dashboard-api
│ ├── shared/ # Shared utilities
│ ├── shared-db/ # Shared database helpers
│ └── roles/ # Role-based access control
├── customers/ # Customer-specific web apps (per-restaurant tenants)
├── migrations/ # Drizzle SQL migrations for PostgreSQL
└── scripts/ # Repo tooling and automation
  • apps/ — every deployable application. upsell-dashboard is the merchant console; the rest are the backend APIs (upsell-api legacy monolith plus the domain and orchestration workers) and customer-facing frontends.
  • libraries/ — shareable TypeScript code: business logic (upsell-kit), the generated dashboard API client, shared DB helpers, and RBAC.
  • customers/ — per-restaurant white-label web applications, one folder per tenant.
  • migrations/ — Drizzle-generated SQL migrations for the PostgreSQL data layer.
  • scripts/ — repository automation, deployment, and data-migration scripts.

The dashboard package itself organizes code under src/:

apps/upsell-dashboard/src/
├── pages/ # Next.js Pages Router (store picker, [storeId] routes, auth, errors)
├── sections/ # Page-specific components (orders, restaurant, customers, pos, ...)
├── components/ # Shared Minimal UI template components
├── layouts/ # dashboard / main / custom layout shells
├── redux/ # Redux Toolkit actions + reducers (legacy entity slices)
├── hooks/ # Custom hooks (useCurrentStoreId, useWaitingTime, usePrintQueue, ...)
├── contexts/ # React Context providers (Axios, DashboardApi, Printer, PrintQueue)
├── utils/ # Domain helpers (orders, payments, reports, printing, ...)
├── validations/ # Yup/Zod schemas for forms
├── auth/ # PropelAuth context and auth types
├── theme/ # MUI theme + component overrides (Minimal UI)
├── locales/ # i18next translations
└── routes/ # Typed route-path definitions (paths.ts)

Database Design

The dashboard reads and writes merchant data primarily through MongoDB (Mongoose), accessed via the legacy upsell-api Express service. MongoDB is the platform's original datastore; the monorepo guidance is to avoid creating new collections and to migrate existing ones to PostgreSQL (Drizzle) over time. The Mongoose models live in apps/upsell-api/src/models/v2/ and map roughly to the dashboard's feature areas:

Collection (model)

Purpose

store, branch, restaurant, branch_setting

Multi-tenant store configuration, branches/locations, and restaurant settings

order, pending_order, order_activity, order-notification

Order lifecycle, pending orders, activity log, and order-related notifications

product, category, tag, variant, variation, extra, ingredient

Menu catalog: products, categories, tags, variants, extras, and ingredients

deal, discount, coupon, discount_usage, coupon_usage

Promotions: deals, discounts, coupons, and their usage tracking

customer, sales_customer, customer-campaign

Customer records, sales-customer linkage, and campaign membership

table, tablegroup, floor

Table management: tables, table groups, and floor plans

payment-gateway, payment-history, settlement, split-payment

Payment gateways, payment history, settlements, and split payments

inventory, inventory_history, inventory_transfer

Inventory stock levels, history, and transfers

giftcard

Gift card products and issued cards

kiosk, kiosk_device, printer_device, counter, drawer

Hardware: kiosks, printers, counters, and cash drawers

widget, widget_content, widget_design, widget_settings

Customer-facing ordering widget configuration

role, accountant, admin, user

Role-based access control and staff/accountant accounts

push-notification, otp, ticket, tax, setting

Notifications, one-time passwords, tickets, tax rules, and misc settings

Newer data (for example, product variants and nutrition) is being moved to PostgreSQL via Drizzle, with the dashboard's src/@types/neon and src/hooks/neon folders reading those newer endpoints. Nutrition data is additionally served from FaunaDB (fauna/schema.fsl). This layered design means a single dashboard screen can transparently draw from Mongo (legacy), Postgres (new), and Fauna (nutrition) depending on which domain the data belongs to.

Impact & Results

  • The merchant-facing console of the Upsell restaurant platform — one of the core apps in a monorepo that serves 16+ customer sites.
  • In production at upsell.is, used by restaurant owners and staff to run menus, orders, promotions, and loyalty.

My Contributions

  • I initiated the dashboard package in 2024 and fixed an early undefined-order-number bug on the kitchen display screen (KDS).
  • I built the waiting-time configuration flow, adding a "default waiting time" setting and a "reset default waiting time" action end-to-end across the dashboard and API.
  • I updated the dashboard's layout design, added an accordion component to the detail page, and fixed the location selector.
  • I fixed the schedule picker's mobile behavior and added an "Apply" button for a scheduling flow.

References

[1] Upsell, "Upsell — Restaurant upsell and sales platform." [Online]. Available: https://upsell.is

[2] Vercel, "Next.js Documentation." [Online]. Available: https://nextjs.org/docs

[3] Nx, "Nx Workspace Documentation." [Online]. Available: https://nx.dev

[4] MUI, "Material UI — React UI component library." [Online]. Available: https://mui.com

[5] PropelAuth, "B2B Authentication and Authorization." [Online]. Available: https://www.propelauth.com

[6] MongoDB, "Mongoose — MongoDB object modeling for Node.js." [Online]. Available: https://mongoosejs.com

[7] Drizzle ORM, "Drizzle ORM Documentation." [Online]. Available: https://orm.drizzle.team

[8] Hono, "Hono — Ultrafast web framework for Cloudflare Workers." [Online]. Available: https://hono.dev

Comments

Leave a Comment

You must be signed in to comment

0 Comments

No comments yet. Be the first to comment!