@dubsdotapp/expo 0.2.53 → 0.2.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -459,6 +459,21 @@ var DubsClient = class {
459
459
  );
460
460
  this._token = null;
461
461
  }
462
+ // ── Push Notifications ──
463
+ async registerPushToken(params) {
464
+ await this.request(
465
+ "POST",
466
+ "/push/expo-token",
467
+ params
468
+ );
469
+ }
470
+ async unregisterPushToken(token) {
471
+ await this.request(
472
+ "DELETE",
473
+ "/push/expo-token",
474
+ { token }
475
+ );
476
+ }
462
477
  // ── Error Utilities ──
463
478
  async parseError(error) {
464
479
  const res = await this.request(
@@ -533,7 +548,7 @@ function createSecureStoreStorage() {
533
548
  }
534
549
 
535
550
  // src/provider.tsx
536
- import { createContext as createContext4, useContext as useContext4, useMemo, useCallback as useCallback14, useState as useState15, useEffect as useEffect10 } from "react";
551
+ import { createContext as createContext4, useContext as useContext4, useMemo as useMemo2, useCallback as useCallback15, useState as useState16, useEffect as useEffect10 } from "react";
537
552
 
538
553
  // src/ui/theme.ts
539
554
  import { createContext, useContext } from "react";
@@ -1598,7 +1613,7 @@ function ManagedWalletProvider({
1598
1613
  }
1599
1614
 
1600
1615
  // src/ui/AuthGate.tsx
1601
- import React2, { useState as useState14, useEffect as useEffect9, useRef as useRef3, useCallback as useCallback13 } from "react";
1616
+ import React2, { useState as useState15, useEffect as useEffect9, useRef as useRef4, useCallback as useCallback14 } from "react";
1602
1617
  import {
1603
1618
  View as View2,
1604
1619
  Text as Text2,
@@ -1608,7 +1623,7 @@ import {
1608
1623
  StyleSheet as StyleSheet2,
1609
1624
  Keyboard,
1610
1625
  KeyboardAvoidingView,
1611
- Platform as Platform3,
1626
+ Platform as Platform4,
1612
1627
  Image as Image2,
1613
1628
  Animated,
1614
1629
  ScrollView
@@ -2277,6 +2292,126 @@ function useUFCFighterDetail(athleteId) {
2277
2292
  return { data, loading, error, refetch: fetchData };
2278
2293
  }
2279
2294
 
2295
+ // src/hooks/usePushNotifications.ts
2296
+ import { useState as useState14, useCallback as useCallback13, useRef as useRef3, useMemo } from "react";
2297
+ import { Platform as Platform3 } from "react-native";
2298
+ function usePushNotifications() {
2299
+ const { client, appName } = useDubs();
2300
+ const channelId = useMemo(() => appName.toLowerCase().replace(/[^a-z0-9-]/g, ""), [appName]);
2301
+ const [hasPermission, setHasPermission] = useState14(false);
2302
+ const [expoPushToken, setExpoPushToken] = useState14(null);
2303
+ const [loading, setLoading] = useState14(false);
2304
+ const [error, setError] = useState14(null);
2305
+ const registering = useRef3(false);
2306
+ const getNotificationsModule = useCallback13(() => {
2307
+ try {
2308
+ return __require("expo-notifications");
2309
+ } catch {
2310
+ return null;
2311
+ }
2312
+ }, []);
2313
+ const getDeviceName = useCallback13(() => {
2314
+ try {
2315
+ const Device = __require("expo-device");
2316
+ return Device.deviceName || null;
2317
+ } catch {
2318
+ return null;
2319
+ }
2320
+ }, []);
2321
+ const setupAndroidChannels = useCallback13((Notifications) => {
2322
+ if (Platform3.OS === "android") {
2323
+ Notifications.setNotificationChannelAsync(channelId || "default", {
2324
+ name: appName || "Default",
2325
+ importance: Notifications.AndroidImportance?.MAX ?? 4,
2326
+ vibrationPattern: [0, 250, 250, 250]
2327
+ }).catch(() => {
2328
+ });
2329
+ }
2330
+ }, [channelId, appName]);
2331
+ const registerTokenWithServer = useCallback13(async (token) => {
2332
+ const deviceName = getDeviceName();
2333
+ await client.registerPushToken({
2334
+ token,
2335
+ platform: Platform3.OS,
2336
+ deviceName: deviceName || void 0
2337
+ });
2338
+ }, [client, getDeviceName]);
2339
+ const register = useCallback13(async () => {
2340
+ if (registering.current) return false;
2341
+ registering.current = true;
2342
+ setLoading(true);
2343
+ setError(null);
2344
+ try {
2345
+ const Notifications = getNotificationsModule();
2346
+ if (!Notifications) {
2347
+ throw new Error("expo-notifications is not installed");
2348
+ }
2349
+ const { status: existingStatus } = await Notifications.getPermissionsAsync();
2350
+ let finalStatus = existingStatus;
2351
+ if (existingStatus !== "granted") {
2352
+ const { status } = await Notifications.requestPermissionsAsync();
2353
+ finalStatus = status;
2354
+ }
2355
+ if (finalStatus !== "granted") {
2356
+ setHasPermission(false);
2357
+ setLoading(false);
2358
+ registering.current = false;
2359
+ return false;
2360
+ }
2361
+ setHasPermission(true);
2362
+ const tokenResult = await Notifications.getExpoPushTokenAsync();
2363
+ const token = tokenResult.data;
2364
+ setExpoPushToken(token);
2365
+ await registerTokenWithServer(token);
2366
+ setupAndroidChannels(Notifications);
2367
+ setLoading(false);
2368
+ registering.current = false;
2369
+ return true;
2370
+ } catch (err) {
2371
+ const e = err instanceof Error ? err : new Error(String(err));
2372
+ setError(e);
2373
+ setLoading(false);
2374
+ registering.current = false;
2375
+ console.error("[usePushNotifications] Registration error:", e.message);
2376
+ return false;
2377
+ }
2378
+ }, [getNotificationsModule, registerTokenWithServer, setupAndroidChannels]);
2379
+ const unregister = useCallback13(async () => {
2380
+ if (!expoPushToken) return;
2381
+ try {
2382
+ await client.unregisterPushToken(expoPushToken);
2383
+ setExpoPushToken(null);
2384
+ } catch (err) {
2385
+ console.error("[usePushNotifications] Unregister error:", err);
2386
+ }
2387
+ }, [client, expoPushToken]);
2388
+ const restoreIfGranted = useCallback13(async () => {
2389
+ try {
2390
+ const Notifications = getNotificationsModule();
2391
+ if (!Notifications) return;
2392
+ const { status } = await Notifications.getPermissionsAsync();
2393
+ if (status !== "granted") return;
2394
+ setHasPermission(true);
2395
+ const tokenResult = await Notifications.getExpoPushTokenAsync();
2396
+ const token = tokenResult.data;
2397
+ setExpoPushToken(token);
2398
+ await registerTokenWithServer(token);
2399
+ setupAndroidChannels(Notifications);
2400
+ } catch (err) {
2401
+ console.log("[usePushNotifications] Restore skipped:", err instanceof Error ? err.message : err);
2402
+ }
2403
+ }, [getNotificationsModule, registerTokenWithServer, setupAndroidChannels]);
2404
+ return {
2405
+ hasPermission,
2406
+ expoPushToken,
2407
+ loading,
2408
+ error,
2409
+ register,
2410
+ unregister,
2411
+ restoreIfGranted
2412
+ };
2413
+ }
2414
+
2280
2415
  // src/ui/AuthGate.tsx
2281
2416
  import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
2282
2417
  var DICEBEAR_STYLES = [
@@ -2305,8 +2440,10 @@ function AuthGate({
2305
2440
  }) {
2306
2441
  const { client } = useDubs();
2307
2442
  const auth = useAuth();
2308
- const [phase, setPhase] = useState14("init");
2309
- const [registrationPhase, setRegistrationPhase] = useState14(false);
2443
+ const [phase, setPhase] = useState15("init");
2444
+ const [registrationPhase, setRegistrationPhase] = useState15(false);
2445
+ const [showPushSetup, setShowPushSetup] = useState15(false);
2446
+ const [isRestoredSession, setIsRestoredSession] = useState15(false);
2310
2447
  useEffect9(() => {
2311
2448
  let cancelled = false;
2312
2449
  (async () => {
@@ -2317,6 +2454,7 @@ function AuthGate({
2317
2454
  const restored = await auth.restoreSession(savedToken);
2318
2455
  if (cancelled) return;
2319
2456
  if (restored) {
2457
+ setIsRestoredSession(true);
2320
2458
  setPhase("active");
2321
2459
  return;
2322
2460
  }
@@ -2336,15 +2474,20 @@ function AuthGate({
2336
2474
  useEffect9(() => {
2337
2475
  if (auth.status === "needsRegistration") setRegistrationPhase(true);
2338
2476
  }, [auth.status]);
2477
+ useEffect9(() => {
2478
+ if (auth.status === "authenticated" && registrationPhase && !isRestoredSession) {
2479
+ setShowPushSetup(true);
2480
+ }
2481
+ }, [auth.status, registrationPhase, isRestoredSession]);
2339
2482
  useEffect9(() => {
2340
2483
  if (auth.token) onSaveToken(auth.token);
2341
2484
  }, [auth.token]);
2342
- const retry = useCallback13(() => {
2485
+ const retry = useCallback14(() => {
2343
2486
  setRegistrationPhase(false);
2344
2487
  auth.reset();
2345
2488
  auth.authenticate();
2346
2489
  }, [auth]);
2347
- const handleRegister = useCallback13(
2490
+ const handleRegister = useCallback14(
2348
2491
  (username, referralCode, avatarUrl) => {
2349
2492
  auth.register(username, referralCode, avatarUrl);
2350
2493
  },
@@ -2355,7 +2498,20 @@ function AuthGate({
2355
2498
  return /* @__PURE__ */ jsx3(DefaultLoadingScreen, { status: "authenticating", appName, accentColor });
2356
2499
  }
2357
2500
  if (auth.status === "authenticated") {
2358
- return /* @__PURE__ */ jsx3(AuthContext.Provider, { value: auth, children });
2501
+ if (showPushSetup) {
2502
+ return /* @__PURE__ */ jsx3(
2503
+ PushSetupScreen,
2504
+ {
2505
+ accentColor,
2506
+ appName,
2507
+ onComplete: () => setShowPushSetup(false)
2508
+ }
2509
+ );
2510
+ }
2511
+ return /* @__PURE__ */ jsxs2(AuthContext.Provider, { value: auth, children: [
2512
+ /* @__PURE__ */ jsx3(PushTokenRestorer, {}),
2513
+ children
2514
+ ] });
2359
2515
  }
2360
2516
  if (registrationPhase) {
2361
2517
  const isRegistering = auth.status === "registering";
@@ -2447,17 +2603,17 @@ function DefaultRegistrationScreen({
2447
2603
  }) {
2448
2604
  const t = useDubsTheme();
2449
2605
  const accent = accentColor || t.accent;
2450
- const [step, setStep] = useState14(0);
2451
- const [avatarSeed, setAvatarSeed] = useState14(generateSeed);
2452
- const [avatarStyle, setAvatarStyle] = useState14("adventurer");
2453
- const [showStyles, setShowStyles] = useState14(false);
2454
- const [username, setUsername] = useState14("");
2455
- const [referralCode, setReferralCode] = useState14("");
2456
- const [checking, setChecking] = useState14(false);
2457
- const [availability, setAvailability] = useState14(null);
2458
- const debounceRef = useRef3(null);
2459
- const fadeAnim = useRef3(new Animated.Value(1)).current;
2460
- const slideAnim = useRef3(new Animated.Value(0)).current;
2606
+ const [step, setStep] = useState15(0);
2607
+ const [avatarSeed, setAvatarSeed] = useState15(generateSeed);
2608
+ const [avatarStyle, setAvatarStyle] = useState15("adventurer");
2609
+ const [showStyles, setShowStyles] = useState15(false);
2610
+ const [username, setUsername] = useState15("");
2611
+ const [referralCode, setReferralCode] = useState15("");
2612
+ const [checking, setChecking] = useState15(false);
2613
+ const [availability, setAvailability] = useState15(null);
2614
+ const debounceRef = useRef4(null);
2615
+ const fadeAnim = useRef4(new Animated.Value(1)).current;
2616
+ const slideAnim = useRef4(new Animated.Value(0)).current;
2461
2617
  const avatarUrl = getAvatarUrl(avatarStyle, avatarSeed);
2462
2618
  useEffect9(() => {
2463
2619
  if (debounceRef.current) clearTimeout(debounceRef.current);
@@ -2482,7 +2638,7 @@ function DefaultRegistrationScreen({
2482
2638
  if (debounceRef.current) clearTimeout(debounceRef.current);
2483
2639
  };
2484
2640
  }, [username, client]);
2485
- const animateToStep = useCallback13((newStep) => {
2641
+ const animateToStep = useCallback14((newStep) => {
2486
2642
  const dir = newStep > step ? 1 : -1;
2487
2643
  Keyboard.dismiss();
2488
2644
  Animated.parallel([
@@ -2694,7 +2850,7 @@ function DefaultRegistrationScreen({
2694
2850
  KeyboardAvoidingView,
2695
2851
  {
2696
2852
  style: [s.container, { backgroundColor: t.background }],
2697
- behavior: Platform3.OS === "ios" ? "padding" : void 0,
2853
+ behavior: Platform4.OS === "ios" ? "padding" : void 0,
2698
2854
  children: /* @__PURE__ */ jsx3(
2699
2855
  ScrollView,
2700
2856
  {
@@ -2716,6 +2872,97 @@ function DefaultRegistrationScreen({
2716
2872
  }
2717
2873
  );
2718
2874
  }
2875
+ function PushTokenRestorer() {
2876
+ const push = usePushNotifications();
2877
+ const restored = useRef4(false);
2878
+ useEffect9(() => {
2879
+ if (restored.current) return;
2880
+ restored.current = true;
2881
+ push.restoreIfGranted();
2882
+ }, []);
2883
+ return null;
2884
+ }
2885
+ function PushSetupScreen({
2886
+ accentColor,
2887
+ appName,
2888
+ onComplete
2889
+ }) {
2890
+ const t = useDubsTheme();
2891
+ const accent = accentColor || t.accent;
2892
+ const push = usePushNotifications();
2893
+ const fadeAnim = useRef4(new Animated.Value(0)).current;
2894
+ const slideAnim = useRef4(new Animated.Value(30)).current;
2895
+ useEffect9(() => {
2896
+ Animated.parallel([
2897
+ Animated.timing(fadeAnim, { toValue: 1, duration: 300, useNativeDriver: true }),
2898
+ Animated.timing(slideAnim, { toValue: 0, duration: 300, useNativeDriver: true })
2899
+ ]).start();
2900
+ }, [fadeAnim, slideAnim]);
2901
+ const handleEnable = async () => {
2902
+ await push.register();
2903
+ onComplete();
2904
+ };
2905
+ const benefits = [
2906
+ "A fight you picked on goes LIVE",
2907
+ "Your pick wins or loses",
2908
+ "Final results and rankings"
2909
+ ];
2910
+ return /* @__PURE__ */ jsx3(View2, { style: [s.container, { backgroundColor: t.background }], children: /* @__PURE__ */ jsxs2(
2911
+ Animated.View,
2912
+ {
2913
+ style: [
2914
+ s.stepContainer,
2915
+ { opacity: fadeAnim, transform: [{ translateY: slideAnim }] }
2916
+ ],
2917
+ children: [
2918
+ /* @__PURE__ */ jsxs2(View2, { style: s.stepTop, children: [
2919
+ /* @__PURE__ */ jsx3(Text2, { style: [s.title, { color: t.text }], children: "Enable Notifications" }),
2920
+ /* @__PURE__ */ jsx3(Text2, { style: [s.subtitle, { color: t.textMuted }], children: "Stay in the loop with real-time updates" }),
2921
+ /* @__PURE__ */ jsx3(StepIndicator, { currentStep: 3 }),
2922
+ /* @__PURE__ */ jsx3(View2, { style: pushStyles.iconContainer, children: /* @__PURE__ */ jsx3(View2, { style: [pushStyles.bellCircle, { backgroundColor: accent + "20" }], children: /* @__PURE__ */ jsx3(Text2, { style: [pushStyles.bellIcon, { color: accent }], children: "\u{1F514}" }) }) }),
2923
+ /* @__PURE__ */ jsxs2(View2, { style: pushStyles.benefitsList, children: [
2924
+ /* @__PURE__ */ jsx3(Text2, { style: [pushStyles.benefitsHeader, { color: t.text }], children: "Get real-time updates when:" }),
2925
+ benefits.map((item, i) => /* @__PURE__ */ jsxs2(View2, { style: pushStyles.benefitRow, children: [
2926
+ /* @__PURE__ */ jsx3(View2, { style: [pushStyles.bulletDot, { backgroundColor: accent }] }),
2927
+ /* @__PURE__ */ jsx3(Text2, { style: [pushStyles.benefitText, { color: t.textMuted }], children: item })
2928
+ ] }, i))
2929
+ ] })
2930
+ ] }),
2931
+ /* @__PURE__ */ jsxs2(View2, { style: s.bottomRow, children: [
2932
+ /* @__PURE__ */ jsx3(
2933
+ TouchableOpacity2,
2934
+ {
2935
+ style: [s.secondaryBtn, { borderColor: t.border }],
2936
+ onPress: onComplete,
2937
+ activeOpacity: 0.7,
2938
+ children: /* @__PURE__ */ jsx3(Text2, { style: [s.secondaryBtnText, { color: t.textMuted }], children: "Maybe Later" })
2939
+ }
2940
+ ),
2941
+ /* @__PURE__ */ jsx3(
2942
+ TouchableOpacity2,
2943
+ {
2944
+ style: [s.primaryBtn, { backgroundColor: accent, flex: 1, opacity: push.loading ? 0.7 : 1 }],
2945
+ onPress: handleEnable,
2946
+ disabled: push.loading,
2947
+ activeOpacity: 0.8,
2948
+ children: push.loading ? /* @__PURE__ */ jsx3(ActivityIndicator2, { color: "#FFFFFF", size: "small" }) : /* @__PURE__ */ jsx3(Text2, { style: s.primaryBtnText, children: "Enable Notifications" })
2949
+ }
2950
+ )
2951
+ ] })
2952
+ ]
2953
+ }
2954
+ ) });
2955
+ }
2956
+ var pushStyles = StyleSheet2.create({
2957
+ iconContainer: { alignItems: "center", marginVertical: 24 },
2958
+ bellCircle: { width: 100, height: 100, borderRadius: 50, justifyContent: "center", alignItems: "center" },
2959
+ bellIcon: { fontSize: 48 },
2960
+ benefitsList: { paddingHorizontal: 24, gap: 12, marginTop: 8 },
2961
+ benefitsHeader: { fontSize: 17, fontWeight: "700", marginBottom: 4 },
2962
+ benefitRow: { flexDirection: "row", alignItems: "center", gap: 12 },
2963
+ bulletDot: { width: 8, height: 8, borderRadius: 4 },
2964
+ benefitText: { fontSize: 16, flex: 1 }
2965
+ });
2719
2966
  var s = StyleSheet2.create({
2720
2967
  container: { flex: 1 },
2721
2968
  // Loading / Error
@@ -2803,10 +3050,10 @@ function DubsProvider({
2803
3050
  const baseUrl = baseUrlOverride || config.baseUrl;
2804
3051
  const rpcUrl = rpcUrlOverride || config.rpcUrl;
2805
3052
  const cluster = config.cluster;
2806
- const client = useMemo(() => new DubsClient({ apiKey, baseUrl }), [apiKey, baseUrl]);
2807
- const connection = useMemo(() => new Connection2(rpcUrl, { commitment: "confirmed" }), [rpcUrl]);
2808
- const storage = useMemo(() => tokenStorage || createSecureStoreStorage(), [tokenStorage]);
2809
- const [uiConfig, setUiConfig] = useState15(null);
3053
+ const client = useMemo2(() => new DubsClient({ apiKey, baseUrl }), [apiKey, baseUrl]);
3054
+ const connection = useMemo2(() => new Connection2(rpcUrl, { commitment: "confirmed" }), [rpcUrl]);
3055
+ const storage = useMemo2(() => tokenStorage || createSecureStoreStorage(), [tokenStorage]);
3056
+ const [uiConfig, setUiConfig] = useState16(null);
2810
3057
  useEffect10(() => {
2811
3058
  client.getAppConfig().then((config2) => {
2812
3059
  console.log("[DubsProvider] UI config loaded:", JSON.stringify(config2));
@@ -2888,11 +3135,11 @@ function ManagedInner({
2888
3135
  children
2889
3136
  }) {
2890
3137
  const managedDisconnect = useDisconnect();
2891
- const disconnect = useCallback14(async () => {
3138
+ const disconnect = useCallback15(async () => {
2892
3139
  client.setToken(null);
2893
3140
  await managedDisconnect?.();
2894
3141
  }, [client, managedDisconnect]);
2895
- const value = useMemo(
3142
+ const value = useMemo2(
2896
3143
  () => ({ client, wallet, connection, appName, network, disconnect, uiConfig }),
2897
3144
  [client, wallet, connection, appName, network, disconnect, uiConfig]
2898
3145
  );
@@ -2928,13 +3175,13 @@ function ExternalWalletProvider({
2928
3175
  uiConfig,
2929
3176
  children
2930
3177
  }) {
2931
- const disconnect = useCallback14(async () => {
3178
+ const disconnect = useCallback15(async () => {
2932
3179
  client.setToken(null);
2933
3180
  await storage.deleteItem(STORAGE_KEYS.JWT_TOKEN).catch(() => {
2934
3181
  });
2935
3182
  await wallet.disconnect?.();
2936
3183
  }, [client, storage, wallet]);
2937
- const value = useMemo(
3184
+ const value = useMemo2(
2938
3185
  () => ({ client, wallet, connection, appName, network, disconnect, uiConfig }),
2939
3186
  [client, wallet, connection, appName, network, disconnect, uiConfig]
2940
3187
  );
@@ -2971,7 +3218,7 @@ function useAppConfig() {
2971
3218
  }
2972
3219
 
2973
3220
  // src/ui/UserProfileCard.tsx
2974
- import { useMemo as useMemo2 } from "react";
3221
+ import { useMemo as useMemo3 } from "react";
2975
3222
  import { View as View3, Text as Text3, Image as Image3, StyleSheet as StyleSheet3 } from "react-native";
2976
3223
  import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
2977
3224
  function truncateAddress(address, chars = 4) {
@@ -2991,7 +3238,7 @@ function UserProfileCard({
2991
3238
  memberSince
2992
3239
  }) {
2993
3240
  const t = useDubsTheme();
2994
- const imageUri = useMemo2(
3241
+ const imageUri = useMemo3(
2995
3242
  () => avatarUrl || `https://api.dicebear.com/9.x/avataaars/png?seed=${walletAddress}&size=128`,
2996
3243
  [avatarUrl, walletAddress]
2997
3244
  );
@@ -3191,7 +3438,7 @@ var styles3 = StyleSheet4.create({
3191
3438
  });
3192
3439
 
3193
3440
  // src/ui/game/GamePoster.tsx
3194
- import { useState as useState16 } from "react";
3441
+ import { useState as useState17 } from "react";
3195
3442
  import { StyleSheet as StyleSheet5, View as View5, Text as Text5 } from "react-native";
3196
3443
  import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
3197
3444
  function computeCountdown(lockTimestamp) {
@@ -3241,7 +3488,7 @@ function GamePoster({ game, ImageComponent }) {
3241
3488
  ] });
3242
3489
  }
3243
3490
  function TeamLogoInternal({ url, size, Img }) {
3244
- const [failed, setFailed] = useState16(false);
3491
+ const [failed, setFailed] = useState17(false);
3245
3492
  if (!url || failed) {
3246
3493
  return /* @__PURE__ */ jsx7(View5, { style: [styles4.logoPlaceholder, { width: size, height: size, borderRadius: size / 2 }] });
3247
3494
  }
@@ -3342,7 +3589,7 @@ var styles4 = StyleSheet5.create({
3342
3589
  });
3343
3590
 
3344
3591
  // src/ui/game/LivePoolsCard.tsx
3345
- import { useMemo as useMemo3 } from "react";
3592
+ import { useMemo as useMemo4 } from "react";
3346
3593
  import { StyleSheet as StyleSheet6, View as View6, Text as Text6 } from "react-native";
3347
3594
  import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
3348
3595
  function LivePoolsCard({
@@ -3358,7 +3605,7 @@ function LivePoolsCard({
3358
3605
  const homePool = game.homePool || 0;
3359
3606
  const awayPool = game.awayPool || 0;
3360
3607
  const totalPool = game.totalPool || 0;
3361
- const { homePercent, awayPercent, homeOdds, awayOdds } = useMemo3(() => {
3608
+ const { homePercent, awayPercent, homeOdds, awayOdds } = useMemo4(() => {
3362
3609
  return {
3363
3610
  homePercent: totalPool > 0 ? homePool / totalPool * 100 : 50,
3364
3611
  awayPercent: totalPool > 0 ? awayPool / totalPool * 100 : 50,
@@ -3421,7 +3668,7 @@ var styles5 = StyleSheet6.create({
3421
3668
  });
3422
3669
 
3423
3670
  // src/ui/game/PickWinnerCard.tsx
3424
- import { useState as useState17, useMemo as useMemo4 } from "react";
3671
+ import { useState as useState18, useMemo as useMemo5 } from "react";
3425
3672
  import { StyleSheet as StyleSheet7, View as View7, Text as Text7, TouchableOpacity as TouchableOpacity4 } from "react-native";
3426
3673
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
3427
3674
  function PickWinnerCard({
@@ -3439,7 +3686,7 @@ function PickWinnerCard({
3439
3686
  const totalPool = game.totalPool || 0;
3440
3687
  const homePool = game.homePool || 0;
3441
3688
  const awayPool = game.awayPool || 0;
3442
- const { homeOdds, awayOdds, homeBets, awayBets } = useMemo4(() => ({
3689
+ const { homeOdds, awayOdds, homeBets, awayBets } = useMemo5(() => ({
3443
3690
  homeOdds: homePool > 0 ? (totalPool / homePool).toFixed(2) : "\u2014",
3444
3691
  awayOdds: awayPool > 0 ? (totalPool / awayPool).toFixed(2) : "\u2014",
3445
3692
  homeBets: bettors.filter((b) => b.team === "home").length,
@@ -3492,7 +3739,7 @@ function TeamOption({
3492
3739
  ImageComponent,
3493
3740
  t
3494
3741
  }) {
3495
- const [imgFailed, setImgFailed] = useState17(false);
3742
+ const [imgFailed, setImgFailed] = useState18(false);
3496
3743
  const Img = ImageComponent || __require("react-native").Image;
3497
3744
  const showImage = imageUrl && !imgFailed;
3498
3745
  return /* @__PURE__ */ jsxs7(
@@ -3533,7 +3780,7 @@ var styles6 = StyleSheet7.create({
3533
3780
  });
3534
3781
 
3535
3782
  // src/ui/game/PlayersCard.tsx
3536
- import { useState as useState18 } from "react";
3783
+ import { useState as useState19 } from "react";
3537
3784
  import { StyleSheet as StyleSheet8, View as View8, Text as Text8 } from "react-native";
3538
3785
  import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
3539
3786
  function truncateWallet(addr, chars) {
@@ -3582,7 +3829,7 @@ function BettorRow({
3582
3829
  ImageComponent,
3583
3830
  t
3584
3831
  }) {
3585
- const [imgFailed, setImgFailed] = useState18(false);
3832
+ const [imgFailed, setImgFailed] = useState19(false);
3586
3833
  const Img = ImageComponent || __require("react-native").Image;
3587
3834
  const showAvatar = bettor.avatar && !imgFailed;
3588
3835
  return /* @__PURE__ */ jsxs8(View8, { style: [styles7.row, !isFirst && { borderTopColor: t.border, borderTopWidth: 1 }], children: [
@@ -3609,7 +3856,7 @@ var styles7 = StyleSheet8.create({
3609
3856
  });
3610
3857
 
3611
3858
  // src/ui/game/JoinGameButton.tsx
3612
- import { useMemo as useMemo5 } from "react";
3859
+ import { useMemo as useMemo6 } from "react";
3613
3860
  import { StyleSheet as StyleSheet9, View as View9, Text as Text9, TouchableOpacity as TouchableOpacity5, ActivityIndicator as ActivityIndicator4 } from "react-native";
3614
3861
  import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
3615
3862
  var STATUS_LABELS = {
@@ -3620,7 +3867,7 @@ var STATUS_LABELS = {
3620
3867
  };
3621
3868
  function JoinGameButton({ game, walletAddress, selectedTeam, status, onJoin }) {
3622
3869
  const t = useDubsTheme();
3623
- const alreadyJoined = useMemo5(() => {
3870
+ const alreadyJoined = useMemo6(() => {
3624
3871
  if (!walletAddress) return false;
3625
3872
  return (game.bettors || []).some((b) => b.wallet === walletAddress);
3626
3873
  }, [game.bettors, walletAddress]);
@@ -3661,7 +3908,7 @@ var styles8 = StyleSheet9.create({
3661
3908
  });
3662
3909
 
3663
3910
  // src/ui/game/CreateCustomGameSheet.tsx
3664
- import { useState as useState19, useEffect as useEffect11, useRef as useRef4, useCallback as useCallback15 } from "react";
3911
+ import { useState as useState20, useEffect as useEffect11, useRef as useRef5, useCallback as useCallback16 } from "react";
3665
3912
  import {
3666
3913
  View as View10,
3667
3914
  Text as Text10,
@@ -3672,7 +3919,7 @@ import {
3672
3919
  Animated as Animated2,
3673
3920
  StyleSheet as StyleSheet10,
3674
3921
  KeyboardAvoidingView as KeyboardAvoidingView2,
3675
- Platform as Platform4
3922
+ Platform as Platform5
3676
3923
  } from "react-native";
3677
3924
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
3678
3925
  var STATUS_LABELS2 = {
@@ -3698,10 +3945,10 @@ function CreateCustomGameSheet({
3698
3945
  const t = useDubsTheme();
3699
3946
  const { wallet } = useDubs();
3700
3947
  const mutation = useCreateCustomGame();
3701
- const [selectedAmount, setSelectedAmount] = useState19(null);
3702
- const [customAmount, setCustomAmount] = useState19("");
3703
- const [isCustom, setIsCustom] = useState19(false);
3704
- const overlayOpacity = useRef4(new Animated2.Value(0)).current;
3948
+ const [selectedAmount, setSelectedAmount] = useState20(null);
3949
+ const [customAmount, setCustomAmount] = useState20("");
3950
+ const [isCustom, setIsCustom] = useState20(false);
3951
+ const overlayOpacity = useRef5(new Animated2.Value(0)).current;
3705
3952
  useEffect11(() => {
3706
3953
  Animated2.timing(overlayOpacity, {
3707
3954
  toValue: visible ? 1 : 0,
@@ -3731,18 +3978,18 @@ function CreateCustomGameSheet({
3731
3978
  onError?.(mutation.error);
3732
3979
  }
3733
3980
  }, [mutation.status, mutation.error]);
3734
- const handlePresetSelect = useCallback15((amount) => {
3981
+ const handlePresetSelect = useCallback16((amount) => {
3735
3982
  setSelectedAmount(amount);
3736
3983
  setIsCustom(false);
3737
3984
  setCustomAmount("");
3738
3985
  onAmountChange?.(amount);
3739
3986
  }, [onAmountChange]);
3740
- const handleCustomSelect = useCallback15(() => {
3987
+ const handleCustomSelect = useCallback16(() => {
3741
3988
  setIsCustom(true);
3742
3989
  setSelectedAmount(null);
3743
3990
  onAmountChange?.(null);
3744
3991
  }, [onAmountChange]);
3745
- const handleCustomAmountChange = useCallback15((text) => {
3992
+ const handleCustomAmountChange = useCallback16((text) => {
3746
3993
  const cleaned = text.replace(/[^0-9.]/g, "").replace(/(\..*?)\..*/g, "$1");
3747
3994
  setCustomAmount(cleaned);
3748
3995
  const parsed = parseFloat(cleaned);
@@ -3757,7 +4004,7 @@ function CreateCustomGameSheet({
3757
4004
  const winnerTakes = pot * (1 - fee / 100);
3758
4005
  const isMutating = mutation.status !== "idle" && mutation.status !== "success" && mutation.status !== "error";
3759
4006
  const canCreate = finalAmount !== null && finalAmount > 0 && !isMutating && mutation.status !== "success";
3760
- const handleCreate = useCallback15(async () => {
4007
+ const handleCreate = useCallback16(async () => {
3761
4008
  if (!finalAmount || !wallet.publicKey) return;
3762
4009
  try {
3763
4010
  await mutation.execute({
@@ -3786,7 +4033,7 @@ function CreateCustomGameSheet({
3786
4033
  KeyboardAvoidingView2,
3787
4034
  {
3788
4035
  style: styles9.keyboardView,
3789
- behavior: Platform4.OS === "ios" ? "padding" : void 0,
4036
+ behavior: Platform5.OS === "ios" ? "padding" : void 0,
3790
4037
  children: /* @__PURE__ */ jsx12(View10, { style: styles9.sheetPositioner, children: /* @__PURE__ */ jsxs10(View10, { style: [styles9.sheet, { backgroundColor: t.background }], children: [
3791
4038
  /* @__PURE__ */ jsx12(View10, { style: styles9.handleRow, children: /* @__PURE__ */ jsx12(View10, { style: [styles9.handle, { backgroundColor: t.textMuted }] }) }),
3792
4039
  /* @__PURE__ */ jsxs10(View10, { style: styles9.header, children: [
@@ -4026,7 +4273,7 @@ var styles9 = StyleSheet10.create({
4026
4273
  });
4027
4274
 
4028
4275
  // src/ui/game/JoinGameSheet.tsx
4029
- import { useState as useState20, useEffect as useEffect12, useRef as useRef5, useCallback as useCallback16, useMemo as useMemo6 } from "react";
4276
+ import { useState as useState21, useEffect as useEffect12, useRef as useRef6, useCallback as useCallback17, useMemo as useMemo7 } from "react";
4030
4277
  import {
4031
4278
  View as View11,
4032
4279
  Text as Text11,
@@ -4036,7 +4283,7 @@ import {
4036
4283
  Animated as Animated3,
4037
4284
  StyleSheet as StyleSheet11,
4038
4285
  KeyboardAvoidingView as KeyboardAvoidingView3,
4039
- Platform as Platform5
4286
+ Platform as Platform6
4040
4287
  } from "react-native";
4041
4288
  import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
4042
4289
  var STATUS_LABELS3 = {
@@ -4062,8 +4309,8 @@ function JoinGameSheet({
4062
4309
  const { wallet } = useDubs();
4063
4310
  const mutation = useJoinGame();
4064
4311
  const isCustomGame = game.gameMode === CUSTOM_GAME_MODE;
4065
- const [selectedTeam, setSelectedTeam] = useState20(null);
4066
- const overlayOpacity = useRef5(new Animated3.Value(0)).current;
4312
+ const [selectedTeam, setSelectedTeam] = useState21(null);
4313
+ const overlayOpacity = useRef6(new Animated3.Value(0)).current;
4067
4314
  useEffect12(() => {
4068
4315
  Animated3.timing(overlayOpacity, {
4069
4316
  toValue: visible ? 1 : 0,
@@ -4097,7 +4344,7 @@ function JoinGameSheet({
4097
4344
  const homePool = game.homePool || 0;
4098
4345
  const awayPool = game.awayPool || 0;
4099
4346
  const buyIn = game.buyIn;
4100
- const { homeOdds, awayOdds, homeBets, awayBets } = useMemo6(() => ({
4347
+ const { homeOdds, awayOdds, homeBets, awayBets } = useMemo7(() => ({
4101
4348
  homeOdds: homePool > 0 ? (totalPool / homePool).toFixed(2) : "\u2014",
4102
4349
  awayOdds: awayPool > 0 ? (totalPool / awayPool).toFixed(2) : "\u2014",
4103
4350
  homeBets: bettors.filter((b) => b.team === "home").length,
@@ -4109,14 +4356,14 @@ function JoinGameSheet({
4109
4356
  const homeName = shortName ? shortName(opponents[0]?.name) : opponents[0]?.name || "Home";
4110
4357
  const awayName = shortName ? shortName(opponents[1]?.name) : opponents[1]?.name || "Away";
4111
4358
  const selectedName = selectedTeam === "home" ? homeName : selectedTeam === "away" ? awayName : "\u2014";
4112
- const alreadyJoined = useMemo6(() => {
4359
+ const alreadyJoined = useMemo7(() => {
4113
4360
  if (!wallet.publicKey) return false;
4114
4361
  const addr = wallet.publicKey.toBase58();
4115
4362
  return bettors.some((b) => b.wallet === addr);
4116
4363
  }, [bettors, wallet.publicKey]);
4117
4364
  const isMutating = mutation.status !== "idle" && mutation.status !== "success" && mutation.status !== "error";
4118
4365
  const canJoin = selectedTeam !== null && !isMutating && mutation.status !== "success" && !alreadyJoined;
4119
- const handleJoin = useCallback16(async () => {
4366
+ const handleJoin = useCallback17(async () => {
4120
4367
  if (!selectedTeam || !wallet.publicKey) return;
4121
4368
  try {
4122
4369
  await mutation.execute({
@@ -4142,7 +4389,7 @@ function JoinGameSheet({
4142
4389
  KeyboardAvoidingView3,
4143
4390
  {
4144
4391
  style: styles10.keyboardView,
4145
- behavior: Platform5.OS === "ios" ? "padding" : void 0,
4392
+ behavior: Platform6.OS === "ios" ? "padding" : void 0,
4146
4393
  children: /* @__PURE__ */ jsx13(View11, { style: styles10.sheetPositioner, children: /* @__PURE__ */ jsxs11(View11, { style: [styles10.sheet, { backgroundColor: t.background }], children: [
4147
4394
  /* @__PURE__ */ jsx13(View11, { style: styles10.handleRow, children: /* @__PURE__ */ jsx13(View11, { style: [styles10.handle, { backgroundColor: t.textMuted }] }) }),
4148
4395
  /* @__PURE__ */ jsxs11(View11, { style: styles10.header, children: [
@@ -4260,7 +4507,7 @@ function TeamButton({
4260
4507
  ImageComponent,
4261
4508
  t
4262
4509
  }) {
4263
- const [imgFailed, setImgFailed] = useState20(false);
4510
+ const [imgFailed, setImgFailed] = useState21(false);
4264
4511
  const Img = ImageComponent || __require("react-native").Image;
4265
4512
  const showImage = imageUrl && !imgFailed;
4266
4513
  return /* @__PURE__ */ jsxs11(
@@ -4437,7 +4684,7 @@ var styles10 = StyleSheet11.create({
4437
4684
  });
4438
4685
 
4439
4686
  // src/ui/game/ClaimPrizeSheet.tsx
4440
- import { useState as useState21, useEffect as useEffect13, useRef as useRef6, useCallback as useCallback17 } from "react";
4687
+ import { useState as useState22, useEffect as useEffect13, useRef as useRef7, useCallback as useCallback18 } from "react";
4441
4688
  import {
4442
4689
  View as View12,
4443
4690
  Text as Text12,
@@ -4447,7 +4694,7 @@ import {
4447
4694
  Animated as Animated4,
4448
4695
  StyleSheet as StyleSheet12,
4449
4696
  KeyboardAvoidingView as KeyboardAvoidingView4,
4450
- Platform as Platform6
4697
+ Platform as Platform7
4451
4698
  } from "react-native";
4452
4699
  import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
4453
4700
  var STATUS_LABELS4 = {
@@ -4468,10 +4715,10 @@ function ClaimPrizeSheet({
4468
4715
  const t = useDubsTheme();
4469
4716
  const { wallet } = useDubs();
4470
4717
  const mutation = useClaim();
4471
- const overlayOpacity = useRef6(new Animated4.Value(0)).current;
4472
- const celebrationScale = useRef6(new Animated4.Value(0)).current;
4473
- const celebrationOpacity = useRef6(new Animated4.Value(0)).current;
4474
- const [showCelebration, setShowCelebration] = useState21(false);
4718
+ const overlayOpacity = useRef7(new Animated4.Value(0)).current;
4719
+ const celebrationScale = useRef7(new Animated4.Value(0)).current;
4720
+ const celebrationOpacity = useRef7(new Animated4.Value(0)).current;
4721
+ const [showCelebration, setShowCelebration] = useState22(false);
4475
4722
  useEffect13(() => {
4476
4723
  Animated4.timing(overlayOpacity, {
4477
4724
  toValue: visible ? 1 : 0,
@@ -4517,7 +4764,7 @@ function ClaimPrizeSheet({
4517
4764
  }, [mutation.status, mutation.error]);
4518
4765
  const isMutating = mutation.status !== "idle" && mutation.status !== "success" && mutation.status !== "error";
4519
4766
  const canClaim = !isMutating && mutation.status !== "success" && !!wallet.publicKey;
4520
- const handleClaim = useCallback17(async () => {
4767
+ const handleClaim = useCallback18(async () => {
4521
4768
  if (!wallet.publicKey) return;
4522
4769
  try {
4523
4770
  await mutation.execute({
@@ -4542,7 +4789,7 @@ function ClaimPrizeSheet({
4542
4789
  KeyboardAvoidingView4,
4543
4790
  {
4544
4791
  style: styles11.keyboardView,
4545
- behavior: Platform6.OS === "ios" ? "padding" : void 0,
4792
+ behavior: Platform7.OS === "ios" ? "padding" : void 0,
4546
4793
  children: /* @__PURE__ */ jsx14(View12, { style: styles11.sheetPositioner, children: /* @__PURE__ */ jsxs12(View12, { style: [styles11.sheet, { backgroundColor: t.background }], children: [
4547
4794
  /* @__PURE__ */ jsx14(View12, { style: styles11.handleRow, children: /* @__PURE__ */ jsx14(View12, { style: [styles11.handle, { backgroundColor: t.textMuted }] }) }),
4548
4795
  /* @__PURE__ */ jsxs12(View12, { style: styles11.header, children: [
@@ -4750,7 +4997,7 @@ var styles11 = StyleSheet12.create({
4750
4997
  });
4751
4998
 
4752
4999
  // src/ui/game/ClaimButton.tsx
4753
- import { useState as useState22, useMemo as useMemo7, useCallback as useCallback18 } from "react";
5000
+ import { useState as useState23, useMemo as useMemo8, useCallback as useCallback19 } from "react";
4754
5001
  import { StyleSheet as StyleSheet13, Text as Text13, TouchableOpacity as TouchableOpacity9 } from "react-native";
4755
5002
  import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
4756
5003
  function ClaimButton({ gameId, style, onSuccess, onError }) {
@@ -4758,9 +5005,9 @@ function ClaimButton({ gameId, style, onSuccess, onError }) {
4758
5005
  const { wallet } = useDubs();
4759
5006
  const game = useGame(gameId);
4760
5007
  const claimStatus = useHasClaimed(gameId);
4761
- const [sheetVisible, setSheetVisible] = useState22(false);
5008
+ const [sheetVisible, setSheetVisible] = useState23(false);
4762
5009
  const walletAddress = wallet.publicKey?.toBase58() ?? null;
4763
- const myBet = useMemo7(() => {
5010
+ const myBet = useMemo8(() => {
4764
5011
  if (!walletAddress || !game.data?.bettors) return null;
4765
5012
  return game.data.bettors.find((b) => b.wallet === walletAddress) ?? null;
4766
5013
  }, [walletAddress, game.data?.bettors]);
@@ -4769,7 +5016,7 @@ function ClaimButton({ gameId, style, onSuccess, onError }) {
4769
5016
  const isWinner = isResolved && myBet != null && myBet.team === game.data?.winnerSide;
4770
5017
  const isEligible = myBet != null && isResolved && (isWinner || isRefund);
4771
5018
  const prizeAmount = isRefund ? myBet?.amount ?? 0 : game.data?.totalPool ?? 0;
4772
- const handleSuccess = useCallback18(
5019
+ const handleSuccess = useCallback19(
4773
5020
  (result) => {
4774
5021
  claimStatus.refetch();
4775
5022
  onSuccess?.(result);
@@ -4897,6 +5144,7 @@ export {
4897
5144
  useHasClaimed,
4898
5145
  useJoinGame,
4899
5146
  useNetworkGames,
5147
+ usePushNotifications,
4900
5148
  useUFCFightCard,
4901
5149
  useUFCFighterDetail
4902
5150
  };