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 File: app/page.tsx Type: Server component (no "use client") Auth: None Components used: DropLogoFull, DropAppIcon, Link, Image, custom icons (IconSendMoney, IconQrScan, IconVirtualCard, IconShield, IconFastTransfer, IconCorridors) Data: Static arrays features (3 items) and stats (3 items) defined inline Sections: Header with DropLogoFull + nav links (Tjenester, Priser, Om oss, Logg inn, Kom i gang) Hero with headline, subtext, CTA buttons, phone mockup placeholder Features grid (Send penger, Betal med QR, Virtuelt kort) Stats bar (0.5% Gebyr, <2t Leveringstid, 30+ Land) Trust section (BankID verified, Rask overføring, 30+ land) Merchant CTA section Footer with ALAI Holding AS credit /login — Login File: app/login/page.tsx Type: Client component Auth: None (entry point) Components used: Image, Link, Button, Mail/Lock/Eye/EyeOff/ArrowRight (lucide) State: email, password, showPassword, error, loading Data fetching: POST /api/auth/login with { email, password } Validation: Email regex ^[^\s@]+@[^\s@]+\.[^\s@]+$ , required fields check On success: router.push("/dashboard") Social login buttons: BankID, Vipps (UI only, not functional) Dev mode: Shows demo credentials amir@example.com / demo1234 /register — Registration File: app/register/page.tsx Type: Client component Auth: None Components used: ArrowLeft/ArrowRight/Check/Eye/EyeOff/Phone/Mail/User/Calendar/Lock (lucide), Button State: step (1-4), form fields, otp, pin, errors Steps: Info — firstName, lastName, email, phone (+47 prefix), dateOfBirth, password Verify — 6-digit OTP input (MVP: any 6 digits accepted) PIN — 4-digit PIN with custom numpad UI Success — Welcome message, redirect to /dashboard Validation: Age >= 18 (calculated from dateOfBirth) XSS protection: names reject < > " ' & ; ( ) { } [ ] Password: min 8 chars, must contain letters AND numbers Phone: must be 8 digits (Norwegian format) Data fetching: POST /api/auth/register /dashboard — Main Dashboard File: app/dashboard/page.tsx Type: Client component Auth: Yes ( useAuth() with redirect) Components used: DropLogo, BottomNav, ScrollArea, Bell/LogOut (lucide) State: transactions, loading Data fetching: GET /api/transactions?limit=10 Interfaces: Transaction { id, type, status, amount, currency, recipientName, createdAt } Layout: Header: DropLogo + notification bell + logout + avatar initials Balance card: primary account balance, formatted NOK Action buttons: Send penger (→ /send), Skann QR (→ /scan) Recent transactions list in ScrollArea BottomNav /accounts — Bank Accounts File: app/accounts/page.tsx Type: Client component Auth: Yes ( useAuth() ) Components used: BottomNav, Card, ArrowLeft/Landmark/Plus/ChevronRight (lucide) Data: Reads user.bankAccounts from auth hook (no separate fetch) Layout: PSD2/Open Banking info banner (blue) Account cards: bankName, masked accountNumber, balance, currency, isPrimary badge Total balance summary "Legg til bankkonto" button (BankID connection note) /transactions — Transaction History File: app/transactions/page.tsx Type: Client component Auth: Yes ( useAuth() ) Components used: BottomNav, Tabs/TabsList/TabsTrigger, ArrowLeft/Clock (lucide) State: transactions, filter, loading Data fetching: GET /api/transactions?type={filter}&limit=50 Filters: Alle (all), Overforinger (remittance), QR-betalinger (qr_payment) Grouping: groupByDate() function groups into: I dag, I gar, Denne uken, Eldre Display: Amount with +/- prefix and color coding (green for received, red for sent) /scan — QR Scanner File: app/scan/page.tsx Type: Client component Auth: Yes ( useAuth() ) Components used: BottomNav, Button, ArrowLeft/Camera/Check/X/Store (lucide) State: scanState (scanning | payment | paying | success), scannedMerchant, amount, paymentResult Interfaces: ScannedMerchant { id, name, category } , PaymentResult { id, status, amount, fee, merchant } Flow: Scanning — Camera viewfinder UI with scan frame, "Simuler skanning" button (demo) Payment — Shows merchant info, amount input, 1% fee calculation Paying — Loading spinner Success — Confirmation with transaction details Data fetching: POST /api/transactions/qr-payment with { merchantId, amount } Demo merchant: "Ahmetov Kebab" (id: "merchant_001", category: "Restaurant") /send — Send Money (Remittance) File: app/send/page.tsx Type: Client component Auth: Yes ( useAuth() ) Components used: Button, ArrowLeft/ArrowRight/Check/ChevronDown/Globe/User (lucide) State: step (1-4), selectedRecipient, amount, recipients, rates, sending, txResult Steps: Select Recipient — List from GET /api/recipients , shows name + country flag Enter Amount — NOK input, real-time conversion with exchange rate, 0.5% fee display Review — Summary of recipient, amount, rate, fee, total Success — Confirmation with reference number Data fetching: GET /api/recipients (on mount) GET /api/rates (on mount) POST /api/transactions/remittance with { recipientId, amountNOK, targetCurrency } Country flags: RS (Serbia), BA (Bosnia), TR (Turkey), PK (Pakistan), PL (Poland) Interface: TxResult { id, status, amount, fee, rate, recipientName, targetAmount, targetCurrency } /profile — User Profile File: app/profile/page.tsx Type: Client component Auth: Yes ( useAuth() ) Components used: BottomNav, ArrowLeft/ChevronRight/LogOut/Settings/Shield/HelpCircle/Bell/CreditCard/Landmark (lucide) Data: Reads user from auth hook Layout: User info card with initials avatar (green bg), full name, email Menu items: Mine kontoer (→ /accounts), Varsler, Innstillinger, Sikkerhet, Hjelp og stotte Logout button with confirmation Version: "Drop v0.1.0 · ALAI Holding AS" /withdrawal — Angrerett (Right of Withdrawal) File: app/withdrawal/page.tsx Type: Client component Auth: No Components used: ChevronLeft, RotateCcw, CheckCircle (lucide) State: submitted, loading Purpose: Norwegian angrerettloven compliance — 14-day right of withdrawal form Layout: Info section explaining angrerett (right to cancel service agreement within 14 days) Warning banner: Angrerett does not apply to completed payment transactions, only to the service agreement itself Form with optional reason dropdown (not_needed, alternative, not_satisfied, other) and comment textarea Submit button (red) with AML retention notice (data kept for 5 years per hvitvaskingsloven) Success screen with confirmation message (14-day processing time) /complaints — Send Complaint File: app/complaints/page.tsx Type: Client component Auth: Yes ( useAuth() ) Components used: MessageSquare, CheckCircle, ChevronLeft, ExternalLink (lucide) State: submitted, loading, formData (category, subject, description) Data fetching: POST /api/complaints with { category, subject, description } Purpose: Finansavtaleloven §3-53 compliance — formal complaint submission with 15 business day response requirement Layout: Info text: All complaints taken seriously, up to 15 business days processing time Form with required fields: Category dropdown: transaction, fees, service, privacy, other Subject text input (max 200 chars) Description textarea (max 2000 chars) Submit button with POST to /api/complaints External complaint authority section: Finansklagenemnda (FinKN) contact info with link to finansklagenemnda.no Success screen: Complaint received, 15 business day review commitment /privacy — Privacy Policy File: app/privacy/page.tsx Type: Client component Auth: None Components used: ChevronLeft, Shield (lucide) Purpose: GDPR-compliant privacy policy page (Norwegian language) Sections: Behandlingsansvarlig: ALAI Holding AS as data controller Hvilke opplysninger vi samler inn: Identification (name, email, phone, BankID), Financial data (bank accounts via Open Banking, transaction history), Technical data (IP, device, app version) Formaal med behandlingen: Transaction processing (PSD2/PISP), KYC/AML compliance, AML/terrorism prevention, service improvement Rettslig grunnlag: Contract (GDPR 6(1)(b)), Legal obligation (6(1)(c)) for AML/KYC, Legitimate interest (6(1)(f)) for service improvement Dine rettigheter: Innsyn (access), Retting (rectification), Sletting (erasure with 5-year AML retention), Dataportabilitet (portability) Oppbevaring: Minimum 5 years per hvitvaskingsloven, anonymization on account deletion but AML data retained Kontakt: personvern@getdrop.no, complaint to Datatilsynet /terms — Terms of Service File: app/terms/page.tsx Type: Client component Auth: None Components used: ChevronLeft, FileText (lucide) Purpose: Legal terms of service (Norwegian language) Sections: Om tjenesten: Drop as PISP/AISP under PSD2, provided by ALAI Holding AS Krav til brukere: 18+ age, Norwegian residency with BankID, KYC required, Norwegian phone (+47) Betalingsmodell: Drop never holds customer money, pass-through model via Open Banking Gebyrer: All fees shown before transaction confirmation, see fees page for full list Ansvar: Drop responsible for correct payment execution per betalingstjenesteloven, refund rights per law, not liable for bank/recipient delays Misbruk og sperring: Right to block accounts for suspected AML/fraud, mandatory STR reporting to Økokrim/EFE Angrerett: 14-day withdrawal right per angrerettloven (does not apply to completed transactions) Tvister: Norwegian law, Oslo tingrett jurisdiction, complaint to Finansklagenemnda /fees — Fee Overview File: app/fees/page.tsx Type: Client component Auth: None Components used: ChevronLeft, Receipt (lucide) Purpose: Transparent fee disclosure page (Norwegian language) Sections: Overføring til utlandet: Transaction fee: 1.5% per transfer Currency markup: 0.5% on mid-market rate Typical total: ~2% (compare with banks at 3-7%) QR-betaling i butikk: For customer: Free (no charge to payer) For merchant: 0.5% (lower than card terminals) Kontotjenester: Account creation: Free Monthly fee: Free Bank account linking (AISP): Free Viktig informasjon: Fees can change with 30-day notice Exchange rates updated in real-time from market Always see final amount before confirming /profile/personal — Personal Information File: app/profile/personal/page.tsx Type: Client component Auth: Yes ( useAuth() ) Components used: ChevronLeft, ShieldCheck (lucide), BottomNav State: Reads user from auth hook Layout: User avatar with initials (green gradient) Full name and email display Read-only form fields: firstName, lastName, email, phone (+47 987 65 432), dateOfBirth (15. mars 1995) BankID verification badge (green banner with ShieldCheck icon) Note: All fields are disabled (cannot edit) — verified via BankID /profile/security — Security Settings File: app/profile/security/page.tsx Type: Client component Auth: Yes ( useAuth() ) Components used: ChevronLeft, ChevronRight, Lock, Smartphone, Laptop (lucide), BottomNav Sections: Passord: Change password button (shows "Sist endret: Aldri") To-faktor autentisering: BankID verification: Active (green badge) Vipps verification: Not activated (gray badge) Aktive enheter: iPhone 15 Pro: Oslo, Norge — Aktiv nå (green dot indicator) MacBook Pro: Oslo, Norge — I går kl. 18:45 Footer: Support contact (support@getdrop.no) Note: UI only, no actual functionality connected /profile/notifications — Notification Settings File: app/profile/notifications/page.tsx Type: Client component Auth: Yes ( useAuth() ) Components used: ChevronLeft, Bell, Mail (lucide), BottomNav State: pushEnabled, emailEnabled, settingsLoaded Data fetching: GET /api/settings (on mount) PATCH /api/settings with { pushEnabled: boolean } or { emailEnabled: boolean } (on toggle) Layout: Push-varsler toggle switch (Bell icon) E-postvarsler toggle switch (Mail icon) Behavior: Toggles immediately update state and send PATCH request, revert on failure /profile/language — Language Settings File: app/profile/language/page.tsx Type: Client component Auth: Yes ( useAuth() ) Components used: ChevronLeft, Check (lucide), BottomNav State: selected, saving Data fetching: GET /api/settings (on mount) PATCH /api/settings with { language: string } (on save) Languages: nb (Norsk Bokmål), en (English), bs (Bosanski), sq (Shqip) Layout: Language list with radio selection (green checkmark for selected) "Lagre" button (green) to save selection Behavior: Selection updates local state, user must click "Lagre" to persist /notifications — Notifications Center File: app/notifications/page.tsx Type: Client component Auth: Yes ( useAuth() ) Components used: BottomNav, ArrowLeft, Bell, ArrowUpRight, ScanLine, Smartphone, TrendingUp (lucide) State: notifications, fetching Data fetching: GET /api/notifications (on mount) PATCH /api/notifications with { notificationIds: [ids] } (mark read, fire-and-forget) Interface: Notification { id, type, title, body, read, createdAt } Layout: Header with back button + "Varsler" title Empty state: Bell icon + "Ingen varsler enna" message Grouped notifications: I DAG / I GÅR / date groups Notification cards: icon based on type, title, body, timestamp, unread dot indicator BottomNav Notification types: transaction_complete (green), qr_payment (yellow), security (blue), rate_update (yellow), default (gray) Auto-read: Automatically marks all unread notifications as read on page load Time formatting: "I dag kl. HH:MM", "I går kl. HH:MM", or "DD.MM.YYYY kl. HH:MM" /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. File: app/cards/page.tsx Type: Client component Auth: Yes ( useAuth() ) Feature flags: virtualCards (gate), physicalCards , cardPin , spendingLimits — all default to false Components used: Button, Dialog, various lucide icons (CreditCard, Plus, ArrowLeft, Smartphone, Eye, EyeOff, Lock, X, Check, Copy, RefreshCw) State: cards, selectedCard, showDetails, showOrderForm, orderForm, loading Data fetching: GET /api/cards (list) POST /api/cards (create virtual) POST /api/cards with type "physical" + address (order physical) PATCH /api/cards/{id} with {status: "frozen" | "active"} DELETE /api/cards/{id} Card visual: Green gradient background, masked card number (•••• •••• •••• 4242), expiry, cardholder name 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: Renders as Outline Button Text Input with Left Icon
Error Message

{error}

Primary Badge Primær konto Avatar (Initials)
{initials}
Transaction Amount (Positive / Negative) // Positive (received) +1 250,00 NOK // Negative (sent) -500,00 NOK 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; refreshUser: () => Promise; } // 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: On mount: fetches GET /api/auth/me with credentials: "include" (httpOnly cookie auth) If 401 and redirectIfUnauthenticated is true: redirects to /login logout() : calls POST /api/auth/logout , clears cookie server-side, redirects to /login 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 ; // user is guaranteed non-null after loading // Page that checks auth without redirect const { user } = useAuth(false); Pages using this hook (data source): accounts/page.tsx — reads user.bankAccounts (no separate fetch) profile/page.tsx — reads user.firstName , user.lastName , user.email 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 featureGate(flagName: string): middleware // Returns 404 if flag disabled // Client-side (React hooks) useFeatureFlag(flagName: string): boolean useFeatureFlags(): Record Usage pattern: // Page-level gate const cardsEnabled = useFeatureFlag("virtualCards"); if (!cardsEnabled) return
Feature ikke tilgjengelig
; // Conditional rendering const physicalEnabled = useFeatureFlag("physicalCards"); {physicalEnabled && } 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: dashboard/page.tsx — fetches GET /api/transactions?limit=10 transactions/page.tsx — fetches GET /api/transactions?type={filter}&limit=50 send/page.tsx — fetches GET /api/recipients and GET /api/rates notifications/page.tsx — fetches GET /api/notifications profile/notifications/page.tsx — fetches GET /api/settings profile/language/page.tsx — fetches GET /api/settings merchant/page.tsx — fetches /api/merchants/dashboard , /api/merchants/transactions , /api/merchants/qr cards/page.tsx — fetches GET /api/cards (feature-flagged) 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: login/page.tsx — POST /api/auth/login register/page.tsx — POST /api/auth/register (4-step: info, OTP, PIN, success) send/page.tsx — POST /api/transactions/remittance scan/page.tsx — POST /api/transactions/qr-payment complaints/page.tsx — POST /api/complaints withdrawal/page.tsx — POST (withdrawal request) cards/page.tsx — POST/PATCH/DELETE /api/cards/* (feature-flagged) 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: transactions/page.tsx — filters: "all", "remittance", "qr_payment" notifications/page.tsx — auto-marks read via PATCH on mount 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: All screens use useState (same as web) Auth data read from api.getMe() on dashboard mount Pull-to-refresh: RefreshControl on FlatList components No global state library — same philosophy as web 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 Each useEffect has explicit dependency arrays useState updates are batched by React 19 No derived state that needs memoization in current implementation Fresh Data on Navigation useAuth() re-fetches /api/auth/me on every page mount — ensures fresh user data Page data re-fetches on every mount — no stale cache issues Trade-off: more network requests, but always fresh data for financial app accuracy Future Optimization Path (if needed) Add SWR or TanStack Query for caching with stale-while-revalidate Persist query cache across navigation (currently re-fetches on each visit) 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 Pure HTML/CSS/JS (no framework) Fonts: Fraunces + DM Sans (Google Fonts CDN) CSS custom properties matching brand system Responsive (mobile-first) 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 Mobile: default (single column) Tablet: @media (min-width: 768px) — 2-column grids Desktop: @media (min-width: 1024px) — 3-column grids, wider hero 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 Handles email collection for early access signup Connected to the waitlist form in the landing page hero/social proof section 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 Single HTML file with embedded CSS and JS showScreen(name) function handles "routing" by showing/hiding divs switchTab(name) for tab navigation within the app screen API calls to localhost:3001/api with Bearer token auth Functions: doLogin() , doRegister() , doSend() , doQRPay() , renderTransactions() 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.