---
title: Frontend Architecture
tags: [architecture, frontend, nextjs]
created: 2026-05-23
updated: 2026-06-03
---

# Frontend Architecture

Module-level architecture of the Next.js 16 (App Router) + TypeScript + MUI v9 frontend. The current integration worktree observed locally is on `integrate-main-into-development`.

> [!info]
> Repo: `git@git.manko.yoga:222/nick/frontend.git` · Active integration branch observed locally: `integrate-main-into-development` · Version: 2.8.94 (`package.json`) · Dev port `3000`, Docker port `8083`.

---

## 1. Folder tree

```
frontend/src/
├── app/                       # Next.js 16 App Router (server + client islands)
│   ├── layout.tsx             # Root layout — providers, fonts, Sentry
│   ├── (public)/              # Unauthenticated routes
│   │   ├── post/[slug]/       # Blog reader
│   │   └── shop/[seller]/[id] # Public seller / item view
│   ├── auth/jwt/              # sign-in, sign-up, verify, reset, update
│   ├── dashboard/             # AuthGuard + EmailVerificationGuard
│   │   ├── overview/          # KPI / home tiles
│   │   ├── chat/              # Real-time chat
│   │   ├── account/           # Profile, address, notifications, wallet, passkey
│   │   ├── request/           # Buyer purchase requests
│   │   ├── request-template/  # Seller request templates
│   │   ├── payment/           # Payment history / detail
│   │   ├── points/            # Loyalty hub (transactions, referrals, levels)
│   │   ├── disputes/          # Dispute hub
│   │   ├── user/              # Admin user management
│   │   ├── post/              # Admin blog editor
│   │   ├── shop-settings/     # Seller shop config
│   │   └── shops/             # Browse / checkout (dashboard scope)
│   ├── telegram/              # Telegram Mini App shell (see §19)
│   │   ├── layout.tsx         # TMA root — TonConnectUIProvider + minimal providers
│   │   ├── shop/              # Seller list + product browsing
│   │   ├── cart/              # In-shell cart + checkout handoff
│   │   └── account/           # Account tab (dashboard parity)
│   ├── error/                 # Global error page
│   └── not-found.tsx          # 404
├── sections/                  # Page-specific composition modules (one folder per feature)
│   └── (chat|payment|request|request-template|dispute|user|points|telegram|...)
├── components/                # Reusable UI primitives (hook-form, table, upload, editor, ...)
├── layouts/                   # Page-template wrappers (auth-centered, auth-split, dashboard, main, telegram)
├── theme/                     # MUI theme creation, palette, typography, overrides
├── settings/                  # Settings drawer (mode, layout, direction, color, font)
├── contexts/                  # React Context providers (socket-context)
├── hooks/                     # 50+ custom hooks (use-boolean, use-socket, use-web3-wagmi, …)
├── lib/                       # Cross-cutting libs (axios.ts with interceptors)
├── locales/                   # i18next config + langs/{en,fa,ar,fr,cn,vi}/*.json
└── types/                     # Cross-app TypeScript types
```

---

## 2. Rendering strategy

- **App Router** with `output: 'standalone'` in `next.config.ts` for production single-binary serving.
- **Server components by default**, **client components** opted into with `"use client"`. Heavy interactive pages (chat, editor, dashboard) are mostly client-side; SEO routes (`shop/`, `post/[slug]`) leverage server rendering.
- **Turbopack** in dev (`next dev --turbopack`).
- **Streaming** via `loading.tsx` and Suspense boundaries.
- **MUI cache** is wired up via `@mui/material-nextjs` in the root layout to avoid FOUC across server/client boundary.

---

## 3. Provider tree (root layout)

```mermaid
flowchart TB
    A[RootLayout]
    A --> B[AppRouterCacheProvider<br/>MUI emotion cache]
    B --> C[ThemeProvider<br/>theme + RTL stylis]
    C --> D[LocalizationProvider<br/>dayjs adapter]
    D --> E[I18nProvider<br/>i18next]
    E --> F[QueryClientProvider<br/>React Query]
    F --> G[SocketProvider<br/>Socket.IO context]
    G --> H[SnackbarProvider<br/>notistack]
    H --> I[Children — routes]
```

Order matters: theme must wrap query (because mutations show snackbars styled by theme); socket wraps snackbar (so socket-driven notifications can fire snackbars).

The Telegram Mini App shell (`app/telegram/`) uses its own slimmer layout that replaces the dashboard shell with `TonConnectUIProvider` and skips the settings drawer (see §19).

---

## 4. Route layout & guards

| Route group | Layout | Guard chain |
|---|---|---|
| `(public)` | `layouts/main` | none |
| `auth/jwt/*` | `layouts/auth-centered` or `auth-split` | redirect if already authed |
| `dashboard/*` | `layouts/dashboard` (sidebar + topbar + breadcrumbs) | `AuthGuard` → `EmailVerificationGuard` |
| `dashboard/user/*` | dashboard | + `role: admin` |
| `dashboard/post/*` (editor) | dashboard | + `role: admin` |
| `dashboard/shop-settings/*` | dashboard | + `role: seller` |
| `telegram/*` | `layouts/telegram` (bottom-tab shell) | Telegram `initData` token guard + role check |

Guards live in `frontend/src/auth/` (HOC + hook). They consult the JWT-derived user context and redirect unauthenticated to `/auth/jwt/sign-in?returnTo=...`. The Telegram guard additionally validates `window.Telegram.WebApp.initData` before issuing a session.

---

## 5. Sections vs components vs hooks

The codebase enforces a three-layer split:

| Layer | Lives in | Purpose | Example |
|---|---|---|---|
| **Section** | `src/sections/<feature>/` | Page-specific composition; orchestrates components | `sections/chat/view/ChatView.tsx` |
| **Component** | `src/components/<name>/` | Reusable across sections | `components/hook-form/RHFTextField.tsx` |
| **Hook** | `src/hooks/<name>.ts` | Encapsulated stateful behavior | `hooks/use-chat-socket.ts` |

> [!tip]
> Per the cursor rules (`backend/.cursor/rules/ui-development-standards.mdc`), every component folder has `index.ts` (barrel export), `<name>.tsx` (component), `classes.ts` (styled-component class names), `types.ts` (interface). Following this layout keeps each component refactorable.

---

## 6. State management

The frontend deliberately uses **three** state mechanisms, each for one concern:

| Concern | Tool | Where |
|---|---|---|
| Server data | **React Query** | every `useXxxQuery` / `useXxxMutation` hook |
| Cross-page shared state | **React Context** | `contexts/socket-context.tsx`, settings context, auth context |
| Per-component / local UI | `useState`, `useReducer`, `use-boolean`, `use-set-state` | inside components |

No Redux, no MobX, no Recoil. The cursor rules also mention **Zustand** as the preferred client store if one is added, but the dev branch currently relies on React Query + Context.

### React Query setup

- `QueryClient` created once at the root layout with `defaultOptions`:
  - `queries: { staleTime: 30_000, retry: 1, refetchOnWindowFocus: false }`
  - `mutations: { retry: 0 }`
- Query keys are tuples — convention: `['<resource>', <filter|id|...>]` e.g. `['requests', { status: 'open' }]`.
- Mutations invalidate related keys in `onSuccess`.

---

## 7. API client (`src/lib/axios.ts`)

A single axios instance underpins every HTTP call:

```ts
const api = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL,
  withCredentials: true,
});

// Request interceptor — attach token
api.interceptors.request.use((cfg) => {
  const token = getStoredToken();
  if (token) cfg.headers.Authorization = `Bearer ${token}`;
  cfg.headers['X-Request-Id'] = randomUUID();
  return cfg;
});

// Response interceptor — handle 401, normalize errors
api.interceptors.response.use(
  (res) => res.data,
  async (err) => {
    if (err.response?.status === 401 && !retried) {
      // attempt refresh-token flow
      await refresh();
      return api.request(err.config);
    }
    throw normalizeError(err);
  }
);
```

Endpoint constants live alongside the hook that uses them — no central `api/endpoints.ts`.

---

## 8. Real-time integration

The `SocketProvider` in `contexts/socket-context.tsx`:

1. Opens the connection after authentication (token from storage).
2. Auto-joins user-specific rooms (`user-{id}`, `seller-{id}` or `buyer-{id}` based on role).
3. Exposes a `useSocket()` hook returning `socket`, `connected`, helper emitters.

Higher-level hooks build on this:

| Hook | Subscribes to |
|---|---|
| `use-chat-socket` | `chat:new-message`, typing indicators, read receipts |
| `use-conversations` | conversation list updates |
| `use-notifications` | `notification:received` |
| `use-purchase-requests` | `request:offer-received`, status changes |
| `use-marketplace-socket` | broad market events |
| `use-unified-real-time` | multi-event aggregator |

The Telegram Mini App shell reuses the same `SocketProvider` — live socket updates are available in the TMA shop, cart, and account tabs.

See [[Real-time Layer]] for the full event catalog.

---

## 9. Web3 integration

```ts
// wagmi config (approx — confirm in src/web3/ or src/lib/wagmi.ts)
const config = createConfig({
  chains: [bsc, polygon, mainnet, sepolia],
  transports: {
    [bsc.id]: http(`https://...alchemy.com/${KEY}`),
    [polygon.id]: http(`https://polygon-mainnet.g.alchemy.com/v2/${KEY}`),
    ...
  },
  connectors: [
    injected(),               // MetaMask
    walletConnect({ projectId: NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID }),
  ],
});
```

Wallet UI: connect / disconnect / show address / show balance via `use-web3-wagmi`, `use-web3-context`. The current checkout target is the Request Network in-house flow; the DePay widget package remains legacy/frontier context and should not be treated as the primary path.

TON wallet support is handled separately via `@ton/core` + `@tonconnect/ui-react` in the Telegram Mini App layer (see §19).

---

## 10. Internationalization

- `i18next` + `react-i18next` initialized in `src/locales/locales-config.ts` with 6 langs (en, fa, ar, fr, cn, vi).
- Translation files in `langs/<locale>/*.json` (e.g., `common.json`).
- Direction (`ltr`|`rtl`) lives in settings; `stylis-plugin-rtl` is wired into the MUI cache when `dir === 'rtl'`.
- Date formatting via `dayjs` with locale auto-loaded.
- Number formatting via `Intl.NumberFormat` + helper at `locales/utils/number-format-locale.ts`.
- DataGrid has Persian-specific locale at `custom-fa-data-grid-locale.ts`.

See [[Internationalization & RTL]] for full detail.

---

## 11. Forms & validation

`react-hook-form` + `zod` schema via `@hookform/resolvers/zod`. Custom field wrappers in `components/hook-form/`:

| Wrapper | Wraps |
|---|---|
| `RHFTextField` | MUI `TextField` |
| `RHFSelect` | MUI `Select` |
| `RHFAutocomplete` | MUI `Autocomplete` |
| `RHFCheckbox` | MUI `Checkbox` |
| `RHFRadioGroup` | MUI `RadioGroup` |
| `RHFSwitch` | MUI `Switch` |
| `RHFUpload` | custom Dropzone (`components/upload`) |
| `RHFEditor` | TipTap editor wrapper |
| `RHFDatePicker` | `@mui/x-date-pickers` |
| `RHFPhoneInput` | `react-phone-number-input` |
| `RHFCountrySelect` | `components/country-select` |

A typical form section:

```tsx
const schema = z.object({ email: z.string().email(), password: z.string().min(8) });
const methods = useForm({ resolver: zodResolver(schema) });
return (
  <FormProvider {...methods}>
    <RHFTextField name="email" label="Email" />
    <RHFTextField name="password" label="Password" type="password" />
    <LoadingButton type="submit" loading={methods.formState.isSubmitting}>Sign in</LoadingButton>
  </FormProvider>
);
```

---

## 12. Theming

`src/theme/index.ts` creates the theme; `src/theme/options/` contains palette / typography / overrides. Light & dark variants share tokens; the active mode is read from the **Settings** drawer.

- Primary: `Public Sans Variable` (per cursor rules).
- Secondary: `Barlow`.
- Breakpoints: xs=600, sm=600, md=900, lg=1200, xl=1536.
- Shape radius: 8 (default), customizable in settings.

See [[Theme Configuration]] and [[Design System Overview]].

---

## 13. Settings drawer

`src/settings/` provides a side-drawer that lets a user toggle:

- Mode (light / dark / system)
- Contrast (default / high)
- Layout (vertical / mini / horizontal nav)
- Direction (ltr / rtl)
- Color preset (one of N curated palettes)
- Font family override

State persists in `localStorage` under `settings-key`.

> [!note]
> The Telegram Mini App shell does not render the settings drawer; theme and direction are inherited from the parent app's stored settings at launch.

---

## 14. Editor (TipTap)

`components/editor/` wraps `@tiptap/react` with these extensions enabled:

- `StarterKit` (paragraph, headings, bold/italic, lists, blockquote)
- `Link` (URL parsing)
- `Image` (upload via `components/upload`)
- `Underline`, `TextAlign`
- `CodeBlock` + `CodeBlockLowlight` (syntax highlighting via `lowlight`)
- `Placeholder`

Used in:
- Blog post editor (`dashboard/post/new`, `dashboard/post/[id]/edit`)
- Long-form description fields in Purchase Request & Request Template forms
- Dispute evidence narrative

Content is stored as HTML in MongoDB. `react-markdown` + `remark-gfm` are available where Markdown rendering is preferred (e.g., chat messages).

---

## 15. File uploads

`components/upload/` provides a dropzone with:
- Multi-file selection
- Drag-and-drop
- Per-file progress
- Preview (`components/file-thumbnail/` shows by MIME)
- Removal
- Total-size enforcement (5 MB default, matches `MAX_FILE_SIZE`)

Backed by `/api/file/*` (multipart upload).

---

## 16. Sentry

`@sentry/nextjs` is initialised at app boot. Errors include user context (role, userId) and breadcrumbs (route changes, API failures). Source maps uploaded at build time.

---

## 17. Build & deploy

`package.json`:
- `dev` → `next dev -p 8083 --turbopack`
- `build` → `next build && cp .next/static .next/standalone/.next/ && cp -r public .next/standalone/`
- `start` → `PORT=8083 node .next/standalone/server.js`

`Dockerfile` is a 2-stage multi-stage build that produces `.next/standalone/` and copies into a small `node:22-alpine` runner image with non-root user `nextjs`. Healthcheck via `curl http://localhost:8083`.

CI: `.gitea/workflows/deploy.yml` runs `scripts/deploy.sh` on push to `main` / `master`.

See [[Docker Setup]], [[CI-CD Pipeline]], and [[Deployment]].

---

## 18. Notable files for orientation

| File | Why it matters |
|---|---|
| `src/app/layout.tsx` | Provider tree |
| `src/app/telegram/layout.tsx` | TMA shell — TonConnectUIProvider + slim provider tree |
| `src/lib/axios.ts` | Every HTTP call goes through this |
| `src/contexts/socket-context.tsx` | Realtime plumbing |
| `src/theme/index.ts` | Theme creation entry |
| `src/locales/locales-config.ts` | i18next setup |
| `src/settings/context/` | Settings context (persistence) |
| `next.config.ts` | Standalone build, COOP/COEP headers for Web3 popups |

---

## 19. Telegram Mini App (TMA) layer

### Overview

The app ships a dedicated Telegram Mini App shell at `app/telegram/`. It is served from the same Next.js process and Docker image as the main web app; no separate deployment is required. The Telegram bot registers the Mini App URL pointing at `/telegram`.

### Provider tree (TMA layout)

The TMA layout replaces the full dashboard shell with a minimal provider stack:

```mermaid
flowchart TB
    A[TelegramLayout]
    A --> B[AppRouterCacheProvider]
    B --> C[ThemeProvider]
    C --> D[QueryClientProvider]
    D --> E[SocketProvider]
    E --> F[TonConnectUIProvider<br/>manifestUrl: /tonconnect-manifest.json]
    F --> G[SnackbarProvider]
    G --> H[Children — telegram routes]
```

`TonConnectUIProvider` is the only addition relative to the web tree. Settings drawer, i18n provider, and auth guards are replaced by a Telegram `initData` token guard.

### Routes and features

| Route | Description |
|---|---|
| `telegram/shop` | Seller list with product browsing; infinite scroll |
| `telegram/shop/[seller]` | Single seller's catalogue |
| `telegram/cart` | In-shell shopping cart; checkout hands off to full web checkout URL |
| `telegram/account` | Account tab with dashboard parity: profile, wallet, order history |

### Authentication flow

1. Telegram injects `window.Telegram.WebApp.initData` on launch.
2. The TMA guard sends `initData` to `/api/auth/telegram` for HMAC verification.
3. On success the backend issues a short-lived JWT that the axios instance attaches as `Bearer`.
4. Role-based access (seller vs buyer views) is honoured via the same guard mechanism used in the dashboard.

### Real-time

`SocketProvider` is reused unchanged. The TMA shop, cart, and account tabs receive live socket updates (new messages, payment status, cart changes) on the same room infrastructure as the web dashboard.

### TON Connect (Telegram Wallet)

**Dependencies added**: `@ton/core`, `@tonconnect/ui-react`.

`TonConnectUIProvider` wraps the TMA routes and exposes a `useTonConnectUI()` hook. The manifest at `public/tonconnect-manifest.json` declares the app identity to the TON Connect protocol.

Current status: the wallet connection UI is in place (connect / disconnect / show address). **Actual TON payment processing is not yet wired to the backend** — the provider is pre-positioned for a future TON payment rail on the escrow platform. When that rail is built, the checkout handoff in `telegram/cart` will be extended to emit a TON transaction instead of redirecting to the web checkout.

### Constraints and differences from web

- No settings drawer (theme follows web localStorage, defaults to light/ltr).
- No TipTap editor or file-upload dropzone in TMA routes.
- `@mui/x-date-pickers` and DataGrid are not loaded in the TMA bundle.
- COOP/COEP headers required for WalletConnect popups are relaxed for TMA routes because Telegram's WebView does not support `SharedArrayBuffer`.

---

## Related

- [[System Architecture]] — bird's-eye topology
- [[Backend Architecture]] — server peer
- [[Real-time Layer]] — Socket.IO plumbing
- [[Design System Overview]] · [[Theme Configuration]] · [[Components]]
- [[Tech Stack]] — versions
- [[Coding Standards]] — UI cursor-rules summary
