The WeWrite financial data system has been refactored into separated, focused contexts that each handle a specific domain of financial data. This eliminates the complexity of a monolithic context and provides clear separation of concerns with dedicated caching and state management.
- Subscription payments: Land in Stripe Payments Balance and are recorded as monthly allocation balances in
ServerUsdService(no immediate storage transfer). - Month-end processing: Cron
POST /api/cron/storage-balance-processingaggregates the month viaServerUsdService.getMonthlyAllocationSummaryand moves allocated amounts to Stripe Storage Balance while keeping unallocated amounts in Payments Balance as platform revenue. - Payouts: Paid from Storage Balance to connected accounts; platform fee (10%) is retained in Payments Balance (with metadata on transfers for auditability).
- Writer earnings availability:
/api/usd/process-writer-earningspromotes pending → available after month-end transfer completes.
The financial system now consists of four dedicated contexts:
Location: app/contexts/UsdBalanceContext.tsx
- Purpose: Manages real USD balance data only
- Scope: Authenticated users with active subscriptions
- Data:
totalUsdCents,allocatedUsdCents,availableUsdCents - Caching: 30-minute cache with persistent storage
Location: app/contexts/SubscriptionContext.tsx
- Purpose: Manages subscription data separately
- Scope: All authenticated users
- Data: Subscription status, amount, billing info
- Caching: 15-minute cache with persistent storage
Location: app/contexts/EarningsContext.tsx
- Purpose: Manages creator earnings data
- Scope: Users with earnings
- Data: Total earnings, available balance, pending balance
- Caching: 10-minute cache with persistent storage
Location: app/contexts/DemoBalanceContext.tsx
- Purpose: Manages demo/trial balance for non-subscribers
- Scope: Logged-out users and users without subscriptions
- Data: Simulated balance stored in localStorage
- Caching: No server caching (localStorage only)
- Single Responsibility: Each context has one clear purpose
- Independent Caching: Optimized cache duration per data type
- Selective Loading: Components only load data they need
- Easier Testing: Each context can be tested independently
- Better Performance: No unnecessary API calls
- Clear Data Flows: Obvious which context provides what data
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Components │───▶│ Separated │───▶│ API Endpoints │
│ │ │ Contexts │ │ │
│ • NavHeader │ │ │ │ │
│ • AllocationBar │ │ UsdBalanceContext│────│ /api/usd/balance│
│ • Settings │ │ SubscriptionCtx │────│ /api/account-* │
│ • Dashboards │ │ EarningsContext │────│ /api/earnings/* │
│ │ │ DemoBalanceCtx │────│ localStorage │
└─────────────────┘ └──────────────────┘ └─────────────────┘
- UsdBalance: 30 minutes (financial data changes less frequently)
- Subscription: 15 minutes (moderate change frequency)
- Earnings: 10 minutes (more dynamic, needs fresher data)
- DemoBalance: No server cache (localStorage only)
- Unified Cache Utility:
SimpleCache<T>with memory + persistent storage
// For USD balance data only
import { useUsdBalance } from '../contexts/UsdBalanceContext';
const { usdBalance, isLoading, refreshUsdBalance } = useUsdBalance();
// For subscription data only
import { useSubscription } from '../contexts/SubscriptionContext';
const { subscription, hasActiveSubscription, subscriptionAmount } = useSubscription();
// For earnings data only
import { useEarnings } from '../contexts/EarningsContext';
const { earnings, hasEarnings, isLoading } = useEarnings();
// For demo balance (logged-out users)
import { useDemoBalance } from '../contexts/DemoBalanceContext';
const { demoBalance, isDemoBalance, allocateDemoBalance } = useDemoBalance();
// Utility hook to determine which balance to use
import { useShouldUseDemoBalance } from '../contexts/DemoBalanceContext';
const shouldUseDemoBalance = useShouldUseDemoBalance(hasActiveSubscription);File: app/components/layout/NavHeader.tsx
const { usdBalance, isLoading: usdLoading } = useUsdBalance();
const { hasActiveSubscription } = useSubscription();
const { earnings, isLoading: earningsLoading } = useEarnings();
const shouldUseDemoBalance = useShouldUseDemoBalance(hasActiveSubscription);
const { demoBalance } = useDemoBalance();
// Use appropriate balance based on subscription status
const currentBalance = shouldUseDemoBalance ? demoBalance : usdBalance;Settings pages use specific contexts for their data needs:
/settings/spend- UsesuseUsdBalance()anduseDemoBalance()for allocation breakdown/settings/earnings- UsesuseEarnings()for payout information/settings/fund-account- UsesuseSubscription()for billing details
Allocation components determine which balance system to use:
// AllocationBar example
const { usdBalance } = useUsdBalance();
const { hasActiveSubscription } = useSubscription();
const shouldUseDemoBalance = useShouldUseDemoBalance(hasActiveSubscription);
const { demoBalance } = useDemoBalance();
const currentBalance = shouldUseDemoBalance ? demoBalance : usdBalance;- Each context has one clear, focused purpose
- Easier to understand and maintain individual contexts
- Reduced complexity in each component
- Components only load data they actually need
- Independent caching strategies per data type
- No unnecessary API calls for unused data
- Each context can be tested independently
- Clear data flows make debugging easier
- Isolated failures don't affect other data types
- Different cache durations based on data volatility
- UsdBalance: 30min (stable financial data)
- Subscription: 15min (moderate changes)
- Earnings: 10min (more dynamic)
- DemoBalance: localStorage only
- No ambiguity about which context provides what data
- Explicit imports make dependencies obvious
- Easier to track data usage across components
// ❌ OLD - Single context with everything
import { useUsdBalance } from '../contexts/UsdBalanceContext';
const {
usdBalance,
subscription,
earnings,
hasActiveSubscription,
earningsLoading,
isDemoBalance
} = useUsdBalance();// ✅ NEW - Separated, focused contexts
import { useUsdBalance } from '../contexts/UsdBalanceContext';
import { useSubscription } from '../contexts/SubscriptionContext';
import { useEarnings } from '../contexts/EarningsContext';
import { useDemoBalance, useShouldUseDemoBalance } from '../contexts/DemoBalanceContext';
// Only import what you need
const { usdBalance } = useUsdBalance();
const { hasActiveSubscription } = useSubscription();
const { earnings } = useEarnings();
const shouldUseDemoBalance = useShouldUseDemoBalance(hasActiveSubscription);
const { demoBalance } = useDemoBalance();Location: app/services/usdDataService.ts
Centralized service for all USD-related API calls:
fetchBalance()- Gets USD balance datafetchSubscription()- Gets subscription datafetchEarnings()- Gets earnings datafetchAllData()- Parallel fetch of all data types
Location: app/utils/simplifiedCache.ts
Unified caching utility with memory + persistent storage:
- Type-safe cache instances for each data type
- Configurable cache duration per data type
- Automatic cache invalidation and cleanup
// Refresh specific data types
const { refreshUsdBalance } = useUsdBalance();
const { refreshSubscription } = useSubscription();
const { refreshEarnings } = useEarnings();
// Force refresh from API
await refreshUsdBalance();
await refreshSubscription();
await refreshEarnings();Each context manages its own cache independently:
- UsdBalance: Invalidated on allocation changes
- Subscription: Invalidated on subscription updates
- Earnings: Invalidated on payout events
- DemoBalance: Updated in localStorage immediately
Each context handles errors independently:
- Isolated Failures: If one context fails, others continue working
- Cached Fallbacks: Uses cached data when API calls fail
- Graceful Degradation: UI shows appropriate loading/error states
- Retry Logic: Built into each context's fetch logic
- Test each context provider independently
- Mock UsdDataService for consistent API responses
- Verify cache behavior and invalidation
- Test fake balance logic separately
- Test component integration with multiple contexts
- Verify proper balance selection (real vs fake)
- Test error scenarios and fallback behavior
- Verify cache coordination between contexts
✅ Separation of Concerns: Each context has single responsibility ✅ Performance: Only load data components actually need ✅ Maintainability: Easier to debug and modify individual contexts ✅ Testability: Independent testing of each data domain ✅ Flexibility: Different caching strategies per data type ✅ Clarity: Obvious data ownership and dependencies
Last Updated: August 16, 2025 Version: 2.0 (Separated Architecture) Status: Production Ready
- Allocation System - Allocation mechanics
- Allocation API Reference - Allocation hooks and components
- USD Payment System - USD implementation details
- Subscription System - Subscription management
- Payment System Guide - Money flow overview
- Collection Naming Standards - Database collections