Frontend Architecture

Pages

Drop Frontend — Pages

All pages are in src/drop-app/src/app/ using Next.js App Router file-based routing.


Page Index

Route File Type Auth Required BottomNav
/ page.tsx Server No No
/login login/page.tsx Client No No
/register register/page.tsx Client No No
/dashboard dashboard/page.tsx Client Yes Yes
/accounts accounts/page.tsx Client Yes Yes
/transactions transactions/page.tsx Client Yes Yes
/scan scan/page.tsx Client Yes Yes
/send send/page.tsx Client Yes No
/profile profile/page.tsx Client Yes Yes
/profile/personal profile/personal/page.tsx Client Yes No
/profile/security profile/security/page.tsx Client Yes No
/profile/notifications profile/notifications/page.tsx Client Yes No
/profile/language profile/language/page.tsx Client Yes No
/notifications notifications/page.tsx Client Yes Yes
/cards cards/page.tsx Client Yes No
/complaints complaints/page.tsx Client No No
/fees fees/page.tsx Client No No
/privacy privacy/page.tsx Client No No
/terms terms/page.tsx Client No No
/withdrawal withdrawal/page.tsx Client No No

Page Details

/ — Home / Marketing Page

/login — Login

/register — Registration

/dashboard — Main Dashboard

/accounts — Bank Accounts

/transactions — Transaction History

/scan — QR Scanner

/send — Send Money (Remittance)

/profile — User Profile

/withdrawal — Angrerett (Right of Withdrawal)


/complaints — Send Complaint


/privacy — Privacy Policy


/terms — Terms of Service


/fees — Fee Overview


/profile/personal — Personal Information


/profile/security — Security Settings


/profile/notifications — Notification Settings


/profile/language — Language Settings


/notifications — Notifications Center


/cards — Card Management (FUTURE — feature-flagged)

Note: Cards are a FUTURE feature, gated behind feature flags (all default to false). Requires a card issuing partner before activation.

Component Inventory

Component Inventory

Project: {{PROJECT_NAME}} Version: {{VERSION}} Date: {{DATE}} Author: {{AUTHOR}} Status: Draft | In Review | Approved Reviewers: {{REVIEWERS}}

Document History

Version Date Author Changes
0.1 {{DATE}} {{AUTHOR}} Initial draft

1. Overview

Total components: {{N}} Library location: src/components/ Storybook: {{https://storybook.PROJECT_NAME.example.com}} Design source: {{Figma file URL}} Owner team: {{TEAM_NAME}}

This inventory tracks every reusable component in {{PROJECT_NAME}}. It follows Atomic Design categorization: atoms → molecules → organisms → templates → pages.


2. Component Hierarchy Diagram

graph TB
    subgraph Pages
        PageLogin["LoginPage"]
        PageDashboard["DashboardPage"]
    end
    subgraph Templates
        TplAuth["AuthLayout"]
        TplApp["AppLayout"]
    end
    subgraph Organisms
        OrgNavbar["Navbar"]
        OrgSidebar["Sidebar"]
        OrgDataTable["DataTable"]
        OrgUserForm["UserForm"]
    end
    subgraph Molecules
        MolFormField["FormField"]
        MolCard["Card"]
        MolSearchBar["SearchBar"]
        MolDropdown["Dropdown"]
        MolPagination["Pagination"]
    end
    subgraph Atoms
        AtomButton["Button"]
        AtomInput["Input"]
        AtomBadge["Badge"]
        AtomSpinner["Spinner"]
        AtomAvatar["Avatar"]
        AtomIcon["Icon"]
    end

    Pages --> Templates
    Templates --> Organisms
    Organisms --> Molecules
    Molecules --> Atoms

TODO: Update diagram to reflect actual component tree.


3. Atoms (Primitive Components)

3.1 Button

Property Value
Category Atom
Status `{{Done
File path src/components/ui/Button/Button.tsx
Storybook {{URL}}
Design ref {{Figma frame URL}}

Props API:

Prop Type Default Required Description
variant 'primary' | 'secondary' | 'ghost' | 'danger' | 'link' 'primary' No Visual style variant
size 'sm' | 'md' | 'lg' 'md' No Button size
disabled boolean false No Disables interaction
loading boolean false No Shows spinner, disables click
leftIcon ReactNode undefined No Icon before label
rightIcon ReactNode undefined No Icon after label
onClick (e: MouseEvent) => void undefined No Click handler
type 'button' | 'submit' | 'reset' 'button' No HTML button type
fullWidth boolean false No 100% width

Variants & States: primary, secondary, ghost, danger, link × default, hover, focus, active, disabled, loading

Accessibility:

Dependencies: Icon component, Spinner component


3.2 Input

Property Value
Category Atom
Status {{Status}}
File path src/components/ui/Input/Input.tsx
Storybook {{URL}}

Props API:

Prop Type Default Required Description
type 'text' | 'email' | 'password' | 'number' | 'search' | 'tel' | 'url' 'text' No Input type
value string Yes (controlled) Input value
onChange (e: ChangeEvent) => void Yes Change handler
placeholder string '' No Placeholder text
disabled boolean false No Disabled state
error boolean false No Triggers error visual state
errorMessage string undefined No Error text (also sets aria-describedby)
leftAdornment ReactNode undefined No Icon or element left of input
rightAdornment ReactNode undefined No Icon or element right of input
size 'sm' | 'md' | 'lg' 'md' No Input height/font size

Variants & States: default, focused, error, disabled, with-left-icon, with-right-icon

Accessibility:


3.3 Badge

Property Value
Category Atom
Status {{Status}}
File path src/components/ui/Badge/Badge.tsx

Props API:

Prop Type Default Required Description
variant 'success' | 'warning' | 'error' | 'info' | 'neutral' 'neutral' No Semantic color variant
size 'sm' | 'md' 'md' No Badge size
dot boolean false No Shows colored dot instead of text
children ReactNode Yes Badge content

TODO: Add remaining atom components following the same pattern (Select, Checkbox, Radio, Toggle, Textarea, Avatar, Tooltip, Spinner, Divider).


4. Molecules (Composite Components)

4.1 FormField

Property Value
Category Molecule
Status {{Status}}
File path src/components/ui/FormField/FormField.tsx
Composes Input, Label, ErrorMessage, HelpText

Props API:

Prop Type Default Required Description
label string Yes Visible label text
htmlFor string Yes Links label to input id
error string undefined No Error message text
helpText string undefined No Helper text below input
required boolean false No Shows required indicator
children ReactNode Yes Input component

Accessibility: Label always associated with input via htmlFor/id pair.


4.2 Card

Property Value
Category Molecule
Status {{Status}}
File path src/components/ui/Card/Card.tsx

Props API:

Prop Type Default Required Description
variant 'default' | 'bordered' | 'elevated' 'default' No Visual style
padding 'sm' | 'md' | 'lg' | 'none' 'md' No Internal padding
as ElementType 'div' No Polymorphic render element
children ReactNode Yes Card content

Sub-components: Card.Header, Card.Body, Card.Footer

TODO: Add remaining molecule components: Modal, Dropdown, Table, Pagination, Toast, SearchBar, Breadcrumb, Tabs, Accordion, DatePicker.


5. Organisms (Complex Components)

5.1 DataTable

Property Value
Category Organism
Status {{Status}}
File path src/components/features/DataTable/DataTable.tsx
Dependencies Table (molecule), Pagination, SearchBar, Spinner, Badge

Features:

TODO: Document full props API for DataTable.


5.2 Navigation / Sidebar

Property Value
Category Organism
Status {{Status}}
File path src/components/layouts/Sidebar/Sidebar.tsx

TODO: Add remaining organism components.


6. Shared Hooks / Composables Inventory

Hook File Purpose Used By
useDebounce src/hooks/useDebounce.ts Debounce value changes SearchBar, Input
useLocalStorage src/hooks/useLocalStorage.ts Persistent local state Theme, preferences
useMediaQuery src/hooks/useMediaQuery.ts Responsive breakpoint checks Layout components
useClickOutside src/hooks/useClickOutside.ts Close on outside click Dropdown, Modal
useFocusTrap src/hooks/useFocusTrap.ts Trap focus inside element Modal, Drawer
useToast src/hooks/useToast.ts Trigger toast notifications Global
usePermission src/hooks/usePermission.ts Check user permissions Auth-gated components
{{HOOK_NAME}} {{PATH}} {{PURPOSE}} {{CONSUMERS}}

7. Third-Party Component Usage

Package Version Components Used Wrapping Strategy
{{@radix-ui/react-dialog}} {{1.x}} Modal base Wrapped in src/components/ui/Modal — custom styling
{{@radix-ui/react-select}} {{2.x}} Select base Wrapped in src/components/ui/Select
{{react-hook-form}} {{7.x}} Form state Used directly + FormField wrapper
{{recharts}} {{2.x}} Charts Wrapped in src/components/charts/

Policy: Never use third-party components directly in feature code — always wrap in local component to control API surface and allow future swap.


8. Component Deprecation Process

Active → Deprecated (soft) → Deprecated (hard) → Removed
Stage Action Required
Deprecated (soft) Add @deprecated JSDoc comment, console warning in dev, migration guide in Storybook
Deprecated (hard) TypeScript @deprecated annotation triggers IDE warning, added to removal milestone
Removed Delete component, update CHANGELOG, run codemod if available

Deprecation notice minimum period: 2 sprint cycles before hard removal.


Approval

Role Name Date Signature
Author
Frontend Lead
Design System Owner

Design System Documentation

Design System Documentation

Project: Drop — Fintech Payment App Version: 0.1.0 Date: 2026-02-23 Author: John (AI Director, ALAI) Status: In Review Reviewers: Alem Bašić (CEO)

Document History

Version Date Author Changes
0.1 2026-02-23 John Initial draft from source code + brand guide analysis

1. Design Principles

Principle Description
Clarity first Every UI element must communicate its purpose without explanation. Amounts are large and legible. Actions are labeled.
Scandinavian minimal Generous whitespace, no visual noise. Forest green + white — nothing extra.
Trust through transparency Fees visible before every transaction. No hidden costs. PSD2 disclosures shown explicitly.
Mobile-first The app is a mobile PWA. max-w-sm containers, pb-24 for BottomNav clearance, touch targets ≥ 44×44px.
Accessible by default WCAG AA compliance is a baseline requirement, not an optional enhancement.
Light mode only (Phase 1) Background is always off-white (#FAFCF8) or white. Dark mode is Phase 2.

2. Color System

2.1 Primitive Palette (Raw Values)

/* Brand primitives — defined in brand/colors.css and globals.css */

/* Green scale */
--color-green-primary:   #0B6E35;   /* Forest Green — primary brand */
--color-green-dark:      #095C2C;   /* Hover/pressed state */
--color-green-light:     #E8F5E9;   /* Light green tint, badges */

/* Gold scale */
--color-gold-primary:    #D4A017;   /* Gold accent, logo arrow, premium elements */
--color-gold-light:      #FFF8E1;   /* Gold tint */

/* Neutral scale */
--color-neutral-0:       #FFFFFF;   /* White — cards, elevated surfaces */
--color-neutral-50:      #FAFCF8;   /* Off-white — page background */
--color-neutral-100:     #F9FAFB;   /* Input field backgrounds */
--color-neutral-200:     #F3F4F6;   /* Section backgrounds, shadcn secondary */
--color-neutral-300:     #E5E7EB;   /* Input borders, card borders, dividers */
--color-neutral-400:     #D1D5DB;   /* Horizontal rule dividers */
--color-neutral-500:     #9CA3AF;   /* Muted text, inactive nav items */
--color-neutral-600:     #6B7280;   /* Secondary text, placeholders */
--color-neutral-700:     #374151;   /* Dark text on light backgrounds */
--color-neutral-900:     #1A1A1A;   /* Near-black — headings, primary text */
--color-neutral-1000:    #000000;

/* Semantic */
--color-success:         #10B981;   /* Emerald — success, positive amounts */
--color-error:           #EF4444;   /* Red — error states, negative amounts */
--color-warning:         #D97706;   /* Warning — pending transactions */
--color-info:            #2563EB;   /* Info — PSD2 banners */

2.2 Semantic Tokens (Light Mode — from globals.css :root)

:root {
  /* shadcn/ui CSS variable tokens */
  --background:           #FAFCF8;
  --foreground:           #1A1A1A;
  --primary:              #0B6E35;
  --primary-foreground:   #FFFFFF;
  --secondary:            #F3F4F6;
  --secondary-foreground: #1A1A1A;
  --accent:               #F3F4F6;
  --accent-foreground:    #1A1A1A;
  --muted:                #F3F4F6;
  --muted-foreground:     #6B7280;
  --destructive:          #EF4444;
  --border:               #E5E7EB;
  --input:                #E5E7EB;
  --ring:                 #0B6E35;
  --radius:               0.75rem;

  /* Drop custom semantic tokens */
  --color-drop-primary:   #0B6E35;
  --color-drop-secondary: #D4A017;
  --color-drop-accent:    #10B981;
  --color-drop-dark:      #1A1A1A;
  --color-drop-light:     #FAFCF8;
  --color-drop-error:     #EF4444;
}

2.3 Semantic Tokens (Dark Mode)

/* Phase 2 — Dark mode TBD */
[data-theme="dark"] {
  /* TBD — requires design review */
  /* Drop is light-mode-first. Dark mode planned for Phase 2. */
}

2.4 Brand Gradient

.bg-gradient-brand {
  background: linear-gradient(135deg, #0B6E35 0%, #D4A017 100%);
}

2.5 Contrast Ratios (WCAG)

Pair Text Color Background Ratio WCAG AA (4.5:1) WCAG AAA (7:1)
Primary text on page bg #1A1A1A #FAFCF8 ~18.6:1 Pass Pass
Secondary text on page bg #6B7280 #FAFCF8 ~4.7:1 Pass Fail
Primary button text #FFFFFF #0B6E35 ~5.0:1 Pass Fail
Success text #10B981 #FFFFFF ~3.0:1 Fail (large text only) Fail
Error text #EF4444 #FFFFFF ~3.9:1 Fail (large text only) Fail
Muted text #9CA3AF #FFFFFF ~2.9:1 Fail Fail

Action items: Success (#10B981), error (#EF4444), and muted (#9CA3AF) colors fail WCAG AA for small text. These are used for semantic indicators (amount colors, hints) — always paired with a non-color indicator. Full audit in accessibility-audit.md.


3. Typography

3.1 Font Families

Token Value Usage
--font-fraunces Fraunces, Georgia, "Times New Roman", serif Display/headings, logo wordmark, brand text
--font-dm-sans "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif Body text, UI labels, inputs (default body font)
--font-geist-mono "Geist Mono", "JetBrains Mono", "Fira Code", "Courier New", monospace Code, monospace elements

Why these fonts:

3.2 Type Scale

Token Size Weight Line Height Letter Spacing Usage
Display 56px 700 (Fraunces) 1.1 -0.02em Hero headlines (landing page)
H1 40px 600 (Fraunces) 1.2 -0.01em Page titles
H2 32px 600 (Fraunces) 1.3 -0.005em Section headers
H3 24px 500 (Fraunces) 1.3 normal Sub-sections
H4 20px 600 (DM Sans) 1.4 normal Card titles
Body Large 18px 400 (DM Sans) 1.7 normal Lead paragraphs
Body 16px 400 (DM Sans) 1.6 normal Default text
Body Small 14px 400 (DM Sans) 1.5 normal Secondary content, captions
Label 14px 500 (DM Sans) 1.4 0.01em Form labels, UI labels
Caption 12px 500 (DM Sans) 1.4 0.02em Timestamps, meta, hints

3.3 In-App Tailwind Typography Patterns

Context Tailwind Class Example
Logo wordmark font-[family-name:var(--font-fraunces)] text-3xl font-bold "drop" on login
Page heading text-xl font-semibold text-[#1A1A1A] Dashboard greeting
Section heading text-lg font-semibold text-[#1A1A1A] "Siste transaksjoner"
Form label text-sm font-medium text-[#1A1A1A] "E-post"
Body text Inherited DM Sans General content
Muted text text-sm text-[#6B7280] Descriptions, timestamps
Small text text-xs text-[#9CA3AF] Footers, hints
Balance (large) text-2xl font-bold text-[#1A1A1A] Balance display
Amount (list) text-sm font-semibold + color Transaction amounts

4. Spacing & Layout

4.1 Spacing Scale (4px Base Unit — Tailwind defaults)

Tailwind Value Usage
p-1 4px Micro gaps
p-2 8px Tight inline spacing
p-3 12px Compact elements
p-4 16px Default content spacing
p-6 24px Card padding, section padding
p-8 32px Large section gaps
px-6 24px Page horizontal padding (standard)
pb-24 96px Bottom padding (BottomNav clearance)

4.2 Page Structure Patterns

/* Standard authenticated page */
min-h-screen bg-[#FAFCF8]      /* Off-white background */
px-6 pb-24 pt-6               /* Padding: horizontal 24px, bottom 96px (nav), top 24px */

/* Login / onboarding (centered) */
min-h-screen bg-[#EEEEEE]      /* Slightly darker bg for auth pages */
max-w-sm mx-auto               /* 384px max width, centered */

/* Card pattern (standard) */
rounded-2xl bg-white p-6 shadow-sm

/* Card pattern (compact) */
rounded-xl bg-white p-4 shadow-sm

4.3 Responsive Breakpoints (Tailwind defaults)

Name Min Width Usage
Default 0px Mobile — primary design target
sm 640px Larger phones
md 768px Tablets — landing page 2-column grid
lg 1024px Desktop — landing page 3-column grid

Note: The app is mobile-first. max-w-sm containers keep content readable on wider screens.

4.4 Bottom Nav

The BottomNav is fixed bottom-0 left-0 right-0 with h-16 border-t bg-white. All pages that include BottomNav must add pb-24 to their content container.


5. Component Library

5.1 Custom Drop Components

Component File Status Description
BottomNav components/bottom-nav.tsx Done Fixed 5-tab navigation
DropLogo components/drop-logo.tsx Done Forward-D SVG mark
DropWordmark components/drop-logo.tsx Done "drop" in Fraunces
DropLogoFull components/drop-logo.tsx Done Logo mark + wordmark
DropAppIcon components/drop-logo.tsx Done App icon — rounded green square
CookieConsent components/cookie-consent.tsx Done GDPR consent banner + modal
PrePaymentDisclosure components/pre-payment-disclosure.tsx Done PSD2 pre-payment modal
PWARegister components/pwa-register.tsx Done Service Worker registration
drop-icons components/drop-icons.tsx Done 9 custom fintech icons

5.2 shadcn/ui Primitive Components (Atoms)

Component File Radix Primitive Status
Alert ui/alert.tsx — (div-based) Done
Avatar ui/avatar.tsx @radix-ui/react-avatar Done
Badge ui/badge.tsx — (cva variants) Done
Button ui/button.tsx @radix-ui/react-slot Done
Card ui/card.tsx — (div-based) Done
Dialog ui/dialog.tsx @radix-ui/react-dialog Done
Input ui/input.tsx — (input element) Done
ScrollArea ui/scroll-area.tsx @radix-ui/react-scroll-area Done
Select ui/select.tsx @radix-ui/react-select Done
Separator ui/separator.tsx @radix-ui/react-separator Done
Sheet ui/sheet.tsx @radix-ui/react-dialog Done
Skeleton ui/skeleton.tsx — (pulse animation) Done
Sonner ui/sonner.tsx sonner toast library Done
Tabs ui/tabs.tsx @radix-ui/react-tabs Done

5.3 Layout Components

Component Description
Page wrapper min-h-screen bg-[#FAFCF8] px-6 pb-24 pt-6
Auth wrapper min-h-screen flex items-center justify-center bg-[#EEEEEE]
Card rounded-2xl bg-white p-6 shadow-sm
Scroll area ScrollArea from shadcn/ui for transaction lists

6. Iconography Guidelines

Item Standard
Primary library lucide-react
Delivery Inline SVG via React component
Sizes h-4 w-4 (inline/small), h-5 w-5 (nav/buttons), h-6 w-6 (feature icons)
Stroke width 1.5px at 24px (Lucide default)
Color currentColor — inherits from parent text color
Custom icons components/drop-icons.tsx — 9 domain-specific icons
Social auth Inline SVG — BankID (green rounded rect, "ID"), Vipps (orange circle, "V")

Custom Drop Icons:

Export Description
IconSendMoney Arrow going up-right from horizontal line
IconQrScan QR code frame with scan corners
IconVirtualCard Credit card outline
IconShield Shield with checkmark
IconFastTransfer Lightning bolt
IconCorridors Globe with meridians
IconWallet Wallet outline (unused — no wallet in pass-through model)
IconHistory Clock with arrow
IconTopUp Plus inside circle (unused — no top-up in pass-through model)

Accessibility rule: Icons conveying meaning must have aria-label. Decorative icons: aria-hidden="true".


7. Motion & Animation Standards

7.1 Duration Tokens

Usage Value
Button hover transition-colors (CSS default ~150ms)
Focus states transition-colors on border/ring
Loading states Text replacement ("Logger inn...", "Sender...") or Skeleton
Scanner frame animate-pulse on scan page corners
Complex animations None in Phase 1 — simplicity-first

7.2 Easing Tokens

Token Value Usage
--ease-default cubic-bezier(0.4, 0, 0.2, 1) General UI transitions
--ease-enter cubic-bezier(0, 0, 0.2, 1) Elements entering
--ease-exit cubic-bezier(0.4, 0, 1, 1) Elements leaving

7.3 Reduced Motion

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

Rule: Every animated component MUST respect prefers-reduced-motion.


8. Accessibility Requirements Per Component

Requirement Buttons Inputs Modals BottomNav Transaction List
Keyboard accessible Required Required Required Required Required
Focus visible ring-[#0B6E35] focus:border-[#0B6E35] Focus trap Required Required
ARIA role button textbox dialog nav list/listitem
Screen reader label Visible text or aria-label <label> associated aria-labelledby aria-label="Navigasjon"
Error state Text message text-[#EF4444]
Touch target h-12 (48px) h-11 (44px) h-16 (64px) min-h-[44px]

9. Design Token Format

9.1 CSS Custom Properties (Source of Truth)

/* src/app/globals.css */
:root {
  --color-drop-primary:   #0B6E35;
  --color-drop-secondary: #D4A017;
  --color-drop-accent:    #10B981;
  --color-drop-dark:      #1A1A1A;
  --color-drop-light:     #FAFCF8;
  --color-drop-error:     #EF4444;
  /* shadcn/ui tokens... */
}

9.2 Tailwind Config Extension

// tailwind.config.ts
export default {
  theme: {
    extend: {
      colors: {
        drop: {
          primary:   '#0B6E35',
          secondary: '#D4A017',
          accent:    '#10B981',
          dark:      '#1A1A1A',
          light:     '#FAFCF8',
          error:     '#EF4444',
        }
      },
      fontFamily: {
        fraunces: ['var(--font-fraunces)', 'Georgia', 'serif'],
        sans:     ['var(--font-dm-sans)', 'system-ui', 'sans-serif'],
        mono:     ['var(--font-geist-mono)', 'monospace'],
      }
    }
  }
}

9.3 JavaScript/TypeScript Constants (for charting/canvas)

// For use in non-CSS contexts (future charts, canvas)
export const dropColors = {
  primary:   '#0B6E35',
  secondary: '#D4A017',
  accent:    '#10B981',
  dark:      '#1A1A1A',
  light:     '#FAFCF8',
  error:     '#EF4444',
} as const;

Token update process: Figma → update globals.css CSS variables → update Tailwind config → PR review.


10. Component Code Patterns

Primary Button

<Button className="h-12 w-full rounded-xl bg-[#0B6E35] text-sm font-semibold text-white shadow-sm hover:bg-[#095C2C]">
  Send penger
</Button>

Outline Button

<button className="flex h-12 flex-1 items-center justify-center gap-2 rounded-xl border border-[#E5E7EB] bg-white text-sm font-medium text-[#1A1A1A] hover:bg-[#F9FAFB]">
  Avbryt
</button>

Text Input with Left Icon

<div className="relative">
  <Mail className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-[#6B7280]" />
  <input
    className="h-11 w-full rounded-lg border border-[#E5E7EB] bg-[#F9FAFB] pl-10 pr-3 text-sm outline-none focus:border-[#0B6E35] focus:ring-1 focus:ring-[#0B6E35]"
    type="email"
    placeholder="din@epost.no"
  />
</div>

Error Message

<p className="rounded-md bg-[#EF4444]/10 p-2 text-sm text-[#EF4444]">
  {error}
</p>

Primary Badge

<span className="rounded-full bg-[#0B6E35]/10 px-2 py-0.5 text-xs font-medium text-[#0B6E35]">
  Primær konto
</span>

Avatar (Initials)

<div className="flex h-10 w-10 items-center justify-center rounded-full bg-[#0B6E35] text-sm font-bold text-white">
  {initials}
</div>

Transaction Amount (Positive / Negative)

// Positive (received)
<span className="text-sm font-semibold text-[#10B981]">+1 250,00 NOK</span>

// Negative (sent)
<span className="text-sm font-semibold text-[#EF4444]">-500,00 NOK</span>

Approval

Role Name Date Signature
Author John (AI Director) 2026-02-23
Lead Designer
Frontend Lead
Accessibility Reviewer

State Management Architecture

State Management Architecture

Project: Drop — Fintech Payment App Version: 0.1.0 Date: 2026-02-23 Author: John (AI Director, ALAI) Status: In Review Reviewers: Alem Bašić (CEO)

Document History

Version Date Author Changes
0.1 2026-02-23 John Initial draft from source code analysis

1. State Architecture Overview

Drop uses a deliberately simple, lightweight state management approach. There are no global state libraries (Redux, Zustand, Jotai, etc.). All state is local component state.

Layer Mechanism Scope
Auth / user state useAuth() custom hook Per-component (fetches /api/auth/me on each mount)
Feature flags useFeatureFlag() hook Per-component (reads NEXT_PUBLIC_FF_* env vars)
Page / API data useState + useEffect fetch Component-local
Form state useState per field Component-local
UI state (modals, tabs, steps) useState Component-local
Navigation Next.js useRouter / usePathname Framework-provided
Auth token (web) httpOnly cookie Server-set, never in JS
Auth token (mobile) Bearer token in-memory (let token) + AsyncStorage Module-level in lib/api.js

Guiding principle: Keep it simple. No global state = no accidental shared state bugs. API data is fetched fresh on each page mount. User authentication is the only cross-cutting concern, handled by the useAuth() hook.


2. Data Flow Diagram

flowchart TD
    User["User Interaction"] --> Component["Component"]

    Component -->|"Auth check (every mount)"| AuthHook["useAuth() Hook\nGET /api/auth/me"]
    Component -->|"Data fetch (useEffect)"| LocalState["useState + useEffect\nLocal component state"]
    Component -->|"Form input"| FormState["useState per field\nComponent-local"]
    Component -->|"Navigate"| Router["Next.js Router\nuseRouter / usePathname"]
    Component -->|"Feature check"| FlagHook["useFeatureFlag()\nReads NEXT_PUBLIC_FF_*"]

    AuthHook -->|"cookie auth"| BFF["BFF API Routes\n/api/auth/me"]
    LocalState -->|"fetch with credentials"| BFF
    BFF -->|"User object + data"| Component

    FlagHook -->|"env var read"| EnvVars["process.env\nNEXT_PUBLIC_FF_*"]

3. State Categories

3.1 Auth State — useAuth() Hook

File: src/lib/use-auth.ts

Interface:

function useAuth(redirectIfUnauthenticated?: boolean): {
  user: User | null;
  loading: boolean;
  logout: () => Promise<void>;
  refreshUser: () => Promise<void>;
}
// Default: redirectIfUnauthenticated = true

User Model:

interface User {
  id: string;
  email: string;
  firstName: string;
  lastName: string;
  totalBalance: number;
  bankAccounts: BankAccount[];
  kycStatus: string;
}

interface BankAccount {
  id: string;
  bankName: string;
  accountNumber: string;
  balance: number;
  currency: string;
  isPrimary: boolean;
}

Behavior:

  1. On mount: fetches GET /api/auth/me with credentials: "include" (httpOnly cookie auth)
  2. If 401 and redirectIfUnauthenticated is true: redirects to /login
  3. logout(): calls POST /api/auth/logout, clears cookie server-side, redirects to /login
  4. refreshUser(): re-fetches /api/auth/me to update user state

Auth flow:

Login page → POST /api/auth/login → httpOnly cookie set → router.push("/dashboard")
Dashboard → useAuth() → GET /api/auth/me → User object
Logout → POST /api/auth/logout → cookie cleared → redirect /login

Usage patterns:

// Standard protected page (redirects if unauthenticated)
const { user, loading } = useAuth();
if (loading) return <Skeleton />;
// user is guaranteed non-null after loading

// Page that checks auth without redirect
const { user } = useAuth(false);

Pages using this hook (data source):


3.2 Feature Flags — useFeatureFlag() Hook

File: src/lib/feature-flags.ts

Available Flags:

Flag Name Default Used In
virtualCards false cards page (gate — shows "not available" if false)
physicalCards false cards page (order physical card option)
cardDetails false cards page (show full card details)
cardFreeze false cards page (freeze/unfreeze)
cardPin false cards page (change PIN)
spendingLimits false cards page (spending limits)
notifications true notification features
merchantDashboard true merchant page (gate)

Environment Variable Pattern:

NEXT_PUBLIC_FF_VIRTUAL_CARDS=true
NEXT_PUBLIC_FF_PHYSICAL_CARDS=false
NEXT_PUBLIC_FF_NOTIFICATIONS=true
NEXT_PUBLIC_FF_MERCHANT_DASHBOARD=true

Convention: NEXT_PUBLIC_FF_ + SCREAMING_SNAKE_CASE version of flag name.

API:

// Server-side (API routes / middleware)
isEnabled(flagName: string): boolean
getAllFlags(): Record<string, boolean>
featureGate(flagName: string): middleware  // Returns 404 if flag disabled

// Client-side (React hooks)
useFeatureFlag(flagName: string): boolean
useFeatureFlags(): Record<string, boolean>

Usage pattern:

// Page-level gate
const cardsEnabled = useFeatureFlag("virtualCards");
if (!cardsEnabled) return <div>Feature ikke tilgjengelig</div>;

// Conditional rendering
const physicalEnabled = useFeatureFlag("physicalCards");
{physicalEnabled && <OrderPhysicalCard />}

3.3 Page Data — useState + useEffect Fetch (Pattern 1)

Most pages fetch data in a useEffect on mount. No SWR, React Query, or other caching library is used.

const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
  fetch("/api/endpoint", { credentials: "include" })
    .then(res => res.json())
    .then(json => setData(json.data))
    .catch(() => {})  // Silent error — empty state
    .finally(() => setLoading(false));
}, []);

Pages using this pattern:


3.4 Form State — useState Per Field (Pattern 3)

Form pages use async handlers that POST data and handle success/error states. No form library.

const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);

const handleSubmit = async () => {
  setLoading(true);
  setError("");
  const res = await fetch("/api/auth/login", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    credentials: "include",
    body: JSON.stringify({ email, password }),
  });
  if (res.ok) {
    router.push("/dashboard");
  } else {
    const data = await res.json();
    setError(data.message || "Noe gikk galt");
  }
  setLoading(false);
};

Pages using this pattern:


3.5 Filter-Driven Refetch (Pattern 4)

Transactions and notification pages refetch when filter changes via useEffect dependency.

const [filter, setFilter] = useState("all");

useEffect(() => {
  fetch(`/api/transactions?type=${filter}&limit=50`, { credentials: "include" })
    .then(res => res.json())
    .then(json => setData(json.transactions))
    .catch(() => {})
    .finally(() => setLoading(false));
}, [filter]);

Pages using this pattern:


4. API Routes (BFF Layer)

All API routes are under src/app/api/. The frontend calls these endpoints via the Next.js BFF (which translates httpOnly cookie auth to Bearer token for the Hono backend).

Endpoint Method Called From
/api/auth/login POST login page
/api/auth/logout POST useAuth hook, profile page
/api/auth/me GET useAuth hook (every protected page mount)
/api/auth/register POST register page
/api/transactions GET dashboard, transactions pages
/api/transactions/remittance POST send page
/api/transactions/qr-payment POST scan page
/api/recipients GET send page
/api/rates GET send page
/api/notifications GET notifications page
/api/notifications PATCH notifications page (mark read)
/api/settings GET profile/notifications, profile/language
/api/settings PATCH profile/notifications, profile/language
/api/complaints POST complaints page
/api/consents POST cookie-consent component
/api/cards GET/POST cards page (feature-flagged)
/api/cards/{id} PATCH cards page (freeze/unfreeze, feature-flagged)
/api/cards/{id} DELETE cards page (delete card, feature-flagged)
/api/merchants/dashboard GET merchant page
/api/merchants/transactions GET merchant page
/api/merchants/qr GET merchant page

5. Mobile State Management

File: src/drop-mobile/lib/api.js

The mobile app uses a different auth mechanism — Bearer token instead of httpOnly cookie.

// Token stored at module level (in-memory)
let token = null;

export const setToken = (t) => { token = t; };
export const getToken = () => token;

// All requests include auth header
const request = async (endpoint, options = {}) => {
  const headers = {
    "Content-Type": "application/json",
    ...(token && { Authorization: `Bearer ${token}` }),
    ...options.headers,
  };
  // ...
};

Token persistence: On login, token stored in AsyncStorage for 7-day lifetime. On app launch, token loaded from AsyncStorage and set via setToken().

Mobile state patterns:


6. Persistent State

Data Storage Notes
Auth session (web) httpOnly cookie Server-set, 7-day lifetime, never in JS
Auth token (mobile) Bearer token, AsyncStorage 7-day lifetime, device-bound
Cookie preferences localStorage CookieConsent component
Language preference Server-side (/api/settings) Synced on login
Push notification prefs Server-side (/api/settings) Synced on page load

RULE: Auth tokens NEVER in localStorage. httpOnly cookies on web, AsyncStorage (module-level) on mobile.


7. State Debugging

Tool Usage Enabled In
React DevTools Component state tree inspection Dev only
__DEV__ flag Enables demo credentials on login Dev only
Network tab Inspect /api/auth/me response Dev only
Console logs API client logs (mobile) Dev only

No state management devtools — not needed without a global store.


8. Performance Considerations

No Unnecessary Re-renders

Fresh Data on Navigation

Future Optimization Path (if needed)

  1. Add SWR or TanStack Query for caching with stale-while-revalidate
  2. Persist query cache across navigation (currently re-fetches on each visit)
  3. Optimistic updates for settings toggles (partially implemented in notification settings)

Approval

Role Name Date Signature
Author John (AI Director) 2026-02-23
Frontend Lead
Tech Lead

Landing Pages

Drop Frontend — Landing Pages

Covers the static marketing site (landing/) and the standalone web prototype (src/drop-web/).


Marketing Landing Page

File: landing/index.html (~646 lines)

Tech Stack

Page Sections

# Section Description
1 Nav DropLogoFull SVG + links (Tjenester, Priser, Om oss) + Logg inn / Kom i gang buttons
2 Hero Headline "Enklere betalinger for alle i Norge", subtext, 2 CTA buttons, phone mockup placeholder
3 Trust Bar 3 stats: "0.5% gebyr", "<2 timer leveringstid", "30+ land"
4 Features 3 cards: Send penger (globe icon), QR-betaling (QR icon), Virtuelt kort (card icon)
5 Calculator Transfer calculator with amount input, country dropdown, live conversion display
6 How It Works 4 steps: Last ned → Koble bank → Velg mottaker → Send
7 Social Proof / Early Access Waitlist signup form with email input
8 CTA Final call-to-action with app store buttons (placeholder)
9 Footer Logo, company info (ALAI Holding AS, Org.nr 932 516 136), nav links to sub-pages

CSS Animations

@keyframes fadeUp    /* Scroll-triggered fade in from below */
@keyframes float     /* Gentle floating on phone mockup */
@keyframes shimmer   /* Shimmer effect on CTA buttons */
@keyframes pulse     /* Subtle pulse on trust badges */

Colors Used

--drop-green: #0B6E35;
--drop-gold: #D4A017;
--drop-dark: #1A1A1A;
--drop-light: #FAFCF8;
--drop-gray: #6B7280;
--drop-border: #E5E7EB;

Responsive Breakpoints


Sub-Pages

Directory: landing/pages/

12 static HTML pages linked from the footer:

File Route (relative) Content
cookies.html /pages/cookies Cookie policy
karriere.html /pages/karriere Careers page
kontakt.html /pages/kontakt Contact form / info
lisenser.html /pages/lisenser Licenses and regulatory info
om-drop.html /pages/om-drop About Drop
personvern.html /pages/personvern Privacy policy
presse.html /pages/presse Press / media kit
priser.html /pages/priser Pricing
qr-betaling.html /pages/qr-betaling QR payment feature page
send-penger.html /pages/send-penger Send money feature page
sikkerhet.html /pages/sikkerhet Security overview
vilkar.html /pages/vilkar Terms and conditions

Waitlist Script

File: landing/pages/waitlist.js


Standalone Web Prototype (drop-web)

File: src/drop-web/index.html (~1305 lines)

Overview

This is an older/alternative prototype with a different design system. It predates the main Next.js app and serves as a standalone demo.

Key Differences from Main App

Aspect Main App (drop-app) Web Prototype (drop-web)
Framework Next.js (React) Vanilla JS (single file)
Theme Light (#FAFCF8 bg) Dark (#0D1117 bg)
Primary green #0B6E35 (Forest Green) #00C853 (Bright Green)
Fonts Fraunces + DM Sans Outfit + Plus Jakarta Sans
API port localhost:3000 localhost:3001
Routing File-based (App Router) JS function showScreen()

Screens

Screen Description
Welcome Dark bg, "drop." wordmark, "Penger uten grenser" tagline, login/register buttons
Login Email + password form, BankID/Vipps buttons, validation
App (Dashboard) 4 tabs: Hjem, Send, QR, Profil
Hjem tab Balance card (dark green gradient), recent transactions
Send tab Recipient selection, amount input, country/currency picker
QR tab Scanner placeholder with merchant list
Profil tab User info, contacts, settings, logout

Architecture

Status

This prototype appears to be an earlier iteration or demo showcase. The main drop-app (Next.js) is the production codebase. The drop-web prototype uses different colors, fonts, and a dark theme that does not match the current brand guide.