@dubsdotapp/expo 0.2.0 → 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.mjs CHANGED
@@ -417,6 +417,12 @@ var DubsClient = class {
417
417
  getErrorCodesLocal() {
418
418
  return { ...SOLANA_PROGRAM_ERRORS };
419
419
  }
420
+ // ── App Config ──
421
+ /** Fetch the app's UI customization config (accent color, icon, tagline) */
422
+ async getAppConfig() {
423
+ const res = await this.request("GET", "/apps/config");
424
+ return res.uiConfig || {};
425
+ }
420
426
  };
421
427
 
422
428
  // src/storage.ts
@@ -455,7 +461,7 @@ function createSecureStoreStorage() {
455
461
  }
456
462
 
457
463
  // src/provider.tsx
458
- import { createContext as createContext3, useContext as useContext3, useMemo, useCallback as useCallback11 } from "react";
464
+ import { createContext as createContext3, useContext as useContext3, useMemo, useCallback as useCallback11, useState as useState11, useEffect as useEffect7 } from "react";
459
465
  import { Connection } from "@solana/web3.js";
460
466
 
461
467
  // src/managed-wallet.tsx
@@ -565,7 +571,8 @@ import {
565
571
  Text,
566
572
  TouchableOpacity,
567
573
  ActivityIndicator,
568
- StyleSheet
574
+ StyleSheet,
575
+ Image
569
576
  } from "react-native";
570
577
 
571
578
  // src/ui/theme.ts
@@ -606,6 +613,9 @@ function useDubsTheme() {
606
613
  const scheme = useColorScheme();
607
614
  return scheme === "light" ? light : dark;
608
615
  }
616
+ function mergeTheme(base, overrides) {
617
+ return { ...base, ...overrides };
618
+ }
609
619
 
610
620
  // src/ui/ConnectWalletScreen.tsx
611
621
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -613,14 +623,24 @@ function ConnectWalletScreen({
613
623
  onConnect,
614
624
  connecting = false,
615
625
  error = null,
616
- appName = "Dubs"
626
+ appName = "Dubs",
627
+ accentColor,
628
+ appIcon,
629
+ tagline
617
630
  }) {
618
631
  const t = useDubsTheme();
632
+ const accent = accentColor || t.accent;
619
633
  return /* @__PURE__ */ jsx(View, { style: [styles.container, { backgroundColor: t.background }], children: /* @__PURE__ */ jsxs(View, { style: styles.content, children: [
620
634
  /* @__PURE__ */ jsxs(View, { style: styles.brandingSection, children: [
621
- /* @__PURE__ */ jsx(View, { style: [styles.logoCircle, { backgroundColor: t.accent }], children: /* @__PURE__ */ jsx(Text, { style: styles.logoText, children: "D" }) }),
635
+ appIcon ? /* @__PURE__ */ jsx(
636
+ Image,
637
+ {
638
+ source: { uri: appIcon },
639
+ style: styles.logoImage
640
+ }
641
+ ) : /* @__PURE__ */ jsx(View, { style: [styles.logoCircle, { backgroundColor: accent }], children: /* @__PURE__ */ jsx(Text, { style: styles.logoText, children: appName.charAt(0).toUpperCase() }) }),
622
642
  /* @__PURE__ */ jsx(Text, { style: [styles.appName, { color: t.text }], children: appName }),
623
- /* @__PURE__ */ jsx(Text, { style: [styles.subtitle, { color: t.textMuted }], children: "Connect your Solana wallet to get started" })
643
+ /* @__PURE__ */ jsx(Text, { style: [styles.subtitle, { color: t.textMuted }], children: tagline || "Connect your Solana wallet to get started" })
624
644
  ] }),
625
645
  /* @__PURE__ */ jsxs(View, { style: styles.actionSection, children: [
626
646
  error ? /* @__PURE__ */ jsx(
@@ -636,7 +656,7 @@ function ConnectWalletScreen({
636
656
  /* @__PURE__ */ jsx(
637
657
  TouchableOpacity,
638
658
  {
639
- style: [styles.connectButton, { backgroundColor: t.accent }],
659
+ style: [styles.connectButton, { backgroundColor: accent }],
640
660
  onPress: onConnect,
641
661
  disabled: connecting,
642
662
  activeOpacity: 0.8,
@@ -671,6 +691,12 @@ var styles = StyleSheet.create({
671
691
  alignItems: "center",
672
692
  marginBottom: 8
673
693
  },
694
+ logoImage: {
695
+ width: 80,
696
+ height: 80,
697
+ borderRadius: 16,
698
+ marginBottom: 8
699
+ },
674
700
  logoText: {
675
701
  fontSize: 36,
676
702
  fontWeight: "800",
@@ -726,6 +752,9 @@ function ManagedWalletProvider({
726
752
  cluster,
727
753
  storage,
728
754
  renderConnectScreen,
755
+ accentColor,
756
+ appIcon,
757
+ tagline,
729
758
  children
730
759
  }) {
731
760
  const [connected, setConnected] = useState(false);
@@ -813,7 +842,10 @@ function ManagedWalletProvider({
813
842
  onConnect: handleConnect,
814
843
  connecting,
815
844
  error,
816
- appName
845
+ appName,
846
+ accentColor,
847
+ appIcon,
848
+ tagline
817
849
  };
818
850
  if (renderConnectScreen) {
819
851
  return /* @__PURE__ */ jsx2(Fragment, { children: renderConnectScreen(connectProps) });
@@ -835,7 +867,7 @@ import {
835
867
  Keyboard,
836
868
  KeyboardAvoidingView,
837
869
  Platform,
838
- Image,
870
+ Image as Image2,
839
871
  Animated,
840
872
  ScrollView
841
873
  } from "react-native";
@@ -1280,7 +1312,8 @@ function AuthGate({
1280
1312
  renderLoading,
1281
1313
  renderError,
1282
1314
  renderRegistration,
1283
- appName = "Dubs"
1315
+ appName = "Dubs",
1316
+ accentColor
1284
1317
  }) {
1285
1318
  const { client } = useDubs();
1286
1319
  const auth = useAuth();
@@ -1349,7 +1382,8 @@ function AuthGate({
1349
1382
  registering: isRegistering,
1350
1383
  error: regError,
1351
1384
  client,
1352
- appName
1385
+ appName,
1386
+ accentColor
1353
1387
  }
1354
1388
  );
1355
1389
  }
@@ -1418,9 +1452,11 @@ function DefaultRegistrationScreen({
1418
1452
  registering,
1419
1453
  error,
1420
1454
  client,
1421
- appName
1455
+ appName,
1456
+ accentColor
1422
1457
  }) {
1423
1458
  const t = useDubsTheme();
1459
+ const accent = accentColor || t.accent;
1424
1460
  const [step, setStep] = useState10(0);
1425
1461
  const [avatarSeed, setAvatarSeed] = useState10(generateSeed);
1426
1462
  const [avatarStyle, setAvatarStyle] = useState10("adventurer");
@@ -1481,8 +1517,8 @@ function DefaultRegistrationScreen({
1481
1517
  /* @__PURE__ */ jsx3(Text2, { style: [s.title, { color: t.text }], children: "Choose Your Avatar" }),
1482
1518
  /* @__PURE__ */ jsx3(Text2, { style: [s.subtitle, { color: t.textMuted }], children: "Pick a look that represents you" }),
1483
1519
  /* @__PURE__ */ jsx3(StepIndicator, { currentStep: 0 }),
1484
- /* @__PURE__ */ jsx3(View2, { style: s.avatarCenter, children: /* @__PURE__ */ jsxs2(View2, { style: [s.avatarFrame, { borderColor: t.accent }], children: [
1485
- /* @__PURE__ */ jsx3(Image, { source: { uri: avatarUrl }, style: s.avatarLarge }),
1520
+ /* @__PURE__ */ jsx3(View2, { style: s.avatarCenter, children: /* @__PURE__ */ jsxs2(View2, { style: [s.avatarFrame, { borderColor: accent }], children: [
1521
+ /* @__PURE__ */ jsx3(Image2, { source: { uri: avatarUrl }, style: s.avatarLarge }),
1486
1522
  /* @__PURE__ */ jsx3(View2, { style: [s.checkBadge, { backgroundColor: t.success }], children: /* @__PURE__ */ jsx3(Text2, { style: s.checkBadgeText, children: "\u2713" }) })
1487
1523
  ] }) }),
1488
1524
  /* @__PURE__ */ jsxs2(View2, { style: s.avatarActions, children: [
@@ -1498,10 +1534,10 @@ function DefaultRegistrationScreen({
1498
1534
  /* @__PURE__ */ jsx3(
1499
1535
  TouchableOpacity2,
1500
1536
  {
1501
- style: [s.outlineBtn, { borderColor: t.accent, backgroundColor: t.accent + "15" }],
1537
+ style: [s.outlineBtn, { borderColor: accent, backgroundColor: accent + "15" }],
1502
1538
  onPress: () => setShowStyles(!showStyles),
1503
1539
  activeOpacity: 0.7,
1504
- children: /* @__PURE__ */ jsx3(Text2, { style: [s.outlineBtnText, { color: t.accent }], children: "\u263A Customize" })
1540
+ children: /* @__PURE__ */ jsx3(Text2, { style: [s.outlineBtnText, { color: accent }], children: "\u263A Customize" })
1505
1541
  }
1506
1542
  )
1507
1543
  ] }),
@@ -1509,8 +1545,8 @@ function DefaultRegistrationScreen({
1509
1545
  TouchableOpacity2,
1510
1546
  {
1511
1547
  onPress: () => setAvatarStyle(st),
1512
- style: [s.styleThumbWrap, { borderColor: st === avatarStyle ? t.accent : t.border }],
1513
- children: /* @__PURE__ */ jsx3(Image, { source: { uri: getAvatarUrl(st, avatarSeed, 80) }, style: s.styleThumb })
1548
+ style: [s.styleThumbWrap, { borderColor: st === avatarStyle ? accent : t.border }],
1549
+ children: /* @__PURE__ */ jsx3(Image2, { source: { uri: getAvatarUrl(st, avatarSeed, 80) }, style: s.styleThumb })
1514
1550
  },
1515
1551
  st
1516
1552
  )) })
@@ -1518,7 +1554,7 @@ function DefaultRegistrationScreen({
1518
1554
  /* @__PURE__ */ jsx3(View2, { style: s.bottomRow, children: /* @__PURE__ */ jsx3(
1519
1555
  TouchableOpacity2,
1520
1556
  {
1521
- style: [s.primaryBtn, { backgroundColor: t.accent, flex: 1 }],
1557
+ style: [s.primaryBtn, { backgroundColor: accent, flex: 1 }],
1522
1558
  onPress: () => animateToStep(1),
1523
1559
  activeOpacity: 0.8,
1524
1560
  children: /* @__PURE__ */ jsx3(Text2, { style: s.primaryBtnText, children: "Continue \u203A" })
@@ -1533,8 +1569,8 @@ function DefaultRegistrationScreen({
1533
1569
  ] }),
1534
1570
  /* @__PURE__ */ jsx3(Text2, { style: [s.subtitle, { color: t.textMuted }], children: "This is how others will see you" }),
1535
1571
  /* @__PURE__ */ jsx3(StepIndicator, { currentStep: 1 }),
1536
- /* @__PURE__ */ jsx3(View2, { style: s.avatarCenter, children: /* @__PURE__ */ jsxs2(View2, { style: [s.avatarFrameSmall, { borderColor: t.accent }], children: [
1537
- /* @__PURE__ */ jsx3(Image, { source: { uri: avatarUrl }, style: s.avatarSmall }),
1572
+ /* @__PURE__ */ jsx3(View2, { style: s.avatarCenter, children: /* @__PURE__ */ jsxs2(View2, { style: [s.avatarFrameSmall, { borderColor: accent }], children: [
1573
+ /* @__PURE__ */ jsx3(Image2, { source: { uri: avatarUrl }, style: s.avatarSmall }),
1538
1574
  /* @__PURE__ */ jsx3(View2, { style: [s.checkBadgeSm, { backgroundColor: t.success }], children: /* @__PURE__ */ jsx3(Text2, { style: s.checkBadgeTextSm, children: "\u2713" }) })
1539
1575
  ] }) }),
1540
1576
  /* @__PURE__ */ jsxs2(View2, { style: s.inputGroup, children: [
@@ -1545,7 +1581,7 @@ function DefaultRegistrationScreen({
1545
1581
  /* @__PURE__ */ jsx3(
1546
1582
  TextInput,
1547
1583
  {
1548
- style: [s.input, { backgroundColor: t.surface, color: t.text, borderColor: t.accent }],
1584
+ style: [s.input, { backgroundColor: t.surface, color: t.text, borderColor: accent }],
1549
1585
  placeholder: "Enter username",
1550
1586
  placeholderTextColor: t.textDim,
1551
1587
  value: username,
@@ -1571,7 +1607,7 @@ function DefaultRegistrationScreen({
1571
1607
  /* @__PURE__ */ jsx3(
1572
1608
  TouchableOpacity2,
1573
1609
  {
1574
- style: [s.primaryBtn, { backgroundColor: t.accent, flex: 1, opacity: canContinueUsername ? 1 : 0.4 }],
1610
+ style: [s.primaryBtn, { backgroundColor: accent, flex: 1, opacity: canContinueUsername ? 1 : 0.4 }],
1575
1611
  onPress: () => animateToStep(2),
1576
1612
  disabled: !canContinueUsername,
1577
1613
  activeOpacity: 0.8,
@@ -1591,7 +1627,7 @@ function DefaultRegistrationScreen({
1591
1627
  /* @__PURE__ */ jsxs2(View2, { style: [s.profileCard, { borderColor: t.border }], children: [
1592
1628
  /* @__PURE__ */ jsx3(Text2, { style: [s.profileLabel, { color: t.textMuted }], children: "Your Profile" }),
1593
1629
  /* @__PURE__ */ jsxs2(View2, { style: s.profileRow, children: [
1594
- /* @__PURE__ */ jsx3(Image, { source: { uri: avatarUrl }, style: s.profileAvatar }),
1630
+ /* @__PURE__ */ jsx3(Image2, { source: { uri: avatarUrl }, style: s.profileAvatar }),
1595
1631
  /* @__PURE__ */ jsxs2(View2, { style: { gap: 4 }, children: [
1596
1632
  /* @__PURE__ */ jsxs2(Text2, { style: [s.profileUsername, { color: t.text }], children: [
1597
1633
  "@",
@@ -1643,7 +1679,7 @@ function DefaultRegistrationScreen({
1643
1679
  /* @__PURE__ */ jsx3(
1644
1680
  TouchableOpacity2,
1645
1681
  {
1646
- style: [s.primaryBtn, { backgroundColor: t.accent, flex: 1, opacity: registering ? 0.7 : 1 }],
1682
+ style: [s.primaryBtn, { backgroundColor: accent, flex: 1, opacity: registering ? 0.7 : 1 }],
1647
1683
  onPress: handleSubmit,
1648
1684
  disabled: registering,
1649
1685
  activeOpacity: 0.8,
@@ -1778,6 +1814,11 @@ function DubsProvider({
1778
1814
  const client = useMemo(() => new DubsClient({ apiKey, baseUrl }), [apiKey, baseUrl]);
1779
1815
  const connection = useMemo(() => new Connection(rpcUrl, { commitment: "confirmed" }), [rpcUrl]);
1780
1816
  const storage = useMemo(() => tokenStorage || createSecureStoreStorage(), [tokenStorage]);
1817
+ const [uiConfig, setUiConfig] = useState11({});
1818
+ useEffect7(() => {
1819
+ client.getAppConfig().then(setUiConfig).catch(() => {
1820
+ });
1821
+ }, [client]);
1781
1822
  if (externalWallet) {
1782
1823
  return /* @__PURE__ */ jsx4(
1783
1824
  ExternalWalletProvider,
@@ -1785,13 +1826,14 @@ function DubsProvider({
1785
1826
  client,
1786
1827
  connection,
1787
1828
  wallet: externalWallet,
1788
- appName,
1829
+ appName: uiConfig.appName || appName,
1789
1830
  network,
1790
1831
  storage,
1791
1832
  managed,
1792
1833
  renderLoading,
1793
1834
  renderError,
1794
1835
  renderRegistration,
1836
+ accentColor: uiConfig.accentColor,
1795
1837
  children
1796
1838
  }
1797
1839
  );
@@ -1799,22 +1841,26 @@ function DubsProvider({
1799
1841
  return /* @__PURE__ */ jsx4(
1800
1842
  ManagedWalletProvider,
1801
1843
  {
1802
- appName,
1844
+ appName: uiConfig.appName || appName,
1803
1845
  cluster,
1804
1846
  storage,
1805
1847
  renderConnectScreen,
1848
+ accentColor: uiConfig.accentColor,
1849
+ appIcon: uiConfig.appIcon,
1850
+ tagline: uiConfig.tagline,
1806
1851
  children: (adapter) => /* @__PURE__ */ jsx4(
1807
1852
  ManagedInner,
1808
1853
  {
1809
1854
  client,
1810
1855
  connection,
1811
1856
  wallet: adapter,
1812
- appName,
1857
+ appName: uiConfig.appName || appName,
1813
1858
  network,
1814
1859
  storage,
1815
1860
  renderLoading,
1816
1861
  renderError,
1817
1862
  renderRegistration,
1863
+ accentColor: uiConfig.accentColor,
1818
1864
  children
1819
1865
  }
1820
1866
  )
@@ -1831,6 +1877,7 @@ function ManagedInner({
1831
1877
  renderLoading,
1832
1878
  renderError,
1833
1879
  renderRegistration,
1880
+ accentColor,
1834
1881
  children
1835
1882
  }) {
1836
1883
  const managedDisconnect = useDisconnect();
@@ -1854,6 +1901,7 @@ function ManagedInner({
1854
1901
  renderError,
1855
1902
  renderRegistration,
1856
1903
  appName,
1904
+ accentColor,
1857
1905
  children
1858
1906
  }
1859
1907
  ) });
@@ -1869,6 +1917,7 @@ function ExternalWalletProvider({
1869
1917
  renderLoading,
1870
1918
  renderError,
1871
1919
  renderRegistration,
1920
+ accentColor,
1872
1921
  children
1873
1922
  }) {
1874
1923
  const disconnect = useCallback11(async () => {
@@ -1896,6 +1945,7 @@ function ExternalWalletProvider({
1896
1945
  renderError,
1897
1946
  renderRegistration,
1898
1947
  appName,
1948
+ accentColor,
1899
1949
  children
1900
1950
  }
1901
1951
  ) });
@@ -1910,7 +1960,7 @@ function useDubs() {
1910
1960
 
1911
1961
  // src/ui/UserProfileCard.tsx
1912
1962
  import { useMemo as useMemo2 } from "react";
1913
- import { View as View3, Text as Text3, Image as Image2, StyleSheet as StyleSheet3 } from "react-native";
1963
+ import { View as View3, Text as Text3, Image as Image3, StyleSheet as StyleSheet3 } from "react-native";
1914
1964
  import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
1915
1965
  function truncateAddress(address, chars = 4) {
1916
1966
  if (address.length <= chars * 2 + 3) return address;
@@ -1934,7 +1984,7 @@ function UserProfileCard({
1934
1984
  [avatarUrl, walletAddress]
1935
1985
  );
1936
1986
  return /* @__PURE__ */ jsxs3(View3, { style: [styles2.card, { backgroundColor: t.surface, borderColor: t.border }], children: [
1937
- /* @__PURE__ */ jsx5(Image2, { source: { uri: imageUri }, style: styles2.avatar }),
1987
+ /* @__PURE__ */ jsx5(Image3, { source: { uri: imageUri }, style: styles2.avatar }),
1938
1988
  /* @__PURE__ */ jsxs3(View3, { style: styles2.info, children: [
1939
1989
  username ? /* @__PURE__ */ jsx5(Text3, { style: [styles2.username, { color: t.text }], children: username }) : null,
1940
1990
  /* @__PURE__ */ jsx5(Text3, { style: [styles2.address, { color: t.textMuted }], children: truncateAddress(walletAddress) }),
@@ -2129,7 +2179,7 @@ var styles3 = StyleSheet4.create({
2129
2179
  });
2130
2180
 
2131
2181
  // src/ui/game/GamePoster.tsx
2132
- import { useState as useState11 } from "react";
2182
+ import { useState as useState12 } from "react";
2133
2183
  import { StyleSheet as StyleSheet5, View as View5, Text as Text5 } from "react-native";
2134
2184
  import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
2135
2185
  function computeCountdown(lockTimestamp) {
@@ -2179,7 +2229,7 @@ function GamePoster({ game, ImageComponent }) {
2179
2229
  ] });
2180
2230
  }
2181
2231
  function TeamLogoInternal({ url, size, Img }) {
2182
- const [failed, setFailed] = useState11(false);
2232
+ const [failed, setFailed] = useState12(false);
2183
2233
  if (!url || failed) {
2184
2234
  return /* @__PURE__ */ jsx7(View5, { style: [styles4.logoPlaceholder, { width: size, height: size, borderRadius: size / 2 }] });
2185
2235
  }
@@ -2359,7 +2409,7 @@ var styles5 = StyleSheet6.create({
2359
2409
  });
2360
2410
 
2361
2411
  // src/ui/game/PickWinnerCard.tsx
2362
- import { useState as useState12, useMemo as useMemo4 } from "react";
2412
+ import { useState as useState13, useMemo as useMemo4 } from "react";
2363
2413
  import { StyleSheet as StyleSheet7, View as View7, Text as Text7, TouchableOpacity as TouchableOpacity4 } from "react-native";
2364
2414
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
2365
2415
  function PickWinnerCard({
@@ -2430,7 +2480,7 @@ function TeamOption({
2430
2480
  ImageComponent,
2431
2481
  t
2432
2482
  }) {
2433
- const [imgFailed, setImgFailed] = useState12(false);
2483
+ const [imgFailed, setImgFailed] = useState13(false);
2434
2484
  const Img = ImageComponent || __require("react-native").Image;
2435
2485
  const showImage = imageUrl && !imgFailed;
2436
2486
  return /* @__PURE__ */ jsxs7(
@@ -2471,7 +2521,7 @@ var styles6 = StyleSheet7.create({
2471
2521
  });
2472
2522
 
2473
2523
  // src/ui/game/PlayersCard.tsx
2474
- import { useState as useState13 } from "react";
2524
+ import { useState as useState14 } from "react";
2475
2525
  import { StyleSheet as StyleSheet8, View as View8, Text as Text8 } from "react-native";
2476
2526
  import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
2477
2527
  function truncateWallet(addr, chars) {
@@ -2520,7 +2570,7 @@ function BettorRow({
2520
2570
  ImageComponent,
2521
2571
  t
2522
2572
  }) {
2523
- const [imgFailed, setImgFailed] = useState13(false);
2573
+ const [imgFailed, setImgFailed] = useState14(false);
2524
2574
  const Img = ImageComponent || __require("react-native").Image;
2525
2575
  const showAvatar = bettor.avatar && !imgFailed;
2526
2576
  return /* @__PURE__ */ jsxs8(View8, { style: [styles7.row, !isFirst && { borderTopColor: t.border, borderTopWidth: 1 }], children: [
@@ -2617,6 +2667,7 @@ export {
2617
2667
  SettingsSheet,
2618
2668
  UserProfileCard,
2619
2669
  createSecureStoreStorage,
2670
+ mergeTheme,
2620
2671
  parseSolanaError,
2621
2672
  signAndSendBase64Transaction,
2622
2673
  useAuth,