Zum Inhalt

04 - State Management

Dokument: State-Management-Architektur Stand: 28.02.2026 Scope: Provider, Context, Custom Hooks Status: Audit-ready


1. Architektur-Übersicht

Die App verwendet React Context API + Custom Hooks (kein Redux, Zustand o.ä.). State wird über 3 Context-Provider bereitgestellt und durch 10 spezialisierte Hooks konsumiert.

Provider-Stack

NextIntlClientProvider (i18n)
  └─ TelegramProvider
      └─ Web3AuthProvider (Haupt-State, 1.152 Zeilen)
          └─ DeviceProvider

2. Web3AuthProvider

Datei: app/providers/Web3AuthProvider.tsx (1.152 Zeilen) Pattern: Singleton (globale web3authInstance, wiederverwendbar nach Logout)

2.1 State-Variablen

Variable Typ Beschreibung
web3auth Web3Auth \| null Web3Auth-Instanz
provider IProvider \| null JSON-RPC Provider
isInitialized boolean Web3Auth init() abgeschlossen
isConnected boolean User eingeloggt
isLoading boolean Operation läuft
address string \| null Wallet-Adresse (checksummed)
balance string \| null ETH-Guthaben (formatiert)
usdtBalance string \| null USDT-Guthaben (2 Dezimalstellen)
userInfo UserInfo \| null Web3Auth Profil (email, name, profileImage)
walletError string \| null Fehlermeldung
playerStats PlayerStats \| null Tagesstatistiken
poolStats PoolStats \| null Pool-Daten (Jackpots, Reserves, Streaks)
claimStatus ClaimStatus \| null Verfügbare Gewinne
affiliate string \| null Gebundener Affiliate
userProfile UserProfile \| null Vollständiges DB-Profil
showProfileModal boolean Profil-Modal anzeigen

2.2 Throttling

Ref Cooldown Zweck
lastBalanceRefresh 10.000 ms Verhindert RPC-Überflutung
lastStatsRefresh 10.000 ms Verhindert API-Überflutung

2.3 Exportierte Funktionen

Funktion Beschreibung
login() Web3Auth Modal öffnen
loginWithEmail(email) Direkter E-Mail-Login (Telegram)
loginWithSocial(provider) Social Login (Google, Apple)
logout() Session beenden, State zurücksetzen
refreshBalance(force?) ETH + USDT neu laden (mit Throttling)
refreshStats(force?) Player/Pool-Stats neu laden (mit Throttling)
refreshUserProfile() Profil aus DB neu laden
getSigner() ethers.Signer für Signaturen
getEthersProvider() ethers.BrowserProvider
openWalletUI() Web3Auth Wallet-UI öffnen
openBuyCrypto() Fiat On-Ramp (Krypto kaufen)
openSwap() Token-Swap-UI
openBridge() Bridge-UI

2.4 Typ-Definitionen

PlayerStats:

{ todayTickets: number, todayTips: number, todayWins: number }

PoolStats:

{
  jackpots: Record<number, number>,
  jackpotReserves: Record<number, number>,
  noHitStreaks: Record<number, number>,
  charityTotal: number
}

ClaimStatus:

{
  winnerCount: number, winnerTotal: string,
  affiliateCount: number, affiliateTotal: string,
  totalCount: number, totalAmount: string, hasClaims: boolean,
  affiliateLocked?: boolean,
  affiliateLockReason?: "not_sunday" | "insufficient_activity",
  affiliateActualTips?: number, affiliateRequiredTips?: number
}

UserProfile:

{
  walletAddress: string,
  firstName/lastName/email: string | null,
  emailVerified/phoneVerified: boolean,
  dateOfBirth/nationality: string | null,
  addressStreet/City/PostalCode/State/Country: string | null,
  kycStatus: "none" | "pending" | "verified" | "rejected",
  kycLevel: number,
  dailyLimitUsdt/weeklyLimitUsdt/monthlyLimitUsdt: number | null,
  selfExclusionUntil: string | null
}


3. TelegramProvider

Datei: app/providers/TelegramProvider.tsx

Context-Werte

Wert Typ Beschreibung
isTelegram boolean In Telegram WebView
isTelegramMobile boolean Android/iOS WebView
telegramUser TelegramUser \| null TG User-Info
startParam string \| null Affiliate-Ref aus /start

Erkennung

  • Prüft window.Telegram?.WebApp?.initData
  • Platform: android, android_x, ios = Mobile
  • Ruft tgWebApp.expand() und tgWebApp.ready() auf

4. DeviceProvider

Datei: app/hooks/useDeviceContext.tsx

Context-Werte

Wert Logik
isMobile UA-Check ODER (Touch + Screen < 768px) ODER Telegram
isDesktop !isMobile UND !Telegram
isTablet Touch + minDimension >= 480px + width >= 1024px + !Telegram
isTelegram Aus TelegramProvider
isIOS iPad/iPhone/iPod im UA
isStandalone PWA-Modus oder navigator.standalone

Side Effects

  • Synchrone Initial-Erkennung (kein Layout-Flash)
  • Window resize Event-Listener
  • Neuberechnung bei isTelegram-Änderung

5. Custom Hooks (10)

5.1 useBuyFlow

Datei: app/hooks/useBuyFlow.ts (~592 Zeilen) Zweck: 6-stufiger Ticket-Kauf-Flow

Return:

{
  step: BuyStep,
  intent: IntentResponse | null,
  txHash: string | null,
  result: ConfirmTxResponse["result"] | null,
  error: string | null,
  showModal: boolean,
  startBuy(poolId, tipps, paidCount?, bonusCount?): Promise<void>,
  resumeIntent(intentId): Promise<void>,
  reset(): void,
  closeModal(): void
}

BuyStep States:

idle → creating_intent → signing → signed → approving → approve_confirmed
  → submitting → tx_pending → confirming → confirmed

Error States: error_balance, error_unsettled, error_limit, error_self_excluded, error_tx_in_progress, error_no_gas, error_allowance, error_rejected, error_timeout, error_network, error_expired, signature_cancelled

API-Calls: 6 Endpoints (intent, confirm-signature, gas-faucet, submit-tx, confirm-tx, resume-intent)

Siehe 05 - Buy Flow für Details.


5.2 useClaimStatus

Datei: app/hooks/useClaimStatus.ts Zweck: Claim-Status mit Signatur-Authentifizierung

Return:

{
  isLoading: boolean,
  hasClaims: boolean,      // totalCount > 0 && !hasPending
  winnerCount/affiliateCount/totalCount: number,
  winnerTotal/affiliateTotal/totalAmount: string,
  hasPending: boolean,
  error: string | null,
  refresh(): Promise<void>
}

Eigenschaft Beschreibung
Authentifizierung EIP-191 signierte Nachricht erforderlich
Polling KEIN automatisches Polling (Claims ändern sich nur 1x/Tag)
Fetch Einmalig pro Adresse, manuell via refresh()
Light-Version useClaimStatusLight ohne Signatur (Debug-Endpoint)

5.3 useDashboard

Datei: app/hooks/useDashboard.ts Zweck: Dashboard-Daten laden

Return:

{
  data: DashboardData | null,
  isLoading: boolean,
  error: string | null,
  fetchDashboard(address): Promise<void>,
  refresh(address): void
}

DashboardData enthält: - Overview: totalTickets, totalTips, totalSpent, spentToday, totalWon, netResult - Win Stats: totalWins, winsByClass (class2-5), bestMatch - Listen: recentTickets[], pendingClaims[], poolStats[] - Win History: winHistory[] (nach Tag mit Klassen-Breakdown) - Affiliate: boundTo, referralCode, referralCount, totalEarned, pendingEarnings, referrals[]

Helper-Funktionen: formatUsdt(), formatAddress(), formatDate(), getNetResultColor(), getNetResultEmoji()


5.4 useGlobalStats

Datei: app/hooks/useGlobalStats.ts Zweck: Plattformweite Statistiken mit Pagination

Return:

{
  data: GlobalStatsData | null,
  isLoading: boolean,
  error: string | null,
  selectedPool: number,
  changePool(poolId): void,
  daysToShow: number,         // Default: 3, Max: 14
  loadMore(): void,           // +3 Tage
  canLoadMore: boolean,
  refresh(poolId?, days?): Promise<void>
}

Eigenschaft Beschreibung
Auto-Fetch Trigger bei Pool- oder Tages-Änderung
Reset daysToShow zurück auf Default bei Pool-Wechsel
Daten today, 7d-metrics, alltime, jackpots, prizeHistory, winDistribution, hotNumbers, coldNumbers, recentWins, charityBalance

5.5 useBonusBalance

Datei: app/hooks/useBonusBalance.ts Zweck: Bonus-Tips-Guthaben und -Historie

Return:

{
  totalBonus: number,
  byType: Record<string, number>,
  history: BonusHistoryItem[],
  isLoading: boolean,
  error: string | null,
  refetch(): void
}

Eigenschaft Beschreibung
Event-Listener bonus-tips-changed Event für sofortige Updates
Auto-Fetch Bei Adress-Änderung
History id, amount, reason, createdAt, used, usedAt, sourceTicketId

5.6 usePrizeHistory

Datei: app/hooks/usePrizeHistory.ts Zweck: Gewinnhistorie pro Pool

Return:

{
  data: PrizeHistoryDay[],
  isLoading: boolean,
  error: string | null,
  fetchPrizeHistory(poolId?, days?): Promise<void>,
  refresh(poolId?, days?): void
}

Helper: getClassColor(classId), getClassEmoji(classId)


5.7 useMaintenanceStatus

Datei: app/hooks/useMaintenanceStatus.ts Zweck: Maintenance-Modus-Erkennung

Return: boolean (true = Maintenance)

Eigenschaft Wert
Polling-Intervall 30 Sekunden
Endpoint GET /api/maintenance
Fehlerverhalten Annahme: KEIN Maintenance bei Fehler

5.8 useOnlineStatus

Datei: app/hooks/useOnlineStatus.ts Zweck: Online/Offline-Erkennung

Return: boolean (true = online)

  • Verwendet navigator.onLine
  • Event-Listener: online, offline
  • SSR-Default: true

5.9 usePullToRefresh

Datei: app/hooks/usePullToRefresh.ts Zweck: Pull-to-Refresh-Geste (Mobile)

Parameter:

{
  containerRef: RefObject<HTMLElement>,
  options: {
    onRefresh: () => Promise<void> | void,
    threshold?: number,   // Default: 60px
    maxPull?: number       // Default: 120px
  }
}

Return:

{
  pullDistance: number,
  isRefreshing: boolean,
  indicatorStyle: CSSProperties,
  onTouchStart/onTouchMove/onTouchEnd: EventHandler
}

Feature Beschreibung
Damping 50% Dämpfung auf Pull-Distanz
Threshold 60px Mindest-Pull für Refresh
Transition 200ms ease-out beim Loslassen
Bedingung Nur aktiv wenn scrollTop === 0

5.10 (Implicit) useWeb3Auth

Hook: Zugriff auf Web3AuthProvider Context Validierung: Wirft Fehler wenn außerhalb des Providers verwendet.


6. Zusammenfassung

Hook Typ Async Polling Auth API-Calls
useBuyFlow Transaktion Ja Nein EIP-712 6
useClaimStatus Query Ja Nein EIP-191 1
useDashboard Query Ja Nein Nein 1
useGlobalStats Query Ja Auto Nein 1
useBonusBalance Query Ja Event Nein 1
usePrizeHistory Query Ja Nein Nein 1
useDeviceContext Detection Nein Resize Nein 0
useMaintenanceStatus Query Ja 30s Nein 1
useOnlineStatus Detection Nein Events Nein 0
usePullToRefresh Gesture Async Nein Nein 0

Weiterführende Dokumente: - 05 - Buy Flow - 06 - Claim Flow - 02 - Authentifizierung