@dubsdotapp/expo 0.1.3 → 0.2.1

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.js CHANGED
@@ -37,12 +37,20 @@ __export(index_exports, {
37
37
  DubsApiError: () => DubsApiError,
38
38
  DubsClient: () => DubsClient,
39
39
  DubsProvider: () => DubsProvider,
40
+ GamePoster: () => GamePoster,
41
+ JoinGameButton: () => JoinGameButton,
42
+ LivePoolsCard: () => LivePoolsCard,
40
43
  MwaWalletAdapter: () => MwaWalletAdapter,
44
+ NETWORK_CONFIG: () => NETWORK_CONFIG,
45
+ PickWinnerCard: () => PickWinnerCard,
46
+ PlayersCard: () => PlayersCard,
41
47
  SOLANA_PROGRAM_ERRORS: () => SOLANA_PROGRAM_ERRORS,
48
+ STORAGE_KEYS: () => STORAGE_KEYS,
42
49
  SettingsSheet: () => SettingsSheet,
43
50
  UserProfileCard: () => UserProfileCard,
51
+ createSecureStoreStorage: () => createSecureStoreStorage,
52
+ mergeTheme: () => mergeTheme,
44
53
  parseSolanaError: () => parseSolanaError,
45
- pollTransactionConfirmation: () => pollTransactionConfirmation,
46
54
  signAndSendBase64Transaction: () => signAndSendBase64Transaction,
47
55
  useAuth: () => useAuth,
48
56
  useClaim: () => useClaim,
@@ -60,6 +68,18 @@ module.exports = __toCommonJS(index_exports);
60
68
  // src/constants.ts
61
69
  var DEFAULT_BASE_URL = "https://dubs-server-prod-9c91d3f01199.herokuapp.com/api/developer/v1";
62
70
  var DEFAULT_RPC_URL = "https://api.mainnet-beta.solana.com";
71
+ var NETWORK_CONFIG = {
72
+ "mainnet-beta": {
73
+ baseUrl: DEFAULT_BASE_URL,
74
+ rpcUrl: DEFAULT_RPC_URL,
75
+ cluster: "mainnet-beta"
76
+ },
77
+ devnet: {
78
+ baseUrl: "https://dubs-server-dev-55d1fba09a97.herokuapp.com/api/developer/v1",
79
+ rpcUrl: "https://api.devnet.solana.com",
80
+ cluster: "devnet"
81
+ }
82
+ };
63
83
 
64
84
  // src/errors.ts
65
85
  var DubsApiError = class extends Error {
@@ -193,12 +213,20 @@ var DubsClient = class {
193
213
  if (this._token) {
194
214
  headers["Authorization"] = `Bearer ${this._token}`;
195
215
  }
216
+ console.log(`[DubsClient] ${method} ${url}`, body ? JSON.stringify(body).slice(0, 200) : "");
196
217
  const res = await fetch(url, {
197
218
  method,
198
219
  headers,
199
220
  body: body ? JSON.stringify(body) : void 0
200
221
  });
201
- const json = await res.json();
222
+ const text = await res.text();
223
+ console.log(`[DubsClient] ${method} ${path} \u2192 ${res.status}`, text.slice(0, 300));
224
+ let json;
225
+ try {
226
+ json = JSON.parse(text);
227
+ } catch {
228
+ throw new DubsApiError("parse_error", `Invalid JSON response: ${text.slice(0, 100)}`, res.status);
229
+ }
202
230
  if (!json.success) {
203
231
  const err = json.error;
204
232
  if (typeof err === "object" && err !== null) {
@@ -318,6 +346,13 @@ var DubsClient = class {
318
346
  );
319
347
  return res.game;
320
348
  }
349
+ async getLiveScore(gameId) {
350
+ const res = await this.request(
351
+ "GET",
352
+ `/games/${encodeURIComponent(gameId)}/live-score`
353
+ );
354
+ return res.liveScore;
355
+ }
321
356
  async getGames(params) {
322
357
  const qs = new URLSearchParams();
323
358
  if (params?.wallet) qs.set("wallet", params.wallet);
@@ -335,6 +370,7 @@ var DubsClient = class {
335
370
  async getNetworkGames(params) {
336
371
  const qs = new URLSearchParams();
337
372
  if (params?.league) qs.set("league", params.league);
373
+ if (params?.exclude_wallet) qs.set("exclude_wallet", params.exclude_wallet);
338
374
  if (params?.limit != null) qs.set("limit", String(params.limit));
339
375
  if (params?.offset != null) qs.set("offset", String(params.offset));
340
376
  const query = qs.toString();
@@ -441,37 +477,64 @@ var DubsClient = class {
441
477
  getErrorCodesLocal() {
442
478
  return { ...SOLANA_PROGRAM_ERRORS };
443
479
  }
480
+ // ── App Config ──
481
+ /** Fetch the app's UI customization config (accent color, icon, tagline) */
482
+ async getAppConfig() {
483
+ const res = await this.request("GET", "/apps/config");
484
+ return res.uiConfig || {};
485
+ }
444
486
  };
445
487
 
446
- // src/provider.tsx
447
- var import_react = require("react");
448
- var import_web3 = require("@solana/web3.js");
449
- var import_jsx_runtime = require("react/jsx-runtime");
450
- var DubsContext = (0, import_react.createContext)(null);
451
- function DubsProvider({ apiKey, baseUrl, rpcUrl, wallet, children }) {
452
- const value = (0, import_react.useMemo)(() => {
453
- const client = new DubsClient({ apiKey, baseUrl });
454
- const connection = new import_web3.Connection(rpcUrl || DEFAULT_RPC_URL, { commitment: "confirmed" });
455
- return { client, wallet, connection };
456
- }, [apiKey, baseUrl, rpcUrl, wallet]);
457
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DubsContext.Provider, { value, children });
458
- }
459
- function useDubs() {
460
- const ctx = (0, import_react.useContext)(DubsContext);
461
- if (!ctx) {
462
- throw new Error("useDubs must be used within a <DubsProvider>");
488
+ // src/storage.ts
489
+ var STORAGE_KEYS = {
490
+ MWA_AUTH_TOKEN: "dubs_mwa_auth_token",
491
+ JWT_TOKEN: "dubs_jwt_token"
492
+ };
493
+ function createSecureStoreStorage() {
494
+ let SecureStore = null;
495
+ function getStore() {
496
+ if (!SecureStore) {
497
+ try {
498
+ SecureStore = require("expo-secure-store");
499
+ } catch {
500
+ throw new Error(
501
+ "@dubsdotapp/expo: expo-secure-store is required for default token storage. Install it with: npx expo install expo-secure-store \u2014 or pass a custom tokenStorage prop to <DubsProvider>."
502
+ );
503
+ }
504
+ }
505
+ return SecureStore;
463
506
  }
464
- return ctx;
507
+ return {
508
+ async getItem(key) {
509
+ const store = getStore();
510
+ return store.getItemAsync(key);
511
+ },
512
+ async setItem(key, value) {
513
+ const store = getStore();
514
+ await store.setItemAsync(key, value);
515
+ },
516
+ async deleteItem(key) {
517
+ const store = getStore();
518
+ await store.deleteItemAsync(key);
519
+ }
520
+ };
465
521
  }
466
522
 
523
+ // src/provider.tsx
524
+ var import_react12 = require("react");
525
+ var import_web33 = require("@solana/web3.js");
526
+
527
+ // src/managed-wallet.tsx
528
+ var import_react = require("react");
529
+
467
530
  // src/wallet/mwa-adapter.ts
468
- var import_web32 = require("@solana/web3.js");
531
+ var import_web3 = require("@solana/web3.js");
469
532
  function toPublicKey(address) {
470
533
  if (address instanceof Uint8Array) {
471
- return new import_web32.PublicKey(address);
534
+ return new import_web3.PublicKey(address);
472
535
  }
473
536
  const bytes = Uint8Array.from(atob(address), (c) => c.charCodeAt(0));
474
- return new import_web32.PublicKey(bytes);
537
+ return new import_web3.PublicKey(bytes);
475
538
  }
476
539
  var MwaWalletAdapter = class {
477
540
  constructor(config) {
@@ -487,6 +550,12 @@ var MwaWalletAdapter = class {
487
550
  get connected() {
488
551
  return this._connected;
489
552
  }
553
+ get authToken() {
554
+ return this._authToken;
555
+ }
556
+ setAuthToken(token) {
557
+ this._authToken = token;
558
+ }
490
559
  /**
491
560
  * Connect to a mobile wallet. Call this before any signing.
492
561
  */
@@ -550,17 +619,299 @@ var MwaWalletAdapter = class {
550
619
  return result[0];
551
620
  });
552
621
  if (signature instanceof Uint8Array) {
553
- const bs582 = await import("@solana/web3.js").then(() => {
554
- return new import_web32.PublicKey(signature).toBase58();
555
- }).catch(() => {
556
- return Buffer.from(signature).toString("base64");
557
- });
558
- return bs582;
622
+ return new import_web3.PublicKey(signature).toBase58();
559
623
  }
560
624
  return String(signature);
561
625
  }
562
626
  };
563
627
 
628
+ // src/ui/ConnectWalletScreen.tsx
629
+ var import_react_native2 = require("react-native");
630
+
631
+ // src/ui/theme.ts
632
+ var import_react_native = require("react-native");
633
+ var dark = {
634
+ background: "#08080D",
635
+ surface: "#111118",
636
+ surfaceActive: "#7C3AED",
637
+ border: "#1A1A24",
638
+ text: "#FFFFFF",
639
+ textSecondary: "#E0E0EE",
640
+ textMuted: "#666666",
641
+ textDim: "#555555",
642
+ accent: "#7C3AED",
643
+ success: "#22C55E",
644
+ live: "#EF4444",
645
+ errorText: "#F87171",
646
+ errorBg: "#1A0A0A",
647
+ errorBorder: "#3A1515"
648
+ };
649
+ var light = {
650
+ background: "#FFFFFF",
651
+ surface: "#F0F0F5",
652
+ surfaceActive: "#7C3AED",
653
+ border: "#E0E0E8",
654
+ text: "#111118",
655
+ textSecondary: "#333333",
656
+ textMuted: "#888888",
657
+ textDim: "#999999",
658
+ accent: "#7C3AED",
659
+ success: "#16A34A",
660
+ live: "#DC2626",
661
+ errorText: "#DC2626",
662
+ errorBg: "#FEF2F2",
663
+ errorBorder: "#FECACA"
664
+ };
665
+ function useDubsTheme() {
666
+ const scheme = (0, import_react_native.useColorScheme)();
667
+ return scheme === "light" ? light : dark;
668
+ }
669
+ function mergeTheme(base, overrides) {
670
+ return { ...base, ...overrides };
671
+ }
672
+
673
+ // src/ui/ConnectWalletScreen.tsx
674
+ var import_jsx_runtime = require("react/jsx-runtime");
675
+ function ConnectWalletScreen({
676
+ onConnect,
677
+ connecting = false,
678
+ error = null,
679
+ appName = "Dubs",
680
+ accentColor,
681
+ appIcon,
682
+ tagline
683
+ }) {
684
+ const t = useDubsTheme();
685
+ const accent = accentColor || t.accent;
686
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [styles.container, { backgroundColor: t.background }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: styles.content, children: [
687
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: styles.brandingSection, children: [
688
+ appIcon ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
689
+ import_react_native2.Image,
690
+ {
691
+ source: { uri: appIcon },
692
+ style: styles.logoImage
693
+ }
694
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.View, { style: [styles.logoCircle, { backgroundColor: accent }], children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: styles.logoText, children: appName.charAt(0).toUpperCase() }) }),
695
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [styles.appName, { color: t.text }], children: appName }),
696
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [styles.subtitle, { color: t.textMuted }], children: tagline || "Connect your Solana wallet to get started" })
697
+ ] }),
698
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_native2.View, { style: styles.actionSection, children: [
699
+ error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
700
+ import_react_native2.View,
701
+ {
702
+ style: [
703
+ styles.errorBox,
704
+ { backgroundColor: t.errorBg, borderColor: t.errorBorder }
705
+ ],
706
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [styles.errorText, { color: t.errorText }], children: error })
707
+ }
708
+ ) : null,
709
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
710
+ import_react_native2.TouchableOpacity,
711
+ {
712
+ style: [styles.connectButton, { backgroundColor: accent }],
713
+ onPress: onConnect,
714
+ disabled: connecting,
715
+ activeOpacity: 0.8,
716
+ children: connecting ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.ActivityIndicator, { color: "#FFFFFF", size: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: styles.connectButtonText, children: "Connect Wallet" })
717
+ }
718
+ ),
719
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_native2.Text, { style: [styles.hint, { color: t.textDim }], children: "Phantom, Solflare, or any Solana wallet" })
720
+ ] })
721
+ ] }) });
722
+ }
723
+ var styles = import_react_native2.StyleSheet.create({
724
+ container: {
725
+ flex: 1,
726
+ justifyContent: "center"
727
+ },
728
+ content: {
729
+ flex: 1,
730
+ justifyContent: "space-between",
731
+ paddingHorizontal: 32,
732
+ paddingTop: 120,
733
+ paddingBottom: 80
734
+ },
735
+ brandingSection: {
736
+ alignItems: "center",
737
+ gap: 12
738
+ },
739
+ logoCircle: {
740
+ width: 80,
741
+ height: 80,
742
+ borderRadius: 40,
743
+ justifyContent: "center",
744
+ alignItems: "center",
745
+ marginBottom: 8
746
+ },
747
+ logoImage: {
748
+ width: 80,
749
+ height: 80,
750
+ borderRadius: 16,
751
+ marginBottom: 8
752
+ },
753
+ logoText: {
754
+ fontSize: 36,
755
+ fontWeight: "800",
756
+ color: "#FFFFFF"
757
+ },
758
+ appName: {
759
+ fontSize: 32,
760
+ fontWeight: "800"
761
+ },
762
+ subtitle: {
763
+ fontSize: 16,
764
+ textAlign: "center",
765
+ lineHeight: 22
766
+ },
767
+ actionSection: {
768
+ gap: 16
769
+ },
770
+ errorBox: {
771
+ borderWidth: 1,
772
+ borderRadius: 12,
773
+ paddingHorizontal: 16,
774
+ paddingVertical: 12
775
+ },
776
+ errorText: {
777
+ fontSize: 14,
778
+ textAlign: "center"
779
+ },
780
+ connectButton: {
781
+ height: 56,
782
+ borderRadius: 16,
783
+ justifyContent: "center",
784
+ alignItems: "center"
785
+ },
786
+ connectButtonText: {
787
+ color: "#FFFFFF",
788
+ fontSize: 18,
789
+ fontWeight: "700"
790
+ },
791
+ hint: {
792
+ fontSize: 13,
793
+ textAlign: "center"
794
+ }
795
+ });
796
+
797
+ // src/managed-wallet.tsx
798
+ var import_jsx_runtime2 = require("react/jsx-runtime");
799
+ var DisconnectContext = (0, import_react.createContext)(null);
800
+ function useDisconnect() {
801
+ return (0, import_react.useContext)(DisconnectContext);
802
+ }
803
+ function ManagedWalletProvider({
804
+ appName,
805
+ cluster,
806
+ storage,
807
+ renderConnectScreen,
808
+ accentColor,
809
+ appIcon,
810
+ tagline,
811
+ children
812
+ }) {
813
+ const [connected, setConnected] = (0, import_react.useState)(false);
814
+ const [connecting, setConnecting] = (0, import_react.useState)(false);
815
+ const [isReady, setIsReady] = (0, import_react.useState)(false);
816
+ const [error, setError] = (0, import_react.useState)(null);
817
+ const adapterRef = (0, import_react.useRef)(null);
818
+ const transactRef = (0, import_react.useRef)(null);
819
+ if (!adapterRef.current) {
820
+ adapterRef.current = new MwaWalletAdapter({
821
+ transact: (...args) => {
822
+ if (!transactRef.current) {
823
+ throw new Error(
824
+ "@dubsdotapp/expo: @solana-mobile/mobile-wallet-adapter-protocol-web3js is required. Install it with: npm install @solana-mobile/mobile-wallet-adapter-protocol-web3js"
825
+ );
826
+ }
827
+ return transactRef.current(...args);
828
+ },
829
+ appIdentity: { name: appName },
830
+ cluster,
831
+ onAuthTokenChange: (token) => {
832
+ if (token) {
833
+ storage.setItem(STORAGE_KEYS.MWA_AUTH_TOKEN, token).catch(() => {
834
+ });
835
+ } else {
836
+ storage.deleteItem(STORAGE_KEYS.MWA_AUTH_TOKEN).catch(() => {
837
+ });
838
+ }
839
+ }
840
+ });
841
+ }
842
+ const adapter = adapterRef.current;
843
+ (0, import_react.useEffect)(() => {
844
+ let cancelled = false;
845
+ (async () => {
846
+ try {
847
+ const mwa = await import("@solana-mobile/mobile-wallet-adapter-protocol-web3js");
848
+ if (cancelled) return;
849
+ transactRef.current = mwa.transact;
850
+ } catch {
851
+ }
852
+ try {
853
+ const savedToken = await storage.getItem(STORAGE_KEYS.MWA_AUTH_TOKEN);
854
+ if (savedToken && !cancelled) {
855
+ adapter.setAuthToken(savedToken);
856
+ await adapter.connect();
857
+ if (!cancelled) setConnected(true);
858
+ }
859
+ } catch {
860
+ } finally {
861
+ if (!cancelled) setIsReady(true);
862
+ }
863
+ })();
864
+ return () => {
865
+ cancelled = true;
866
+ };
867
+ }, [adapter, storage]);
868
+ const handleConnect = (0, import_react.useCallback)(async () => {
869
+ setConnecting(true);
870
+ setError(null);
871
+ try {
872
+ await adapter.connect();
873
+ setConnected(true);
874
+ } catch (err) {
875
+ const message = err instanceof Error ? err.message : "Connection failed";
876
+ setError(message);
877
+ } finally {
878
+ setConnecting(false);
879
+ }
880
+ }, [adapter]);
881
+ const disconnect = (0, import_react.useCallback)(async () => {
882
+ adapter.disconnect();
883
+ await storage.deleteItem(STORAGE_KEYS.MWA_AUTH_TOKEN).catch(() => {
884
+ });
885
+ await storage.deleteItem(STORAGE_KEYS.JWT_TOKEN).catch(() => {
886
+ });
887
+ setConnected(false);
888
+ }, [adapter, storage]);
889
+ if (!isReady) return null;
890
+ if (!connected) {
891
+ if (renderConnectScreen === false) {
892
+ return null;
893
+ }
894
+ const connectProps = {
895
+ onConnect: handleConnect,
896
+ connecting,
897
+ error,
898
+ appName,
899
+ accentColor,
900
+ appIcon,
901
+ tagline
902
+ };
903
+ if (renderConnectScreen) {
904
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: renderConnectScreen(connectProps) });
905
+ }
906
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ConnectWalletScreen, { ...connectProps });
907
+ }
908
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DisconnectContext.Provider, { value: disconnect, children: children(adapter) });
909
+ }
910
+
911
+ // src/ui/AuthGate.tsx
912
+ var import_react11 = __toESM(require("react"));
913
+ var import_react_native3 = require("react-native");
914
+
564
915
  // src/hooks/useEvents.ts
565
916
  var import_react2 = require("react");
566
917
  function useEvents(params) {
@@ -669,44 +1020,24 @@ function useNetworkGames(params) {
669
1020
  var import_react6 = require("react");
670
1021
 
671
1022
  // src/utils/transaction.ts
672
- var import_web33 = require("@solana/web3.js");
673
- async function signAndSendBase64Transaction(base64Tx, wallet, connection) {
1023
+ var import_web32 = require("@solana/web3.js");
1024
+ async function signAndSendBase64Transaction(base64Tx, wallet) {
674
1025
  if (!wallet.publicKey) throw new Error("Wallet not connected");
675
- const txBuffer = Buffer.from(base64Tx, "base64");
676
- const transaction = import_web33.Transaction.from(txBuffer);
1026
+ const binaryStr = atob(base64Tx);
1027
+ const bytes = new Uint8Array(binaryStr.length);
1028
+ for (let i = 0; i < binaryStr.length; i++) {
1029
+ bytes[i] = binaryStr.charCodeAt(i);
1030
+ }
1031
+ const transaction = import_web32.Transaction.from(bytes);
677
1032
  if (wallet.signAndSendTransaction) {
678
1033
  return wallet.signAndSendTransaction(transaction);
679
1034
  }
680
- const signed = await wallet.signTransaction(transaction);
681
- const signature = await connection.sendRawTransaction(signed.serialize(), {
682
- skipPreflight: true
683
- });
684
- return signature;
685
- }
686
- async function pollTransactionConfirmation(signature, connection, commitment = "confirmed", timeout = 6e4, interval = 1500) {
687
- const start = Date.now();
688
- const confirmationOrder = ["processed", "confirmed", "finalized"];
689
- const targetIndex = confirmationOrder.indexOf(commitment);
690
- while (Date.now() - start < timeout) {
691
- const statuses = await connection.getSignatureStatuses([signature]);
692
- const status = statuses?.value?.[0];
693
- if (status?.err) {
694
- throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
695
- }
696
- if (status?.confirmationStatus) {
697
- const currentIndex = confirmationOrder.indexOf(status.confirmationStatus);
698
- if (currentIndex >= targetIndex) {
699
- return;
700
- }
701
- }
702
- await new Promise((resolve) => setTimeout(resolve, interval));
703
- }
704
- throw new Error(`Transaction confirmation timeout after ${timeout}ms`);
1035
+ throw new Error("Wallet does not support signAndSendTransaction");
705
1036
  }
706
1037
 
707
1038
  // src/hooks/useCreateGame.ts
708
1039
  function useCreateGame() {
709
- const { client, wallet, connection } = useDubs();
1040
+ const { client, wallet } = useDubs();
710
1041
  const [status, setStatus] = (0, import_react6.useState)("idle");
711
1042
  const [error, setError] = (0, import_react6.useState)(null);
712
1043
  const [data, setData] = (0, import_react6.useState)(null);
@@ -720,17 +1051,18 @@ function useCreateGame() {
720
1051
  setError(null);
721
1052
  setData(null);
722
1053
  try {
1054
+ console.log("[useCreateGame] Step 1: Building transaction...");
723
1055
  const createResult = await client.createGame(params);
1056
+ console.log("[useCreateGame] Step 1 done:", { gameId: createResult.gameId, gameAddress: createResult.gameAddress });
724
1057
  setStatus("signing");
1058
+ console.log("[useCreateGame] Step 2: Signing and sending...");
725
1059
  const signature = await signAndSendBase64Transaction(
726
1060
  createResult.transaction,
727
- wallet,
728
- connection
1061
+ wallet
729
1062
  );
1063
+ console.log("[useCreateGame] Step 2 done. Signature:", signature);
730
1064
  setStatus("confirming");
731
- await pollTransactionConfirmation(signature, connection);
732
- setStatus("saving");
733
- const explorerUrl = `https://solscan.io/tx/${signature}`;
1065
+ console.log("[useCreateGame] Step 3: Confirming with backend...");
734
1066
  await client.confirmGame({
735
1067
  gameId: createResult.gameId,
736
1068
  playerWallet: params.playerWallet,
@@ -740,6 +1072,8 @@ function useCreateGame() {
740
1072
  role: "creator",
741
1073
  gameAddress: createResult.gameAddress
742
1074
  });
1075
+ console.log("[useCreateGame] Step 3 done.");
1076
+ const explorerUrl = `https://solscan.io/tx/${signature}`;
743
1077
  const result = {
744
1078
  gameId: createResult.gameId,
745
1079
  gameAddress: createResult.gameAddress,
@@ -748,21 +1082,23 @@ function useCreateGame() {
748
1082
  };
749
1083
  setData(result);
750
1084
  setStatus("success");
1085
+ console.log("[useCreateGame] Complete!");
751
1086
  return result;
752
1087
  } catch (err) {
1088
+ console.error("[useCreateGame] FAILED:", err);
753
1089
  const error2 = err instanceof Error ? err : new Error(String(err));
754
1090
  setError(error2);
755
1091
  setStatus("error");
756
1092
  throw error2;
757
1093
  }
758
- }, [client, wallet, connection]);
1094
+ }, [client, wallet]);
759
1095
  return { execute, status, error, data, reset };
760
1096
  }
761
1097
 
762
1098
  // src/hooks/useJoinGame.ts
763
1099
  var import_react7 = require("react");
764
1100
  function useJoinGame() {
765
- const { client, wallet, connection } = useDubs();
1101
+ const { client, wallet } = useDubs();
766
1102
  const [status, setStatus] = (0, import_react7.useState)("idle");
767
1103
  const [error, setError] = (0, import_react7.useState)(null);
768
1104
  const [data, setData] = (0, import_react7.useState)(null);
@@ -776,18 +1112,18 @@ function useJoinGame() {
776
1112
  setError(null);
777
1113
  setData(null);
778
1114
  try {
1115
+ console.log("[useJoinGame] Step 1: Building transaction...", { gameId: params.gameId, playerWallet: params.playerWallet, teamChoice: params.teamChoice, amount: params.amount });
779
1116
  const joinResult = await client.joinGame(params);
1117
+ console.log("[useJoinGame] Step 1 done:", { gameId: joinResult.gameId, gameAddress: joinResult.gameAddress, hasTx: !!joinResult.transaction });
780
1118
  setStatus("signing");
1119
+ console.log("[useJoinGame] Step 2: Signing and sending transaction...");
781
1120
  const signature = await signAndSendBase64Transaction(
782
1121
  joinResult.transaction,
783
- wallet,
784
- connection
1122
+ wallet
785
1123
  );
1124
+ console.log("[useJoinGame] Step 2 done. Signature:", signature);
786
1125
  setStatus("confirming");
787
- await pollTransactionConfirmation(signature, connection);
788
- setStatus("saving");
789
- const explorerUrl = `https://solscan.io/tx/${signature}`;
790
- await client.confirmGame({
1126
+ const confirmParams = {
791
1127
  gameId: params.gameId,
792
1128
  playerWallet: params.playerWallet,
793
1129
  signature,
@@ -795,7 +1131,11 @@ function useJoinGame() {
795
1131
  wagerAmount: params.amount,
796
1132
  role: "joiner",
797
1133
  gameAddress: joinResult.gameAddress
798
- });
1134
+ };
1135
+ console.log("[useJoinGame] Step 3: Confirming with backend...", confirmParams);
1136
+ await client.confirmGame(confirmParams);
1137
+ console.log("[useJoinGame] Step 3 done. Backend confirmed.");
1138
+ const explorerUrl = `https://solscan.io/tx/${signature}`;
799
1139
  const result = {
800
1140
  gameId: params.gameId,
801
1141
  gameAddress: joinResult.gameAddress,
@@ -804,21 +1144,23 @@ function useJoinGame() {
804
1144
  };
805
1145
  setData(result);
806
1146
  setStatus("success");
1147
+ console.log("[useJoinGame] Complete!");
807
1148
  return result;
808
1149
  } catch (err) {
1150
+ console.error("[useJoinGame] FAILED at status:", status, err);
809
1151
  const error2 = err instanceof Error ? err : new Error(String(err));
810
1152
  setError(error2);
811
1153
  setStatus("error");
812
1154
  throw error2;
813
1155
  }
814
- }, [client, wallet, connection]);
1156
+ }, [client, wallet]);
815
1157
  return { execute, status, error, data, reset };
816
1158
  }
817
1159
 
818
1160
  // src/hooks/useClaim.ts
819
1161
  var import_react8 = require("react");
820
1162
  function useClaim() {
821
- const { client, wallet, connection } = useDubs();
1163
+ const { client, wallet } = useDubs();
822
1164
  const [status, setStatus] = (0, import_react8.useState)("idle");
823
1165
  const [error, setError] = (0, import_react8.useState)(null);
824
1166
  const [data, setData] = (0, import_react8.useState)(null);
@@ -832,15 +1174,16 @@ function useClaim() {
832
1174
  setError(null);
833
1175
  setData(null);
834
1176
  try {
1177
+ console.log("[useClaim] Step 1: Building claim transaction...");
835
1178
  const claimResult = await client.buildClaimTransaction(params);
1179
+ console.log("[useClaim] Step 1 done.");
836
1180
  setStatus("signing");
1181
+ console.log("[useClaim] Step 2: Signing and sending...");
837
1182
  const signature = await signAndSendBase64Transaction(
838
1183
  claimResult.transaction,
839
- wallet,
840
- connection
1184
+ wallet
841
1185
  );
842
- setStatus("confirming");
843
- await pollTransactionConfirmation(signature, connection);
1186
+ console.log("[useClaim] Step 2 done. Signature:", signature);
844
1187
  const explorerUrl = `https://solscan.io/tx/${signature}`;
845
1188
  const result = {
846
1189
  gameId: params.gameId,
@@ -849,28 +1192,37 @@ function useClaim() {
849
1192
  };
850
1193
  setData(result);
851
1194
  setStatus("success");
1195
+ console.log("[useClaim] Complete!");
852
1196
  return result;
853
1197
  } catch (err) {
1198
+ console.error("[useClaim] FAILED:", err);
854
1199
  const error2 = err instanceof Error ? err : new Error(String(err));
855
1200
  setError(error2);
856
1201
  setStatus("error");
857
1202
  throw error2;
858
1203
  }
859
- }, [client, wallet, connection]);
1204
+ }, [client, wallet]);
860
1205
  return { execute, status, error, data, reset };
861
1206
  }
862
1207
 
863
1208
  // src/hooks/useAuth.ts
864
- var import_react9 = require("react");
1209
+ var import_react10 = require("react");
865
1210
  var import_bs58 = __toESM(require("bs58"));
1211
+
1212
+ // src/auth-context.ts
1213
+ var import_react9 = require("react");
1214
+ var AuthContext = (0, import_react9.createContext)(null);
1215
+
1216
+ // src/hooks/useAuth.ts
866
1217
  function useAuth() {
1218
+ const sharedAuth = (0, import_react10.useContext)(AuthContext);
867
1219
  const { client, wallet } = useDubs();
868
- const [status, setStatus] = (0, import_react9.useState)("idle");
869
- const [user, setUser] = (0, import_react9.useState)(null);
870
- const [token, setToken] = (0, import_react9.useState)(null);
871
- const [error, setError] = (0, import_react9.useState)(null);
872
- const pendingAuth = (0, import_react9.useRef)(null);
873
- const reset = (0, import_react9.useCallback)(() => {
1220
+ const [status, setStatus] = (0, import_react10.useState)("idle");
1221
+ const [user, setUser] = (0, import_react10.useState)(null);
1222
+ const [token, setToken] = (0, import_react10.useState)(null);
1223
+ const [error, setError] = (0, import_react10.useState)(null);
1224
+ const pendingAuth = (0, import_react10.useRef)(null);
1225
+ const reset = (0, import_react10.useCallback)(() => {
874
1226
  setStatus("idle");
875
1227
  setUser(null);
876
1228
  setToken(null);
@@ -878,7 +1230,7 @@ function useAuth() {
878
1230
  pendingAuth.current = null;
879
1231
  client.setToken(null);
880
1232
  }, [client]);
881
- const authenticate = (0, import_react9.useCallback)(async () => {
1233
+ const authenticate = (0, import_react10.useCallback)(async () => {
882
1234
  try {
883
1235
  if (!wallet.publicKey) {
884
1236
  throw new Error("Wallet not connected");
@@ -909,7 +1261,7 @@ function useAuth() {
909
1261
  setStatus("error");
910
1262
  }
911
1263
  }, [client, wallet]);
912
- const register = (0, import_react9.useCallback)(async (username, referralCode, avatarUrl) => {
1264
+ const register = (0, import_react10.useCallback)(async (username, referralCode, avatarUrl) => {
913
1265
  try {
914
1266
  const pending = pendingAuth.current;
915
1267
  if (!pending) {
@@ -926,7 +1278,8 @@ function useAuth() {
926
1278
  avatarUrl
927
1279
  });
928
1280
  pendingAuth.current = null;
929
- setUser(result.user);
1281
+ const user2 = avatarUrl && !result.user.avatar ? { ...result.user, avatar: avatarUrl } : result.user;
1282
+ setUser(user2);
930
1283
  setToken(result.token);
931
1284
  setStatus("authenticated");
932
1285
  } catch (err) {
@@ -934,7 +1287,7 @@ function useAuth() {
934
1287
  setStatus("error");
935
1288
  }
936
1289
  }, [client]);
937
- const logout = (0, import_react9.useCallback)(async () => {
1290
+ const logout = (0, import_react10.useCallback)(async () => {
938
1291
  try {
939
1292
  await client.logout();
940
1293
  } catch {
@@ -945,7 +1298,7 @@ function useAuth() {
945
1298
  setError(null);
946
1299
  pendingAuth.current = null;
947
1300
  }, [client]);
948
- const restoreSession = (0, import_react9.useCallback)(async (savedToken) => {
1301
+ const restoreSession = (0, import_react10.useCallback)(async (savedToken) => {
949
1302
  try {
950
1303
  client.setToken(savedToken);
951
1304
  const me = await client.getMe();
@@ -961,6 +1314,7 @@ function useAuth() {
961
1314
  return false;
962
1315
  }
963
1316
  }, [client]);
1317
+ if (sharedAuth) return sharedAuth;
964
1318
  return {
965
1319
  status,
966
1320
  user,
@@ -976,50 +1330,7 @@ function useAuth() {
976
1330
  }
977
1331
 
978
1332
  // src/ui/AuthGate.tsx
979
- var import_react10 = __toESM(require("react"));
980
- var import_react_native2 = require("react-native");
981
-
982
- // src/ui/theme.ts
983
- var import_react_native = require("react-native");
984
- var dark = {
985
- background: "#08080D",
986
- surface: "#111118",
987
- surfaceActive: "#7C3AED",
988
- border: "#1A1A24",
989
- text: "#FFFFFF",
990
- textSecondary: "#E0E0EE",
991
- textMuted: "#666666",
992
- textDim: "#555555",
993
- accent: "#7C3AED",
994
- success: "#22C55E",
995
- live: "#EF4444",
996
- errorText: "#F87171",
997
- errorBg: "#1A0A0A",
998
- errorBorder: "#3A1515"
999
- };
1000
- var light = {
1001
- background: "#FFFFFF",
1002
- surface: "#F0F0F5",
1003
- surfaceActive: "#7C3AED",
1004
- border: "#E0E0E8",
1005
- text: "#111118",
1006
- textSecondary: "#333333",
1007
- textMuted: "#888888",
1008
- textDim: "#999999",
1009
- accent: "#7C3AED",
1010
- success: "#16A34A",
1011
- live: "#DC2626",
1012
- errorText: "#DC2626",
1013
- errorBg: "#FEF2F2",
1014
- errorBorder: "#FECACA"
1015
- };
1016
- function useDubsTheme() {
1017
- const scheme = (0, import_react_native.useColorScheme)();
1018
- return scheme === "light" ? light : dark;
1019
- }
1020
-
1021
- // src/ui/AuthGate.tsx
1022
- var import_jsx_runtime2 = require("react/jsx-runtime");
1333
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1023
1334
  var DICEBEAR_STYLES = [
1024
1335
  "adventurer",
1025
1336
  "avataaars",
@@ -1041,13 +1352,14 @@ function AuthGate({
1041
1352
  renderLoading,
1042
1353
  renderError,
1043
1354
  renderRegistration,
1044
- appName = "Dubs"
1355
+ appName = "Dubs",
1356
+ accentColor
1045
1357
  }) {
1046
1358
  const { client } = useDubs();
1047
1359
  const auth = useAuth();
1048
- const [phase, setPhase] = (0, import_react10.useState)("init");
1049
- const [registrationPhase, setRegistrationPhase] = (0, import_react10.useState)(false);
1050
- (0, import_react10.useEffect)(() => {
1360
+ const [phase, setPhase] = (0, import_react11.useState)("init");
1361
+ const [registrationPhase, setRegistrationPhase] = (0, import_react11.useState)(false);
1362
+ (0, import_react11.useEffect)(() => {
1051
1363
  let cancelled = false;
1052
1364
  (async () => {
1053
1365
  try {
@@ -1073,51 +1385,54 @@ function AuthGate({
1073
1385
  cancelled = true;
1074
1386
  };
1075
1387
  }, []);
1076
- (0, import_react10.useEffect)(() => {
1388
+ (0, import_react11.useEffect)(() => {
1077
1389
  if (auth.status === "needsRegistration") setRegistrationPhase(true);
1078
1390
  }, [auth.status]);
1079
- (0, import_react10.useEffect)(() => {
1391
+ (0, import_react11.useEffect)(() => {
1080
1392
  if (auth.token) onSaveToken(auth.token);
1081
1393
  }, [auth.token]);
1082
- const retry = (0, import_react10.useCallback)(() => {
1394
+ const retry = (0, import_react11.useCallback)(() => {
1083
1395
  setRegistrationPhase(false);
1084
1396
  auth.reset();
1085
1397
  auth.authenticate();
1086
1398
  }, [auth]);
1087
- const handleRegister = (0, import_react10.useCallback)(
1399
+ const handleRegister = (0, import_react11.useCallback)(
1088
1400
  (username, referralCode, avatarUrl) => {
1089
1401
  auth.register(username, referralCode, avatarUrl);
1090
1402
  },
1091
1403
  [auth]
1092
1404
  );
1093
1405
  if (phase === "init") {
1094
- if (renderLoading) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: renderLoading("authenticating") });
1095
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DefaultLoadingScreen, { status: "authenticating", appName });
1406
+ if (renderLoading) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: renderLoading("authenticating") });
1407
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DefaultLoadingScreen, { status: "authenticating", appName });
1408
+ }
1409
+ if (auth.status === "authenticated") {
1410
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AuthContext.Provider, { value: auth, children });
1096
1411
  }
1097
- if (auth.status === "authenticated") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
1098
1412
  if (registrationPhase) {
1099
1413
  const isRegistering = auth.status === "registering";
1100
1414
  const regError = auth.status === "error" ? auth.error : null;
1101
1415
  if (renderRegistration) {
1102
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: renderRegistration({ onRegister: handleRegister, registering: isRegistering, error: regError, client }) });
1416
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: renderRegistration({ onRegister: handleRegister, registering: isRegistering, error: regError, client }) });
1103
1417
  }
1104
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1418
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1105
1419
  DefaultRegistrationScreen,
1106
1420
  {
1107
1421
  onRegister: handleRegister,
1108
1422
  registering: isRegistering,
1109
1423
  error: regError,
1110
1424
  client,
1111
- appName
1425
+ appName,
1426
+ accentColor
1112
1427
  }
1113
1428
  );
1114
1429
  }
1115
1430
  if (auth.status === "error" && auth.error) {
1116
- if (renderError) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: renderError(auth.error, retry) });
1117
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DefaultErrorScreen, { error: auth.error, onRetry: retry, appName });
1431
+ if (renderError) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: renderError(auth.error, retry) });
1432
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DefaultErrorScreen, { error: auth.error, onRetry: retry, appName });
1118
1433
  }
1119
- if (renderLoading) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: renderLoading(auth.status) });
1120
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DefaultLoadingScreen, { status: auth.status, appName });
1434
+ if (renderLoading) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: renderLoading(auth.status) });
1435
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DefaultLoadingScreen, { status: auth.status, appName });
1121
1436
  }
1122
1437
  function DefaultLoadingScreen({ status, appName }) {
1123
1438
  const t = useDubsTheme();
@@ -1131,43 +1446,43 @@ function DefaultLoadingScreen({ status, appName }) {
1131
1446
  authenticated: "Ready!",
1132
1447
  error: "Something went wrong"
1133
1448
  };
1134
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [s.container, { backgroundColor: t.background }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.centerContent, children: [
1135
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.brandingSection, children: [
1136
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [s.logoCircle, { backgroundColor: t.accent }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: s.logoText, children: "D" }) }),
1137
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.appNameText, { color: t.text }], children: appName })
1449
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [s.container, { backgroundColor: t.background }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.centerContent, children: [
1450
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.brandingSection, children: [
1451
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [s.logoCircle, { backgroundColor: t.accent }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: s.logoText, children: "D" }) }),
1452
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.appNameText, { color: t.text }], children: appName })
1138
1453
  ] }),
1139
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.loadingSection, children: [
1140
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.ActivityIndicator, { size: "large", color: t.accent }),
1141
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.statusText, { color: t.textMuted }], children: statusText[status] || "Loading..." })
1454
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.loadingSection, children: [
1455
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.ActivityIndicator, { size: "large", color: t.accent }),
1456
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.statusText, { color: t.textMuted }], children: statusText[status] || "Loading..." })
1142
1457
  ] })
1143
1458
  ] }) });
1144
1459
  }
1145
1460
  function DefaultErrorScreen({ error, onRetry, appName }) {
1146
1461
  const t = useDubsTheme();
1147
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [s.container, { backgroundColor: t.background }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.spreadContent, children: [
1148
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.brandingSection, children: [
1149
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [s.logoCircle, { backgroundColor: t.accent }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: s.logoText, children: "D" }) }),
1150
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.appNameText, { color: t.text }], children: appName })
1462
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [s.container, { backgroundColor: t.background }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.spreadContent, children: [
1463
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.brandingSection, children: [
1464
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [s.logoCircle, { backgroundColor: t.accent }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: s.logoText, children: "D" }) }),
1465
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.appNameText, { color: t.text }], children: appName })
1151
1466
  ] }),
1152
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: { gap: 16 }, children: [
1153
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [s.errorBox, { backgroundColor: t.errorBg, borderColor: t.errorBorder }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.errorText, { color: t.errorText }], children: error.message }) }),
1154
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.TouchableOpacity, { style: [s.primaryBtn, { backgroundColor: t.accent }], onPress: onRetry, activeOpacity: 0.8, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: s.primaryBtnText, children: "Try Again" }) })
1467
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: { gap: 16 }, children: [
1468
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [s.errorBox, { backgroundColor: t.errorBg, borderColor: t.errorBorder }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.errorText, { color: t.errorText }], children: error.message }) }),
1469
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.TouchableOpacity, { style: [s.primaryBtn, { backgroundColor: t.accent }], onPress: onRetry, activeOpacity: 0.8, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: s.primaryBtnText, children: "Try Again" }) })
1155
1470
  ] })
1156
1471
  ] }) });
1157
1472
  }
1158
1473
  function StepIndicator({ currentStep }) {
1159
1474
  const t = useDubsTheme();
1160
1475
  const steps = [0, 1, 2, 3];
1161
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: s.stepRow, children: steps.map((i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react10.default.Fragment, { children: [
1162
- i > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [s.stepLine, { backgroundColor: i <= currentStep ? t.success : t.border }] }),
1163
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1164
- import_react_native2.View,
1476
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: s.stepRow, children: steps.map((i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react11.default.Fragment, { children: [
1477
+ i > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [s.stepLine, { backgroundColor: i <= currentStep ? t.success : t.border }] }),
1478
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1479
+ import_react_native3.View,
1165
1480
  {
1166
1481
  style: [
1167
1482
  s.stepCircle,
1168
1483
  i < currentStep ? { backgroundColor: t.success } : i === currentStep ? { backgroundColor: t.accent } : { backgroundColor: "transparent", borderWidth: 2, borderColor: t.border }
1169
1484
  ],
1170
- children: i < currentStep ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: s.stepCheck, children: "\u2713" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.stepNum, { color: i === currentStep ? "#FFF" : t.textMuted }], children: i + 1 })
1485
+ children: i < currentStep ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: s.stepCheck, children: "\u2713" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.stepNum, { color: i === currentStep ? "#FFF" : t.textMuted }], children: i + 1 })
1171
1486
  }
1172
1487
  )
1173
1488
  ] }, i)) });
@@ -1177,22 +1492,24 @@ function DefaultRegistrationScreen({
1177
1492
  registering,
1178
1493
  error,
1179
1494
  client,
1180
- appName
1495
+ appName,
1496
+ accentColor
1181
1497
  }) {
1182
1498
  const t = useDubsTheme();
1183
- const [step, setStep] = (0, import_react10.useState)(0);
1184
- const [avatarSeed, setAvatarSeed] = (0, import_react10.useState)(generateSeed);
1185
- const [avatarStyle, setAvatarStyle] = (0, import_react10.useState)("adventurer");
1186
- const [showStyles, setShowStyles] = (0, import_react10.useState)(false);
1187
- const [username, setUsername] = (0, import_react10.useState)("");
1188
- const [referralCode, setReferralCode] = (0, import_react10.useState)("");
1189
- const [checking, setChecking] = (0, import_react10.useState)(false);
1190
- const [availability, setAvailability] = (0, import_react10.useState)(null);
1191
- const debounceRef = (0, import_react10.useRef)(null);
1192
- const fadeAnim = (0, import_react10.useRef)(new import_react_native2.Animated.Value(1)).current;
1193
- const slideAnim = (0, import_react10.useRef)(new import_react_native2.Animated.Value(0)).current;
1499
+ const accent = accentColor || t.accent;
1500
+ const [step, setStep] = (0, import_react11.useState)(0);
1501
+ const [avatarSeed, setAvatarSeed] = (0, import_react11.useState)(generateSeed);
1502
+ const [avatarStyle, setAvatarStyle] = (0, import_react11.useState)("adventurer");
1503
+ const [showStyles, setShowStyles] = (0, import_react11.useState)(false);
1504
+ const [username, setUsername] = (0, import_react11.useState)("");
1505
+ const [referralCode, setReferralCode] = (0, import_react11.useState)("");
1506
+ const [checking, setChecking] = (0, import_react11.useState)(false);
1507
+ const [availability, setAvailability] = (0, import_react11.useState)(null);
1508
+ const debounceRef = (0, import_react11.useRef)(null);
1509
+ const fadeAnim = (0, import_react11.useRef)(new import_react_native3.Animated.Value(1)).current;
1510
+ const slideAnim = (0, import_react11.useRef)(new import_react_native3.Animated.Value(0)).current;
1194
1511
  const avatarUrl = getAvatarUrl(avatarStyle, avatarSeed);
1195
- (0, import_react10.useEffect)(() => {
1512
+ (0, import_react11.useEffect)(() => {
1196
1513
  if (debounceRef.current) clearTimeout(debounceRef.current);
1197
1514
  const trimmed = username.trim();
1198
1515
  if (trimmed.length < 3) {
@@ -1215,96 +1532,96 @@ function DefaultRegistrationScreen({
1215
1532
  if (debounceRef.current) clearTimeout(debounceRef.current);
1216
1533
  };
1217
1534
  }, [username, client]);
1218
- const animateToStep = (0, import_react10.useCallback)((newStep) => {
1535
+ const animateToStep = (0, import_react11.useCallback)((newStep) => {
1219
1536
  const dir = newStep > step ? 1 : -1;
1220
- import_react_native2.Keyboard.dismiss();
1221
- import_react_native2.Animated.parallel([
1222
- import_react_native2.Animated.timing(fadeAnim, { toValue: 0, duration: 120, useNativeDriver: true }),
1223
- import_react_native2.Animated.timing(slideAnim, { toValue: -dir * 40, duration: 120, useNativeDriver: true })
1537
+ import_react_native3.Keyboard.dismiss();
1538
+ import_react_native3.Animated.parallel([
1539
+ import_react_native3.Animated.timing(fadeAnim, { toValue: 0, duration: 120, useNativeDriver: true }),
1540
+ import_react_native3.Animated.timing(slideAnim, { toValue: -dir * 40, duration: 120, useNativeDriver: true })
1224
1541
  ]).start(() => {
1225
1542
  setStep(newStep);
1226
1543
  slideAnim.setValue(dir * 40);
1227
- import_react_native2.Animated.parallel([
1228
- import_react_native2.Animated.timing(fadeAnim, { toValue: 1, duration: 200, useNativeDriver: true }),
1229
- import_react_native2.Animated.timing(slideAnim, { toValue: 0, duration: 200, useNativeDriver: true })
1544
+ import_react_native3.Animated.parallel([
1545
+ import_react_native3.Animated.timing(fadeAnim, { toValue: 1, duration: 200, useNativeDriver: true }),
1546
+ import_react_native3.Animated.timing(slideAnim, { toValue: 0, duration: 200, useNativeDriver: true })
1230
1547
  ]).start();
1231
1548
  });
1232
1549
  }, [step, fadeAnim, slideAnim]);
1233
1550
  const canContinueUsername = username.trim().length >= 3 && availability?.available === true && !checking;
1234
1551
  const handleSubmit = () => {
1235
- import_react_native2.Keyboard.dismiss();
1552
+ import_react_native3.Keyboard.dismiss();
1236
1553
  onRegister(username.trim(), referralCode.trim() || void 0, avatarUrl);
1237
1554
  };
1238
- const renderAvatarStep = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.stepContainer, children: [
1239
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.stepTop, children: [
1240
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.title, { color: t.text }], children: "Choose Your Avatar" }),
1241
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.subtitle, { color: t.textMuted }], children: "Pick a look that represents you" }),
1242
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StepIndicator, { currentStep: 0 }),
1243
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: s.avatarCenter, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: [s.avatarFrame, { borderColor: t.accent }], children: [
1244
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Image, { source: { uri: avatarUrl }, style: s.avatarLarge }),
1245
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [s.checkBadge, { backgroundColor: t.success }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: s.checkBadgeText, children: "\u2713" }) })
1555
+ const renderAvatarStep = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.stepContainer, children: [
1556
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.stepTop, children: [
1557
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.title, { color: t.text }], children: "Choose Your Avatar" }),
1558
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.subtitle, { color: t.textMuted }], children: "Pick a look that represents you" }),
1559
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StepIndicator, { currentStep: 0 }),
1560
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: s.avatarCenter, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: [s.avatarFrame, { borderColor: accent }], children: [
1561
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Image, { source: { uri: avatarUrl }, style: s.avatarLarge }),
1562
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [s.checkBadge, { backgroundColor: t.success }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: s.checkBadgeText, children: "\u2713" }) })
1246
1563
  ] }) }),
1247
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.avatarActions, children: [
1248
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1249
- import_react_native2.TouchableOpacity,
1564
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.avatarActions, children: [
1565
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1566
+ import_react_native3.TouchableOpacity,
1250
1567
  {
1251
1568
  style: [s.outlineBtn, { borderColor: t.border }],
1252
1569
  onPress: () => setAvatarSeed(generateSeed()),
1253
1570
  activeOpacity: 0.7,
1254
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.outlineBtnText, { color: t.text }], children: "\u21BB Shuffle" })
1571
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.outlineBtnText, { color: t.text }], children: "\u21BB Shuffle" })
1255
1572
  }
1256
1573
  ),
1257
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1258
- import_react_native2.TouchableOpacity,
1574
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1575
+ import_react_native3.TouchableOpacity,
1259
1576
  {
1260
- style: [s.outlineBtn, { borderColor: t.accent, backgroundColor: t.accent + "15" }],
1577
+ style: [s.outlineBtn, { borderColor: accent, backgroundColor: accent + "15" }],
1261
1578
  onPress: () => setShowStyles(!showStyles),
1262
1579
  activeOpacity: 0.7,
1263
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.outlineBtnText, { color: t.accent }], children: "\u263A Customize" })
1580
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.outlineBtnText, { color: accent }], children: "\u263A Customize" })
1264
1581
  }
1265
1582
  )
1266
1583
  ] }),
1267
- showStyles && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: s.styleScroll, children: DICEBEAR_STYLES.map((st) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1268
- import_react_native2.TouchableOpacity,
1584
+ showStyles && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.ScrollView, { horizontal: true, showsHorizontalScrollIndicator: false, style: s.styleScroll, children: DICEBEAR_STYLES.map((st) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1585
+ import_react_native3.TouchableOpacity,
1269
1586
  {
1270
1587
  onPress: () => setAvatarStyle(st),
1271
- style: [s.styleThumbWrap, { borderColor: st === avatarStyle ? t.accent : t.border }],
1272
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Image, { source: { uri: getAvatarUrl(st, avatarSeed, 80) }, style: s.styleThumb })
1588
+ style: [s.styleThumbWrap, { borderColor: st === avatarStyle ? accent : t.border }],
1589
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Image, { source: { uri: getAvatarUrl(st, avatarSeed, 80) }, style: s.styleThumb })
1273
1590
  },
1274
1591
  st
1275
1592
  )) })
1276
1593
  ] }),
1277
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: s.bottomRow, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1278
- import_react_native2.TouchableOpacity,
1594
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: s.bottomRow, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1595
+ import_react_native3.TouchableOpacity,
1279
1596
  {
1280
- style: [s.primaryBtn, { backgroundColor: t.accent, flex: 1 }],
1597
+ style: [s.primaryBtn, { backgroundColor: accent, flex: 1 }],
1281
1598
  onPress: () => animateToStep(1),
1282
1599
  activeOpacity: 0.8,
1283
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: s.primaryBtnText, children: "Continue \u203A" })
1600
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: s.primaryBtnText, children: "Continue \u203A" })
1284
1601
  }
1285
1602
  ) })
1286
1603
  ] });
1287
- const renderUsernameStep = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.stepContainer, children: [
1288
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.stepTop, children: [
1289
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.headerRow, children: [
1290
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.TouchableOpacity, { onPress: () => animateToStep(0), hitSlop: { top: 12, bottom: 12, left: 12, right: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.backChevron, { color: t.text }], children: "\u2039" }) }),
1291
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.titleInline, { color: t.text }], children: "Pick a Username" })
1604
+ const renderUsernameStep = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.stepContainer, children: [
1605
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.stepTop, children: [
1606
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.headerRow, children: [
1607
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.TouchableOpacity, { onPress: () => animateToStep(0), hitSlop: { top: 12, bottom: 12, left: 12, right: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.backChevron, { color: t.text }], children: "\u2039" }) }),
1608
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.titleInline, { color: t.text }], children: "Pick a Username" })
1292
1609
  ] }),
1293
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.subtitle, { color: t.textMuted }], children: "This is how others will see you" }),
1294
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StepIndicator, { currentStep: 1 }),
1295
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: s.avatarCenter, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: [s.avatarFrameSmall, { borderColor: t.accent }], children: [
1296
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Image, { source: { uri: avatarUrl }, style: s.avatarSmall }),
1297
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [s.checkBadgeSm, { backgroundColor: t.success }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: s.checkBadgeTextSm, children: "\u2713" }) })
1610
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.subtitle, { color: t.textMuted }], children: "This is how others will see you" }),
1611
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StepIndicator, { currentStep: 1 }),
1612
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: s.avatarCenter, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: [s.avatarFrameSmall, { borderColor: accent }], children: [
1613
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Image, { source: { uri: avatarUrl }, style: s.avatarSmall }),
1614
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [s.checkBadgeSm, { backgroundColor: t.success }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: s.checkBadgeTextSm, children: "\u2713" }) })
1298
1615
  ] }) }),
1299
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.inputGroup, children: [
1300
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.Text, { style: [s.inputLabel, { color: t.text }], children: [
1616
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.inputGroup, children: [
1617
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.Text, { style: [s.inputLabel, { color: t.text }], children: [
1301
1618
  "Username ",
1302
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: { color: t.errorText }, children: "*" })
1619
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: { color: t.errorText }, children: "*" })
1303
1620
  ] }),
1304
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1305
- import_react_native2.TextInput,
1621
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1622
+ import_react_native3.TextInput,
1306
1623
  {
1307
- style: [s.input, { backgroundColor: t.surface, color: t.text, borderColor: t.accent }],
1624
+ style: [s.input, { backgroundColor: t.surface, color: t.text, borderColor: accent }],
1308
1625
  placeholder: "Enter username",
1309
1626
  placeholderTextColor: t.textDim,
1310
1627
  value: username,
@@ -1314,63 +1631,63 @@ function DefaultRegistrationScreen({
1314
1631
  autoFocus: true
1315
1632
  }
1316
1633
  ),
1317
- checking ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.hint, { color: t.textDim }], children: "Checking..." }) : availability ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.hint, { color: availability.available ? t.success : t.errorText }], children: availability.available ? "\u2713 Available!" : availability.reason || "Username taken" }) : username.trim().length > 0 && username.trim().length < 3 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.hint, { color: t.textDim }], children: "At least 3 characters" }) : null
1634
+ checking ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.hint, { color: t.textDim }], children: "Checking..." }) : availability ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.hint, { color: availability.available ? t.success : t.errorText }], children: availability.available ? "\u2713 Available!" : availability.reason || "Username taken" }) : username.trim().length > 0 && username.trim().length < 3 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.hint, { color: t.textDim }], children: "At least 3 characters" }) : null
1318
1635
  ] })
1319
1636
  ] }),
1320
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.bottomRow, children: [
1321
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1322
- import_react_native2.TouchableOpacity,
1637
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.bottomRow, children: [
1638
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1639
+ import_react_native3.TouchableOpacity,
1323
1640
  {
1324
1641
  style: [s.secondaryBtn, { borderColor: t.border }],
1325
1642
  onPress: () => animateToStep(0),
1326
1643
  activeOpacity: 0.7,
1327
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.secondaryBtnText, { color: t.text }], children: "\u2039 Back" })
1644
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.secondaryBtnText, { color: t.text }], children: "\u2039 Back" })
1328
1645
  }
1329
1646
  ),
1330
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1331
- import_react_native2.TouchableOpacity,
1647
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1648
+ import_react_native3.TouchableOpacity,
1332
1649
  {
1333
- style: [s.primaryBtn, { backgroundColor: t.accent, flex: 1, opacity: canContinueUsername ? 1 : 0.4 }],
1650
+ style: [s.primaryBtn, { backgroundColor: accent, flex: 1, opacity: canContinueUsername ? 1 : 0.4 }],
1334
1651
  onPress: () => animateToStep(2),
1335
1652
  disabled: !canContinueUsername,
1336
1653
  activeOpacity: 0.8,
1337
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: s.primaryBtnText, children: "Continue \u203A" })
1654
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: s.primaryBtnText, children: "Continue \u203A" })
1338
1655
  }
1339
1656
  )
1340
1657
  ] })
1341
1658
  ] });
1342
- const renderReferralStep = () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.stepContainer, children: [
1343
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.stepTop, children: [
1344
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.headerRow, children: [
1345
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.TouchableOpacity, { onPress: () => animateToStep(1), hitSlop: { top: 12, bottom: 12, left: 12, right: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.backChevron, { color: t.text }], children: "\u2039" }) }),
1346
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.titleInline, { color: t.text }], children: "Almost There!" })
1659
+ const renderReferralStep = () => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.stepContainer, children: [
1660
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.stepTop, children: [
1661
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.headerRow, children: [
1662
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.TouchableOpacity, { onPress: () => animateToStep(1), hitSlop: { top: 12, bottom: 12, left: 12, right: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.backChevron, { color: t.text }], children: "\u2039" }) }),
1663
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.titleInline, { color: t.text }], children: "Almost There!" })
1347
1664
  ] }),
1348
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.subtitle, { color: t.textMuted }], children: "Got a referral code? (optional)" }),
1349
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StepIndicator, { currentStep: 2 }),
1350
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: [s.profileCard, { borderColor: t.border }], children: [
1351
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.profileLabel, { color: t.textMuted }], children: "Your Profile" }),
1352
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.profileRow, children: [
1353
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Image, { source: { uri: avatarUrl }, style: s.profileAvatar }),
1354
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: { gap: 4 }, children: [
1355
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.Text, { style: [s.profileUsername, { color: t.text }], children: [
1665
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.subtitle, { color: t.textMuted }], children: "Got a referral code? (optional)" }),
1666
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StepIndicator, { currentStep: 2 }),
1667
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: [s.profileCard, { borderColor: t.border }], children: [
1668
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.profileLabel, { color: t.textMuted }], children: "Your Profile" }),
1669
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.profileRow, children: [
1670
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Image, { source: { uri: avatarUrl }, style: s.profileAvatar }),
1671
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: { gap: 4 }, children: [
1672
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.Text, { style: [s.profileUsername, { color: t.text }], children: [
1356
1673
  "@",
1357
1674
  username
1358
1675
  ] }),
1359
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.Text, { style: [s.profileReady, { color: t.success }], children: [
1676
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.Text, { style: [s.profileReady, { color: t.success }], children: [
1360
1677
  "\u2713",
1361
1678
  " Ready to go!"
1362
1679
  ] })
1363
1680
  ] })
1364
1681
  ] })
1365
1682
  ] }),
1366
- error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [s.errorBox, { backgroundColor: t.errorBg, borderColor: t.errorBorder }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.errorText, { color: t.errorText }], children: error.message }) }) : null,
1367
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.inputGroup, children: [
1368
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.Text, { style: [s.inputLabel, { color: t.text }], children: [
1683
+ error ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [s.errorBox, { backgroundColor: t.errorBg, borderColor: t.errorBorder }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.errorText, { color: t.errorText }], children: error.message }) }) : null,
1684
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.inputGroup, children: [
1685
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.Text, { style: [s.inputLabel, { color: t.text }], children: [
1369
1686
  "Referral Code ",
1370
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: { color: t.textMuted }, children: "(optional)" })
1687
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: { color: t.textMuted }, children: "(optional)" })
1371
1688
  ] }),
1372
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1373
- import_react_native2.TextInput,
1689
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1690
+ import_react_native3.TextInput,
1374
1691
  {
1375
1692
  style: [s.input, { backgroundColor: t.surface, color: t.text, borderColor: t.border }],
1376
1693
  placeholder: "Enter referral code",
@@ -1382,31 +1699,31 @@ function DefaultRegistrationScreen({
1382
1699
  editable: !registering
1383
1700
  }
1384
1701
  ),
1385
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.Text, { style: [s.hint, { color: t.textMuted }], children: [
1702
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.Text, { style: [s.hint, { color: t.textMuted }], children: [
1386
1703
  "\u{1F381}",
1387
1704
  " If a friend invited you, enter their code to give them credit!"
1388
1705
  ] })
1389
1706
  ] })
1390
1707
  ] }),
1391
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: s.bottomRow, children: [
1392
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1393
- import_react_native2.TouchableOpacity,
1708
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: s.bottomRow, children: [
1709
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1710
+ import_react_native3.TouchableOpacity,
1394
1711
  {
1395
1712
  style: [s.secondaryBtn, { borderColor: t.border }],
1396
1713
  onPress: () => animateToStep(1),
1397
1714
  disabled: registering,
1398
1715
  activeOpacity: 0.7,
1399
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [s.secondaryBtnText, { color: t.text }], children: "\u2039 Back" })
1716
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [s.secondaryBtnText, { color: t.text }], children: "\u2039 Back" })
1400
1717
  }
1401
1718
  ),
1402
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1403
- import_react_native2.TouchableOpacity,
1719
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1720
+ import_react_native3.TouchableOpacity,
1404
1721
  {
1405
- style: [s.primaryBtn, { backgroundColor: t.accent, flex: 1, opacity: registering ? 0.7 : 1 }],
1722
+ style: [s.primaryBtn, { backgroundColor: accent, flex: 1, opacity: registering ? 0.7 : 1 }],
1406
1723
  onPress: handleSubmit,
1407
1724
  disabled: registering,
1408
1725
  activeOpacity: 0.8,
1409
- children: registering ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.ActivityIndicator, { color: "#FFFFFF", size: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: s.primaryBtnText, children: "Create Account" })
1726
+ children: registering ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.ActivityIndicator, { color: "#FFFFFF", size: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: s.primaryBtnText, children: "Create Account" })
1410
1727
  }
1411
1728
  )
1412
1729
  ] })
@@ -1423,19 +1740,19 @@ function DefaultRegistrationScreen({
1423
1740
  return null;
1424
1741
  }
1425
1742
  };
1426
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1427
- import_react_native2.KeyboardAvoidingView,
1743
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1744
+ import_react_native3.KeyboardAvoidingView,
1428
1745
  {
1429
1746
  style: [s.container, { backgroundColor: t.background }],
1430
- behavior: import_react_native2.Platform.OS === "ios" ? "padding" : void 0,
1431
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1432
- import_react_native2.ScrollView,
1747
+ behavior: import_react_native3.Platform.OS === "ios" ? "padding" : void 0,
1748
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1749
+ import_react_native3.ScrollView,
1433
1750
  {
1434
1751
  contentContainerStyle: { flexGrow: 1 },
1435
1752
  keyboardShouldPersistTaps: "handled",
1436
1753
  bounces: false,
1437
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1438
- import_react_native2.Animated.View,
1754
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1755
+ import_react_native3.Animated.View,
1439
1756
  {
1440
1757
  style: [
1441
1758
  { flex: 1 },
@@ -1449,7 +1766,7 @@ function DefaultRegistrationScreen({
1449
1766
  }
1450
1767
  );
1451
1768
  }
1452
- var s = import_react_native2.StyleSheet.create({
1769
+ var s = import_react_native3.StyleSheet.create({
1453
1770
  container: { flex: 1 },
1454
1771
  // Loading / Error
1455
1772
  centerContent: { flex: 1, justifyContent: "center", alignItems: "center", paddingHorizontal: 32, gap: 48 },
@@ -1512,119 +1829,179 @@ var s = import_react_native2.StyleSheet.create({
1512
1829
  secondaryBtnText: { fontSize: 16, fontWeight: "600" }
1513
1830
  });
1514
1831
 
1515
- // src/ui/ConnectWalletScreen.tsx
1516
- var import_react_native3 = require("react-native");
1517
- var import_jsx_runtime3 = require("react/jsx-runtime");
1518
- function ConnectWalletScreen({
1519
- onConnect,
1520
- connecting = false,
1521
- error = null,
1522
- appName = "Dubs"
1832
+ // src/provider.tsx
1833
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1834
+ var DubsContext = (0, import_react12.createContext)(null);
1835
+ function DubsProvider({
1836
+ apiKey,
1837
+ children,
1838
+ appName = "Dubs",
1839
+ network = "mainnet-beta",
1840
+ wallet: externalWallet,
1841
+ tokenStorage,
1842
+ baseUrl: baseUrlOverride,
1843
+ rpcUrl: rpcUrlOverride,
1844
+ renderConnectScreen,
1845
+ renderLoading,
1846
+ renderError,
1847
+ renderRegistration,
1848
+ managed = true
1523
1849
  }) {
1524
- const t = useDubsTheme();
1525
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [styles.container, { backgroundColor: t.background }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles.content, children: [
1526
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles.brandingSection, children: [
1527
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: [styles.logoCircle, { backgroundColor: t.accent }], children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles.logoText, children: "D" }) }),
1528
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [styles.appName, { color: t.text }], children: appName }),
1529
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [styles.subtitle, { color: t.textMuted }], children: "Connect your Solana wallet to get started" })
1530
- ] }),
1531
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles.actionSection, children: [
1532
- error ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1533
- import_react_native3.View,
1534
- {
1535
- style: [
1536
- styles.errorBox,
1537
- { backgroundColor: t.errorBg, borderColor: t.errorBorder }
1538
- ],
1539
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [styles.errorText, { color: t.errorText }], children: error })
1540
- }
1541
- ) : null,
1542
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1543
- import_react_native3.TouchableOpacity,
1850
+ const config = NETWORK_CONFIG[network];
1851
+ const baseUrl = baseUrlOverride || config.baseUrl;
1852
+ const rpcUrl = rpcUrlOverride || config.rpcUrl;
1853
+ const cluster = config.cluster;
1854
+ const client = (0, import_react12.useMemo)(() => new DubsClient({ apiKey, baseUrl }), [apiKey, baseUrl]);
1855
+ const connection = (0, import_react12.useMemo)(() => new import_web33.Connection(rpcUrl, { commitment: "confirmed" }), [rpcUrl]);
1856
+ const storage = (0, import_react12.useMemo)(() => tokenStorage || createSecureStoreStorage(), [tokenStorage]);
1857
+ const [uiConfig, setUiConfig] = (0, import_react12.useState)({});
1858
+ (0, import_react12.useEffect)(() => {
1859
+ client.getAppConfig().then(setUiConfig).catch(() => {
1860
+ });
1861
+ }, [client]);
1862
+ if (externalWallet) {
1863
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1864
+ ExternalWalletProvider,
1865
+ {
1866
+ client,
1867
+ connection,
1868
+ wallet: externalWallet,
1869
+ appName: uiConfig.appName || appName,
1870
+ network,
1871
+ storage,
1872
+ managed,
1873
+ renderLoading,
1874
+ renderError,
1875
+ renderRegistration,
1876
+ accentColor: uiConfig.accentColor,
1877
+ children
1878
+ }
1879
+ );
1880
+ }
1881
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1882
+ ManagedWalletProvider,
1883
+ {
1884
+ appName: uiConfig.appName || appName,
1885
+ cluster,
1886
+ storage,
1887
+ renderConnectScreen,
1888
+ accentColor: uiConfig.accentColor,
1889
+ appIcon: uiConfig.appIcon,
1890
+ tagline: uiConfig.tagline,
1891
+ children: (adapter) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1892
+ ManagedInner,
1544
1893
  {
1545
- style: [styles.connectButton, { backgroundColor: t.accent }],
1546
- onPress: onConnect,
1547
- disabled: connecting,
1548
- activeOpacity: 0.8,
1549
- children: connecting ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.ActivityIndicator, { color: "#FFFFFF", size: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles.connectButtonText, children: "Connect Wallet" })
1894
+ client,
1895
+ connection,
1896
+ wallet: adapter,
1897
+ appName: uiConfig.appName || appName,
1898
+ network,
1899
+ storage,
1900
+ renderLoading,
1901
+ renderError,
1902
+ renderRegistration,
1903
+ accentColor: uiConfig.accentColor,
1904
+ children
1550
1905
  }
1551
- ),
1552
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [styles.hint, { color: t.textDim }], children: "Phantom, Solflare, or any Solana wallet" })
1553
- ] })
1554
- ] }) });
1906
+ )
1907
+ }
1908
+ );
1555
1909
  }
1556
- var styles = import_react_native3.StyleSheet.create({
1557
- container: {
1558
- flex: 1,
1559
- justifyContent: "center"
1560
- },
1561
- content: {
1562
- flex: 1,
1563
- justifyContent: "space-between",
1564
- paddingHorizontal: 32,
1565
- paddingTop: 120,
1566
- paddingBottom: 80
1567
- },
1568
- brandingSection: {
1569
- alignItems: "center",
1570
- gap: 12
1571
- },
1572
- logoCircle: {
1573
- width: 80,
1574
- height: 80,
1575
- borderRadius: 40,
1576
- justifyContent: "center",
1577
- alignItems: "center",
1578
- marginBottom: 8
1579
- },
1580
- logoText: {
1581
- fontSize: 36,
1582
- fontWeight: "800",
1583
- color: "#FFFFFF"
1584
- },
1585
- appName: {
1586
- fontSize: 32,
1587
- fontWeight: "800"
1588
- },
1589
- subtitle: {
1590
- fontSize: 16,
1591
- textAlign: "center",
1592
- lineHeight: 22
1593
- },
1594
- actionSection: {
1595
- gap: 16
1596
- },
1597
- errorBox: {
1598
- borderWidth: 1,
1599
- borderRadius: 12,
1600
- paddingHorizontal: 16,
1601
- paddingVertical: 12
1602
- },
1603
- errorText: {
1604
- fontSize: 14,
1605
- textAlign: "center"
1606
- },
1607
- connectButton: {
1608
- height: 56,
1609
- borderRadius: 16,
1610
- justifyContent: "center",
1611
- alignItems: "center"
1612
- },
1613
- connectButtonText: {
1614
- color: "#FFFFFF",
1615
- fontSize: 18,
1616
- fontWeight: "700"
1617
- },
1618
- hint: {
1619
- fontSize: 13,
1620
- textAlign: "center"
1910
+ function ManagedInner({
1911
+ client,
1912
+ connection,
1913
+ wallet,
1914
+ appName,
1915
+ network,
1916
+ storage,
1917
+ renderLoading,
1918
+ renderError,
1919
+ renderRegistration,
1920
+ accentColor,
1921
+ children
1922
+ }) {
1923
+ const managedDisconnect = useDisconnect();
1924
+ const disconnect = (0, import_react12.useCallback)(async () => {
1925
+ client.setToken(null);
1926
+ await managedDisconnect?.();
1927
+ }, [client, managedDisconnect]);
1928
+ const value = (0, import_react12.useMemo)(
1929
+ () => ({ client, wallet, connection, appName, network, disconnect }),
1930
+ [client, wallet, connection, appName, network, disconnect]
1931
+ );
1932
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DubsContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1933
+ AuthGate,
1934
+ {
1935
+ onSaveToken: (token) => {
1936
+ if (token) return storage.setItem(STORAGE_KEYS.JWT_TOKEN, token);
1937
+ return storage.deleteItem(STORAGE_KEYS.JWT_TOKEN);
1938
+ },
1939
+ onLoadToken: () => storage.getItem(STORAGE_KEYS.JWT_TOKEN),
1940
+ renderLoading,
1941
+ renderError,
1942
+ renderRegistration,
1943
+ appName,
1944
+ accentColor,
1945
+ children
1946
+ }
1947
+ ) });
1948
+ }
1949
+ function ExternalWalletProvider({
1950
+ client,
1951
+ connection,
1952
+ wallet,
1953
+ appName,
1954
+ network,
1955
+ storage,
1956
+ managed,
1957
+ renderLoading,
1958
+ renderError,
1959
+ renderRegistration,
1960
+ accentColor,
1961
+ children
1962
+ }) {
1963
+ const disconnect = (0, import_react12.useCallback)(async () => {
1964
+ client.setToken(null);
1965
+ await storage.deleteItem(STORAGE_KEYS.JWT_TOKEN).catch(() => {
1966
+ });
1967
+ await wallet.disconnect?.();
1968
+ }, [client, storage, wallet]);
1969
+ const value = (0, import_react12.useMemo)(
1970
+ () => ({ client, wallet, connection, appName, network, disconnect }),
1971
+ [client, wallet, connection, appName, network, disconnect]
1972
+ );
1973
+ if (!managed) {
1974
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DubsContext.Provider, { value, children });
1621
1975
  }
1622
- });
1976
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(DubsContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1977
+ AuthGate,
1978
+ {
1979
+ onSaveToken: (token) => {
1980
+ if (token) return storage.setItem(STORAGE_KEYS.JWT_TOKEN, token);
1981
+ return storage.deleteItem(STORAGE_KEYS.JWT_TOKEN);
1982
+ },
1983
+ onLoadToken: () => storage.getItem(STORAGE_KEYS.JWT_TOKEN),
1984
+ renderLoading,
1985
+ renderError,
1986
+ renderRegistration,
1987
+ appName,
1988
+ accentColor,
1989
+ children
1990
+ }
1991
+ ) });
1992
+ }
1993
+ function useDubs() {
1994
+ const ctx = (0, import_react12.useContext)(DubsContext);
1995
+ if (!ctx) {
1996
+ throw new Error("useDubs must be used within a <DubsProvider>");
1997
+ }
1998
+ return ctx;
1999
+ }
1623
2000
 
1624
2001
  // src/ui/UserProfileCard.tsx
1625
- var import_react11 = require("react");
2002
+ var import_react13 = require("react");
1626
2003
  var import_react_native4 = require("react-native");
1627
- var import_jsx_runtime4 = require("react/jsx-runtime");
2004
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1628
2005
  function truncateAddress(address, chars = 4) {
1629
2006
  if (address.length <= chars * 2 + 3) return address;
1630
2007
  return `${address.slice(0, chars)}...${address.slice(-chars)}`;
@@ -1642,16 +2019,16 @@ function UserProfileCard({
1642
2019
  memberSince
1643
2020
  }) {
1644
2021
  const t = useDubsTheme();
1645
- const imageUri = (0, import_react11.useMemo)(
2022
+ const imageUri = (0, import_react13.useMemo)(
1646
2023
  () => avatarUrl || `https://api.dicebear.com/9.x/avataaars/png?seed=${walletAddress}&size=128`,
1647
2024
  [avatarUrl, walletAddress]
1648
2025
  );
1649
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: [styles2.card, { backgroundColor: t.surface, borderColor: t.border }], children: [
1650
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Image, { source: { uri: imageUri }, style: styles2.avatar }),
1651
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles2.info, children: [
1652
- username ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles2.username, { color: t.text }], children: username }) : null,
1653
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles2.address, { color: t.textMuted }], children: truncateAddress(walletAddress) }),
1654
- memberSince ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles2.memberSince, { color: t.textDim }], children: formatMemberSince(memberSince) }) : null
2026
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native4.View, { style: [styles2.card, { backgroundColor: t.surface, borderColor: t.border }], children: [
2027
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native4.Image, { source: { uri: imageUri }, style: styles2.avatar }),
2028
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native4.View, { style: styles2.info, children: [
2029
+ username ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native4.Text, { style: [styles2.username, { color: t.text }], children: username }) : null,
2030
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native4.Text, { style: [styles2.address, { color: t.textMuted }], children: truncateAddress(walletAddress) }),
2031
+ memberSince ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native4.Text, { style: [styles2.memberSince, { color: t.textDim }], children: formatMemberSince(memberSince) }) : null
1655
2032
  ] })
1656
2033
  ] });
1657
2034
  }
@@ -1690,7 +2067,7 @@ var styles2 = import_react_native4.StyleSheet.create({
1690
2067
 
1691
2068
  // src/ui/SettingsSheet.tsx
1692
2069
  var import_react_native5 = require("react-native");
1693
- var import_jsx_runtime5 = require("react/jsx-runtime");
2070
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1694
2071
  function truncateAddress2(address, chars = 4) {
1695
2072
  if (address.length <= chars * 2 + 3) return address;
1696
2073
  return `${address.slice(0, chars)}...${address.slice(-chars)}`;
@@ -1707,13 +2084,13 @@ function SettingsSheet({
1707
2084
  loggingOut = false
1708
2085
  }) {
1709
2086
  const t = useDubsTheme();
1710
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2087
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1711
2088
  import_react_native5.ScrollView,
1712
2089
  {
1713
2090
  style: [styles3.container, { backgroundColor: t.background }],
1714
2091
  contentContainerStyle: styles3.content,
1715
2092
  children: [
1716
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2093
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1717
2094
  UserProfileCard,
1718
2095
  {
1719
2096
  walletAddress,
@@ -1722,49 +2099,49 @@ function SettingsSheet({
1722
2099
  memberSince
1723
2100
  }
1724
2101
  ),
1725
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: [styles3.actionsCard, { backgroundColor: t.surface, borderColor: t.border }], children: [
1726
- onCopyAddress ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2102
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native5.View, { style: [styles3.actionsCard, { backgroundColor: t.surface, borderColor: t.border }], children: [
2103
+ onCopyAddress ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1727
2104
  import_react_native5.TouchableOpacity,
1728
2105
  {
1729
2106
  style: styles3.actionRow,
1730
2107
  onPress: onCopyAddress,
1731
2108
  activeOpacity: 0.7,
1732
2109
  children: [
1733
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.View, { style: styles3.actionRowLeft, children: [
1734
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles3.actionLabel, { color: t.text }], children: "Wallet Address" }),
1735
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles3.actionValue, { color: t.textMuted }], children: truncateAddress2(walletAddress) })
2110
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native5.View, { style: styles3.actionRowLeft, children: [
2111
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.Text, { style: [styles3.actionLabel, { color: t.text }], children: "Wallet Address" }),
2112
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.Text, { style: [styles3.actionValue, { color: t.textMuted }], children: truncateAddress2(walletAddress) })
1736
2113
  ] }),
1737
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles3.copyLabel, { color: t.accent }], children: "Copy" })
2114
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.Text, { style: [styles3.copyLabel, { color: t.accent }], children: "Copy" })
1738
2115
  ]
1739
2116
  }
1740
2117
  ) : null,
1741
- onSupport ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1742
- onCopyAddress ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { style: [styles3.separator, { backgroundColor: t.border }] }) : null,
1743
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
2118
+ onSupport ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
2119
+ onCopyAddress ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.View, { style: [styles3.separator, { backgroundColor: t.border }] }) : null,
2120
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1744
2121
  import_react_native5.TouchableOpacity,
1745
2122
  {
1746
2123
  style: styles3.actionRow,
1747
2124
  onPress: onSupport,
1748
2125
  activeOpacity: 0.7,
1749
2126
  children: [
1750
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles3.actionLabel, { color: t.text }], children: "Help & Support" }),
1751
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles3.chevron, { color: t.textMuted }], children: "\u203A" })
2127
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.Text, { style: [styles3.actionLabel, { color: t.text }], children: "Help & Support" }),
2128
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.Text, { style: [styles3.chevron, { color: t.textMuted }], children: "\u203A" })
1752
2129
  ]
1753
2130
  }
1754
2131
  )
1755
2132
  ] }) : null
1756
2133
  ] }),
1757
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
2134
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1758
2135
  import_react_native5.TouchableOpacity,
1759
2136
  {
1760
2137
  style: [styles3.logoutButton, { borderColor: t.live }],
1761
2138
  onPress: onLogout,
1762
2139
  disabled: loggingOut,
1763
2140
  activeOpacity: 0.7,
1764
- children: loggingOut ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.ActivityIndicator, { color: t.live, size: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.Text, { style: [styles3.logoutText, { color: t.live }], children: "Log Out" })
2141
+ children: loggingOut ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.ActivityIndicator, { color: t.live, size: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.Text, { style: [styles3.logoutText, { color: t.live }], children: "Log Out" })
1765
2142
  }
1766
2143
  ),
1767
- appVersion ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react_native5.Text, { style: [styles3.version, { color: t.textDim }], children: [
2144
+ appVersion ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native5.Text, { style: [styles3.version, { color: t.textDim }], children: [
1768
2145
  "v",
1769
2146
  appVersion
1770
2147
  ] }) : null
@@ -1833,6 +2210,476 @@ var styles3 = import_react_native5.StyleSheet.create({
1833
2210
  textAlign: "center"
1834
2211
  }
1835
2212
  });
2213
+
2214
+ // src/ui/game/GamePoster.tsx
2215
+ var import_react14 = require("react");
2216
+ var import_react_native6 = require("react-native");
2217
+ var import_jsx_runtime7 = require("react/jsx-runtime");
2218
+ function computeCountdown(lockTimestamp) {
2219
+ if (!lockTimestamp) return "";
2220
+ const ts = typeof lockTimestamp === "string" ? parseInt(lockTimestamp) : lockTimestamp;
2221
+ const diff = ts * 1e3 - Date.now();
2222
+ if (diff <= 0) return "LIVE";
2223
+ const days = Math.floor(diff / 864e5);
2224
+ const hours = Math.floor(diff % 864e5 / 36e5);
2225
+ const mins = Math.floor(diff % 36e5 / 6e4);
2226
+ if (days > 0) return `${days}d ${hours}h`;
2227
+ if (hours > 0) return `${hours}h ${mins}m`;
2228
+ return `${mins}m`;
2229
+ }
2230
+ function GamePoster({ game, ImageComponent }) {
2231
+ const t = useDubsTheme();
2232
+ const Img = ImageComponent || require("react-native").Image;
2233
+ const opponents = game.opponents || [];
2234
+ const home = opponents[0];
2235
+ const away = opponents[1];
2236
+ const countdown = computeCountdown(game.lockTimestamp);
2237
+ const isLive = countdown === "LIVE";
2238
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native6.View, { style: styles4.container, children: [
2239
+ game.media?.poster ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2240
+ Img,
2241
+ {
2242
+ source: { uri: game.media.poster },
2243
+ style: styles4.image,
2244
+ resizeMode: "cover"
2245
+ }
2246
+ ) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.View, { style: [styles4.image, { backgroundColor: t.surface }], children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native6.View, { style: styles4.fallback, children: [
2247
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TeamLogoInternal, { url: home?.imageUrl, size: 56, Img }),
2248
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.Text, { style: styles4.vs, children: "VS" }),
2249
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TeamLogoInternal, { url: away?.imageUrl, size: 56, Img })
2250
+ ] }) }),
2251
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.View, { style: styles4.overlay }),
2252
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native6.View, { style: styles4.teamNames, children: [
2253
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.Text, { style: styles4.teamNameText, numberOfLines: 1, children: home?.name || "Home" }),
2254
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.Text, { style: styles4.teamNameVs, children: "vs" }),
2255
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.Text, { style: styles4.teamNameText, numberOfLines: 1, children: away?.name || "Away" })
2256
+ ] }),
2257
+ countdown ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.View, { style: styles4.countdownPill, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.Text, { style: [styles4.countdownText, isLive && styles4.countdownLive], children: countdown }) }) : null,
2258
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.View, { style: styles4.poolPill, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native6.Text, { style: styles4.poolText, children: [
2259
+ game.totalPool || 0,
2260
+ " SOL"
2261
+ ] }) })
2262
+ ] });
2263
+ }
2264
+ function TeamLogoInternal({ url, size, Img }) {
2265
+ const [failed, setFailed] = (0, import_react14.useState)(false);
2266
+ if (!url || failed) {
2267
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.View, { style: [styles4.logoPlaceholder, { width: size, height: size, borderRadius: size / 2 }] });
2268
+ }
2269
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
2270
+ Img,
2271
+ {
2272
+ source: { uri: url },
2273
+ style: { width: size, height: size, borderRadius: size / 2 },
2274
+ resizeMode: "contain",
2275
+ onError: () => setFailed(true)
2276
+ }
2277
+ );
2278
+ }
2279
+ var styles4 = import_react_native6.StyleSheet.create({
2280
+ container: {
2281
+ height: 200,
2282
+ borderRadius: 16,
2283
+ overflow: "hidden",
2284
+ position: "relative"
2285
+ },
2286
+ image: {
2287
+ ...import_react_native6.StyleSheet.absoluteFillObject,
2288
+ justifyContent: "center",
2289
+ alignItems: "center"
2290
+ },
2291
+ overlay: {
2292
+ ...import_react_native6.StyleSheet.absoluteFillObject,
2293
+ backgroundColor: "rgba(0,0,0,0.35)"
2294
+ },
2295
+ fallback: {
2296
+ flexDirection: "row",
2297
+ alignItems: "center",
2298
+ gap: 24,
2299
+ zIndex: 2
2300
+ },
2301
+ vs: {
2302
+ color: "#FFF",
2303
+ fontSize: 24,
2304
+ fontWeight: "900",
2305
+ zIndex: 2
2306
+ },
2307
+ logoPlaceholder: {
2308
+ backgroundColor: "rgba(255,255,255,0.15)",
2309
+ zIndex: 2
2310
+ },
2311
+ teamNames: {
2312
+ position: "absolute",
2313
+ top: 12,
2314
+ left: 12,
2315
+ right: 12,
2316
+ flexDirection: "row",
2317
+ alignItems: "center",
2318
+ justifyContent: "center",
2319
+ gap: 8
2320
+ },
2321
+ teamNameText: {
2322
+ color: "#FFF",
2323
+ fontSize: 14,
2324
+ fontWeight: "700",
2325
+ maxWidth: "40%"
2326
+ },
2327
+ teamNameVs: {
2328
+ color: "rgba(255,255,255,0.6)",
2329
+ fontSize: 12,
2330
+ fontWeight: "600"
2331
+ },
2332
+ countdownPill: {
2333
+ position: "absolute",
2334
+ bottom: 12,
2335
+ left: 12,
2336
+ backgroundColor: "rgba(0,0,0,0.65)",
2337
+ borderRadius: 8,
2338
+ paddingHorizontal: 10,
2339
+ paddingVertical: 5
2340
+ },
2341
+ countdownText: {
2342
+ color: "#FFF",
2343
+ fontSize: 13,
2344
+ fontWeight: "700"
2345
+ },
2346
+ countdownLive: {
2347
+ color: "#EF4444"
2348
+ },
2349
+ poolPill: {
2350
+ position: "absolute",
2351
+ bottom: 12,
2352
+ right: 12,
2353
+ backgroundColor: "#7C3AED",
2354
+ borderRadius: 8,
2355
+ paddingHorizontal: 12,
2356
+ paddingVertical: 5
2357
+ },
2358
+ poolText: {
2359
+ color: "#FFF",
2360
+ fontSize: 13,
2361
+ fontWeight: "800"
2362
+ }
2363
+ });
2364
+
2365
+ // src/ui/game/LivePoolsCard.tsx
2366
+ var import_react15 = require("react");
2367
+ var import_react_native7 = require("react-native");
2368
+ var import_jsx_runtime8 = require("react/jsx-runtime");
2369
+ function LivePoolsCard({
2370
+ game,
2371
+ shortName,
2372
+ homeColor = "#3B82F6",
2373
+ awayColor = "#EF4444"
2374
+ }) {
2375
+ const t = useDubsTheme();
2376
+ const opponents = game.opponents || [];
2377
+ const homeName = shortName ? shortName(opponents[0]?.name) : opponents[0]?.name || "Home";
2378
+ const awayName = shortName ? shortName(opponents[1]?.name) : opponents[1]?.name || "Away";
2379
+ const homePool = game.homePool || 0;
2380
+ const awayPool = game.awayPool || 0;
2381
+ const totalPool = game.totalPool || 0;
2382
+ const { homePercent, awayPercent, homeOdds, awayOdds } = (0, import_react15.useMemo)(() => {
2383
+ return {
2384
+ homePercent: totalPool > 0 ? homePool / totalPool * 100 : 50,
2385
+ awayPercent: totalPool > 0 ? awayPool / totalPool * 100 : 50,
2386
+ homeOdds: homePool > 0 ? (totalPool / homePool).toFixed(2) : "\u2014",
2387
+ awayOdds: awayPool > 0 ? (totalPool / awayPool).toFixed(2) : "\u2014"
2388
+ };
2389
+ }, [homePool, awayPool, totalPool]);
2390
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.View, { style: [styles5.card, { backgroundColor: t.surface, borderColor: t.border }], children: [
2391
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: [styles5.title, { color: t.text }], children: "Live Pools" }),
2392
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.Text, { style: [styles5.total, { color: t.accent }], children: [
2393
+ totalPool,
2394
+ " SOL total"
2395
+ ] }),
2396
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.View, { style: styles5.bars, children: [
2397
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PoolBar, { name: homeName, amount: homePool, percent: homePercent, color: homeColor, t }),
2398
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PoolBar, { name: awayName, amount: awayPool, percent: awayPercent, color: awayColor, t })
2399
+ ] }),
2400
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.View, { style: styles5.oddsRow, children: [
2401
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.Text, { style: [styles5.oddsText, { color: t.textMuted }], children: [
2402
+ homeName,
2403
+ ": ",
2404
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.Text, { style: { color: t.text, fontWeight: "700" }, children: [
2405
+ homeOdds,
2406
+ "x"
2407
+ ] })
2408
+ ] }),
2409
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.Text, { style: [styles5.oddsText, { color: t.textMuted }], children: [
2410
+ awayName,
2411
+ ": ",
2412
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.Text, { style: { color: t.text, fontWeight: "700" }, children: [
2413
+ awayOdds,
2414
+ "x"
2415
+ ] })
2416
+ ] })
2417
+ ] })
2418
+ ] });
2419
+ }
2420
+ function PoolBar({ name, amount, percent, color, t }) {
2421
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.View, { style: styles5.barRow, children: [
2422
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: [styles5.barLabel, { color: t.textSecondary }], numberOfLines: 1, children: name }),
2423
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: [styles5.barTrack, { backgroundColor: t.border }], children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: [styles5.barFill, { width: `${Math.max(percent, 2)}%`, backgroundColor: color }] }) }),
2424
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.Text, { style: [styles5.barAmount, { color: t.text }], children: [
2425
+ amount,
2426
+ " SOL"
2427
+ ] })
2428
+ ] });
2429
+ }
2430
+ var styles5 = import_react_native7.StyleSheet.create({
2431
+ card: { borderRadius: 16, borderWidth: 1, padding: 16 },
2432
+ title: { fontSize: 17, fontWeight: "700", marginBottom: 4 },
2433
+ total: { fontSize: 14, fontWeight: "600", marginBottom: 14 },
2434
+ bars: { gap: 10 },
2435
+ barRow: { flexDirection: "row", alignItems: "center", gap: 10 },
2436
+ barLabel: { width: 80, fontSize: 13, fontWeight: "600" },
2437
+ barTrack: { flex: 1, height: 10, borderRadius: 5, overflow: "hidden" },
2438
+ barFill: { height: "100%", borderRadius: 5 },
2439
+ barAmount: { width: 70, textAlign: "right", fontSize: 13, fontWeight: "700" },
2440
+ oddsRow: { flexDirection: "row", justifyContent: "space-between", marginTop: 12 },
2441
+ oddsText: { fontSize: 12 }
2442
+ });
2443
+
2444
+ // src/ui/game/PickWinnerCard.tsx
2445
+ var import_react16 = require("react");
2446
+ var import_react_native8 = require("react-native");
2447
+ var import_jsx_runtime9 = require("react/jsx-runtime");
2448
+ function PickWinnerCard({
2449
+ game,
2450
+ selectedTeam,
2451
+ onSelect,
2452
+ shortName,
2453
+ homeColor = "#3B82F6",
2454
+ awayColor = "#EF4444",
2455
+ ImageComponent
2456
+ }) {
2457
+ const t = useDubsTheme();
2458
+ const opponents = game.opponents || [];
2459
+ const bettors = game.bettors || [];
2460
+ const totalPool = game.totalPool || 0;
2461
+ const homePool = game.homePool || 0;
2462
+ const awayPool = game.awayPool || 0;
2463
+ const { homeOdds, awayOdds, homeBets, awayBets } = (0, import_react16.useMemo)(() => ({
2464
+ homeOdds: homePool > 0 ? (totalPool / homePool).toFixed(2) : "\u2014",
2465
+ awayOdds: awayPool > 0 ? (totalPool / awayPool).toFixed(2) : "\u2014",
2466
+ homeBets: bettors.filter((b) => b.team === "home").length,
2467
+ awayBets: bettors.filter((b) => b.team === "away").length
2468
+ }), [totalPool, homePool, awayPool, bettors]);
2469
+ const homeName = shortName ? shortName(opponents[0]?.name) : opponents[0]?.name || "Home";
2470
+ const awayName = shortName ? shortName(opponents[1]?.name) : opponents[1]?.name || "Away";
2471
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native8.View, { style: [styles6.card, { backgroundColor: t.surface, borderColor: t.border }], children: [
2472
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native8.Text, { style: [styles6.title, { color: t.text }], children: "Pick Your Winner" }),
2473
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native8.View, { style: styles6.row, children: [
2474
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2475
+ TeamOption,
2476
+ {
2477
+ name: homeName,
2478
+ imageUrl: opponents[0]?.imageUrl,
2479
+ odds: homeOdds,
2480
+ bets: homeBets,
2481
+ color: homeColor,
2482
+ selected: selectedTeam === "home",
2483
+ onPress: () => onSelect("home"),
2484
+ ImageComponent,
2485
+ t
2486
+ }
2487
+ ),
2488
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2489
+ TeamOption,
2490
+ {
2491
+ name: awayName,
2492
+ imageUrl: opponents[1]?.imageUrl,
2493
+ odds: awayOdds,
2494
+ bets: awayBets,
2495
+ color: awayColor,
2496
+ selected: selectedTeam === "away",
2497
+ onPress: () => onSelect("away"),
2498
+ ImageComponent,
2499
+ t
2500
+ }
2501
+ )
2502
+ ] })
2503
+ ] });
2504
+ }
2505
+ function TeamOption({
2506
+ name,
2507
+ imageUrl,
2508
+ odds,
2509
+ bets,
2510
+ color,
2511
+ selected,
2512
+ onPress,
2513
+ ImageComponent,
2514
+ t
2515
+ }) {
2516
+ const [imgFailed, setImgFailed] = (0, import_react16.useState)(false);
2517
+ const Img = ImageComponent || require("react-native").Image;
2518
+ const showImage = imageUrl && !imgFailed;
2519
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2520
+ import_react_native8.TouchableOpacity,
2521
+ {
2522
+ style: [styles6.option, { borderColor: selected ? color : t.border, backgroundColor: selected ? color + "15" : t.background }],
2523
+ onPress,
2524
+ activeOpacity: 0.7,
2525
+ children: [
2526
+ showImage ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Img, { source: { uri: imageUrl }, style: styles6.logo, resizeMode: "contain", onError: () => setImgFailed(true) }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native8.View, { style: [styles6.logo, styles6.logoPlaceholder] }),
2527
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native8.Text, { style: [styles6.name, { color: t.text }], numberOfLines: 1, children: name }),
2528
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native8.Text, { style: [styles6.odds, { color }], children: [
2529
+ odds,
2530
+ "x"
2531
+ ] }),
2532
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native8.Text, { style: [styles6.bets, { color: t.textMuted }], children: [
2533
+ bets,
2534
+ " ",
2535
+ bets === 1 ? "bet" : "bets"
2536
+ ] }),
2537
+ selected && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native8.View, { style: [styles6.badge, { backgroundColor: color }], children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native8.Text, { style: styles6.badgeText, children: "Selected" }) })
2538
+ ]
2539
+ }
2540
+ );
2541
+ }
2542
+ var styles6 = import_react_native8.StyleSheet.create({
2543
+ card: { borderRadius: 16, borderWidth: 1, padding: 16 },
2544
+ title: { fontSize: 17, fontWeight: "700", marginBottom: 12 },
2545
+ row: { flexDirection: "row", gap: 12 },
2546
+ option: { flex: 1, borderWidth: 2, borderRadius: 16, padding: 16, alignItems: "center", gap: 8 },
2547
+ logo: { width: 48, height: 48, borderRadius: 24 },
2548
+ logoPlaceholder: { backgroundColor: "rgba(128,128,128,0.2)" },
2549
+ name: { fontSize: 15, fontWeight: "700" },
2550
+ odds: { fontSize: 20, fontWeight: "800" },
2551
+ bets: { fontSize: 12 },
2552
+ badge: { borderRadius: 8, paddingHorizontal: 12, paddingVertical: 4, marginTop: 4 },
2553
+ badgeText: { color: "#FFF", fontSize: 12, fontWeight: "700" }
2554
+ });
2555
+
2556
+ // src/ui/game/PlayersCard.tsx
2557
+ var import_react17 = require("react");
2558
+ var import_react_native9 = require("react-native");
2559
+ var import_jsx_runtime10 = require("react/jsx-runtime");
2560
+ function truncateWallet(addr, chars) {
2561
+ if (addr.length <= chars * 2 + 3) return addr;
2562
+ return `${addr.slice(0, chars)}...${addr.slice(-chars)}`;
2563
+ }
2564
+ function PlayersCard({
2565
+ game,
2566
+ truncateChars = 4,
2567
+ homeColor = "#3B82F6",
2568
+ awayColor = "#EF4444",
2569
+ drawColor = "#A855F7",
2570
+ ImageComponent
2571
+ }) {
2572
+ const t = useDubsTheme();
2573
+ const bettors = game.bettors || [];
2574
+ const dotColor = (team) => {
2575
+ if (team === "home") return homeColor;
2576
+ if (team === "away") return awayColor;
2577
+ return drawColor;
2578
+ };
2579
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native9.View, { style: [styles7.card, { backgroundColor: t.surface, borderColor: t.border }], children: [
2580
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native9.Text, { style: [styles7.title, { color: t.text }], children: [
2581
+ "Players",
2582
+ bettors.length > 0 ? ` (${bettors.length})` : ""
2583
+ ] }),
2584
+ bettors.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native9.Text, { style: [styles7.empty, { color: t.textMuted }], children: "No players yet \u2014 be the first!" }) : bettors.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2585
+ BettorRow,
2586
+ {
2587
+ bettor: b,
2588
+ dotColor: dotColor(b.team),
2589
+ truncateChars,
2590
+ isFirst: i === 0,
2591
+ ImageComponent,
2592
+ t
2593
+ },
2594
+ `${b.wallet}-${i}`
2595
+ ))
2596
+ ] });
2597
+ }
2598
+ function BettorRow({
2599
+ bettor,
2600
+ dotColor,
2601
+ truncateChars,
2602
+ isFirst,
2603
+ ImageComponent,
2604
+ t
2605
+ }) {
2606
+ const [imgFailed, setImgFailed] = (0, import_react17.useState)(false);
2607
+ const Img = ImageComponent || require("react-native").Image;
2608
+ const showAvatar = bettor.avatar && !imgFailed;
2609
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native9.View, { style: [styles7.row, !isFirst && { borderTopColor: t.border, borderTopWidth: 1 }], children: [
2610
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native9.View, { style: [styles7.dot, { backgroundColor: dotColor }] }),
2611
+ showAvatar ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Img, { source: { uri: bettor.avatar }, style: styles7.avatar, resizeMode: "cover", onError: () => setImgFailed(true) }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native9.View, { style: [styles7.avatar, styles7.avatarPlaceholder] }),
2612
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native9.View, { style: styles7.nameCol, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native9.Text, { style: [styles7.username, { color: t.text }], numberOfLines: 1, children: bettor.username || truncateWallet(bettor.wallet, truncateChars) }) }),
2613
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native9.Text, { style: [styles7.amount, { color: t.textSecondary }], children: [
2614
+ bettor.amount,
2615
+ " SOL"
2616
+ ] })
2617
+ ] });
2618
+ }
2619
+ var styles7 = import_react_native9.StyleSheet.create({
2620
+ card: { borderRadius: 16, borderWidth: 1, padding: 16 },
2621
+ title: { fontSize: 17, fontWeight: "700", marginBottom: 12 },
2622
+ empty: { fontSize: 14, textAlign: "center", paddingVertical: 16 },
2623
+ row: { flexDirection: "row", alignItems: "center", paddingVertical: 10, gap: 10 },
2624
+ dot: { width: 8, height: 8, borderRadius: 4 },
2625
+ avatar: { width: 28, height: 28, borderRadius: 14 },
2626
+ avatarPlaceholder: { backgroundColor: "rgba(128,128,128,0.2)" },
2627
+ nameCol: { flex: 1 },
2628
+ username: { fontSize: 14, fontWeight: "600" },
2629
+ amount: { fontSize: 13, fontWeight: "700" }
2630
+ });
2631
+
2632
+ // src/ui/game/JoinGameButton.tsx
2633
+ var import_react18 = require("react");
2634
+ var import_react_native10 = require("react-native");
2635
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2636
+ var STATUS_LABELS = {
2637
+ building: "Building transaction...",
2638
+ signing: "Approve in wallet...",
2639
+ confirming: "Confirming on-chain...",
2640
+ saving: "Saving..."
2641
+ };
2642
+ function JoinGameButton({ game, walletAddress, selectedTeam, status, onJoin }) {
2643
+ const t = useDubsTheme();
2644
+ const alreadyJoined = (0, import_react18.useMemo)(() => {
2645
+ if (!walletAddress) return false;
2646
+ return (game.bettors || []).some((b) => b.wallet === walletAddress);
2647
+ }, [game.bettors, walletAddress]);
2648
+ if (alreadyJoined || game.isLocked || game.isResolved) return null;
2649
+ const isJoining = status !== "idle" && status !== "success" && status !== "error";
2650
+ const statusLabel = STATUS_LABELS[status] || "";
2651
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native10.View, { style: [styles8.bar, { backgroundColor: t.background, borderTopColor: t.border }], children: [
2652
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native10.View, { style: styles8.buyInRow, children: [
2653
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native10.Text, { style: [styles8.buyInLabel, { color: t.textMuted }], children: "Buy-in" }),
2654
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native10.Text, { style: [styles8.buyInValue, { color: t.text }], children: [
2655
+ game.buyIn,
2656
+ " SOL"
2657
+ ] })
2658
+ ] }),
2659
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2660
+ import_react_native10.TouchableOpacity,
2661
+ {
2662
+ style: [styles8.button, { backgroundColor: selectedTeam ? "#22D3EE" : t.border }],
2663
+ disabled: !selectedTeam || isJoining,
2664
+ onPress: onJoin,
2665
+ activeOpacity: 0.8,
2666
+ children: isJoining ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native10.View, { style: styles8.joiningRow, children: [
2667
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native10.ActivityIndicator, { size: "small", color: "#000" }),
2668
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native10.Text, { style: styles8.buttonText, children: statusLabel })
2669
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native10.Text, { style: [styles8.buttonText, !selectedTeam && { color: t.textMuted }], children: selectedTeam ? `Join Bet \u2014 ${game.buyIn} SOL` : "Pick a team to bet" })
2670
+ }
2671
+ )
2672
+ ] });
2673
+ }
2674
+ var styles8 = import_react_native10.StyleSheet.create({
2675
+ bar: { position: "absolute", bottom: 0, left: 0, right: 0, paddingHorizontal: 16, paddingTop: 12, paddingBottom: 36, borderTopWidth: 1 },
2676
+ buyInRow: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", marginBottom: 10 },
2677
+ buyInLabel: { fontSize: 13 },
2678
+ buyInValue: { fontSize: 15, fontWeight: "800" },
2679
+ button: { borderRadius: 14, paddingVertical: 16, alignItems: "center" },
2680
+ buttonText: { color: "#000", fontSize: 16, fontWeight: "800" },
2681
+ joiningRow: { flexDirection: "row", alignItems: "center", gap: 10 }
2682
+ });
1836
2683
  // Annotate the CommonJS export names for ESM import in node:
1837
2684
  0 && (module.exports = {
1838
2685
  AuthGate,
@@ -1842,12 +2689,20 @@ var styles3 = import_react_native5.StyleSheet.create({
1842
2689
  DubsApiError,
1843
2690
  DubsClient,
1844
2691
  DubsProvider,
2692
+ GamePoster,
2693
+ JoinGameButton,
2694
+ LivePoolsCard,
1845
2695
  MwaWalletAdapter,
2696
+ NETWORK_CONFIG,
2697
+ PickWinnerCard,
2698
+ PlayersCard,
1846
2699
  SOLANA_PROGRAM_ERRORS,
2700
+ STORAGE_KEYS,
1847
2701
  SettingsSheet,
1848
2702
  UserProfileCard,
2703
+ createSecureStoreStorage,
2704
+ mergeTheme,
1849
2705
  parseSolanaError,
1850
- pollTransactionConfirmation,
1851
2706
  signAndSendBase64Transaction,
1852
2707
  useAuth,
1853
2708
  useClaim,