Skip to main content

State Management

Drop Frontend — State Management

Covers auth, feature flags, data fetching patterns, and client-side state in src/drop-app/.


Authentication — 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"
  2. If 401 and redirectIfUnauthenticated is true: redirects to /login
  3. logout(): calls POST /api/auth/logout, redirects to /login
  4. refreshUser(): re-fetches /api/auth/me to update user state

Usage Pattern

// Standard protected page
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);

Auth Flow

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

Feature Flags

File: src/lib/feature-flags.ts

Available Flags

Flag Name Default Used In
virtualCards false cards page (gate)
physicalCards false cards page (order physical)
cardDetails false cards page (show 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

Convention: NEXT_PUBLIC_FF_ + SCREAMING_SNAKE_CASE version of flag name.

API

// Server-side
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 (redirects if feature disabled)
const cardsEnabled = useFeatureFlag("virtualCards");
if (!cardsEnabled) return <div>Feature not available</div>;

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

Data Fetching Patterns

Pattern 1: Page-Level Fetch on Mount

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(() => {})
    .finally(() => setLoading(false));
}, []);

Pages using this pattern:

  • dashboard/page.tsx — fetches /api/transactions?limit=10
  • history/page.tsx — fetches /api/transactions?type={filter}&limit=50
  • send/page.tsx — fetches /api/recipients and /api/rates
  • merchant/page.tsx — fetches /api/merchants/dashboard, /api/merchants/transactions, /api/merchants/qr
  • cards/page.tsx — fetches /api/cards (FUTURE — feature-flagged)

Pattern 2: User Data from Auth Hook

Some pages rely entirely on the useAuth() hook for their data, with no additional fetches.

Pages using this pattern:

  • accounts/page.tsx — reads user.bankAccounts
  • profile/page.tsx — reads user.firstName, user.lastName, user.email

Pattern 3: Form Submission

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

const handleSubmit = async () => {
  setLoading(true);
  const res = await fetch("/api/endpoint", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(formData),
  });
  if (res.ok) { /* success state */ }
  else { /* error state */ }
  setLoading(false);
};

Pages using this pattern:

  • login/page.tsx — POST /api/auth/login
  • onboarding/page.tsx — POST /api/auth/register
  • send/page.tsx — POST /api/transactions/remittance
  • scan/page.tsx — POST /api/transactions/qr-payment
  • cards/page.tsx — POST/PATCH/DELETE /api/cards/* (FUTURE — feature-flagged)

Pattern 4: Filter-Driven Refetch

History page refetches when filter changes via useEffect dependency.

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

useEffect(() => {
  // refetch with new filter
  fetch(`/api/transactions?type=${filter}&limit=50`, ...)
}, [filter]);

Client State Summary

No global state management library (Redux, Zustand, Jotai, etc.) is used. All state is local component state via useState.

State Type Mechanism Scope
Auth/User useAuth() custom hook Per-component (re-fetches on each mount)
Feature flags useFeatureFlag() hook Per-component (reads env vars)
Page data useState + useEffect fetch Component-local
Form state useState per field Component-local
UI state (modals, tabs) useState Component-local
Navigation Next.js useRouter / usePathname Framework-provided

API Routes (from source code)

All API routes are under src/app/api/. The frontend calls these endpoints:

Endpoint Method Called From
/api/auth/login POST login page
/api/auth/logout POST useAuth hook, profile page
/api/auth/me GET useAuth hook
/api/auth/register POST onboarding page
/api/transactions GET dashboard, history 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/cards GET/POST cards page (FUTURE)
/api/cards/{id} PATCH cards page (freeze/unfreeze) (FUTURE)
/api/cards/{id} DELETE cards page (FUTURE)
/api/merchants/dashboard GET merchant page
/api/merchants/transactions GET merchant page
/api/merchants/qr GET merchant page

Middleware

File: src/middleware.ts (Next.js middleware)

Additional middleware modules in src/lib/middleware/:

  • auth-middleware.ts — JWT/session validation
  • error-handler.ts — Centralized error handling
  • validation.ts — Request validation