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
- On mount: fetches
GET /api/auth/mewithcredentials: "include" - If 401 and
redirectIfUnauthenticatedis true: redirects to/login logout(): callsPOST /api/auth/logout, redirects to/loginrefreshUser(): re-fetches/api/auth/meto 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=10history/page.tsx— fetches/api/transactions?type={filter}&limit=50send/page.tsx— fetches/api/recipientsand/api/ratesmerchant/page.tsx— fetches/api/merchants/dashboard,/api/merchants/transactions,/api/merchants/qrcards/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— readsuser.bankAccountsprofile/page.tsx— readsuser.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/loginonboarding/page.tsx— POST/api/auth/registersend/page.tsx— POST/api/transactions/remittancescan/page.tsx— POST/api/transactions/qr-paymentcards/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 validationerror-handler.ts— Centralized error handlingvalidation.ts— Request validation