English summary. Frontend of a banking application, built as a university project for two courses: Web Applications, and Information Systems in Banking. Next.js 16 (App Router, Server Components, Turbopack), React 19, TypeScript in strict mode with typed routes, Tailwind 4 and shadcn/ui, running on Bun.
It follows a BFF pattern: the browser never calls the API directly. Reads go through Server Components and writes through Server Actions, so access tokens stay on the server and never reach client-side JavaScript.
Covers registration and login with two-factor confirmation, dashboard, accounts, transfers, cards, loans, deposits, the KYC flow, user settings, and a separate admin area for approving KYC submissions, loans and deposits. It talks to
bank-api.The rest of this document is in Polish.
| Pulpit klienta | Nowy przelew |
|---|---|
![]() |
![]() |
| Włączanie 2FA (TOTP) | Panel admina — użytkownicy |
|---|---|
![]() |
![]() |
Zrzuty pochodzą z lokalnego uruchomienia na danych z bun run db:seed:demo
(efemeryczny Postgres z docker-compose.test.yml). Sekret TOTP widoczny na
zrzucie należy do jednorazowego konta demo w bazie trzymanej w tmpfs.
Frontend aplikacji bankowej — projekt zaliczeniowy (Aplikacje Webowe + Systemy Informatyczne w Bankowości).
- Runtime / PM: Bun 1.3+
- Framework: Next.js 16 (App Router, Turbopack, Server Components)
- React: 19
- Style: Tailwind CSS 4 (CSS-first) + shadcn/ui (
@base-ui/reactjako primitives) - Form: react-hook-form + zod + @hookform/resolvers
- Toast: sonner
- TypeScript: strict +
typedRoutes
Frontend nigdy nie woła bank-api bezpośrednio z przeglądarki. Wszystkie wywołania API lecą przez:
- Server Components — pobieranie danych (read)
- Server Actions — mutacje (write, np. login, transfer)
Tokeny JWT (access + refresh) z bank-api są przechowywane wyłącznie w httpOnly cookies w domenie bank-web. Klient (przeglądarka) nigdy ich nie widzi.
┌───────────┐ httpOnly ┌───────────────────────────┐ Bearer ┌──────────┐
│ Browser │ cookie │ bank-web (Next 16) │ JWT │ bank-api │
│ (no JWT) │◄─────────►│ Server Actions + RSC │◄───────►│ (NestJS) │
└───────────┘ │ + proxy.ts (gate) │ └──────────┘
└───────────────────────────┘
- Bun ≥ 1.3
- Działający
bank-api(http://localhost:3001/api/v1)
bun install
cp .env.example .env.local
# uzupełnij BANK_API_URL i SESSION_SECRET (openssl rand -base64 64)
bun run dev # http://localhost:3000| Komenda | Opis |
|---|---|
bun run dev |
Dev server (Turbopack) |
bun run build |
Production build (standalone output) |
bun run start |
Production server (po build) |
bun run lint |
ESLint |
app/
├── (auth)/ # group route — brandowany shell
│ ├── layout.tsx
│ ├── login/page.tsx
│ ├── login/2fa/page.tsx # krok TOTP po haśle
│ └── register/page.tsx
│
├── (dashboard)/ # chronione strony klienta (server-side auth check)
│ ├── layout.tsx # /auth/me + nav + KYC banner + logout
│ ├── _components/ # nav, account-card, transaction-row, kyc-banner
│ ├── dashboard/page.tsx # suma sald + szybkie akcje + ostatnie transakcje
│ ├── accounts/ # list, [id], new
│ ├── cards/ # list, [id], new, new/success (reveal raz)
│ ├── transfers/ # list, [id], new
│ ├── loans/ # list, [id] (harmonogram), applications/new, applications/[id]
│ ├── deposits/ # list, [id], products, products/[id]/open
│ └── kyc/ # submit + status
│
├── admin/ # panel admina (RoleGuard w layoucie)
│ ├── layout.tsx # sprawdza role === ADMIN
│ ├── _components/admin-nav.tsx
│ ├── page.tsx # dashboard ze stats
│ ├── kyc/ # review queue (approve/reject z reason)
│ ├── loans/ # pending list, [id] decision forms (approve/reject/disburse)
│ ├── deposits/products/ # CRUD produktów lokat
│ └── users/ # lista z search, [id] z akcjami (suspend/activate/promote)
│
├── layout.tsx # root (Toaster, font, lang=pl)
└── page.tsx # / → redirect login lub dashboard
components/ui/ # shadcn/ui — button, input, form, card, sonner, etc.
# + link-button.tsx (Link + buttonVariants, bo nowy shadcn nie ma asChild)
lib/
├── api/
│ ├── client.ts # server-only fetch wrapper z auto-refresh 401
│ ├── types.ts # wszystkie typy API
│ ├── accounts.ts, cards.ts, transfers.ts, loans.ts, deposits.ts, kyc.ts, admin.ts
│ │ # server-only queries
│ └── *-actions.ts # server actions (login, transfer, issue card, etc.)
├── auth/
│ ├── session.ts # httpOnly cookies (access/refresh/2fa-pending)
│ └── schemas.ts # zod schemas dla wszystkich formularzy
├── env.ts # zod-walidacja env (server vs client split)
├── format.ts # formatMoney, formatDate, labels (PL)
└── utils.ts # cn() z shadcn
proxy.ts # Next 16 Proxy (dawny middleware) — gate sesji
next.config.ts # standalone output + security headers + typedRoutes
/dashboard— pulpit/accounts,/accounts/[id],/accounts/new/cards,/cards/[id],/cards/new,/cards/new/success/transfers,/transfers/[id],/transfers/new/loans,/loans/[id],/loans/applications/new,/loans/applications/[id]/deposits,/deposits/[id],/deposits/products,/deposits/products/[id]/open/kyc
/admin— dashboard ze stats/admin/kyc— kolejka wniosków/admin/loans,/admin/loans/[id]— decyzja + wypłata/admin/deposits/products,/admin/deposits/products/new/admin/users,/admin/users/[id]
- httpOnly cookies dla tokenów (nigdy
localStorage) - BFF pattern — klient nie widzi sekretów ani API URL
- Security headers: X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy
-
proxy.ts: optimistic gate na chronione ścieżki (/dashboard,/accounts,/transfers,/cards,/admin, ...) - Layouty
(dashboard)iadmin/robią pełną weryfikację przez API (defense-in-depth) - Walidacja env przez zod (fail-fast przy starcie)
- Auto-refresh 401 w
lib/api/client.ts(rotacja tokenów transparentna dla UI) - Idempotency keys generowane na kliencie przez
crypto.randomUUID()przy każdej krytycznej operacji - Confirm dialogs przy nieodwracalnych akcjach (zamknięcie konta/karty, zerwanie lokaty, suspend usera)
- CSP header — do dodania przed produkcją (po podpięciu konkretnych domen)
- Middleware przemianowane na Proxy (
proxy.ts, eksportproxy) cookies(),headers(),paramssą async (await cookies())- Domyślnie Turbopack (
bun run dev) - React 19 z React Server Components
typedRouteswłączone —redirect(...)iLink hrefweryfikowane w czasie kompilacji- Po dodaniu nowych ścieżek wymagany rebuild (
bun run build) żeby zregenerować typy routes - Najnowsze shadcn używa
@base-ui/reactzamiast Radix — nie ma propuasChildwButton. Do linków stylizowanych jak buttony używamy<LinkButton>(components/ui/link-button.tsx).



