Merchant dashboardApp architecture

App architecture

The dashboard is a Next.js App Router application. Routes live under src/app, UI under src/components, data hooks under src/hooks, and shared logic under src/lib.

Route tree

There are four top-level route groups:

GroupPurpose
/ , /overviewLanding / entry redirects
/login, /signup, /forgot-password, /reset-password, /verify-email, /auth/oauth/*, /invite/*Authentication, OAuth linking, and invite acceptance
/check-in/t/[ticketCode]Public ticket check-in (its own minimal layout)
/dashboard/*The authenticated admin surface — everything else

/dashboard/layout.tsx wraps the admin surface with the sidebar chrome on desktop and the MobileShell on small viewports. A /dashboard/[...slug] catch-all handles dynamic/module routes that aren’t statically declared.

The server-side proxy

The browser never calls the Box API directly. Every backend request goes through a single catch-all route handler:

/api/proxy/[...path]  →  ${BACKEND_API_URL}/<path>

BACKEND_API_URL (falling back to NEXT_PUBLIC_API_URL, default http://localhost:4000) is server-side only and is never shipped to the client bundle. The handler applies four protections before forwarding:

  1. Path allowlist. Only requests whose first path segment is on the allowlist are forwarded; anything else is rejected. Allowed prefixes include tenants, accounts, billing, products, orders, bookings, customers, members, invoices, finance, loyalty, discounts, content, blog, campaigns, automations, media, analytics, onboarding, domains, shipping, locations, and more.
  2. Cookie allowlist. Only auth cookies are forwarded upstream — pb_auth_token, pb_csrf_token, pb_refresh_token. All other cookies are stripped.
  3. Rate limiting. A simple per-IP limiter (100 requests / minute by default) guards the proxy. Client IP is read from x-forwarded-for / x-real-ip.
  4. Method coverage. GET, POST, PUT, PATCH and DELETE are all proxied through the same hardened path.

The in-memory rate limiter is per-instance and intended for development. In production behind multiple instances, back it with a shared store (e.g. Redis), as the source notes.

Authentication

Auth is cookie-based. After sign-in the Box API sets HTTP-only cookies (pb_auth_token, pb_refresh_token, pb_csrf_token); because they are HTTP-only, the access token cannot be read from client JavaScript. The proxy forwards those cookies upstream on every call, and the auth/OAuth/invite pages under /auth/* and /invite/* handle the sign-in, OAuth-link and invite-acceptance flows.

Data layer

The dashboard uses TanStack Query (@tanstack/react-query) for all server-state. Components call typed hooks in src/hooks that issue requests through the proxy; queries handle caching, refetching and optimistic updates. Desktop and mobile bodies for the same route share the same hooks — there is no separate mobile data path.

Desktop and mobile share one URL tree

There is no separate /m/* route tree. The mobile experience is delivered as “chrome + body” on the same /dashboard/* URLs:

  • Chrome — on small viewports the layout mounts MobileShell (top bar, bottom tab bar, FAB) instead of the desktop sidebar.
  • Body — each route renders a mobile body and a desktop body, switched by CSS (md:hidden / hidden md:block) so deep links, auth, and SEO keep working for both:
export default function Page() {
  return (
    <>
      <div className="md:hidden"><MobileOrdersList /></div>
      <div className="hidden md:block"><DesktopOrdersList /></div>
    </>
  )
}

Both bodies reuse the same data hooks; only presentation differs. This keeps a single URL per screen for push/email links, shared URLs and middleware.

Notable libraries

ConcernLibrary
Server state@tanstack/react-query
Rich text / blog editor@tiptap/*
Calendar / scheduling@fullcalendar/*
Drag-and-drop@dnd-kit/*
Chartsrecharts
Image editingreact-easy-crop, react-filerobot-image-editor, konva
QR / check-inhtml5-qrcode, qrcode.react
Validationzod
Telemetry@sentry/nextjs, posthog-js