@b3dotfun/sdk 0.1.0 → 0.1.1-alpha.0
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/cjs/anyspend/react/components/AnySpend.js +1 -1
- package/dist/cjs/anyspend/react/components/AnySpendDeposit.d.ts +15 -10
- package/dist/cjs/anyspend/react/components/AnySpendDeposit.js +22 -14
- package/dist/cjs/anyspend/react/components/QRDeposit.js +31 -5
- package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.js +9 -2
- package/dist/cjs/anyspend/react/components/common/GasIndicator.d.ts +1 -1
- package/dist/cjs/anyspend/react/components/common/GasIndicator.js +6 -16
- package/dist/cjs/anyspend/react/components/common/TokenBalance.js +15 -4
- package/dist/cjs/anyspend/react/components/common/TransferResultScreen.d.ts +22 -0
- package/dist/cjs/anyspend/react/components/common/TransferResultScreen.js +25 -0
- package/dist/cjs/anyspend/react/hooks/index.d.ts +1 -0
- package/dist/cjs/anyspend/react/hooks/index.js +1 -0
- package/dist/cjs/anyspend/react/hooks/useWatchTransfer.d.ts +41 -0
- package/dist/cjs/anyspend/react/hooks/useWatchTransfer.js +75 -0
- package/dist/cjs/anyspend/react/providers/AnyspendProvider.js +1 -2
- package/dist/cjs/anyspend/utils/address.d.ts +5 -0
- package/dist/cjs/anyspend/utils/address.js +8 -0
- package/dist/cjs/global-account/react/components/AccountAssets/AccountAssets.js +7 -3
- package/dist/cjs/global-account/react/components/B3DynamicModal.js +4 -14
- package/dist/cjs/global-account/react/components/B3Provider/B3ConfigProvider.d.ts +31 -0
- package/dist/cjs/global-account/react/components/B3Provider/B3ConfigProvider.js +37 -0
- package/dist/cjs/global-account/react/components/B3Provider/B3Provider.d.ts +2 -1
- package/dist/cjs/global-account/react/components/B3Provider/B3Provider.js +3 -31
- package/dist/cjs/global-account/react/components/B3Provider/B3Provider.native.js +4 -31
- package/dist/cjs/global-account/react/components/B3Provider/LocalSDKProvider.d.ts +3 -1
- package/dist/cjs/global-account/react/components/B3Provider/LocalSDKProvider.js +3 -1
- package/dist/cjs/global-account/react/components/B3Provider/useB3.d.ts +1 -12
- package/dist/cjs/global-account/react/components/B3Provider/useB3Config.d.ts +1 -17
- package/dist/cjs/global-account/react/components/B3Provider/useB3Config.js +2 -21
- package/dist/cjs/global-account/react/components/ManageAccount/NFTContent.js +2 -2
- package/dist/cjs/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +12 -1
- package/dist/cjs/global-account/react/components/SingleUserSearchSelector/SingleUserSearchSelector.d.ts +64 -0
- package/dist/cjs/global-account/react/components/SingleUserSearchSelector/SingleUserSearchSelector.js +163 -0
- package/dist/cjs/global-account/react/components/SingleUserSearchSelector/index.d.ts +2 -0
- package/dist/cjs/global-account/react/components/SingleUserSearchSelector/index.js +5 -0
- package/dist/cjs/global-account/react/components/WalletImage/WalletImage.d.ts +1 -1
- package/dist/cjs/global-account/react/components/index.d.ts +2 -0
- package/dist/cjs/global-account/react/components/index.js +6 -3
- package/dist/cjs/global-account/react/hooks/index.d.ts +2 -1
- package/dist/cjs/global-account/react/hooks/index.js +5 -1
- package/dist/cjs/global-account/react/hooks/useAuthentication.js +5 -2
- package/dist/cjs/global-account/react/hooks/useProfile.js +4 -23
- package/dist/cjs/global-account/react/hooks/useSimBalance.d.ts +7 -0
- package/dist/cjs/global-account/react/hooks/useSimBalance.js +43 -11
- package/dist/cjs/global-account/react/hooks/useSimCollectibles.d.ts +45 -0
- package/dist/cjs/global-account/react/hooks/useSimCollectibles.js +190 -0
- package/dist/cjs/global-account/react/stores/index.d.ts +0 -1
- package/dist/cjs/global-account/react/stores/index.js +1 -3
- package/dist/cjs/global-account/react/stores/useModalStore.d.ts +63 -1
- package/dist/cjs/global-account/react/stores/useModalStore.js +3 -0
- package/dist/cjs/global-account/react/utils/profileApi.d.ts +13 -0
- package/dist/cjs/global-account/react/utils/profileApi.js +29 -0
- package/dist/cjs/global-account/react/utils/simdune.d.ts +7 -0
- package/dist/cjs/global-account/react/utils/simdune.js +21 -0
- package/dist/esm/anyspend/react/components/AnySpend.js +1 -1
- package/dist/esm/anyspend/react/components/AnySpendDeposit.d.ts +15 -10
- package/dist/esm/anyspend/react/components/AnySpendDeposit.js +23 -15
- package/dist/esm/anyspend/react/components/QRDeposit.js +32 -6
- package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.js +10 -3
- package/dist/esm/anyspend/react/components/common/GasIndicator.d.ts +1 -1
- package/dist/esm/anyspend/react/components/common/GasIndicator.js +7 -17
- package/dist/esm/anyspend/react/components/common/TokenBalance.js +16 -5
- package/dist/esm/anyspend/react/components/common/TransferResultScreen.d.ts +22 -0
- package/dist/esm/anyspend/react/components/common/TransferResultScreen.js +22 -0
- package/dist/esm/anyspend/react/hooks/index.d.ts +1 -0
- package/dist/esm/anyspend/react/hooks/index.js +1 -0
- package/dist/esm/anyspend/react/hooks/useWatchTransfer.d.ts +41 -0
- package/dist/esm/anyspend/react/hooks/useWatchTransfer.js +72 -0
- package/dist/esm/anyspend/react/providers/AnyspendProvider.js +1 -2
- package/dist/esm/anyspend/utils/address.d.ts +5 -0
- package/dist/esm/anyspend/utils/address.js +7 -0
- package/dist/esm/global-account/react/components/AccountAssets/AccountAssets.js +7 -3
- package/dist/esm/global-account/react/components/B3DynamicModal.js +5 -15
- package/dist/esm/global-account/react/components/B3Provider/B3ConfigProvider.d.ts +31 -0
- package/dist/esm/global-account/react/components/B3Provider/B3ConfigProvider.js +33 -0
- package/dist/esm/global-account/react/components/B3Provider/B3Provider.d.ts +2 -1
- package/dist/esm/global-account/react/components/B3Provider/B3Provider.js +3 -31
- package/dist/esm/global-account/react/components/B3Provider/B3Provider.native.js +3 -30
- package/dist/esm/global-account/react/components/B3Provider/LocalSDKProvider.d.ts +3 -1
- package/dist/esm/global-account/react/components/B3Provider/LocalSDKProvider.js +3 -1
- package/dist/esm/global-account/react/components/B3Provider/useB3.d.ts +1 -12
- package/dist/esm/global-account/react/components/B3Provider/useB3Config.d.ts +1 -17
- package/dist/esm/global-account/react/components/B3Provider/useB3Config.js +1 -20
- package/dist/esm/global-account/react/components/ManageAccount/NFTContent.js +3 -3
- package/dist/esm/global-account/react/components/SignInWithB3/SignInWithB3Flow.js +13 -2
- package/dist/esm/global-account/react/components/SingleUserSearchSelector/SingleUserSearchSelector.d.ts +64 -0
- package/dist/esm/global-account/react/components/SingleUserSearchSelector/SingleUserSearchSelector.js +160 -0
- package/dist/esm/global-account/react/components/SingleUserSearchSelector/index.d.ts +2 -0
- package/dist/esm/global-account/react/components/SingleUserSearchSelector/index.js +1 -0
- package/dist/esm/global-account/react/components/WalletImage/WalletImage.d.ts +1 -1
- package/dist/esm/global-account/react/components/index.d.ts +2 -0
- package/dist/esm/global-account/react/components/index.js +2 -0
- package/dist/esm/global-account/react/hooks/index.d.ts +2 -1
- package/dist/esm/global-account/react/hooks/index.js +2 -1
- package/dist/esm/global-account/react/hooks/useAuthentication.js +5 -2
- package/dist/esm/global-account/react/hooks/useProfile.js +1 -20
- package/dist/esm/global-account/react/hooks/useSimBalance.d.ts +7 -0
- package/dist/esm/global-account/react/hooks/useSimBalance.js +42 -11
- package/dist/esm/global-account/react/hooks/useSimCollectibles.d.ts +45 -0
- package/dist/esm/global-account/react/hooks/useSimCollectibles.js +187 -0
- package/dist/esm/global-account/react/stores/index.d.ts +0 -1
- package/dist/esm/global-account/react/stores/index.js +0 -1
- package/dist/esm/global-account/react/stores/useModalStore.d.ts +63 -1
- package/dist/esm/global-account/react/stores/useModalStore.js +3 -0
- package/dist/esm/global-account/react/utils/profileApi.d.ts +13 -0
- package/dist/esm/global-account/react/utils/profileApi.js +25 -0
- package/dist/esm/global-account/react/utils/simdune.d.ts +7 -0
- package/dist/esm/global-account/react/utils/simdune.js +17 -0
- package/dist/styles/index.css +1 -1
- package/dist/types/anyspend/react/components/AnySpendDeposit.d.ts +15 -10
- package/dist/types/anyspend/react/components/common/GasIndicator.d.ts +1 -1
- package/dist/types/anyspend/react/components/common/TransferResultScreen.d.ts +22 -0
- package/dist/types/anyspend/react/hooks/index.d.ts +1 -0
- package/dist/types/anyspend/react/hooks/useWatchTransfer.d.ts +41 -0
- package/dist/types/anyspend/utils/address.d.ts +5 -0
- package/dist/types/global-account/react/components/B3Provider/B3ConfigProvider.d.ts +31 -0
- package/dist/types/global-account/react/components/B3Provider/B3Provider.d.ts +2 -1
- package/dist/types/global-account/react/components/B3Provider/LocalSDKProvider.d.ts +3 -1
- package/dist/types/global-account/react/components/B3Provider/useB3.d.ts +1 -12
- package/dist/types/global-account/react/components/B3Provider/useB3Config.d.ts +1 -17
- package/dist/types/global-account/react/components/SingleUserSearchSelector/SingleUserSearchSelector.d.ts +64 -0
- package/dist/types/global-account/react/components/SingleUserSearchSelector/index.d.ts +2 -0
- package/dist/types/global-account/react/components/WalletImage/WalletImage.d.ts +1 -1
- package/dist/types/global-account/react/components/index.d.ts +2 -0
- package/dist/types/global-account/react/hooks/index.d.ts +2 -1
- package/dist/types/global-account/react/hooks/useSimBalance.d.ts +7 -0
- package/dist/types/global-account/react/hooks/useSimCollectibles.d.ts +45 -0
- package/dist/types/global-account/react/stores/index.d.ts +0 -1
- package/dist/types/global-account/react/stores/useModalStore.d.ts +63 -1
- package/dist/types/global-account/react/utils/profileApi.d.ts +13 -0
- package/dist/types/global-account/react/utils/simdune.d.ts +7 -0
- package/package.json +6 -1
- package/src/anyspend/react/components/AnySpend.tsx +1 -0
- package/src/anyspend/react/components/AnySpendDeposit.tsx +60 -42
- package/src/anyspend/react/components/QRDeposit.tsx +57 -5
- package/src/anyspend/react/components/common/CryptoPaymentMethod.tsx +13 -3
- package/src/anyspend/react/components/common/GasIndicator.tsx +11 -30
- package/src/anyspend/react/components/common/TokenBalance.tsx +17 -5
- package/src/anyspend/react/components/common/TransferResultScreen.tsx +107 -0
- package/src/anyspend/react/hooks/index.ts +1 -0
- package/src/anyspend/react/hooks/useWatchTransfer.ts +114 -0
- package/src/anyspend/react/providers/AnyspendProvider.tsx +2 -5
- package/src/anyspend/utils/address.ts +13 -0
- package/src/global-account/react/components/AccountAssets/AccountAssets.tsx +25 -13
- package/src/global-account/react/components/B3DynamicModal.tsx +5 -17
- package/src/global-account/react/components/B3Provider/B3ConfigProvider.tsx +84 -0
- package/src/global-account/react/components/B3Provider/B3Provider.native.tsx +28 -36
- package/src/global-account/react/components/B3Provider/B3Provider.tsx +24 -39
- package/src/global-account/react/components/B3Provider/LocalSDKProvider.tsx +5 -0
- package/src/global-account/react/components/B3Provider/useB3Config.ts +1 -21
- package/src/global-account/react/components/ManageAccount/NFTContent.tsx +4 -4
- package/src/global-account/react/components/SignInWithB3/SignInWithB3Flow.tsx +13 -2
- package/src/global-account/react/components/SingleUserSearchSelector/README.md +266 -0
- package/src/global-account/react/components/SingleUserSearchSelector/SingleUserSearchSelector.tsx +330 -0
- package/src/global-account/react/components/SingleUserSearchSelector/index.ts +2 -0
- package/src/global-account/react/components/index.ts +7 -0
- package/src/global-account/react/hooks/index.ts +2 -1
- package/src/global-account/react/hooks/useAuthentication.ts +6 -2
- package/src/global-account/react/hooks/useProfile.ts +1 -32
- package/src/global-account/react/hooks/useSimBalance.ts +49 -12
- package/src/global-account/react/hooks/useSimCollectibles.ts +238 -0
- package/src/global-account/react/stores/index.ts +0 -1
- package/src/global-account/react/stores/useModalStore.ts +67 -1
- package/src/global-account/react/utils/profileApi.ts +38 -0
- package/src/global-account/react/utils/simdune.ts +20 -0
- package/dist/cjs/global-account/react/stores/configStore.d.ts +0 -24
- package/dist/cjs/global-account/react/stores/configStore.js +0 -30
- package/dist/esm/global-account/react/stores/configStore.d.ts +0 -24
- package/dist/esm/global-account/react/stores/configStore.js +0 -27
- package/dist/types/global-account/react/stores/configStore.d.ts +0 -24
- package/src/global-account/react/stores/configStore.ts +0 -51
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# SingleUserSearchSelector Component
|
|
2
|
+
|
|
3
|
+
A specialized React component for searching and selecting a single user profile. This component is designed specifically for single-user selection scenarios, not for multi-user or general profile browsing.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔍 Search by address or name
|
|
8
|
+
- 🎯 Filter results by profile type (b3-ens, thirdweb-\*, ens-data, global-account)
|
|
9
|
+
- 📋 Shows a single result in a dropdown
|
|
10
|
+
- ⚡ Debounced search (500ms)
|
|
11
|
+
- 🎨 Styled consistently with B3 design system
|
|
12
|
+
- ♿ Accessible with proper keyboard navigation
|
|
13
|
+
- 📱 Responsive design
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Basic Example
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { SingleUserSearchSelector } from "@b3dotfun/sdk/global-account/react";
|
|
21
|
+
|
|
22
|
+
function MyComponent() {
|
|
23
|
+
return (
|
|
24
|
+
<SingleUserSearchSelector
|
|
25
|
+
onSelectUser={profile => {
|
|
26
|
+
console.log("Selected user:", profile);
|
|
27
|
+
// Handle user selection
|
|
28
|
+
}}
|
|
29
|
+
placeholder="Search by address or name..."
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### With Profile Type Filter
|
|
36
|
+
|
|
37
|
+
Filter results to only show specific profile types:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
<SingleUserSearchSelector
|
|
41
|
+
onSelectUser={profile => {
|
|
42
|
+
console.log("Selected user:", profile);
|
|
43
|
+
}}
|
|
44
|
+
profileTypeFilter={["b3-ens", "global-account"]}
|
|
45
|
+
placeholder="Search B3 users..."
|
|
46
|
+
/>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Available Profile Types
|
|
50
|
+
|
|
51
|
+
- `b3-ens` - B3 ENS profiles
|
|
52
|
+
- `thirdweb-${string}` - Thirdweb profiles (e.g., thirdweb-email, thirdweb-wallet)
|
|
53
|
+
- `ens-data` - ENS data profiles
|
|
54
|
+
- `global-account` - Global account profiles
|
|
55
|
+
|
|
56
|
+
### Custom Styling
|
|
57
|
+
|
|
58
|
+
You can apply custom styles using the `className` prop or target specific elements using CSS class names:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
<SingleUserSearchSelector
|
|
62
|
+
onSelectUser={profile => {
|
|
63
|
+
console.log("Selected user:", profile);
|
|
64
|
+
}}
|
|
65
|
+
className="mx-auto max-w-md"
|
|
66
|
+
placeholder="Find a user..."
|
|
67
|
+
showClearButton={true}
|
|
68
|
+
minSearchLength={3}
|
|
69
|
+
/>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### CSS Class Names for Custom Styling
|
|
73
|
+
|
|
74
|
+
The component includes semantic class names for easy customization:
|
|
75
|
+
|
|
76
|
+
```css
|
|
77
|
+
/* Main container */
|
|
78
|
+
.single-user-search-selector {
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Input wrapper */
|
|
82
|
+
.single-user-search-input-wrapper {
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Search icon */
|
|
86
|
+
.single-user-search-icon {
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/* Input field */
|
|
90
|
+
.single-user-search-input {
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Clear button */
|
|
94
|
+
.single-user-search-clear-button {
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Loading state */
|
|
98
|
+
.single-user-search-loading {
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Error state */
|
|
102
|
+
.single-user-search-error {
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Dropdown container */
|
|
106
|
+
.single-user-search-dropdown {
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Result button */
|
|
110
|
+
.single-user-search-result-button {
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/* Avatar container */
|
|
114
|
+
.single-user-search-result-avatar {
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Profile info container */
|
|
118
|
+
.single-user-search-result-info {
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Display name */
|
|
122
|
+
.single-user-search-result-name {
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Address */
|
|
126
|
+
.single-user-search-result-address {
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Bio */
|
|
130
|
+
.single-user-search-result-bio {
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Badges container */
|
|
134
|
+
.single-user-search-result-badges {
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* Individual badge */
|
|
138
|
+
.single-user-search-result-badge {
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Example customization:
|
|
143
|
+
|
|
144
|
+
```css
|
|
145
|
+
/* Custom styling example */
|
|
146
|
+
.single-user-search-input {
|
|
147
|
+
border-radius: 12px;
|
|
148
|
+
font-size: 16px;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.single-user-search-dropdown {
|
|
152
|
+
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.single-user-search-result-name {
|
|
156
|
+
color: #0c68e9;
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Props
|
|
161
|
+
|
|
162
|
+
| Prop | Type | Default | Description |
|
|
163
|
+
| ------------------- | ------------------------------------ | ------------------------------------- | ----------------------------------------------------------------------------- |
|
|
164
|
+
| `onSelectUser` | `(profile: CombinedProfile) => void` | **Required** | Callback function when a user is selected. Returns the complete profile data. |
|
|
165
|
+
| `profileTypeFilter` | `ProfileTypeFilter[]` | `undefined` | Optional filter to only show profiles with specific types. |
|
|
166
|
+
| `placeholder` | `string` | `"Search by address, name, or ID..."` | Custom placeholder text for the search input. |
|
|
167
|
+
| `className` | `string` | `undefined` | Custom class name for the container. |
|
|
168
|
+
| `showClearButton` | `boolean` | `true` | Show clear button when there's input. |
|
|
169
|
+
| `minSearchLength` | `number` | `3` | Minimum characters before triggering search. |
|
|
170
|
+
|
|
171
|
+
## Profile Data Structure
|
|
172
|
+
|
|
173
|
+
The `onSelectUser` callback receives a `CombinedProfile` object:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
interface CombinedProfile {
|
|
177
|
+
name: string | null;
|
|
178
|
+
address: string | null;
|
|
179
|
+
avatar: string | undefined;
|
|
180
|
+
bio: string | null;
|
|
181
|
+
displayName: string | null;
|
|
182
|
+
profiles: Profile[];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
interface Profile {
|
|
186
|
+
type: string;
|
|
187
|
+
address?: string;
|
|
188
|
+
name?: string;
|
|
189
|
+
avatar?: string | null;
|
|
190
|
+
bio?: string | null;
|
|
191
|
+
displayName?: string | null;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Examples
|
|
196
|
+
|
|
197
|
+
### In a Modal
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
import { SingleUserSearchSelector } from "@b3dotfun/sdk/global-account/react";
|
|
201
|
+
import { Dialog } from "@radix-ui/react-dialog";
|
|
202
|
+
|
|
203
|
+
function UserSearchModal({ isOpen, onClose }) {
|
|
204
|
+
return (
|
|
205
|
+
<Dialog open={isOpen} onOpenChange={onClose}>
|
|
206
|
+
<DialogContent>
|
|
207
|
+
<h2>Find User</h2>
|
|
208
|
+
<SingleUserSearchSelector
|
|
209
|
+
onSelectUser={profile => {
|
|
210
|
+
console.log("Selected:", profile);
|
|
211
|
+
onClose();
|
|
212
|
+
}}
|
|
213
|
+
profileTypeFilter={["b3-ens", "global-account"]}
|
|
214
|
+
/>
|
|
215
|
+
</DialogContent>
|
|
216
|
+
</Dialog>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### With State Management
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
import { SingleUserSearchSelector } from "@b3dotfun/sdk/global-account/react";
|
|
225
|
+
import type { CombinedProfile } from "@b3dotfun/sdk/global-account/react";
|
|
226
|
+
import { useState } from "react";
|
|
227
|
+
|
|
228
|
+
function UserSelector() {
|
|
229
|
+
const [selectedUser, setSelectedUser] = useState<CombinedProfile | null>(null);
|
|
230
|
+
|
|
231
|
+
return (
|
|
232
|
+
<div>
|
|
233
|
+
<SingleUserSearchSelector onSelectUser={setSelectedUser} profileTypeFilter={["b3-ens"]} />
|
|
234
|
+
|
|
235
|
+
{selectedUser && (
|
|
236
|
+
<div className="mt-4">
|
|
237
|
+
<h3>Selected User</h3>
|
|
238
|
+
<p>Name: {selectedUser.displayName || selectedUser.name}</p>
|
|
239
|
+
<p>Address: {selectedUser.address}</p>
|
|
240
|
+
</div>
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Notes
|
|
248
|
+
|
|
249
|
+
- The component uses the B3 Profiles API (`https://profiles.b3.fun`)
|
|
250
|
+
- Search is debounced by 500ms to avoid excessive API calls
|
|
251
|
+
- Only one result is shown at a time (by design for single-user selection)
|
|
252
|
+
- The dropdown closes automatically when a user is selected
|
|
253
|
+
- Click outside the dropdown to close it
|
|
254
|
+
- The component handles loading and error states automatically
|
|
255
|
+
|
|
256
|
+
## Exporting
|
|
257
|
+
|
|
258
|
+
The component is exported from the SDK package:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
// From main export
|
|
262
|
+
import { SingleUserSearchSelector } from "@b3dotfun/sdk/global-account/react";
|
|
263
|
+
|
|
264
|
+
// From specific path
|
|
265
|
+
import { SingleUserSearchSelector } from "@b3dotfun/sdk/global-account/react/components/SingleUserSearchSelector";
|
|
266
|
+
```
|
package/src/global-account/react/components/SingleUserSearchSelector/SingleUserSearchSelector.tsx
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@b3dotfun/sdk/shared/utils";
|
|
4
|
+
import { Search, X } from "lucide-react";
|
|
5
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
6
|
+
import type { CombinedProfile, Profile } from "../../hooks/useProfile";
|
|
7
|
+
import { fetchProfile as fetchProfileApi } from "../../utils/profileApi";
|
|
8
|
+
import { IPFSMediaRenderer } from "../IPFSMediaRenderer/IPFSMediaRenderer";
|
|
9
|
+
import { Input } from "../ui/input";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Profile type filter options for SingleUserSearchSelector
|
|
13
|
+
* - b3-ens: B3 ENS profiles
|
|
14
|
+
* - thirdweb-${string}: Thirdweb profiles (e.g., thirdweb-email, thirdweb-wallet)
|
|
15
|
+
* - ens-data: ENS data profiles
|
|
16
|
+
* - global-account: Global account profiles
|
|
17
|
+
*/
|
|
18
|
+
export type ProfileTypeFilter = "b3-ens" | `thirdweb-${string}` | "ens-data" | "global-account";
|
|
19
|
+
|
|
20
|
+
export interface SingleUserSearchSelectorProps {
|
|
21
|
+
/**
|
|
22
|
+
* Callback function when a user is selected
|
|
23
|
+
* Returns the complete profile data including all profile types
|
|
24
|
+
*/
|
|
25
|
+
onSelectUser: (profile: CombinedProfile) => void;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Optional: Filter results to only show profiles that include specific types
|
|
29
|
+
* If provided, only profiles containing at least one of these types will be shown
|
|
30
|
+
*/
|
|
31
|
+
profileTypeFilter?: ProfileTypeFilter[];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Optional: Custom placeholder text for the search input
|
|
35
|
+
*/
|
|
36
|
+
placeholder?: string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Optional: Custom class name for the container
|
|
40
|
+
*/
|
|
41
|
+
className?: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Optional: Show clear button when there's input
|
|
45
|
+
*/
|
|
46
|
+
showClearButton?: boolean;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Optional: Show profile type badges in the result
|
|
50
|
+
*/
|
|
51
|
+
showBadges?: boolean;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Optional: Minimum characters before triggering search
|
|
55
|
+
*/
|
|
56
|
+
minSearchLength?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* SingleUserSearchSelector Component
|
|
61
|
+
*
|
|
62
|
+
* A specialized component for searching and selecting a single user profile.
|
|
63
|
+
* This component is designed specifically for single-user selection scenarios,
|
|
64
|
+
* not for multi-user or general profile browsing.
|
|
65
|
+
*
|
|
66
|
+
* Features:
|
|
67
|
+
* - Search by address or name
|
|
68
|
+
* - Filter results by profile type (b3-ens, thirdweb-*, ens-data, global-account)
|
|
69
|
+
* - Shows a single result in a dropdown
|
|
70
|
+
* - Callback with complete profile data on selection
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```tsx
|
|
74
|
+
* <SingleUserSearchSelector
|
|
75
|
+
* onSelectUser={(profile) => console.log('Selected:', profile)}
|
|
76
|
+
* profileTypeFilter={['b3-ens', 'global-account']}
|
|
77
|
+
* placeholder="Search by address or name..."
|
|
78
|
+
* />
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export function SingleUserSearchSelector({
|
|
82
|
+
onSelectUser,
|
|
83
|
+
profileTypeFilter,
|
|
84
|
+
placeholder = "Search by address, name, or ID...",
|
|
85
|
+
className,
|
|
86
|
+
showClearButton = true,
|
|
87
|
+
showBadges = false,
|
|
88
|
+
minSearchLength = 3,
|
|
89
|
+
}: SingleUserSearchSelectorProps) {
|
|
90
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
91
|
+
const [isSearching, setIsSearching] = useState(false);
|
|
92
|
+
const [searchResult, setSearchResult] = useState<CombinedProfile | null>(null);
|
|
93
|
+
const [error, setError] = useState<string | null>(null);
|
|
94
|
+
const [showDropdown, setShowDropdown] = useState(false);
|
|
95
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
96
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
97
|
+
const searchTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
98
|
+
|
|
99
|
+
// Fetch profile from API using shared utility
|
|
100
|
+
const fetchProfile = useCallback(
|
|
101
|
+
async (query: string): Promise<CombinedProfile | null> => {
|
|
102
|
+
if (!query || query.length < minSearchLength) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
// Determine if query is an address (starts with 0x) or a name
|
|
108
|
+
const params = query.startsWith("0x") ? { address: query } : { name: query };
|
|
109
|
+
|
|
110
|
+
const profile = await fetchProfileApi(params);
|
|
111
|
+
return profile;
|
|
112
|
+
} catch (err) {
|
|
113
|
+
// Return null for 404s (user not found)
|
|
114
|
+
if (err instanceof Error && err.message.includes("404")) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
console.error("Error fetching profile:", err);
|
|
118
|
+
throw err;
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
[minSearchLength],
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Filter profile by type
|
|
125
|
+
const filterProfileByType = useCallback(
|
|
126
|
+
(profile: CombinedProfile): boolean => {
|
|
127
|
+
if (!profileTypeFilter || profileTypeFilter.length === 0) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check if any of the profile's types match the filter
|
|
132
|
+
return profile.profiles.some(p => {
|
|
133
|
+
return profileTypeFilter.some(filter => {
|
|
134
|
+
// Handle thirdweb-* wildcard matching
|
|
135
|
+
if (filter.startsWith("thirdweb-")) {
|
|
136
|
+
return p.type.startsWith("thirdweb-");
|
|
137
|
+
}
|
|
138
|
+
return p.type === filter;
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
[profileTypeFilter],
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Handle search with debouncing
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (searchTimeoutRef.current) {
|
|
148
|
+
clearTimeout(searchTimeoutRef.current);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!searchQuery || searchQuery.length < minSearchLength) {
|
|
152
|
+
setSearchResult(null);
|
|
153
|
+
setShowDropdown(false);
|
|
154
|
+
setError(null);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
setIsSearching(true);
|
|
159
|
+
setError(null);
|
|
160
|
+
|
|
161
|
+
searchTimeoutRef.current = setTimeout(async () => {
|
|
162
|
+
try {
|
|
163
|
+
const result = await fetchProfile(searchQuery);
|
|
164
|
+
|
|
165
|
+
if (result) {
|
|
166
|
+
// Apply profile type filter
|
|
167
|
+
if (filterProfileByType(result)) {
|
|
168
|
+
setSearchResult(result);
|
|
169
|
+
setShowDropdown(true);
|
|
170
|
+
setError(null); // Clear any previous errors
|
|
171
|
+
} else {
|
|
172
|
+
setSearchResult(null);
|
|
173
|
+
setShowDropdown(false);
|
|
174
|
+
setError("No matching profile types found");
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
setSearchResult(null);
|
|
178
|
+
setShowDropdown(false);
|
|
179
|
+
setError("No user found");
|
|
180
|
+
}
|
|
181
|
+
} catch (err) {
|
|
182
|
+
setError(err instanceof Error ? err.message : "Failed to search");
|
|
183
|
+
setSearchResult(null);
|
|
184
|
+
setShowDropdown(false);
|
|
185
|
+
} finally {
|
|
186
|
+
setIsSearching(false);
|
|
187
|
+
}
|
|
188
|
+
}, 500); // 500ms debounce
|
|
189
|
+
|
|
190
|
+
return () => {
|
|
191
|
+
if (searchTimeoutRef.current) {
|
|
192
|
+
clearTimeout(searchTimeoutRef.current);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}, [searchQuery, fetchProfile, filterProfileByType, minSearchLength]);
|
|
196
|
+
|
|
197
|
+
// Handle click outside to close dropdown
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
200
|
+
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
|
201
|
+
setShowDropdown(false);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
206
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
207
|
+
}, []);
|
|
208
|
+
|
|
209
|
+
// Handle user selection
|
|
210
|
+
const handleSelectUser = useCallback(
|
|
211
|
+
(profile: CombinedProfile) => {
|
|
212
|
+
onSelectUser(profile);
|
|
213
|
+
setShowDropdown(false);
|
|
214
|
+
setSearchQuery("");
|
|
215
|
+
setSearchResult(null);
|
|
216
|
+
},
|
|
217
|
+
[onSelectUser],
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// Handle clear search
|
|
221
|
+
const handleClear = useCallback(() => {
|
|
222
|
+
setSearchQuery("");
|
|
223
|
+
setSearchResult(null);
|
|
224
|
+
setShowDropdown(false);
|
|
225
|
+
setError(null);
|
|
226
|
+
inputRef.current?.focus();
|
|
227
|
+
}, []);
|
|
228
|
+
|
|
229
|
+
// Get display name for profile
|
|
230
|
+
const getDisplayName = (profile: CombinedProfile): string => {
|
|
231
|
+
return profile.displayName || profile.name || profile.address || "Unknown";
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Get profile type badges
|
|
235
|
+
const getProfileTypeBadges = (profiles: Profile[]): string[] => {
|
|
236
|
+
return profiles.map(p => p.type);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
return (
|
|
240
|
+
<div className={cn("single-user-search-selector b3-root relative w-full", className)} ref={dropdownRef}>
|
|
241
|
+
{/* Search Input */}
|
|
242
|
+
<div className="single-user-search-input-wrapper relative flex items-center">
|
|
243
|
+
<Search
|
|
244
|
+
className="single-user-search-icon pointer-events-none absolute text-gray-400"
|
|
245
|
+
style={{ left: "12px", width: "16px", height: "16px" }}
|
|
246
|
+
/>
|
|
247
|
+
<Input
|
|
248
|
+
ref={inputRef}
|
|
249
|
+
type="text"
|
|
250
|
+
value={searchQuery}
|
|
251
|
+
onChange={e => setSearchQuery(e.target.value)}
|
|
252
|
+
placeholder={placeholder}
|
|
253
|
+
className={cn("single-user-search-input w-full border-gray-300 focus:border-blue-500 focus:ring-blue-500")}
|
|
254
|
+
style={{ paddingLeft: "44px", paddingRight: "44px" }}
|
|
255
|
+
/>
|
|
256
|
+
{showClearButton && searchQuery && (
|
|
257
|
+
<button
|
|
258
|
+
onClick={handleClear}
|
|
259
|
+
className="single-user-search-clear-button absolute text-gray-400 transition-colors hover:text-gray-600"
|
|
260
|
+
style={{ right: "12px" }}
|
|
261
|
+
type="button"
|
|
262
|
+
>
|
|
263
|
+
<X style={{ width: "16px", height: "16px" }} />
|
|
264
|
+
</button>
|
|
265
|
+
)}
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
{/* Loading State */}
|
|
269
|
+
{isSearching && <div className="single-user-search-loading mt-2 text-sm text-gray-500">Searching...</div>}
|
|
270
|
+
|
|
271
|
+
{/* Error State */}
|
|
272
|
+
{error && !isSearching && <div className="single-user-search-error mt-2 text-sm text-red-500">{error}</div>}
|
|
273
|
+
|
|
274
|
+
{/* Dropdown with Search Result */}
|
|
275
|
+
{showDropdown && searchResult && !isSearching && (
|
|
276
|
+
<div className="single-user-search-dropdown absolute z-50 mt-2 w-full rounded-lg border border-gray-200 bg-white shadow-lg">
|
|
277
|
+
<button
|
|
278
|
+
onClick={() => handleSelectUser(searchResult)}
|
|
279
|
+
className="single-user-search-result-button w-full px-4 py-3 text-left transition-colors hover:bg-gray-50"
|
|
280
|
+
type="button"
|
|
281
|
+
>
|
|
282
|
+
<div className="flex items-start gap-3">
|
|
283
|
+
{/* Avatar */}
|
|
284
|
+
<div className="single-user-search-result-avatar h-11 w-11 shrink-0">
|
|
285
|
+
<IPFSMediaRenderer
|
|
286
|
+
src={searchResult.avatar}
|
|
287
|
+
alt={getDisplayName(searchResult)}
|
|
288
|
+
className="h-full w-full rounded-full object-cover"
|
|
289
|
+
/>
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
{/* Profile Info */}
|
|
293
|
+
<div className="single-user-search-result-info min-w-0 flex-1 pt-0.5">
|
|
294
|
+
<div className="single-user-search-result-name text-base font-semibold text-gray-900">
|
|
295
|
+
{getDisplayName(searchResult)}
|
|
296
|
+
</div>
|
|
297
|
+
|
|
298
|
+
{searchResult.address && (
|
|
299
|
+
<div className="single-user-search-result-address mt-1 font-mono text-xs text-gray-500">
|
|
300
|
+
{searchResult.address.slice(0, 6)}...{searchResult.address.slice(-4)}
|
|
301
|
+
</div>
|
|
302
|
+
)}
|
|
303
|
+
|
|
304
|
+
{searchResult.bio && (
|
|
305
|
+
<div className="single-user-search-result-bio mt-1.5 line-clamp-2 text-sm text-gray-600">
|
|
306
|
+
{searchResult.bio}
|
|
307
|
+
</div>
|
|
308
|
+
)}
|
|
309
|
+
|
|
310
|
+
{/* Profile Type Badges */}
|
|
311
|
+
{showBadges && (
|
|
312
|
+
<div className="single-user-search-result-badges mt-2 flex flex-wrap gap-1.5">
|
|
313
|
+
{getProfileTypeBadges(searchResult.profiles).map((type, index) => (
|
|
314
|
+
<span
|
|
315
|
+
key={`${type}-${index}`}
|
|
316
|
+
className="single-user-search-result-badge inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800"
|
|
317
|
+
>
|
|
318
|
+
{type}
|
|
319
|
+
</span>
|
|
320
|
+
))}
|
|
321
|
+
</div>
|
|
322
|
+
)}
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
</button>
|
|
326
|
+
</div>
|
|
327
|
+
)}
|
|
328
|
+
</div>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
@@ -49,6 +49,13 @@ export { SendETHButton } from "./SendETHButton/SendETHButton";
|
|
|
49
49
|
// SendERC20Button Components
|
|
50
50
|
export { SendERC20Button } from "./SendERC20Button/SendERC20Button";
|
|
51
51
|
|
|
52
|
+
// SingleUserSearchSelector Components
|
|
53
|
+
export { SingleUserSearchSelector } from "./SingleUserSearchSelector/SingleUserSearchSelector";
|
|
54
|
+
export type {
|
|
55
|
+
ProfileTypeFilter,
|
|
56
|
+
SingleUserSearchSelectorProps,
|
|
57
|
+
} from "./SingleUserSearchSelector/SingleUserSearchSelector";
|
|
58
|
+
|
|
52
59
|
// Custom Components
|
|
53
60
|
export { Button as CustomButton, buttonVariants as customButtonVariants } from "./custom/Button";
|
|
54
61
|
export { ClientOnly } from "./custom/ClientOnly";
|
|
@@ -39,7 +39,8 @@ export { useQueryBSMNT } from "./useQueryBSMNT";
|
|
|
39
39
|
export { useRemoveSessionKey } from "./useRemoveSessionKey";
|
|
40
40
|
export { useRouter } from "./useRouter";
|
|
41
41
|
export { useSearchParamsSSR } from "./useSearchParamsSSR";
|
|
42
|
-
export { useSimBalance, useSimSvmBalance } from "./useSimBalance";
|
|
42
|
+
export { useSimBalance, useSimSvmBalance, useSimTokenBalance } from "./useSimBalance";
|
|
43
|
+
export { useSimCollectibles } from "./useSimCollectibles";
|
|
43
44
|
export { useSiwe } from "./useSiwe";
|
|
44
45
|
export { useTokenBalance } from "./useTokenBalance";
|
|
45
46
|
export { useTokenBalanceDirect } from "./useTokenBalanceDirect";
|
|
@@ -25,7 +25,7 @@ import { useUserQuery } from "./useUserQuery";
|
|
|
25
25
|
const debug = debugB3React("useAuthentication");
|
|
26
26
|
|
|
27
27
|
export function useAuthentication(partnerId: string) {
|
|
28
|
-
const { onConnectCallback } = useContext(LocalSDKContext);
|
|
28
|
+
const { onConnectCallback, onLogoutCallback } = useContext(LocalSDKContext);
|
|
29
29
|
const { disconnect } = useDisconnect();
|
|
30
30
|
const wallets = useConnectedWallets();
|
|
31
31
|
const activeWallet = useActiveWallet();
|
|
@@ -181,8 +181,12 @@ export function useAuthentication(partnerId: string) {
|
|
|
181
181
|
setIsConnected(false);
|
|
182
182
|
setUser();
|
|
183
183
|
callback?.();
|
|
184
|
+
|
|
185
|
+
if (onLogoutCallback) {
|
|
186
|
+
await onLogoutCallback();
|
|
187
|
+
}
|
|
184
188
|
},
|
|
185
|
-
[activeWallet, disconnect, wallets, setIsAuthenticated, setUser, setIsConnected],
|
|
189
|
+
[activeWallet, disconnect, wallets, setIsAuthenticated, setUser, setIsConnected, onLogoutCallback],
|
|
186
190
|
);
|
|
187
191
|
|
|
188
192
|
const onConnect = useCallback(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
import { fetchProfile, PROFILES_API_URL } from "../utils/profileApi";
|
|
2
3
|
|
|
3
4
|
// TypeScript interface for profile data
|
|
4
5
|
export interface Profile {
|
|
@@ -37,38 +38,6 @@ export interface DisplayNameRequestBody {
|
|
|
37
38
|
timestamp: number;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
const PROFILES_API_URL = "https://profiles.b3.fun";
|
|
41
|
-
|
|
42
|
-
async function fetchProfile({
|
|
43
|
-
address,
|
|
44
|
-
name,
|
|
45
|
-
b3GlobalId,
|
|
46
|
-
fresh = false,
|
|
47
|
-
}: {
|
|
48
|
-
address?: string;
|
|
49
|
-
name?: string;
|
|
50
|
-
b3GlobalId?: string;
|
|
51
|
-
fresh?: boolean;
|
|
52
|
-
}): Promise<CombinedProfile> {
|
|
53
|
-
if (!address && !name && !b3GlobalId) {
|
|
54
|
-
throw new Error("Either address or name or b3GlobalId must be provided");
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const params = new URLSearchParams();
|
|
58
|
-
if (address) params.append("address", address);
|
|
59
|
-
if (name) params.append("name", name);
|
|
60
|
-
if (b3GlobalId) params.append("b3GlobalId", b3GlobalId);
|
|
61
|
-
if (fresh) params.append("fresh", "true");
|
|
62
|
-
|
|
63
|
-
const response = await fetch(`${PROFILES_API_URL}?${params.toString()}`);
|
|
64
|
-
|
|
65
|
-
if (!response.ok) {
|
|
66
|
-
throw new Error(`Failed to fetch profile: ${response.statusText}`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return response.json();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
41
|
async function setProfilePreference({
|
|
73
42
|
key,
|
|
74
43
|
preferredType,
|