@b3dotfun/sdk 0.0.26 → 0.0.27-alpha.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.
Files changed (59) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpendCustom.d.ts +1 -0
  2. package/dist/cjs/anyspend/react/components/AnySpendCustom.js +3 -2
  3. package/dist/cjs/anyspend/react/components/AnySpendNFT.js +2 -2
  4. package/dist/cjs/anyspend/react/components/common/PaymentStripeWeb2.d.ts +2 -1
  5. package/dist/cjs/anyspend/react/components/common/PaymentStripeWeb2.js +3 -3
  6. package/dist/cjs/anyspend/react/components/common/PaymentVendorUI.js +2 -2
  7. package/dist/cjs/anyspend/utils/chain.js +2 -2
  8. package/dist/cjs/global-account/react/components/B3DynamicModal.js +4 -0
  9. package/dist/cjs/global-account/react/components/LinkAccount/LinkAccount.d.ts +2 -0
  10. package/dist/cjs/global-account/react/components/LinkAccount/LinkAccount.js +228 -0
  11. package/dist/cjs/global-account/react/components/ManageAccount/ManageAccount.js +56 -3
  12. package/dist/cjs/global-account/react/components/SignInWithB3/steps/LoginStep.js +1 -1
  13. package/dist/cjs/global-account/react/components/custom/Button.d.ts +1 -1
  14. package/dist/cjs/global-account/react/components/ui/button.d.ts +1 -1
  15. package/dist/cjs/global-account/react/hooks/useUnifiedChainSwitchAndExecute.js +8 -2
  16. package/dist/cjs/global-account/react/stores/useModalStore.d.ts +20 -1
  17. package/dist/cjs/global-account/react/stores/useModalStore.js +3 -0
  18. package/dist/cjs/global-account/react/utils/profileDisplay.d.ts +21 -0
  19. package/dist/cjs/global-account/react/utils/profileDisplay.js +63 -0
  20. package/dist/esm/anyspend/react/components/AnySpendCustom.d.ts +1 -0
  21. package/dist/esm/anyspend/react/components/AnySpendCustom.js +3 -2
  22. package/dist/esm/anyspend/react/components/AnySpendNFT.js +2 -2
  23. package/dist/esm/anyspend/react/components/common/PaymentStripeWeb2.d.ts +2 -1
  24. package/dist/esm/anyspend/react/components/common/PaymentStripeWeb2.js +3 -3
  25. package/dist/esm/anyspend/react/components/common/PaymentVendorUI.js +2 -2
  26. package/dist/esm/anyspend/utils/chain.js +2 -2
  27. package/dist/esm/global-account/react/components/B3DynamicModal.js +4 -0
  28. package/dist/esm/global-account/react/components/LinkAccount/LinkAccount.d.ts +2 -0
  29. package/dist/esm/global-account/react/components/LinkAccount/LinkAccount.js +225 -0
  30. package/dist/esm/global-account/react/components/ManageAccount/ManageAccount.js +58 -5
  31. package/dist/esm/global-account/react/components/SignInWithB3/steps/LoginStep.js +1 -1
  32. package/dist/esm/global-account/react/components/custom/Button.d.ts +1 -1
  33. package/dist/esm/global-account/react/components/ui/button.d.ts +1 -1
  34. package/dist/esm/global-account/react/hooks/useUnifiedChainSwitchAndExecute.js +8 -2
  35. package/dist/esm/global-account/react/stores/useModalStore.d.ts +20 -1
  36. package/dist/esm/global-account/react/stores/useModalStore.js +3 -0
  37. package/dist/esm/global-account/react/utils/profileDisplay.d.ts +21 -0
  38. package/dist/esm/global-account/react/utils/profileDisplay.js +60 -0
  39. package/dist/styles/index.css +1 -1
  40. package/dist/types/anyspend/react/components/AnySpendCustom.d.ts +1 -0
  41. package/dist/types/anyspend/react/components/common/PaymentStripeWeb2.d.ts +2 -1
  42. package/dist/types/global-account/react/components/LinkAccount/LinkAccount.d.ts +2 -0
  43. package/dist/types/global-account/react/components/custom/Button.d.ts +1 -1
  44. package/dist/types/global-account/react/components/ui/button.d.ts +1 -1
  45. package/dist/types/global-account/react/stores/useModalStore.d.ts +20 -1
  46. package/dist/types/global-account/react/utils/profileDisplay.d.ts +21 -0
  47. package/package.json +2 -2
  48. package/src/anyspend/react/components/AnySpendCustom.tsx +7 -3
  49. package/src/anyspend/react/components/AnySpendNFT.tsx +2 -1
  50. package/src/anyspend/react/components/common/PaymentStripeWeb2.tsx +5 -28
  51. package/src/anyspend/react/components/common/PaymentVendorUI.tsx +2 -2
  52. package/src/anyspend/utils/chain.ts +2 -2
  53. package/src/global-account/react/components/B3DynamicModal.tsx +4 -0
  54. package/src/global-account/react/components/LinkAccount/LinkAccount.tsx +369 -0
  55. package/src/global-account/react/components/ManageAccount/ManageAccount.tsx +187 -5
  56. package/src/global-account/react/components/SignInWithB3/steps/LoginStep.tsx +3 -1
  57. package/src/global-account/react/hooks/useUnifiedChainSwitchAndExecute.ts +9 -2
  58. package/src/global-account/react/stores/useModalStore.ts +26 -1
  59. package/src/global-account/react/utils/profileDisplay.ts +87 -0
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  Button,
3
3
  CopyToClipboard,
4
+ ManageAccountModalProps,
4
5
  TabsContentPrimitive,
5
6
  TabsListPrimitive,
6
7
  TabsPrimitive,
@@ -20,14 +21,18 @@ import { SignOutIcon } from "@b3dotfun/sdk/global-account/react/components/icons
20
21
  import { SwapIcon } from "@b3dotfun/sdk/global-account/react/components/icons/SwapIcon";
21
22
  import { formatUsername } from "@b3dotfun/sdk/shared/utils";
22
23
  import { formatNumber } from "@b3dotfun/sdk/shared/utils/formatNumber";
23
- import { Loader2, Pencil, Triangle } from "lucide-react";
24
+ import { client } from "@b3dotfun/sdk/shared/utils/thirdweb";
25
+ import { LinkIcon, Loader2, Pencil, Triangle, UnlinkIcon } from "lucide-react";
24
26
  import { useState } from "react";
25
27
  import { Chain } from "thirdweb";
26
- import { useActiveAccount } from "thirdweb/react";
28
+ import { useActiveAccount, useProfiles, useUnlinkProfile } from "thirdweb/react";
27
29
  import { formatUnits } from "viem";
28
30
  import useFirstEOA from "../../hooks/useFirstEOA";
31
+ import { getProfileDisplayInfo } from "../../utils/profileDisplay";
29
32
  import { AccountAssets } from "../AccountAssets/AccountAssets";
30
33
 
34
+ type TabValue = "balance" | "assets" | "apps" | "settings";
35
+
31
36
  interface ManageAccountProps {
32
37
  onLogout?: () => void;
33
38
  onSwap?: () => void;
@@ -50,7 +55,6 @@ export function ManageAccount({
50
55
  chain,
51
56
  partnerId,
52
57
  }: ManageAccountProps) {
53
- const [activeTab, setActiveTab] = useState("balance");
54
58
  const [revokingSignerId, setRevokingSignerId] = useState<string | null>(null);
55
59
  const account = useActiveAccount();
56
60
  const { data: assets, isLoading } = useAccountAssets(account?.address);
@@ -67,7 +71,8 @@ export function ManageAccount({
67
71
  chain,
68
72
  accountAddress: account?.address,
69
73
  });
70
- const { setB3ModalOpen, setB3ModalContentType } = useModalStore();
74
+ const { setB3ModalOpen, setB3ModalContentType, contentType } = useModalStore();
75
+ const { activeTab = "balance", setActiveTab } = contentType as ManageAccountModalProps;
71
76
  const { logout } = useAuthentication(partnerId);
72
77
  const [logoutLoading, setLogoutLoading] = useState(false);
73
78
 
@@ -408,10 +413,177 @@ export function ManageAccount({
408
413
  </div>
409
414
  );
410
415
 
416
+ const SettingsContent = () => {
417
+ const [unlinkingAccountId, setUnlinkingAccountId] = useState<string | null>(null);
418
+ const { data: profilesRaw = [], isLoading: isLoadingProfiles } = useProfiles({ client });
419
+ const { mutate: unlinkProfile, isPending: isUnlinking } = useUnlinkProfile();
420
+ const { setB3ModalOpen, setB3ModalContentType, isLinking } = useModalStore();
421
+
422
+ const profiles = profilesRaw
423
+ .filter((profile: any) => !["custom_auth_endpoint", "siwe"].includes(profile.type))
424
+ .map((profile: any) => ({
425
+ ...getProfileDisplayInfo(profile),
426
+ originalProfile: profile,
427
+ }));
428
+
429
+ const handleUnlink = async (profile: any) => {
430
+ setUnlinkingAccountId(profile.title);
431
+ try {
432
+ await unlinkProfile({
433
+ client,
434
+ profileToUnlink: profile.originalProfile,
435
+ });
436
+ } catch (error) {
437
+ console.error("Error unlinking account:", error);
438
+ } finally {
439
+ setUnlinkingAccountId(null);
440
+ }
441
+ };
442
+
443
+ const handleOpenLinkModal = () => {
444
+ setB3ModalOpen(true);
445
+ setB3ModalContentType({
446
+ type: "linkAccount",
447
+ showBackButton: true,
448
+ partnerId,
449
+ chain,
450
+ onSuccess: async () => {
451
+ // Let the LinkAccount component handle modal closing
452
+ },
453
+ onError: () => {
454
+ // Let the LinkAccount component handle errors
455
+ },
456
+ onClose: () => {
457
+ // Let the LinkAccount component handle closing
458
+ },
459
+ });
460
+ };
461
+
462
+ return (
463
+ <div className="space-y-8">
464
+ {/* Linked Accounts Section */}
465
+ <div className="space-y-4">
466
+ <div className="flex items-center justify-between">
467
+ <h3 className="text-b3-grey font-neue-montreal-semibold text-xl">Linked Accounts</h3>
468
+ <Button
469
+ className="bg-b3-primary-wash hover:bg-b3-primary-wash/70 flex items-center gap-2 rounded-full px-4 py-2"
470
+ onClick={handleOpenLinkModal}
471
+ disabled={isLinking}
472
+ >
473
+ {isLinking ? (
474
+ <Loader2 className="text-b3-primary-blue animate-spin" size={16} />
475
+ ) : (
476
+ <LinkIcon size={16} className="text-b3-primary-blue" />
477
+ )}
478
+ <span className="text-b3-grey font-neue-montreal-semibold">
479
+ {isLinking ? "Linking..." : "Link New Account"}
480
+ </span>
481
+ </Button>
482
+ </div>
483
+
484
+ {isLoadingProfiles ? (
485
+ <div className="flex justify-center py-8">
486
+ <Loader2 className="text-b3-grey animate-spin" />
487
+ </div>
488
+ ) : profiles.length > 0 ? (
489
+ <div className="space-y-4">
490
+ {profiles.map(profile => (
491
+ <div key={profile.title} className="bg-b3-line flex items-center justify-between rounded-xl p-4">
492
+ <div className="flex items-center gap-3">
493
+ {profile.imageUrl ? (
494
+ <img src={profile.imageUrl} alt={profile.title} className="size-10 rounded-full" />
495
+ ) : (
496
+ <div className="bg-b3-primary-wash flex h-10 w-10 items-center justify-center rounded-full">
497
+ <span className="text-b3-grey font-neue-montreal-semibold text-sm uppercase">
498
+ {profile.initial}
499
+ </span>
500
+ </div>
501
+ )}
502
+ <div>
503
+ <div className="flex items-center gap-2">
504
+ <span className="text-b3-grey font-neue-montreal-semibold">{profile.title}</span>
505
+ <span className="text-b3-foreground-muted font-neue-montreal-medium bg-b3-primary-wash rounded px-2 py-0.5 text-xs">
506
+ {profile.type.toUpperCase()}
507
+ </span>
508
+ </div>
509
+ <div className="text-b3-foreground-muted font-neue-montreal-medium text-sm">
510
+ {profile.subtitle}
511
+ </div>
512
+ </div>
513
+ </div>
514
+ <Button
515
+ variant="ghost"
516
+ size="icon"
517
+ className="text-b3-grey hover:text-b3-negative"
518
+ onClick={() => handleUnlink(profile)}
519
+ disabled={unlinkingAccountId === profile.title || isUnlinking}
520
+ >
521
+ {unlinkingAccountId === profile.title || isUnlinking ? (
522
+ <Loader2 className="animate-spin" />
523
+ ) : (
524
+ <UnlinkIcon size={16} />
525
+ )}
526
+ </Button>
527
+ </div>
528
+ ))}
529
+ </div>
530
+ ) : (
531
+ <div className="text-b3-foreground-muted py-8 text-center">No linked accounts found</div>
532
+ )}
533
+ </div>
534
+
535
+ {/* Additional Settings Sections */}
536
+ <div className="space-y-4">
537
+ <h3 className="text-b3-grey font-neue-montreal-semibold text-xl">Account Preferences</h3>
538
+ <div className="bg-b3-line rounded-xl p-4">
539
+ <div className="flex items-center justify-between">
540
+ <div>
541
+ <div className="text-b3-grey font-neue-montreal-semibold">Dark Mode</div>
542
+ <div className="text-b3-foreground-muted font-neue-montreal-medium text-sm">
543
+ Switch between light and dark theme
544
+ </div>
545
+ </div>
546
+ {/* Theme toggle placeholder - can be implemented later */}
547
+ <div className="bg-b3-primary-wash h-6 w-12 rounded-full"></div>
548
+ </div>
549
+ </div>
550
+ </div>
551
+
552
+ {/* Global Account Info */}
553
+ <div className="border-b3-line flex items-center justify-between rounded-2xl border p-4">
554
+ <div>
555
+ <div className="flex items-center gap-2">
556
+ <img src="https://cdn.b3.fun/b3_logo.svg" alt="B3" className="h-4" />
557
+ <h3 className="font-neue-montreal-semibold text-b3-grey">Global Account</h3>
558
+ </div>
559
+
560
+ <p className="text-b3-foreground-muted font-neue-montreal-medium mt-2 text-sm">
561
+ Your universal account for all B3-powered apps
562
+ </p>
563
+ </div>
564
+ <button
565
+ className="text-b3-grey hover:text-b3-grey/80 hover:bg-b3-line border-b3-line flex size-12 items-center justify-center rounded-full border"
566
+ onClick={onLogoutEnhanced}
567
+ >
568
+ {logoutLoading ? <Loader2 className="animate-spin" /> : <SignOutIcon size={16} className="text-b3-grey" />}
569
+ </button>
570
+ </div>
571
+ </div>
572
+ );
573
+ };
574
+
411
575
  return (
412
576
  <div className="b3-manage-account bg-b3-background flex flex-col rounded-xl">
413
577
  <div className="flex-1">
414
- <TabsPrimitive defaultValue={activeTab} onValueChange={setActiveTab}>
578
+ <TabsPrimitive
579
+ defaultValue={activeTab}
580
+ onValueChange={value => {
581
+ const tab = value as TabValue;
582
+ if (["balance", "assets", "apps", "settings"].includes(tab)) {
583
+ setActiveTab?.(tab);
584
+ }
585
+ }}
586
+ >
415
587
  <TabsListPrimitive className="font-neue-montreal-semibold text-b3-grey flex h-8 w-full items-start justify-start gap-8 border-0 text-xl md:p-4">
416
588
  <TabTriggerPrimitive
417
589
  value="balance"
@@ -431,6 +603,12 @@ export function ManageAccount({
431
603
  >
432
604
  Apps
433
605
  </TabTriggerPrimitive>
606
+ <TabTriggerPrimitive
607
+ value="settings"
608
+ className="data-[state=active]:text-b3-primary-blue data-[state=active]:border-b-b3-primary-blue flex-none rounded-none border-0 p-0 pb-1 text-xl leading-none tracking-wide transition-colors data-[state=active]:border-b data-[state=active]:bg-white md:pb-4"
609
+ >
610
+ Settings
611
+ </TabTriggerPrimitive>
434
612
  </TabsListPrimitive>
435
613
 
436
614
  <TabsContentPrimitive value="balance" className="pt-4 md:p-4">
@@ -444,6 +622,10 @@ export function ManageAccount({
444
622
  <TabsContentPrimitive value="apps" className="pt-4 md:p-4">
445
623
  <AppsContent />
446
624
  </TabsContentPrimitive>
625
+
626
+ <TabsContentPrimitive value="settings" className="pt-4 md:p-4">
627
+ <SettingsContent />
628
+ </TabsContentPrimitive>
447
629
  </TabsPrimitive>
448
630
  </div>
449
631
  </div>
@@ -43,7 +43,9 @@ export function LoginStepContainer({ children, partnerId }: LoginStepContainerPr
43
43
 
44
44
  return (
45
45
  <div className="flex flex-col items-center justify-center">
46
- {partnerLogo && <img src={partnerLogo} alt="Partner Logo" className="mb-6 mt-6 h-12 w-auto object-contain" />}
46
+ {partnerLogo && (
47
+ <img src={partnerLogo} alt="Partner Logo" className="partner-logo mb-6 mt-6 h-12 w-auto object-contain" />
48
+ )}
47
49
  {children}
48
50
  <h2 className="mt-6 flex items-center gap-2 text-lg font-bold">
49
51
  Powered by
@@ -50,9 +50,15 @@ export function useUnifiedChainSwitchAndExecute() {
50
50
  throw new Error("No account connected");
51
51
  }
52
52
 
53
+ // Get the target chain configuration instead of using potentially stale walletClient.chain
54
+ const targetChain = supportedChains.find(chain => chain.id === targetChainId);
55
+ if (!targetChain) {
56
+ throw new Error(`Chain ${targetChainId} is not supported`);
57
+ }
58
+
53
59
  const hash = await walletClient.sendTransaction({
54
60
  account: signer,
55
- chain: walletClient.chain,
61
+ chain: targetChain,
56
62
  to: params.to,
57
63
  data: params.data as `0x${string}`,
58
64
  value: params.value,
@@ -69,7 +75,7 @@ export function useUnifiedChainSwitchAndExecute() {
69
75
  return await executeTransaction();
70
76
  }
71
77
 
72
- toast.info(`Switching to ${getChainName(targetChainId)}…`);
78
+ const switchingToastId = toast.info(`Switching to ${getChainName(targetChainId)}…`);
73
79
 
74
80
  const targetChain = supportedChains.find(chain => chain.id === targetChainId);
75
81
  if (!targetChain) {
@@ -95,6 +101,7 @@ export function useUnifiedChainSwitchAndExecute() {
95
101
  },
96
102
  });
97
103
 
104
+ toast.dismiss(switchingToastId);
98
105
  return await executeTransaction();
99
106
  } catch (e: any) {
100
107
  if (e?.code === -32603 || e?.message?.includes("f is not a function")) {
@@ -83,6 +83,10 @@ export interface ManageAccountModalProps extends BaseModalProps {
83
83
  chain: Chain;
84
84
  /** Partner ID */
85
85
  partnerId: string;
86
+ /** Active Tab */
87
+ activeTab?: "balance" | "assets" | "apps" | "settings";
88
+ /** Function to set the active tab */
89
+ setActiveTab?: (tab: "balance" | "assets" | "apps" | "settings") => void;
86
90
  }
87
91
 
88
92
  /**
@@ -291,6 +295,16 @@ export interface AnySpendBondKitProps extends BaseModalProps {
291
295
  onSuccess?: (txHash?: string) => void;
292
296
  }
293
297
 
298
+ export interface LinkAccountModalProps extends BaseModalProps {
299
+ type: "linkAccount";
300
+ showBackButton?: boolean;
301
+ onSuccess?: () => void;
302
+ onError?: (error: Error) => void;
303
+ onClose?: () => void;
304
+ partnerId: string;
305
+ chain: Chain;
306
+ }
307
+
294
308
  /**
295
309
  * Union type of all possible modal content types
296
310
  */
@@ -308,7 +322,8 @@ export type ModalContentType =
308
322
  | AnySpendStakeB3Props
309
323
  | AnySpendBuySpinProps
310
324
  | AnySpendSignatureMintProps
311
- | AnySpendBondKitProps;
325
+ | AnySpendBondKitProps
326
+ | LinkAccountModalProps;
312
327
  // Add other modal types here like: | OtherModalProps | AnotherModalProps
313
328
 
314
329
  /**
@@ -333,6 +348,12 @@ interface ModalState {
333
348
  ecoSystemAccountAddress?: Address;
334
349
  /** Function to set the ecosystem account address */
335
350
  setEcoSystemAccountAddress: (address: Address) => void;
351
+ /** Whether an account linking operation is in progress */
352
+ isLinking: boolean;
353
+ /** The method currently being linked */
354
+ linkingMethod: string | null;
355
+ /** Function to set the linking state */
356
+ setLinkingState: (isLinking: boolean, method?: string | null) => void;
336
357
  }
337
358
 
338
359
  /**
@@ -368,4 +389,8 @@ export const useModalStore = create<ModalState>(set => ({
368
389
  clearHistory: () => set({ history: [] }),
369
390
  ecoSystemAccountAddress: undefined,
370
391
  setEcoSystemAccountAddress: (address: Address) => set({ ecoSystemAccountAddress: address }),
392
+ isLinking: false,
393
+ linkingMethod: null,
394
+ setLinkingState: (isLinking: boolean, method: string | null = null) =>
395
+ set({ isLinking, linkingMethod: isLinking ? method : null }),
371
396
  }));
@@ -0,0 +1,87 @@
1
+ import { type Profile } from "thirdweb/wallets";
2
+
3
+ export interface ExtendedProfileDetails {
4
+ id?: string;
5
+ email?: string;
6
+ phone?: string;
7
+ address?: string;
8
+ name?: string;
9
+ username?: string;
10
+ profileImageUrl?: string;
11
+ }
12
+
13
+ export interface ExtendedProfile extends Omit<Profile, "details"> {
14
+ details: ExtendedProfileDetails;
15
+ }
16
+
17
+ export interface ProfileDisplayInfo {
18
+ title: string;
19
+ subtitle: string;
20
+ imageUrl: string | null;
21
+ initial: string;
22
+ type: Profile["type"];
23
+ }
24
+
25
+ export function getProfileDisplayInfo(profile: ExtendedProfile): ProfileDisplayInfo {
26
+ const { type, details } = profile;
27
+
28
+ // Default display info
29
+ let displayInfo: ProfileDisplayInfo = {
30
+ title: details.email || details.phone || details.address || "Unknown",
31
+ subtitle: `Connected with ${type}`,
32
+ imageUrl: null,
33
+ initial: (type.charAt(0) || "U").toUpperCase(),
34
+ type,
35
+ };
36
+
37
+ // Handle specific providers
38
+ switch (type) {
39
+ case "x":
40
+ displayInfo = {
41
+ title: details.name || details.username || "Unknown",
42
+ subtitle: details.username ? `@${details.username}` : "X Account",
43
+ imageUrl: details.profileImageUrl || null,
44
+ initial: "X",
45
+ type,
46
+ };
47
+ break;
48
+ case "google":
49
+ displayInfo = {
50
+ title: details.name || details.email || "Unknown",
51
+ subtitle: details.email || "Google Account",
52
+ imageUrl: details.profileImageUrl || null,
53
+ initial: "G",
54
+ type,
55
+ };
56
+ break;
57
+ case "discord":
58
+ displayInfo = {
59
+ title: details.username || details.name || "Unknown",
60
+ subtitle: "Discord Account",
61
+ imageUrl: details.profileImageUrl || null,
62
+ initial: "D",
63
+ type,
64
+ };
65
+ break;
66
+ case "email":
67
+ displayInfo = {
68
+ title: details.email || "Unknown",
69
+ subtitle: "Email Account",
70
+ imageUrl: null,
71
+ initial: "E",
72
+ type,
73
+ };
74
+ break;
75
+ case "phone":
76
+ displayInfo = {
77
+ title: details.phone || "Unknown",
78
+ subtitle: "Phone Number",
79
+ imageUrl: null,
80
+ initial: "P",
81
+ type,
82
+ };
83
+ break;
84
+ }
85
+
86
+ return displayInfo;
87
+ }