Frontend — Architecture Overview

The pakAG frontend is a Next.js 16 App Router application written in TypeScript. It is the distributor-facing web interface for daily route execution, package tracking, and account settings.


Tech Stack

LayerLibrary / Version
FrameworkNext.js 16.2.4 (App Router)
UI RuntimeReact 19.2.4
Server-stateTanStack React Query 5.99.1
HTTP clientAxios 1.15.0
StylingTailwind CSS v4
Map engineHERE Maps JS API (loaded via CDN)
Skeleton loadersboneyard-js 1.7.9
Guided tourreact-joyride 3.0.2
LanguageTypeScript 5

[!NOTE] The package name in package.json is @repo/frontend-app, reflecting the monorepo workspace structure. Both frontend-app/ and backend-js/ live alongside each other at the repo root.


Repository Location

erronka2025_js/
├── frontend-app/       ← THIS application
├── backend-js/         ← Next.js API (separate app)
├── docs/               ← Documentation site (this site)
└── schema.sql          ← MySQL source of truth

All commands below assume frontend-app/ as the working directory.


Folder Structure

frontend-app/
├── app/
│   ├── layout.tsx                  ← Root layout — wraps with AppProviders
│   ├── page.tsx                    ← Redirects "/" → "/dashboard"
│   ├── not-found.tsx               ← Global 404 page
│   ├── globals.css                 ← PakAG palette + Tailwind base
│   │
│   ├── (auth)/                     ← Route group: unauthenticated pages
│   │   ├── layout.tsx              ← Passthrough layout (no shell)
│   │   └── login/
│   │       ├── page.tsx
│   │       ├── hooks/
│   │       └── components/
│   │
│   ├── (main)/                     ← Route group: authenticated shell
│   │   ├── layout.tsx              ← Adds RoleGuard, SessionKeepAlive, AsideMenu, Header
│   │   ├── (pages)/                ← Inner route group (pages under the shell)
│   │   │   ├── dashboard/
│   │   │   ├── myPackages/
│   │   │   │   └── [packageId]/
│   │   │   ├── myRoute/
│   │   │   ├── history/
│   │   │   └── settings/
│   │   ├── components/             ← Shell UI: AsideMenu, Header, RoleGuard, StatusBadge…
│   │   ├── hooks/                  ← Page-level hooks (e.g. useHereMaps)
│   │   ├── lib/                    ← HERE Maps loader helper
│   │   └── types/                  ← allowedRoles.ts (role → route mapping)
│   │
│   ├── components/                 ← App-wide shared components
│   │   ├── SessionKeepAlive.tsx    ← Mounts refresh query in the background
│   │   ├── PreferencesSync.tsx     ← Syncs user preferences on mount
│   │   ├── PackLogo.tsx
│   │   ├── icons.tsx
│   │   └── tutorial/
│   │
│   ├── config/
│   │   └── envConfig.ts            ← All env vars and URL constants
│   │
│   ├── hooks/                      ← Domain hooks (thin wrappers around query options)
│   │   ├── auth/
│   │   ├── packages/
│   │   ├── routes/
│   │   └── tracking/
│   │
│   ├── lib/
│   │   └── api/
│   │       ├── auth-api.ts
│   │       ├── packages-api.ts
│   │       ├── routes-api.ts
│   │       ├── logs-api.ts
│   │       ├── tracking-api.ts
│   │       └── helpers/
│   │           ├── client.ts       ← Axios instance + interceptors
│   │           ├── auth-token.ts   ← Cookie read/write for access_token
│   │           ├── errors.ts       ← toAppError() normalizer
│   │           └── response-helpers.ts
│   │
│   ├── providers/
│   │   ├── AppProviders.tsx        ← Root provider tree
│   │   ├── ReactQueryProvider.tsx  ← QueryClient instantiation
│   │   └── TutorialProvider.tsx    ← react-joyride context
│   │
│   ├── query/
│   │   ├── keys/                   ← Query key factories by domain
│   │   │   ├── auth.keys.ts
│   │   │   ├── packages.keys.ts
│   │   │   ├── routes.keys.ts
│   │   │   ├── logs.keys.ts
│   │   │   └── tracking.keys.ts
│   │   └── options/                ← queryOptions() definitions by domain
│   │       ├── auth.options.ts
│   │       ├── packages.options.ts
│   │       ├── routes.options.ts
│   │       ├── logs.options.ts
│   │       └── tracking.options.ts
│   │
│   └── utils/
│       ├── date.utils.ts
│       ├── preferences.ts
│       ├── status.utils.ts
│       ├── token.utils.ts
│       ├── tutorial.storage.ts
│       └── types/
│           ├── index.ts            ← USER_ROLES, UserRole, etc.
│           └── api/                ← Per-domain API type contracts
│               ├── auth.types.ts
│               ├── package.types.ts
│               ├── route.types.ts
│               ├── log.types.ts
│               ├── tracking.types.ts
│               └── common.types.ts ← AppError, ApiErrorResponse

Layout and Provider Tree

app/layout.tsx  (RootLayout)
└── AppProviders
    └── ReactQueryProvider  (QueryClientProvider)
        └── {children}
            └── (main)/layout.tsx  (AuthLayout)
                └── RoleGuard
                    └── TutorialProvider
                        ├── PreferencesSync        (effect-only component)
                        ├── SessionKeepAlive       (background refresh query)
                        ├── AsideMenu
                        ├── Header
                        └── <page content>

AppProviders (app/providers/AppProviders.tsx) is the single place to add new global providers — it currently delegates entirely to ReactQueryProvider.


Pages and Routes

URLFileDescription
/app/page.tsxInstant redirect → /dashboard
/loginapp/(auth)/login/page.tsxLogin + forgot-password modal
/dashboardapp/(main)/(pages)/dashboard/Profile summary, stats, map
/myPackagesapp/(main)/(pages)/myPackages/List/grid view with filters
/myPackages/[packageId]app/(main)/(pages)/myPackages/[packageId]/Package detail + log history
/myRouteapp/(main)/(pages)/myRoute/Daily route execution, stops, map
/historyapp/(main)/(pages)/history/Historical analytics
/settingsapp/(main)/(pages)/settings/Profile / security / appearance

[!NOTE] All (main) pages except /settings are restricted to the distributor role. /settings allows both distributor and admin. See routing.mdx for the full role-guard matrix.


Component Conventions

'use client' directive

Client components (hooks, event handlers, useEffect) must declare 'use client' at the very top of the file. Server components omit it (layouts, pages that only compose client children).

Hook co-location

Each page folder can have a local hooks/ subfolder for page-scoped logic. App-wide domain hooks live in app/hooks/{domain}/.

Query / Mutation pattern

  1. Define the key factory in app/query/keys/{domain}.keys.ts
  2. Define queryOptions() in app/query/options/{domain}.options.ts
  3. Create a use{Domain}{Action} hook in app/hooks/{domain}/ that calls useQuery or useMutation with those options.
// ✅ Correct layering
// app/query/keys/packages.keys.ts
export const packagesKeys = {
  all: () => ['packages'] as const,
  myPackages: () => [...packagesKeys.all(), 'myPackages'] as const,
  detail: (id: number) => [...packagesKeys.all(), 'detail', id] as const,
};
 
// app/hooks/packages/useMyPackages.ts
export function useMyPackages() {
  return useQuery(myPackagesQueryOptions());
}

Environment Variables

VariableRequiredDescription
NEXT_PUBLIC_API_BASE_URLDev onlyBackend API origin (e.g. http://localhost:3001/api)
NEXT_PUBLIC_DOCS_BASE_URLDev onlyDocs site origin (e.g. http://localhost:3002)
NEXT_PUBLIC_HERE_API_KEYAlwaysHERE Maps JS API key

In production, API_BASE_URL is hard-coded in app/config/envConfig.ts to https://api.tolosaerronka.es/api and DOCS_BASE_URL to https://docs.tolosaerronka.es.

[!WARNING] All three NEXT_PUBLIC_* variables use the ! non-null assertion in envConfig.ts. If they are undefined at build time the app will compile but may fail at runtime. Add startup validation if deploying to new environments.

Create .env.local at frontend-app/:

NEXT_PUBLIC_API_BASE_URL=http://localhost:3001/api
NEXT_PUBLIC_DOCS_BASE_URL=http://localhost:3002
NEXT_PUBLIC_HERE_API_KEY=your-here-api-key

Dev Server

# from the repo root — or from frontend-app/
cd frontend-app
npm run dev          # starts on 0.0.0.0 (all interfaces), port 3000 by default

The --hostname 0.0.0.0 flag in package.json enables access from other devices on the local network. next.config.ts whitelists 192.168.1.*, 10.23.*.*, and localhost as allowed dev origins.


Styling and PakAG Identity

app/globals.css defines the PakAG design token palette using Tailwind CSS v4 custom properties:

  • Deep purple surfaces as primary backgrounds
  • Violet accents for interactive elements
  • Semantic status colours for package states (pending, in_transit, delivered, etc.)

The documentation site under /docs intentionally mirrors this palette.


Skeleton / Loading Strategy

Pages do not use Next.js loading.tsx files. Instead, each domain has dedicated skeleton components (e.g. DashboardSkeleton, MyRouteSkeleton, HistorySkeleton) that are rendered while React Query is in a loading state. The boneyard-js library provides the low-level Skeleton primitive.


HERE Maps Integration

Maps are rendered client-side using the HERE Maps JS API v3, loaded via <Script> tags in app/(main)/lib/here.ts. The HereMap component in app/(main)/components/Here/HereMap.tsx handles initialisation and cleanup. The useHereMap hook manages the map lifecycle inside a useEffect.

Remote images from image.maps.ls.hereapi.com are whitelisted in next.config.ts for use with the Next.js <Image> component.


Further Reading

  • State management → — React Query setup, query keys, mutations, error handling
  • Auth flow → — Token storage, axios interceptors, refresh, logout
  • Routing → — App Router conventions, protected routes, role guard