@coinlist-co/react 0.4.0 → 0.5.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 (61) hide show
  1. package/dist/chunk-CRACFEJ4.js +17 -0
  2. package/dist/chunk-CRACFEJ4.js.map +1 -0
  3. package/dist/{chunk-NC45IO63.js → chunk-N3WBC2VS.js} +20 -325
  4. package/dist/chunk-N3WBC2VS.js.map +1 -0
  5. package/dist/chunk-V6WO67RO.js +311 -0
  6. package/dist/chunk-V6WO67RO.js.map +1 -0
  7. package/dist/client/index.cjs +1013 -220
  8. package/dist/client/index.cjs.map +1 -1
  9. package/dist/client/index.d.cts +526 -9
  10. package/dist/client/index.d.ts +526 -9
  11. package/dist/client/index.js +1464 -94
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/{participation-DMMEDxON.d.cts → requirement-BEO42QOr.d.cts} +204 -2
  14. package/dist/{participation-E-bT-GXJ.d.ts → requirement-BEO42QOr.d.ts} +204 -2
  15. package/dist/server/index.cjs +50 -100
  16. package/dist/server/index.cjs.map +1 -1
  17. package/dist/server/index.d.cts +67 -24
  18. package/dist/server/index.d.ts +67 -24
  19. package/dist/server/index.js +54 -87
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/shared/index.cjs +375 -0
  22. package/dist/shared/index.cjs.map +1 -0
  23. package/dist/shared/index.d.cts +35 -0
  24. package/dist/shared/index.d.ts +35 -0
  25. package/dist/{client/core → shared}/index.js +35 -16
  26. package/dist/shared/index.js.map +1 -0
  27. package/package.json +7 -22
  28. package/dist/RequirementItem-B-MvcxSr.d.cts +0 -37
  29. package/dist/RequirementItem-DcYot2uC.d.ts +0 -37
  30. package/dist/chunk-EHZ6PIGN.js +0 -414
  31. package/dist/chunk-EHZ6PIGN.js.map +0 -1
  32. package/dist/chunk-NC45IO63.js.map +0 -1
  33. package/dist/chunk-RIFATQB5.js +0 -257
  34. package/dist/chunk-RIFATQB5.js.map +0 -1
  35. package/dist/chunk-UGJUTRXC.js +0 -304
  36. package/dist/chunk-UGJUTRXC.js.map +0 -1
  37. package/dist/chunk-YJQMJFA7.js +0 -16
  38. package/dist/chunk-YJQMJFA7.js.map +0 -1
  39. package/dist/client/components/index.cjs +0 -1108
  40. package/dist/client/components/index.cjs.map +0 -1
  41. package/dist/client/components/index.d.cts +0 -139
  42. package/dist/client/components/index.d.ts +0 -139
  43. package/dist/client/components/index.js +0 -558
  44. package/dist/client/components/index.js.map +0 -1
  45. package/dist/client/core/index.cjs +0 -1047
  46. package/dist/client/core/index.cjs.map +0 -1
  47. package/dist/client/core/index.d.cts +0 -23
  48. package/dist/client/core/index.d.ts +0 -23
  49. package/dist/client/core/index.js.map +0 -1
  50. package/dist/client/hooks/index.cjs +0 -307
  51. package/dist/client/hooks/index.cjs.map +0 -1
  52. package/dist/client/hooks/index.d.cts +0 -126
  53. package/dist/client/hooks/index.d.ts +0 -126
  54. package/dist/client/hooks/index.js +0 -19
  55. package/dist/client/hooks/index.js.map +0 -1
  56. package/dist/coinlist-client-BSxpqylo.d.ts +0 -158
  57. package/dist/coinlist-client-Cah5c_aF.d.cts +0 -158
  58. package/dist/requirement-XWT0T_zO.d.cts +0 -205
  59. package/dist/requirement-XWT0T_zO.d.ts +0 -205
  60. package/dist/useCoinListRequirements-B5-ZPOMz.d.ts +0 -80
  61. package/dist/useCoinListRequirements-Cuc32Tqw.d.cts +0 -80
@@ -1,107 +1,1477 @@
1
1
  import {
2
- ChecklistStatus,
3
- RequirementStatus,
4
- RequirementVariant
5
- } from "../chunk-EHZ6PIGN.js";
6
- import {
7
- CoinListProvider,
8
- useCoinList,
9
- useCoinListOfferDetails,
10
- useCoinListOffers,
11
- useCoinListRequirements,
12
- useCompleteCoinListOAuth,
13
- useParticipations
14
- } from "../chunk-UGJUTRXC.js";
15
- import {
16
- User,
17
- UserEmail,
18
- UserId
19
- } from "../chunk-YJQMJFA7.js";
2
+ API_VERSION,
3
+ Attributes,
4
+ AuthenticatedApiClient,
5
+ COINLIST_BASE_URL,
6
+ HEADER_USER_AGENT,
7
+ OAUTH_PAGE_PATH,
8
+ PUBLIC_API_BASE_URL,
9
+ SUPPORT_NEW_TICKET_URL,
10
+ VERIFY_IDENTITY_ACCREDITATION_PATH,
11
+ VERIFY_IDENTITY_PATH,
12
+ VERIFY_IDENTITY_PROOF_OF_ADDRESS_PATH,
13
+ VERIFY_IDENTITY_SOURCE_OF_FUNDS_PATH,
14
+ VERIFY_IDENTITY_VERIFIED_PATH,
15
+ WALLET_PATH,
16
+ createParticipation,
17
+ fetchOfferDetails,
18
+ fetchOfferRequirements,
19
+ fetchOffers,
20
+ fetchOffersPage,
21
+ fetchParticipation,
22
+ fetchParticipations,
23
+ fetchParticipationsPage,
24
+ fetchRequirementStatuses
25
+ } from "../chunk-N3WBC2VS.js";
20
26
  import {
21
- createCoinListClient
22
- } from "../chunk-RIFATQB5.js";
27
+ AuthorizationCode,
28
+ CodeVerifier
29
+ } from "../chunk-CRACFEJ4.js";
23
30
  import {
24
- OAuthRefreshToken,
25
- OAuthSession
26
- } from "../chunk-5E3P7AMH.js";
31
+ NotAuthenticatedError,
32
+ arrayBufferToBase64Url,
33
+ generateSecureRandomBase64Url,
34
+ getUUIDv4,
35
+ sha256
36
+ } from "../chunk-V6WO67RO.js";
37
+
38
+ // src/client/index.ts
39
+ import "./styles.css";
40
+
41
+ // src/client/CoinListProvider.tsx
27
42
  import {
28
- AuthorizationCode,
29
- Blockchain,
30
- ClientId,
31
- ClientSecret,
32
- CodeChallenge,
33
- CodeVerifier,
34
- CreateParticipationParams,
35
- Cursor,
36
- FaqItem,
37
- Link,
38
- Milestone,
39
- Offer,
40
- OfferDetail,
41
- OfferId,
42
- OfferOption,
43
- OfferOptionId,
44
- OfferOptionSlug,
45
- OfferSlug,
46
- PaginatedResponse,
47
- PaginationParams,
48
- Participation,
49
- ParticipationId,
50
- ParticipationsPaginationParams,
51
- RedirectUri,
52
- Requirement,
53
- RequirementId,
54
- RequirementStatusInfo,
55
- TermItem,
56
- WalletAddress,
57
- fetchAllPages
58
- } from "../chunk-NC45IO63.js";
43
+ createContext,
44
+ useEffect,
45
+ useMemo,
46
+ useState
47
+ } from "react";
48
+
49
+ // src/client/core/api/middleware/device-id.ts
50
+ var DEVICE_ID_KEY = "coinlist.device_id";
51
+ function getDeviceId() {
52
+ let deviceId = localStorage.getItem(DEVICE_ID_KEY);
53
+ if (!deviceId) {
54
+ deviceId = getUUIDv4();
55
+ localStorage.setItem(DEVICE_ID_KEY, deviceId);
56
+ }
57
+ return deviceId;
58
+ }
59
+
60
+ // src/client/core/api/middleware/user-agent.ts
61
+ var userAgentMiddleware = async (request) => {
62
+ if (!Attributes.needUserAgent(request.attributes)) {
63
+ return request;
64
+ }
65
+ const userAgent = generateUserAgent();
66
+ return {
67
+ ...request,
68
+ headers: {
69
+ ...request.headers ?? {},
70
+ [HEADER_USER_AGENT]: userAgent
71
+ }
72
+ };
73
+ };
74
+ function generateUserAgent() {
75
+ const appInfo = `CoinList/1.0`;
76
+ const deviceInfo = "Web Browser";
77
+ const devicePlatform = "DevicePlatform web";
78
+ const deviceAppId = "DeviceAppID co.coinlist.sdk";
79
+ const deviceId = getDeviceId();
80
+ const finalDeviceId = `DeviceID ${deviceId}`;
81
+ return `${appInfo} (${deviceInfo}; ${devicePlatform}; ${deviceAppId}; ${finalDeviceId})`;
82
+ }
83
+
84
+ // src/client/core/api/api-client.ts
85
+ var ApiClient = class {
86
+ constructor(config, fetchAccessToken) {
87
+ this.client = new AuthenticatedApiClient(config, fetchAccessToken, [
88
+ userAgentMiddleware
89
+ ]);
90
+ }
91
+ async send(request) {
92
+ return this.client.send(request);
93
+ }
94
+ };
95
+
96
+ // src/client/core/coinlist-client.ts
97
+ var OAUTH_STATE_KEY = "coinlist.oauth_state";
98
+ var OAUTH_CODE_VERIFIER_KEY = "coinlist.oauth_code_verifier";
99
+ var CoinListClientImpl = class {
100
+ constructor(_config) {
101
+ this._config = _config;
102
+ /** Cached access token: undefined = loading, null = no token, AccessToken = logged in */
103
+ this._accessToken = void 0;
104
+ this.api = new ApiClient(
105
+ {
106
+ baseUrl: _config.baseUrl || PUBLIC_API_BASE_URL,
107
+ xApiVersion: API_VERSION
108
+ },
109
+ this.fetchAccessToken.bind(this)
110
+ );
111
+ }
112
+ async init() {
113
+ await this.fetchAccessToken(true);
114
+ return this.getAuthState();
115
+ }
116
+ async fetchAccessToken(refresh) {
117
+ if (!refresh && this._accessToken !== void 0) return this._accessToken;
118
+ try {
119
+ const accessToken = await this._config.getAccessToken();
120
+ this._accessToken = accessToken;
121
+ return accessToken;
122
+ } catch (_) {
123
+ this._accessToken = null;
124
+ return null;
125
+ }
126
+ }
127
+ getAuthState() {
128
+ if (this._accessToken === void 0) {
129
+ return "unknown";
130
+ }
131
+ return this._accessToken !== null ? "logged-in" : "logged-out";
132
+ }
133
+ async startOAuth() {
134
+ const state = generateSecureRandomBase64Url(32);
135
+ const codeVerifier = generateSecureRandomBase64Url(32);
136
+ const codeChallengeRaw = await sha256(codeVerifier);
137
+ const codeChallenge = arrayBufferToBase64Url(codeChallengeRaw, false);
138
+ sessionStorage.setItem(OAUTH_STATE_KEY, state);
139
+ sessionStorage.setItem(OAUTH_CODE_VERIFIER_KEY, codeVerifier);
140
+ const params = new URLSearchParams({
141
+ client_id: this._config.clientId,
142
+ response_type: "code",
143
+ redirect_uri: this._config.redirectUri,
144
+ code_challenge: codeChallenge,
145
+ code_challenge_method: "S256",
146
+ state
147
+ });
148
+ const baseUrl = this._config.coinlistBaseUrl ?? COINLIST_BASE_URL;
149
+ const url = `${baseUrl}${OAUTH_PAGE_PATH}?${params.toString()}`;
150
+ window.location.href = url;
151
+ }
152
+ completeOAuth() {
153
+ const params = new URLSearchParams(window.location.search);
154
+ const coinlistError = params.get("error");
155
+ if (coinlistError) {
156
+ return { type: "error", reason: "user_canceled" };
157
+ }
158
+ const stateParam = params.get("state");
159
+ if (!stateParam) {
160
+ return { type: "error", reason: "missing_state" };
161
+ }
162
+ const storedState = sessionStorage.getItem(OAUTH_STATE_KEY);
163
+ if (storedState === null || storedState !== stateParam) {
164
+ return { type: "error", reason: "invalid_state" };
165
+ }
166
+ const codeParam = params.get("code");
167
+ if (!codeParam) {
168
+ return { type: "error", reason: "missing_code" };
169
+ }
170
+ const codeVerifierValue = sessionStorage.getItem(OAUTH_CODE_VERIFIER_KEY);
171
+ if (!codeVerifierValue) {
172
+ return { type: "error", reason: "missing_code_verifier" };
173
+ }
174
+ return {
175
+ type: "ok",
176
+ code: AuthorizationCode(codeParam),
177
+ codeVerifier: CodeVerifier(codeVerifierValue)
178
+ };
179
+ }
180
+ logout() {
181
+ this._accessToken = null;
182
+ }
183
+ async fetchOffers() {
184
+ this.ensureAuthenticated();
185
+ return fetchOffers(this.api);
186
+ }
187
+ async fetchOffersPage(params) {
188
+ this.ensureAuthenticated();
189
+ return fetchOffersPage(this.api, params);
190
+ }
191
+ async fetchOfferDetails(id) {
192
+ this.ensureAuthenticated();
193
+ return fetchOfferDetails(this.api, id);
194
+ }
195
+ async fetchParticipations(offerId) {
196
+ this.ensureAuthenticated();
197
+ return fetchParticipations(this.api, offerId);
198
+ }
199
+ async fetchParticipationsPage(params) {
200
+ this.ensureAuthenticated();
201
+ return fetchParticipationsPage(this.api, params);
202
+ }
203
+ async fetchParticipation(id) {
204
+ this.ensureAuthenticated();
205
+ return fetchParticipation(this.api, id);
206
+ }
207
+ async createParticipation(params) {
208
+ this.ensureAuthenticated();
209
+ return createParticipation(this.api, params);
210
+ }
211
+ async fetchOfferRequirements(offerId) {
212
+ this.ensureAuthenticated();
213
+ return fetchOfferRequirements(this.api, offerId);
214
+ }
215
+ async fetchRequirementStatuses(offerId) {
216
+ this.ensureAuthenticated();
217
+ return fetchRequirementStatuses(this.api, offerId);
218
+ }
219
+ handleRequirement(requirement) {
220
+ const baseUrl = this._config.coinlistBaseUrl ?? COINLIST_BASE_URL;
221
+ let path;
222
+ switch (requirement.type) {
223
+ case "kyc_approved":
224
+ path = VERIFY_IDENTITY_PATH;
225
+ break;
226
+ case "identity_verified":
227
+ path = VERIFY_IDENTITY_VERIFIED_PATH;
228
+ break;
229
+ case "proof_of_address":
230
+ path = VERIFY_IDENTITY_PROOF_OF_ADDRESS_PATH;
231
+ break;
232
+ case "source_of_funds":
233
+ path = VERIFY_IDENTITY_SOURCE_OF_FUNDS_PATH;
234
+ break;
235
+ case "accreditation":
236
+ path = VERIFY_IDENTITY_ACCREDITATION_PATH;
237
+ break;
238
+ case "external_wallet":
239
+ case "whitelisted_wallet":
240
+ path = WALLET_PATH;
241
+ break;
242
+ case "jurisdiction":
243
+ path = null;
244
+ break;
245
+ default: {
246
+ const _exhaustive = requirement.type;
247
+ return;
248
+ }
249
+ }
250
+ if (path === null) return;
251
+ openInNewTab(new URL(path, baseUrl).toString());
252
+ }
253
+ contactSupport() {
254
+ openInNewTab(SUPPORT_NEW_TICKET_URL);
255
+ }
256
+ ensureAuthenticated() {
257
+ if (this.getAuthState() !== "logged-in") {
258
+ throw new NotAuthenticatedError();
259
+ }
260
+ }
261
+ };
262
+ function openInNewTab(url) {
263
+ window.open(url, "_blank", "noopener,noreferrer");
264
+ }
265
+ function createCoinListClient(config) {
266
+ return new CoinListClientImpl(config);
267
+ }
268
+
269
+ // src/client/CoinListProvider.tsx
270
+ import { jsx } from "react/jsx-runtime";
271
+ var CoinListContext = createContext(null);
272
+ function CoinListProvider({ config, children }) {
273
+ const [isReady, setIsReady] = useState(false);
274
+ const coinlist = useMemo(() => createCoinListClient(config), [config]);
275
+ useEffect(() => {
276
+ let isCancelled = false;
277
+ if (coinlist.getAuthState() === "unknown") {
278
+ coinlist.init().then(() => {
279
+ if (!isCancelled) {
280
+ setIsReady(true);
281
+ }
282
+ }).catch(() => {
283
+ if (!isCancelled) {
284
+ setIsReady(true);
285
+ }
286
+ });
287
+ } else {
288
+ setIsReady(true);
289
+ }
290
+ return () => {
291
+ isCancelled = true;
292
+ };
293
+ }, [coinlist]);
294
+ const value = useMemo(() => ({ coinlist, isReady }), [coinlist, isReady]);
295
+ return /* @__PURE__ */ jsx(CoinListContext.Provider, { value, children });
296
+ }
297
+
298
+ // src/client/components/design-system/AlertBanner.tsx
299
+ import { CircleCheck, Info } from "lucide-react";
300
+
301
+ // src/client/components/design-system/tokens/typography.ts
302
+ var typeClasses = {
303
+ hero: "text-[64px] leading-[110%] font-semibold",
304
+ display: "text-[48px] leading-[110%] font-semibold",
305
+ h1: "text-[36px] leading-[120%] font-semibold",
306
+ h2: "text-[28px] leading-[125%] font-semibold",
307
+ h3: "text-[22px] leading-[130%] font-semibold",
308
+ h4: "text-[18px] leading-[135%] font-medium",
309
+ body: "text-[16px] leading-[150%] font-normal",
310
+ subhead: "text-[14px] leading-[150%] font-normal",
311
+ caption: "text-[12px] leading-[140%] font-normal",
312
+ label: "text-[14px] leading-[140%] font-medium",
313
+ data: "text-[14px] leading-[140%] font-normal"
314
+ };
315
+
316
+ // src/client/components/design-system/utils.ts
317
+ import { clsx } from "clsx";
318
+ function cn(...inputs) {
319
+ return clsx(inputs);
320
+ }
321
+
322
+ // src/client/components/design-system/AlertBanner.tsx
323
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
324
+ var variantConfig = {
325
+ info: {
326
+ container: "bg-surface border-border",
327
+ text: "text-text-secondary",
328
+ Icon: Info
329
+ },
330
+ success: {
331
+ container: "bg-success-subtle border-success-border",
332
+ text: "text-success",
333
+ Icon: CircleCheck
334
+ },
335
+ warning: {
336
+ container: "bg-warning-subtle border-warning-border",
337
+ text: "text-warning",
338
+ Icon: Info
339
+ },
340
+ error: {
341
+ container: "bg-error-subtle border-error-border",
342
+ text: "text-error",
343
+ Icon: Info
344
+ }
345
+ };
346
+ function AlertBanner({
347
+ variant,
348
+ content,
349
+ icon = false,
350
+ className
351
+ }) {
352
+ const { container, text, Icon } = variantConfig[variant];
353
+ return /* @__PURE__ */ jsxs(
354
+ "div",
355
+ {
356
+ className: cn(
357
+ "flex items-center rounded-btn border p-3",
358
+ icon && "gap-2",
359
+ container,
360
+ className
361
+ ),
362
+ children: [
363
+ icon && /* @__PURE__ */ jsx2(Icon, { className: cn("h-4 w-4 shrink-0", text), "aria-hidden": "true" }),
364
+ /* @__PURE__ */ jsx2("p", { className: cn(typeClasses.subhead, text), children: content })
365
+ ]
366
+ }
367
+ );
368
+ }
369
+
370
+ // src/client/components/design-system/CloseButton.tsx
371
+ import { X } from "lucide-react";
372
+ import { jsx as jsx3 } from "react/jsx-runtime";
373
+ function CloseButton({
374
+ onClick,
375
+ "aria-label": ariaLabel = "Close",
376
+ className
377
+ }) {
378
+ return /* @__PURE__ */ jsx3(
379
+ "button",
380
+ {
381
+ type: "button",
382
+ onClick,
383
+ "aria-label": ariaLabel,
384
+ className: cn(
385
+ "cursor-pointer rounded-full p-1.5 text-text-secondary transition-colors hover:bg-surface hover:text-text-primary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-400 focus-visible:ring-offset-2",
386
+ className
387
+ ),
388
+ children: /* @__PURE__ */ jsx3(X, { className: "h-5 w-5" })
389
+ }
390
+ );
391
+ }
392
+
393
+ // src/client/components/design-system/logos/CoinListWordmark.tsx
394
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
395
+ function CoinListWordmark(props) {
396
+ return /* @__PURE__ */ jsxs2(
397
+ "svg",
398
+ {
399
+ width: "638",
400
+ height: "125",
401
+ viewBox: "0 0 638 125",
402
+ fill: "none",
403
+ xmlns: "http://www.w3.org/2000/svg",
404
+ "aria-hidden": "true",
405
+ ...props,
406
+ children: [
407
+ /* @__PURE__ */ jsx4(
408
+ "path",
409
+ {
410
+ d: "M68.7724 123.966C74.8226 124.172 80.7696 123.519 86.5104 121.799C97.4076 118.531 106.551 112.58 113.942 103.947C113.942 103.947 113.942 103.947 113.908 103.912C111.879 102.193 109.851 100.507 107.857 98.8218L105.829 97.1363C105.451 96.8268 105.107 96.5172 104.729 96.2076C103.939 95.5197 103.148 94.8662 102.323 94.2126C95.3107 98.8562 87.6104 101.539 79.1196 101.883C74.8569 102.055 70.6287 101.642 66.5036 100.507C59.5252 98.6154 53.2688 95.1757 48.2499 89.7755C39.1059 79.938 35.8745 68.1743 38.2465 55.0348C39.7246 46.9172 43.4373 40.0723 49.1093 34.5C47.9061 34.7408 46.703 35.016 45.5342 35.3943C36.5277 38.2148 29.6181 43.6839 24.6336 51.6639C20.6803 58.0273 18.5834 64.9411 18.3428 72.4739C18.2053 76.7047 18.4803 80.7979 19.4428 84.7879C22.5023 97.2395 29.3087 107.043 39.6903 114.335H39.7934V114.403C40.1372 114.644 40.4809 114.919 40.8247 115.16C49.2812 120.801 58.5971 123.691 68.7724 124.035V123.966Z",
411
+ fill: "currentColor"
412
+ }
413
+ ),
414
+ /* @__PURE__ */ jsx4(
415
+ "path",
416
+ {
417
+ d: "M72.2101 89.052C68.1881 90.8406 64.0286 91.8725 59.6973 92.2165C64.9912 95.209 70.8007 96.688 77.1259 96.8256C79.1885 96.86 81.251 96.6536 83.4167 96.3097C88.745 95.4841 93.6951 93.5579 98.3015 90.7374C93.9358 87.057 89.6044 83.4109 85.2386 79.7305C81.526 83.6861 77.1946 86.8506 72.2101 89.052Z",
418
+ fill: "currentColor"
419
+ }
420
+ ),
421
+ /* @__PURE__ */ jsx4(
422
+ "path",
423
+ {
424
+ d: "M66.9848 0.241609C66.4348 0.276006 65.8848 0.344799 65.3691 0.413592C70.663 1.4111 75.7851 3.13093 80.6321 5.71068C90.0511 10.7326 97.3732 17.9215 102.289 27.4494L104.936 25.1448C107.652 22.7714 110.368 20.3981 113.084 18.0247C106.655 11.1797 99.092 6.15784 90.223 3.06214C84.3103 0.998336 78.2601 -0.0335643 72.0381 0.000832319C70.388 0.000832319 68.7036 0.138419 66.9848 0.241609Z",
425
+ fill: "currentColor"
426
+ }
427
+ ),
428
+ /* @__PURE__ */ jsx4(
429
+ "path",
430
+ {
431
+ d: "M25.4242 107.871C17.4833 97.9988 13.1519 86.7167 13.2207 73.818C13.2207 67.317 14.5614 61.0568 17.1396 55.1062C20.9553 46.2662 26.9711 39.1805 35.4276 34.2962C43.3685 29.687 51.9968 27.8984 61.1752 29.2743C70.5943 30.7189 78.3976 35.2249 84.6541 42.2762C84.6541 42.2762 84.8603 42.5514 84.9634 42.6546C85.8916 41.8291 86.8541 41.0035 87.7823 40.178L88.9167 39.1805C89.8792 38.355 90.8417 37.4951 91.8043 36.6696C94.0043 34.7434 96.17 32.8515 98.3357 30.9253C94.1075 22.223 87.7479 15.55 79.3258 10.8377C69.3911 5.26541 58.7002 3.54557 47.3904 5.02463C41.7528 5.78136 36.3557 7.50119 31.1993 10.0465C13.702 18.7145 1.08597 36.532 0.0546833 58.4771C-0.117196 62.0887 0.123436 65.7348 0.673452 69.3464C2.04849 78.3239 5.34859 86.4759 10.6425 93.8368C14.7676 99.5123 19.6834 104.19 25.3898 107.871H25.4242Z",
432
+ fill: "currentColor"
433
+ }
434
+ ),
435
+ /* @__PURE__ */ jsx4(
436
+ "path",
437
+ {
438
+ fillRule: "evenodd",
439
+ clipRule: "evenodd",
440
+ d: "M366.034 58.1654C366.034 46.8145 375.178 37.665 386.419 37.665C397.66 37.665 406.804 46.8145 406.804 58.1654V99.3727H420.727V58.1654C420.727 39.1785 405.395 23.7344 386.419 23.7344C367.444 23.7344 352.112 39.1785 352.112 58.1654V99.3727H366.034V58.1654ZM278.616 38.1466C265.141 38.1466 254.244 49.0503 254.244 62.5338C254.244 76.0173 265.141 86.9211 278.616 86.9211C292.092 86.9211 302.989 76.0173 302.989 62.5338C302.989 49.0503 292.092 38.1466 278.616 38.1466ZM240.321 62.5338C240.321 41.3799 257.475 24.2159 278.616 24.2159C299.757 24.2159 316.911 41.3799 316.911 62.5338C316.911 83.6878 299.757 100.852 278.616 100.852C257.475 100.852 240.321 83.6878 240.321 62.5338ZM208.386 38.2154C195.082 37.9402 184.048 48.5 183.738 61.8115C183.463 75.123 194.017 86.1643 207.32 86.4739C215.192 86.6459 222.274 82.9998 226.812 77.2212L237.777 85.8204C230.662 94.9011 219.455 100.645 207.011 100.37C186.007 99.923 169.335 82.5183 169.782 61.5019C170.229 40.4856 187.623 23.8032 208.627 24.2503C219.524 24.4911 229.287 29.3066 236.059 36.8051L225.712 46.1266C221.415 41.3455 215.227 38.3186 208.317 38.1466L208.386 38.2154ZM340.527 25.2134V99.3727H326.605V25.2134H340.527ZM508.282 25.2134H494.36V99.8542H508.282V25.2134ZM483.154 99.8542H448.846C439.221 99.8542 431.452 92.0462 431.452 82.4495V25.2134H445.374V82.4495C445.374 84.3757 446.921 85.9236 448.846 85.9236H483.154V99.8542ZM583.772 25.2134H567.856H542.83C530.661 25.2134 520.933 35.1885 520.933 47.2273C520.933 59.2661 530.661 69.2412 542.83 69.2412H552.421C556.821 69.2412 560.396 72.8184 560.396 77.1868V77.9435C560.396 82.3463 556.821 85.8892 552.421 85.8892H520.451V99.8198H552.421C564.522 99.8198 574.319 90.0168 574.319 77.9091V77.1524C574.319 65.0448 564.522 55.2417 552.421 55.2417H542.83C538.499 55.2417 534.855 51.6301 534.855 47.1585C534.855 42.6869 538.499 39.0753 542.83 39.0753H574.319H583.772C591.472 39.0753 597.694 45.3011 597.694 53.0059V99.3039H611.617V53.0059C611.617 37.6306 599.138 25.1446 583.772 25.1446V25.2134ZM637.021 39.1441H609.038V25.2134H637.021V39.1441Z",
441
+ fill: "currentColor"
442
+ }
443
+ )
444
+ ]
445
+ }
446
+ );
447
+ }
448
+
449
+ // src/client/components/design-system/PoweredByCoinList.tsx
450
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
451
+ function PoweredByCoinList({ className }) {
452
+ return /* @__PURE__ */ jsxs3(
453
+ "div",
454
+ {
455
+ className: cn(
456
+ "flex items-center justify-center gap-[5px] text-text-secondary",
457
+ className
458
+ ),
459
+ children: [
460
+ /* @__PURE__ */ jsx5("span", { className: cn(typeClasses.caption, "tracking-[0.12px]"), children: "Powered by" }),
461
+ /* @__PURE__ */ jsx5(CoinListWordmark, { className: "h-3 w-auto" })
462
+ ]
463
+ }
464
+ );
465
+ }
466
+
467
+ // src/client/components/design-system/ClButton.tsx
468
+ import { cva } from "class-variance-authority";
469
+ import { forwardRef } from "react";
470
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
471
+ var buttonVariants = cva(
472
+ "inline-flex items-center justify-center gap-3 whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-40 cursor-pointer",
473
+ {
474
+ variants: {
475
+ variant: {
476
+ // Primary — tokens auto-switch: dark bg (#171717) → light bg (#f4f4f5) in dark mode
477
+ primary: "bg-brand-primary text-text-inverse font-semibold hover:bg-brand-primary-hover active:bg-brand-primary-active focus-visible:ring-gray-500",
478
+ // Secondary — bg-section, border-border, text-text-primary all auto-switch
479
+ // hover maps to border token value; active has no token so uses dark: override
480
+ secondary: "bg-section border border-border text-text-primary font-medium hover:bg-border active:bg-[#d9d9d9] dark:active:bg-divider focus-visible:ring-gray-400",
481
+ // Destructive — error tokens auto-switch to translucent dark equivalents
482
+ // hover/active have no tokens so use dark: overrides
483
+ destructive: "bg-error-subtle border border-error-border text-error font-semibold hover:bg-[#fcdcd8] active:bg-[#f9c9c4] dark:hover:bg-[rgba(248,113,113,0.20)] dark:active:bg-[rgba(248,113,113,0.30)] focus-visible:ring-red-400",
484
+ // Ghost — transparent base; hover/active use section/border tokens (auto-switch)
485
+ ghost: "bg-transparent text-text-primary font-medium hover:bg-section active:bg-border focus-visible:ring-gray-400",
486
+ // Link — text-text-primary auto-switches light/dark
487
+ link: "h-auto p-0 text-text-primary font-medium underline-offset-4 hover:underline focus-visible:ring-gray-500",
488
+ // Destructive link — text-error auto-switches (#b42318 → #f87171 in dark)
489
+ destructiveLink: "h-auto p-0 text-error font-medium hover:text-[#8b1a13] active:text-[#6d1510] dark:hover:text-[#fca5a5] dark:active:text-[#f87171] focus-visible:ring-red-500"
490
+ },
491
+ size: {
492
+ sm: "h-9 px-4 text-sm tracking-[0.28px]",
493
+ default: "h-11 px-5 text-sm tracking-[0.28px]",
494
+ lg: "h-14 px-6 text-base",
495
+ icon: "h-12 w-12"
496
+ },
497
+ radius: {
498
+ pill: "rounded-full",
499
+ // compound variants below handle size-specific radii
500
+ rounded: ""
501
+ },
502
+ fullWidth: {
503
+ true: "w-full",
504
+ false: ""
505
+ }
506
+ },
507
+ compoundVariants: [
508
+ { size: "sm", radius: "rounded", className: "rounded-lg" },
509
+ { size: "default", radius: "rounded", className: "rounded-2xl" },
510
+ { size: "lg", radius: "rounded", className: "rounded-2xl" },
511
+ { size: "icon", radius: "rounded", className: "rounded-md" }
512
+ ],
513
+ defaultVariants: {
514
+ variant: "primary",
515
+ size: "default",
516
+ radius: "pill",
517
+ fullWidth: false
518
+ }
519
+ }
520
+ );
521
+ var ClButton = forwardRef(
522
+ ({
523
+ className,
524
+ variant,
525
+ size,
526
+ radius,
527
+ fullWidth,
528
+ children,
529
+ isLoading = false,
530
+ loadingText,
531
+ startIcon,
532
+ endIcon,
533
+ disabled,
534
+ ...props
535
+ }, ref) => {
536
+ const isDisabled = disabled || isLoading;
537
+ return /* @__PURE__ */ jsxs4(
538
+ "button",
539
+ {
540
+ className: cn(
541
+ buttonVariants({ variant, size, radius, fullWidth }),
542
+ className
543
+ ),
544
+ ref,
545
+ disabled: isDisabled,
546
+ ...props,
547
+ children: [
548
+ isLoading && /* @__PURE__ */ jsx6(
549
+ "span",
550
+ {
551
+ className: "h-4 w-4 shrink-0 animate-spin rounded-full border-2 border-current border-t-transparent opacity-60",
552
+ "aria-hidden": "true"
553
+ }
554
+ ),
555
+ startIcon && !isLoading && /* @__PURE__ */ jsx6("span", { className: "shrink-0", children: startIcon }),
556
+ /* @__PURE__ */ jsx6("span", { className: cn(isLoading && "opacity-60"), children: isLoading && loadingText ? loadingText : children }),
557
+ endIcon && !isLoading && /* @__PURE__ */ jsx6("span", { className: "shrink-0", children: endIcon })
558
+ ]
559
+ }
560
+ );
561
+ }
562
+ );
563
+ ClButton.displayName = "ClButton";
564
+
565
+ // src/client/components/design-system/logos/CoinListLogomark.tsx
566
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
567
+ function CoinListLogomark(props) {
568
+ return /* @__PURE__ */ jsxs5(
569
+ "svg",
570
+ {
571
+ width: "115",
572
+ height: "125",
573
+ viewBox: "0 0 115 125",
574
+ fill: "none",
575
+ xmlns: "http://www.w3.org/2000/svg",
576
+ "aria-hidden": "true",
577
+ ...props,
578
+ children: [
579
+ /* @__PURE__ */ jsx7(
580
+ "path",
581
+ {
582
+ d: "M69.7519 123.966C75.8021 124.172 81.7491 123.519 87.4899 121.799C98.3871 118.531 107.531 112.58 114.922 103.947C114.922 103.947 114.922 103.947 114.888 103.912C112.859 102.193 110.831 100.507 108.837 98.8218L106.809 97.1363C106.431 96.8268 106.087 96.5172 105.709 96.2076C104.919 95.5197 104.128 94.8662 103.303 94.2126C96.2902 98.8562 88.5899 101.539 80.0991 101.883C75.8364 102.055 71.6082 101.642 67.4831 100.507C60.5047 98.6154 54.2483 95.1757 49.2294 89.7755C40.0854 79.938 36.854 68.1743 39.226 55.0348C40.7041 46.9172 44.4168 40.0723 50.0888 34.5C48.8856 34.7408 47.6825 35.016 46.5137 35.3943C37.5072 38.2148 30.5976 43.6839 25.6131 51.6639C21.6598 58.0273 19.5629 64.9411 19.3223 72.4739C19.1848 76.7047 19.4598 80.7979 20.4223 84.7879C23.4818 97.2395 30.2882 107.043 40.6698 114.335H40.7729V114.403C41.1167 114.644 41.4604 114.919 41.8042 115.16C50.2607 120.801 59.5766 123.691 69.7519 124.035V123.966Z",
583
+ fill: "currentColor"
584
+ }
585
+ ),
586
+ /* @__PURE__ */ jsx7(
587
+ "path",
588
+ {
589
+ d: "M73.1896 89.052C69.1676 90.8406 65.0081 91.8725 60.6768 92.2165C65.9707 95.209 71.7802 96.688 78.1054 96.8256C80.168 96.86 82.2305 96.6536 84.3962 96.3097C89.7245 95.4841 94.6746 93.5579 99.281 90.7374C94.9153 87.057 90.5839 83.4109 86.2181 79.7305C82.5055 83.6861 78.1741 86.8506 73.1896 89.052Z",
590
+ fill: "currentColor"
591
+ }
592
+ ),
593
+ /* @__PURE__ */ jsx7(
594
+ "path",
595
+ {
596
+ d: "M67.9643 0.241609C67.4143 0.276006 66.8643 0.344799 66.3486 0.413592C71.6425 1.4111 76.7646 3.13093 81.6116 5.71068C91.0306 10.7326 98.3527 17.9215 103.268 27.4494L105.915 25.1448C108.631 22.7714 111.347 20.3981 114.063 18.0247C107.634 11.1797 100.072 6.15784 91.2025 3.06214C85.2898 0.998336 79.2396 -0.0335643 73.0176 0.000832322C71.3675 0.000832322 69.6831 0.138419 67.9643 0.241609Z",
597
+ fill: "currentColor"
598
+ }
599
+ ),
600
+ /* @__PURE__ */ jsx7(
601
+ "path",
602
+ {
603
+ d: "M26.4037 107.871C18.4628 97.9988 14.1314 86.7167 14.2002 73.818C14.2002 67.317 15.5409 61.0568 18.1191 55.1062C21.9348 46.2662 27.9506 39.1805 36.4071 34.2962C44.348 29.687 52.9763 27.8984 62.1547 29.2743C71.5738 30.7189 79.3771 35.2249 85.6336 42.2762C85.6336 42.2762 85.8398 42.5514 85.9429 42.6546C86.8711 41.8291 87.8336 41.0035 88.7618 40.178L89.8962 39.1805C90.8587 38.355 91.8212 37.4951 92.7838 36.6696C94.9838 34.7434 97.1495 32.8515 99.3152 30.9253C95.087 22.223 88.7274 15.55 80.3053 10.8377C70.3706 5.26541 59.6797 3.54557 48.37 5.02463C42.7323 5.78136 37.3352 7.50119 32.1788 10.0465C14.6815 18.7145 2.06546 36.532 1.03418 58.4771C0.862296 62.0887 1.10293 65.7348 1.65294 69.3464C3.02798 78.3239 6.32808 86.4759 11.622 93.8368C15.7471 99.5123 20.6629 104.19 26.3693 107.871H26.4037Z",
604
+ fill: "currentColor"
605
+ }
606
+ )
607
+ ]
608
+ }
609
+ );
610
+ }
611
+
612
+ // src/client/components/oauth/CoinListSignInButton.tsx
613
+ import { jsx as jsx8 } from "react/jsx-runtime";
614
+ function CoinListSignInButton({
615
+ onClick,
616
+ disabled = false,
617
+ fullWidth = false,
618
+ isLoading = false,
619
+ className
620
+ }) {
621
+ return /* @__PURE__ */ jsx8(
622
+ ClButton,
623
+ {
624
+ variant: "primary",
625
+ size: "lg",
626
+ fullWidth,
627
+ onClick,
628
+ disabled,
629
+ isLoading,
630
+ loadingText: "Signing in...",
631
+ startIcon: /* @__PURE__ */ jsx8(CoinListLogomark, { className: "h-[18px] w-[16px] shrink-0" }),
632
+ className,
633
+ children: "Continue with CoinList"
634
+ }
635
+ );
636
+ }
637
+
638
+ // src/client/hooks/useCoinList.ts
639
+ import { useContext } from "react";
640
+ function useCoinList() {
641
+ const context = useContext(CoinListContext);
642
+ if (context === null) {
643
+ throw new CoinListClientInitializationError();
644
+ }
645
+ return {
646
+ isReady: context.isReady,
647
+ coinlist: context.coinlist
648
+ };
649
+ }
650
+ var CoinListClientInitializationError = class extends Error {
651
+ constructor(message = "useCoinList() must be used within CoinListProvider. Wrap your app with <CoinListProvider config={...}>.") {
652
+ super(message);
653
+ this.name = "CoinListClientInitializationError";
654
+ }
655
+ };
656
+
657
+ // src/client/components/oauth/CoinListSignInCard.tsx
658
+ import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
659
+ function CoinListSignInCard({
660
+ state = "idle",
661
+ onSignIn,
662
+ onClose,
663
+ className
664
+ } = {}) {
665
+ const { coinlist } = useCoinList();
666
+ const handleSignIn = onSignIn ?? (() => {
667
+ void coinlist.startOAuth();
668
+ });
669
+ return /* @__PURE__ */ jsxs6(
670
+ "div",
671
+ {
672
+ className: cn(
673
+ "relative flex w-full max-w-md flex-col rounded-card border border-border bg-background p-6 shadow-card",
674
+ className
675
+ ),
676
+ children: [
677
+ onClose && /* @__PURE__ */ jsx9(CloseButton, { onClick: onClose, className: "absolute right-4 top-4" }),
678
+ /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-2", children: [
679
+ /* @__PURE__ */ jsx9("h3", { className: cn(typeClasses.h3, "text-text-primary"), children: "Sign in to participate" }),
680
+ /* @__PURE__ */ jsx9("p", { className: cn(typeClasses.body, "text-text-secondary"), children: "A CoinList account is required to participate in this sale." })
681
+ ] }),
682
+ /* @__PURE__ */ jsx9(CoinListSignInButton, { onClick: handleSignIn, fullWidth: true, className: "mt-4" }),
683
+ state === "error" && /* @__PURE__ */ jsx9(
684
+ AlertBanner,
685
+ {
686
+ variant: "error",
687
+ icon: true,
688
+ content: "Error signing in. Please try again.",
689
+ className: "mt-4"
690
+ }
691
+ ),
692
+ /* @__PURE__ */ jsx9(PoweredByCoinList, { className: "mt-4" })
693
+ ]
694
+ }
695
+ );
696
+ }
697
+
698
+ // src/client/components/offers/OfferCard.tsx
699
+ import { Fragment, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
700
+ var monthYearFormatter = new Intl.DateTimeFormat("en-US", {
701
+ month: "short",
702
+ year: "numeric"
703
+ });
704
+ var OfferCardUi = {
705
+ fromDomain: (offer) => ({
706
+ tagline: offer.tagline,
707
+ bannerUrl: offer.bannerUrl,
708
+ logoUrl: offer.logoUrl,
709
+ formattedStartsAt: monthYearFormatter.format(offer.startsAt),
710
+ formattedEndsAt: monthYearFormatter.format(offer.endsAt)
711
+ })
712
+ };
713
+ function OfferCard({
714
+ offer,
715
+ onClick,
716
+ className,
717
+ containerClassName
718
+ }) {
719
+ const cardLabel = "Open offer details";
720
+ const cardContent = /* @__PURE__ */ jsxs7(Fragment, { children: [
721
+ /* @__PURE__ */ jsxs7("div", { className: "relative mb-6 h-52 w-full overflow-hidden rounded-2xl border border-border/60 bg-section", children: [
722
+ offer.bannerUrl ? /* @__PURE__ */ jsx10(
723
+ "img",
724
+ {
725
+ src: offer.bannerUrl,
726
+ alt: "Offer banner",
727
+ className: "h-full w-full object-cover"
728
+ }
729
+ ) : /* @__PURE__ */ jsx10("div", { className: "h-full w-full bg-gradient-to-br from-surface via-section to-background" }),
730
+ /* @__PURE__ */ jsx10("div", { className: "absolute inset-0 bg-gradient-to-b from-transparent via-transparent to-overlay/35" }),
731
+ /* @__PURE__ */ jsx10("div", { className: "absolute inset-0 flex items-center justify-center p-6", children: offer.logoUrl ? /* @__PURE__ */ jsx10(
732
+ "img",
733
+ {
734
+ src: offer.logoUrl,
735
+ alt: "Offer logo",
736
+ className: "h-28 w-28 rounded-full border border-border bg-surface object-cover shadow-[0_10px_24px_rgba(0,0,0,0.25)]"
737
+ }
738
+ ) : /* @__PURE__ */ jsx10("div", { className: "h-28 w-28 rounded-full border border-border bg-surface shadow-[0_10px_24px_rgba(0,0,0,0.18)]" }) })
739
+ ] }),
740
+ offer.tagline && /* @__PURE__ */ jsx10("p", { className: cn(typeClasses.h3, "min-h-[2.4rem] text-text-primary"), children: offer.tagline }),
741
+ /* @__PURE__ */ jsxs7("div", { className: "mt-4 grid grid-cols-2 gap-5 border-t border-divider pt-4", children: [
742
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1", children: [
743
+ /* @__PURE__ */ jsx10("span", { className: cn(typeClasses.subhead, "text-text-secondary"), children: "Starts" }),
744
+ /* @__PURE__ */ jsx10("span", { className: cn(typeClasses.label, "text-text-primary"), children: offer.formattedStartsAt })
745
+ ] }),
746
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1", children: [
747
+ /* @__PURE__ */ jsx10("span", { className: cn(typeClasses.subhead, "text-text-secondary"), children: "Ends" }),
748
+ /* @__PURE__ */ jsx10("span", { className: cn(typeClasses.label, "text-text-primary"), children: offer.formattedEndsAt })
749
+ ] })
750
+ ] })
751
+ ] });
752
+ const baseClasses = cn(
753
+ "relative flex w-full max-w-[28rem] flex-col rounded-card border border-border bg-background p-6 text-left shadow-card transition-all duration-200",
754
+ className,
755
+ onClick && "cursor-pointer hover:-translate-y-0.5 hover:border-divider hover:shadow-[0px_14px_32px_-10px_rgba(0,0,0,0.35)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-focus focus-visible:ring-offset-2"
756
+ );
757
+ let cardNode;
758
+ if (onClick) {
759
+ cardNode = /* @__PURE__ */ jsx10(
760
+ "button",
761
+ {
762
+ type: "button",
763
+ "aria-label": cardLabel,
764
+ className: baseClasses,
765
+ onClick,
766
+ children: cardContent
767
+ }
768
+ );
769
+ } else {
770
+ cardNode = /* @__PURE__ */ jsx10("div", { className: baseClasses, children: cardContent });
771
+ }
772
+ if (!containerClassName) {
773
+ return cardNode;
774
+ }
775
+ return /* @__PURE__ */ jsx10("div", { className: containerClassName, children: cardNode });
776
+ }
777
+
778
+ // src/client/hooks/useOffers.ts
779
+ import { useEffect as useEffect2, useState as useState2 } from "react";
780
+ var LOADING_STATE = { type: "LOADING" };
781
+ function useOffers(options = {}) {
782
+ const { coinlist, isReady } = useCoinList();
783
+ const { data: serverOffers } = options;
784
+ const [offersState, setOffersState] = useState2(
785
+ serverOffers !== void 0 ? { type: "CONTENT", offers: serverOffers } : LOADING_STATE
786
+ );
787
+ useEffect2(() => {
788
+ let isCancelled = false;
789
+ if (!isReady) {
790
+ if (serverOffers === void 0) {
791
+ setOffersState(LOADING_STATE);
792
+ }
793
+ return () => {
794
+ isCancelled = true;
795
+ };
796
+ }
797
+ if (serverOffers !== void 0) {
798
+ return () => {
799
+ isCancelled = true;
800
+ };
801
+ }
802
+ setOffersState(LOADING_STATE);
803
+ coinlist.fetchOffers().then((offers) => {
804
+ if (!isCancelled) {
805
+ setOffersState({ type: "CONTENT", offers });
806
+ }
807
+ }).catch((error) => {
808
+ if (!isCancelled) {
809
+ const reason = error instanceof NotAuthenticatedError ? "not-authenticated" : "generic-error";
810
+ setOffersState({ type: "ERROR", reason });
811
+ }
812
+ });
813
+ return () => {
814
+ isCancelled = true;
815
+ };
816
+ }, [coinlist, isReady, serverOffers]);
817
+ return { offersState };
818
+ }
819
+
820
+ // src/client/components/offers/OffersGrid.tsx
821
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
822
+ var DEFAULT_MAX_COLUMNS = 3;
823
+ function OffersGrid({
824
+ data,
825
+ maxColumns,
826
+ className,
827
+ containerClassName,
828
+ loading,
829
+ error,
830
+ emptyState,
831
+ onOfferClick
832
+ } = {}) {
833
+ const { offersState } = useOffers({ data });
834
+ const cols = Math.max(1, maxColumns ?? DEFAULT_MAX_COLUMNS);
835
+ if (offersState.type === "LOADING") {
836
+ if (loading) {
837
+ return loading;
838
+ }
839
+ return /* @__PURE__ */ jsx11(
840
+ "output",
841
+ {
842
+ "aria-live": "polite",
843
+ className: "flex min-h-40 items-center justify-center rounded-2xl border border-border bg-background p-6",
844
+ children: /* @__PURE__ */ jsx11("p", { className: cn(typeClasses.body, "text-text-secondary"), children: "Loading offers..." })
845
+ }
846
+ );
847
+ }
848
+ if (offersState.type === "ERROR") {
849
+ if (error) {
850
+ return error;
851
+ }
852
+ const errorMessage = offersState.reason === "not-authenticated" ? "Sign in to view available offers." : "Unable to load offers right now. Please try again.";
853
+ return /* @__PURE__ */ jsx11(
854
+ AlertBanner,
855
+ {
856
+ variant: "error",
857
+ icon: true,
858
+ content: errorMessage,
859
+ className: "w-full max-w-[28rem]"
860
+ }
861
+ );
862
+ }
863
+ if (offersState.offers.length === 0) {
864
+ if (emptyState) {
865
+ return emptyState;
866
+ }
867
+ return /* @__PURE__ */ jsxs8(
868
+ "output",
869
+ {
870
+ "aria-live": "polite",
871
+ className: "flex min-h-40 flex-col items-center justify-center rounded-2xl border border-border bg-background p-6 text-center",
872
+ children: [
873
+ /* @__PURE__ */ jsx11("p", { className: cn(typeClasses.body, "font-medium text-text-primary"), children: "No offers available" }),
874
+ /* @__PURE__ */ jsx11("p", { className: cn(typeClasses.subhead, "text-text-secondary"), children: "Check back soon for upcoming opportunities." })
875
+ ]
876
+ }
877
+ );
878
+ }
879
+ const maxGridWidth = `calc(${cols} * 28rem + ${Math.max(cols - 1, 0)} * 1.5rem)`;
880
+ return /* @__PURE__ */ jsx11("div", { className: cn("flex w-full justify-center", containerClassName), children: /* @__PURE__ */ jsx11(
881
+ "div",
882
+ {
883
+ className: cn("grid w-full gap-6", className),
884
+ style: {
885
+ gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 20rem), 1fr))",
886
+ maxWidth: maxGridWidth
887
+ },
888
+ children: offersState.offers.map((offer) => /* @__PURE__ */ jsx11(
889
+ OfferCard,
890
+ {
891
+ offer: OfferCardUi.fromDomain(offer),
892
+ onClick: onOfferClick ? () => onOfferClick(offer) : void 0
893
+ },
894
+ offer.id
895
+ ))
896
+ }
897
+ ) });
898
+ }
899
+
900
+ // src/client/components/requirements/RequirementItem.tsx
901
+ import { ChevronRight } from "lucide-react";
902
+
903
+ // src/client/components/design-system/StepStatusIcon.tsx
904
+ import { CircleCheck as CircleCheck2, CircleX, Info as Info2 } from "lucide-react";
905
+ import { jsx as jsx12 } from "react/jsx-runtime";
906
+ var statusConfig = {
907
+ required: {
908
+ Icon: Info2,
909
+ colorClass: "text-warning",
910
+ fillClass: "[&_circle]:fill-warning-subtle"
911
+ },
912
+ pending: {
913
+ Icon: Info2,
914
+ colorClass: "text-warning",
915
+ fillClass: "[&_circle]:fill-warning-subtle"
916
+ },
917
+ completed: {
918
+ Icon: CircleCheck2,
919
+ colorClass: "text-success",
920
+ fillClass: "[&_circle]:fill-success-subtle"
921
+ },
922
+ rejected: {
923
+ Icon: CircleX,
924
+ colorClass: "text-error",
925
+ fillClass: "[&_circle]:fill-error-subtle"
926
+ }
927
+ };
928
+ function StepStatusIcon({ status, className }) {
929
+ const { Icon, colorClass, fillClass } = statusConfig[status];
930
+ return /* @__PURE__ */ jsx12(
931
+ Icon,
932
+ {
933
+ className: cn("h-6 w-6 shrink-0", colorClass, fillClass, className),
934
+ "aria-hidden": "true"
935
+ }
936
+ );
937
+ }
938
+
939
+ // src/client/components/requirements/RequirementItem.tsx
940
+ import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
941
+ var RequirementVariant = {
942
+ fromType(type) {
943
+ switch (type) {
944
+ case "external_wallet":
945
+ case "whitelisted_wallet":
946
+ return "wallet";
947
+ default:
948
+ return "verification";
949
+ }
950
+ }
951
+ };
952
+ var RequirementStatus = {
953
+ fromStatusValue(status) {
954
+ switch (status) {
955
+ case "not_started":
956
+ case "action_needed":
957
+ return "required";
958
+ case "in_progress":
959
+ return "pending";
960
+ case "completed":
961
+ return "completed";
962
+ case "rejected":
963
+ return "rejected";
964
+ }
965
+ }
966
+ };
967
+ var ChecklistStatus = {
968
+ derive(statuses) {
969
+ if (statuses.some((s) => s.status === "rejected")) return "ineligible";
970
+ if (statuses.every((s) => s.status === "completed")) return "eligible";
971
+ return "inProgress";
972
+ }
973
+ };
974
+ var RequirementItemUi = {
975
+ fromDomain(req, statusInfo, label, description, walletAddress) {
976
+ return {
977
+ variant: RequirementVariant.fromType(req.type),
978
+ status: statusInfo ? RequirementStatus.fromStatusValue(statusInfo.status) : "required",
979
+ label,
980
+ description,
981
+ walletAddress
982
+ };
983
+ }
984
+ };
985
+ function VerificationActionZone({
986
+ status,
987
+ onAction,
988
+ onContactSupport
989
+ }) {
990
+ switch (status) {
991
+ case "required":
992
+ if (onAction === null) return null;
993
+ return /* @__PURE__ */ jsx13(ClButton, { size: "sm", onClick: onAction, children: "Continue" });
994
+ case "pending":
995
+ return /* @__PURE__ */ jsx13(
996
+ AlertBanner,
997
+ {
998
+ variant: "warning",
999
+ content: "Under review. We'll notify you when complete."
1000
+ }
1001
+ );
1002
+ case "completed":
1003
+ return /* @__PURE__ */ jsx13(AlertBanner, { variant: "success", content: "Identity verified." });
1004
+ case "rejected":
1005
+ return /* @__PURE__ */ jsx13(
1006
+ AlertBanner,
1007
+ {
1008
+ variant: "error",
1009
+ content: onContactSupport === null ? "Verification rejected." : /* @__PURE__ */ jsxs9(Fragment2, { children: [
1010
+ "Verification rejected.",
1011
+ " ",
1012
+ /* @__PURE__ */ jsx13(
1013
+ "button",
1014
+ {
1015
+ type: "button",
1016
+ onClick: onContactSupport,
1017
+ className: "underline",
1018
+ children: "Contact support"
1019
+ }
1020
+ ),
1021
+ "."
1022
+ ] })
1023
+ }
1024
+ );
1025
+ }
1026
+ }
1027
+ function WalletActionZone({
1028
+ status,
1029
+ walletAddress,
1030
+ onAction
1031
+ }) {
1032
+ if (status === "completed") {
1033
+ return /* @__PURE__ */ jsxs9(Fragment2, { children: [
1034
+ onAction !== null && /* @__PURE__ */ jsx13(ClButton, { size: "sm", variant: "secondary", onClick: onAction, children: "Change wallet" }),
1035
+ /* @__PURE__ */ jsx13(
1036
+ AlertBanner,
1037
+ {
1038
+ variant: "success",
1039
+ content: walletAddress ?? "Connected: 0xBa4b...9f2c"
1040
+ }
1041
+ )
1042
+ ] });
1043
+ }
1044
+ if (status === "rejected") {
1045
+ return /* @__PURE__ */ jsxs9(Fragment2, { children: [
1046
+ onAction !== null && /* @__PURE__ */ jsx13(ClButton, { size: "sm", onClick: onAction, children: "Connect wallet" }),
1047
+ /* @__PURE__ */ jsx13(
1048
+ AlertBanner,
1049
+ {
1050
+ variant: "error",
1051
+ content: "This wallet is not eligible for this sale."
1052
+ }
1053
+ )
1054
+ ] });
1055
+ }
1056
+ if (onAction === null) return null;
1057
+ return /* @__PURE__ */ jsx13(ClButton, { size: "sm", onClick: onAction, children: "Connect wallet" });
1058
+ }
1059
+ function RequirementItem({
1060
+ ui,
1061
+ expanded = false,
1062
+ onToggle,
1063
+ className,
1064
+ onAction,
1065
+ onContactSupport
1066
+ }) {
1067
+ return /* @__PURE__ */ jsxs9("div", { className: cn("flex w-full flex-col", className), children: [
1068
+ /* @__PURE__ */ jsxs9(
1069
+ "button",
1070
+ {
1071
+ type: "button",
1072
+ onClick: onToggle,
1073
+ "aria-expanded": expanded,
1074
+ className: "flex h-14 w-full cursor-pointer items-center justify-between rounded-2xl px-2 transition-colors hover:bg-surface",
1075
+ children: [
1076
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-3", children: [
1077
+ /* @__PURE__ */ jsx13(StepStatusIcon, { status: ui.status }),
1078
+ /* @__PURE__ */ jsx13(
1079
+ "span",
1080
+ {
1081
+ className: cn(
1082
+ typeClasses.body,
1083
+ "whitespace-nowrap font-medium text-text-primary"
1084
+ ),
1085
+ children: ui.label
1086
+ }
1087
+ )
1088
+ ] }),
1089
+ /* @__PURE__ */ jsx13(
1090
+ ChevronRight,
1091
+ {
1092
+ className: cn(
1093
+ "h-6 w-6 shrink-0 text-text-secondary transition-transform duration-300",
1094
+ expanded && "rotate-90"
1095
+ ),
1096
+ "aria-hidden": "true"
1097
+ }
1098
+ )
1099
+ ]
1100
+ }
1101
+ ),
1102
+ /* @__PURE__ */ jsx13(
1103
+ "div",
1104
+ {
1105
+ className: cn(
1106
+ "grid transition-[grid-template-rows] duration-300 ease-in-out",
1107
+ expanded ? "grid-rows-[1fr]" : "grid-rows-[0fr]"
1108
+ ),
1109
+ children: /* @__PURE__ */ jsx13("div", { className: "min-h-0 overflow-hidden", children: /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-4 px-2 pb-4", children: [
1110
+ /* @__PURE__ */ jsx13("p", { className: cn(typeClasses.subhead, "text-text-secondary"), children: ui.description }),
1111
+ ui.variant === "verification" ? /* @__PURE__ */ jsx13(
1112
+ VerificationActionZone,
1113
+ {
1114
+ status: ui.status,
1115
+ onAction,
1116
+ onContactSupport
1117
+ }
1118
+ ) : /* @__PURE__ */ jsx13(
1119
+ WalletActionZone,
1120
+ {
1121
+ status: ui.status,
1122
+ walletAddress: ui.walletAddress,
1123
+ onAction
1124
+ }
1125
+ )
1126
+ ] }) })
1127
+ }
1128
+ )
1129
+ ] });
1130
+ }
1131
+
1132
+ // src/client/components/requirements/RequirementsChecklist.tsx
1133
+ import { useState as useState4 } from "react";
1134
+
1135
+ // src/client/hooks/useRequirements.ts
1136
+ import { useCallback, useEffect as useEffect3, useState as useState3 } from "react";
1137
+ var LOADING_STATE2 = { type: "LOADING" };
1138
+ function useRequirements(offerId, options = {}) {
1139
+ const { coinlist, isReady } = useCoinList();
1140
+ const { data } = options;
1141
+ const [requirementsState, setRequirementsState] = useState3(
1142
+ data !== void 0 ? { type: "CONTENT", ...data } : LOADING_STATE2
1143
+ );
1144
+ const [fetchKey, setFetchKey] = useState3(0);
1145
+ const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
1146
+ useEffect3(() => {
1147
+ let isCancelled = false;
1148
+ if (!isReady) {
1149
+ if (data === void 0) {
1150
+ setRequirementsState(LOADING_STATE2);
1151
+ }
1152
+ return () => {
1153
+ isCancelled = true;
1154
+ };
1155
+ }
1156
+ if (data !== void 0 && fetchKey === 0) {
1157
+ return () => {
1158
+ isCancelled = true;
1159
+ };
1160
+ }
1161
+ setRequirementsState(LOADING_STATE2);
1162
+ Promise.all([
1163
+ coinlist.fetchOfferRequirements(offerId),
1164
+ coinlist.fetchRequirementStatuses(offerId)
1165
+ ]).then(([requirementsByOptionId, statuses]) => {
1166
+ if (!isCancelled) {
1167
+ setRequirementsState({
1168
+ type: "CONTENT",
1169
+ requirements: requirementsByOptionId,
1170
+ statuses
1171
+ });
1172
+ }
1173
+ }).catch((error) => {
1174
+ if (!isCancelled) {
1175
+ const reason = error instanceof NotAuthenticatedError ? "not-authenticated" : "generic-error";
1176
+ setRequirementsState({ type: "ERROR", reason });
1177
+ }
1178
+ });
1179
+ return () => {
1180
+ isCancelled = true;
1181
+ };
1182
+ }, [coinlist, isReady, offerId, fetchKey]);
1183
+ return { requirementsState, refetch };
1184
+ }
1185
+
1186
+ // src/client/components/requirements/RequirementsChecklist.tsx
1187
+ import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
1188
+ var DEFAULT_LABELS = {
1189
+ kyc_approved: "KYC Verification",
1190
+ identity_verified: "Identity Verification",
1191
+ proof_of_address: "Proof of Address",
1192
+ source_of_funds: "Source of Funds",
1193
+ external_wallet: "External Wallet",
1194
+ whitelisted_wallet: "Whitelisted Wallet",
1195
+ jurisdiction: "Jurisdiction Check",
1196
+ accreditation: "Accreditation"
1197
+ };
1198
+ var DEFAULT_DESCRIPTIONS = {
1199
+ kyc_approved: "Complete Know Your Customer verification.",
1200
+ identity_verified: "Verify your identity.",
1201
+ proof_of_address: "Provide proof of your residential address.",
1202
+ source_of_funds: "Provide documentation for your source of funds.",
1203
+ external_wallet: "Connect an external wallet.",
1204
+ whitelisted_wallet: "Connect a whitelisted wallet.",
1205
+ jurisdiction: "Confirm your jurisdiction eligibility.",
1206
+ accreditation: "Verify your accreditation status."
1207
+ };
1208
+ function RequirementsChecklist({
1209
+ offerId,
1210
+ optionId,
1211
+ title,
1212
+ description,
1213
+ onContinue,
1214
+ onRequirementActionOverride,
1215
+ onContactSupportOverride,
1216
+ getLabel,
1217
+ getDescription,
1218
+ loading,
1219
+ error,
1220
+ className,
1221
+ data
1222
+ }) {
1223
+ const { coinlist } = useCoinList();
1224
+ const { requirementsState } = useRequirements(offerId, { data });
1225
+ const [expandedIndex, setExpandedIndex] = useState4(null);
1226
+ const onRequirementAction = onRequirementActionOverride !== void 0 ? onRequirementActionOverride : (requirement) => coinlist.handleRequirement(requirement);
1227
+ const onContactSupport = onContactSupportOverride !== void 0 ? onContactSupportOverride : () => coinlist.contactSupport();
1228
+ if (requirementsState.type === "LOADING") {
1229
+ if (loading) return loading;
1230
+ return /* @__PURE__ */ jsx14(
1231
+ "output",
1232
+ {
1233
+ "aria-live": "polite",
1234
+ className: "flex min-h-40 items-center justify-center rounded-2xl border border-border bg-background p-6",
1235
+ children: /* @__PURE__ */ jsx14("p", { className: cn(typeClasses.body, "text-text-secondary"), children: "Loading requirements..." })
1236
+ }
1237
+ );
1238
+ }
1239
+ if (requirementsState.type === "ERROR") {
1240
+ if (error) return error;
1241
+ const errorMessage = requirementsState.reason === "not-authenticated" ? "Sign in to view requirements." : "Unable to load requirements right now. Please try again.";
1242
+ return /* @__PURE__ */ jsx14(
1243
+ AlertBanner,
1244
+ {
1245
+ variant: "error",
1246
+ icon: true,
1247
+ content: errorMessage,
1248
+ className: "w-full max-w-[28rem]"
1249
+ }
1250
+ );
1251
+ }
1252
+ const { requirements: requirementsByOptionId, statuses } = requirementsState;
1253
+ const requirements = requirementsByOptionId[optionId] ?? [];
1254
+ const requirementIds = new Set(requirements.map((r) => r.id));
1255
+ const optionStatuses = statuses.filter((s) => requirementIds.has(s.id));
1256
+ const statusByRequirementId = new Map(optionStatuses.map((s) => [s.id, s]));
1257
+ const checklistStatus = ChecklistStatus.derive(optionStatuses);
1258
+ return /* @__PURE__ */ jsxs10("div", { className: cn("flex w-full flex-col gap-4", className), children: [
1259
+ /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-2", children: [
1260
+ /* @__PURE__ */ jsx14("h3", { className: cn(typeClasses.h3, "text-text-primary"), children: title }),
1261
+ /* @__PURE__ */ jsx14("p", { className: cn(typeClasses.body, "text-text-secondary"), children: description })
1262
+ ] }),
1263
+ /* @__PURE__ */ jsx14("div", { className: "flex flex-col divide-y divide-divider", children: requirements.map((req, index) => {
1264
+ const statusInfo = statusByRequirementId.get(req.id);
1265
+ const label = getLabel ? getLabel(req) : DEFAULT_LABELS[req.type];
1266
+ const itemDescription = (getDescription ? getDescription(req) : null) ?? DEFAULT_DESCRIPTIONS[req.type];
1267
+ let onActionBound;
1268
+ if (onRequirementAction === null) {
1269
+ onActionBound = null;
1270
+ } else if (onRequirementAction) {
1271
+ onActionBound = () => onRequirementAction(req);
1272
+ }
1273
+ let onContactSupportBound;
1274
+ if (onContactSupport === null) {
1275
+ onContactSupportBound = null;
1276
+ } else if (onContactSupport) {
1277
+ onContactSupportBound = () => onContactSupport(req);
1278
+ }
1279
+ return /* @__PURE__ */ jsx14(
1280
+ RequirementItem,
1281
+ {
1282
+ ui: RequirementItemUi.fromDomain(
1283
+ req,
1284
+ statusInfo,
1285
+ label,
1286
+ itemDescription
1287
+ ),
1288
+ expanded: expandedIndex === index,
1289
+ onToggle: () => setExpandedIndex((prev) => prev === index ? null : index),
1290
+ onAction: onActionBound,
1291
+ onContactSupport: onContactSupportBound
1292
+ },
1293
+ req.id
1294
+ );
1295
+ }) }),
1296
+ checklistStatus === "ineligible" && /* @__PURE__ */ jsx14(
1297
+ AlertBanner,
1298
+ {
1299
+ variant: "error",
1300
+ icon: true,
1301
+ content: "You are not eligible to participate."
1302
+ }
1303
+ ),
1304
+ onContinue && /* @__PURE__ */ jsx14(
1305
+ ClButton,
1306
+ {
1307
+ fullWidth: true,
1308
+ disabled: checklistStatus !== "eligible",
1309
+ onClick: onContinue,
1310
+ children: "Continue"
1311
+ }
1312
+ )
1313
+ ] });
1314
+ }
1315
+
1316
+ // src/client/hooks/useCompleteOAuth.ts
1317
+ import { useEffect as useEffect4, useRef } from "react";
1318
+ function useCompleteOAuth(options) {
1319
+ const { coinlist } = useCoinList();
1320
+ const postOAuthCompleteRef = useRef(options.postOAuthComplete);
1321
+ const onFailureRef = useRef(options.onFailure);
1322
+ const onSuccessRef = useRef(options.onSuccess);
1323
+ postOAuthCompleteRef.current = options.postOAuthComplete;
1324
+ onFailureRef.current = options.onFailure;
1325
+ onSuccessRef.current = options.onSuccess;
1326
+ const hasStartedExchangeRef = useRef(false);
1327
+ useEffect4(() => {
1328
+ if (typeof window === "undefined") return;
1329
+ const params = new URLSearchParams(window.location.search);
1330
+ if (!params.has("code") && !params.has("error")) return;
1331
+ if (hasStartedExchangeRef.current) return;
1332
+ const oauthResult = coinlist.completeOAuth();
1333
+ if (oauthResult.type === "error") {
1334
+ void Promise.resolve(onFailureRef.current(oauthResult.reason)).catch(
1335
+ () => {
1336
+ }
1337
+ );
1338
+ return;
1339
+ }
1340
+ hasStartedExchangeRef.current = true;
1341
+ let cancelled = false;
1342
+ void (async () => {
1343
+ try {
1344
+ const ok = await postOAuthCompleteRef.current({
1345
+ code: oauthResult.code,
1346
+ codeVerifier: oauthResult.codeVerifier
1347
+ });
1348
+ if (cancelled) return;
1349
+ if (!ok) {
1350
+ hasStartedExchangeRef.current = false;
1351
+ await onFailureRef.current("complete_request_failed");
1352
+ return;
1353
+ }
1354
+ await coinlist.init();
1355
+ if (cancelled) return;
1356
+ await onSuccessRef.current();
1357
+ } catch {
1358
+ if (cancelled) return;
1359
+ hasStartedExchangeRef.current = false;
1360
+ await onFailureRef.current("complete_request_failed");
1361
+ }
1362
+ })();
1363
+ return () => {
1364
+ cancelled = true;
1365
+ hasStartedExchangeRef.current = false;
1366
+ };
1367
+ }, [coinlist]);
1368
+ }
1369
+
1370
+ // src/client/hooks/useOfferDetails.ts
1371
+ import { useEffect as useEffect5, useState as useState5 } from "react";
1372
+ var LOADING_STATE3 = { type: "LOADING" };
1373
+ function useOfferDetails(offerId, options = {}) {
1374
+ const { coinlist, isReady } = useCoinList();
1375
+ const { data } = options;
1376
+ const [offerDetailsState, setOfferDetailsState] = useState5(
1377
+ data !== void 0 ? { type: "CONTENT", offerDetail: data } : LOADING_STATE3
1378
+ );
1379
+ useEffect5(() => {
1380
+ let isCancelled = false;
1381
+ if (!isReady) {
1382
+ if (data === void 0) {
1383
+ setOfferDetailsState(LOADING_STATE3);
1384
+ }
1385
+ return () => {
1386
+ isCancelled = true;
1387
+ };
1388
+ }
1389
+ if (data !== void 0) {
1390
+ return () => {
1391
+ isCancelled = true;
1392
+ };
1393
+ }
1394
+ setOfferDetailsState(LOADING_STATE3);
1395
+ coinlist.fetchOfferDetails(offerId).then((offerDetail) => {
1396
+ if (!isCancelled) {
1397
+ setOfferDetailsState({ type: "CONTENT", offerDetail });
1398
+ }
1399
+ }).catch((error) => {
1400
+ if (!isCancelled) {
1401
+ const reason = error instanceof NotAuthenticatedError ? "not-authenticated" : "generic-error";
1402
+ setOfferDetailsState({ type: "ERROR", reason });
1403
+ }
1404
+ });
1405
+ return () => {
1406
+ isCancelled = true;
1407
+ };
1408
+ }, [coinlist, isReady, offerId, data]);
1409
+ return { offerDetailsState };
1410
+ }
1411
+
1412
+ // src/client/hooks/useParticipations.ts
1413
+ import { useEffect as useEffect6, useState as useState6 } from "react";
1414
+ var LOADING_STATE4 = { type: "LOADING" };
1415
+ function useParticipations(offerId, options = {}) {
1416
+ const { coinlist, isReady } = useCoinList();
1417
+ const { data } = options;
1418
+ const [participationsState, setParticipationsState] = useState6(
1419
+ data !== void 0 ? { type: "CONTENT", participations: data } : LOADING_STATE4
1420
+ );
1421
+ useEffect6(() => {
1422
+ let isCancelled = false;
1423
+ if (!isReady) {
1424
+ if (data === void 0) {
1425
+ setParticipationsState(LOADING_STATE4);
1426
+ }
1427
+ return () => {
1428
+ isCancelled = true;
1429
+ };
1430
+ }
1431
+ if (data !== void 0) {
1432
+ return () => {
1433
+ isCancelled = true;
1434
+ };
1435
+ }
1436
+ setParticipationsState(LOADING_STATE4);
1437
+ coinlist.fetchParticipations(offerId).then((participations) => {
1438
+ if (!isCancelled) {
1439
+ setParticipationsState({ type: "CONTENT", participations });
1440
+ }
1441
+ }).catch((error) => {
1442
+ if (!isCancelled) {
1443
+ const reason = error instanceof NotAuthenticatedError ? "not-authenticated" : "generic-error";
1444
+ setParticipationsState({ type: "ERROR", reason });
1445
+ }
1446
+ });
1447
+ return () => {
1448
+ isCancelled = true;
1449
+ };
1450
+ }, [coinlist, isReady, offerId, data]);
1451
+ return { participationsState };
1452
+ }
59
1453
  export {
60
- AuthorizationCode,
61
- Blockchain,
62
1454
  ChecklistStatus,
63
- ClientId,
64
- ClientSecret,
65
- CodeChallenge,
66
- CodeVerifier,
1455
+ CoinListClientInitializationError,
1456
+ CoinListContext,
67
1457
  CoinListProvider,
68
- CreateParticipationParams,
69
- Cursor,
70
- FaqItem,
71
- Link,
72
- Milestone,
73
- OAuthRefreshToken,
74
- OAuthSession,
75
- Offer,
76
- OfferDetail,
77
- OfferId,
78
- OfferOption,
79
- OfferOptionId,
80
- OfferOptionSlug,
81
- OfferSlug,
82
- PaginatedResponse,
83
- PaginationParams,
84
- Participation,
85
- ParticipationId,
86
- ParticipationsPaginationParams,
87
- RedirectUri,
88
- Requirement,
89
- RequirementId,
1458
+ CoinListSignInCard,
1459
+ OAUTH_CODE_VERIFIER_KEY,
1460
+ OAUTH_STATE_KEY,
1461
+ OfferCard,
1462
+ OfferCardUi,
1463
+ OffersGrid,
1464
+ RequirementItem,
1465
+ RequirementItemUi,
90
1466
  RequirementStatus,
91
- RequirementStatusInfo,
92
1467
  RequirementVariant,
93
- TermItem,
94
- User,
95
- UserEmail,
96
- UserId,
97
- WalletAddress,
1468
+ RequirementsChecklist,
98
1469
  createCoinListClient,
99
- fetchAllPages,
100
1470
  useCoinList,
101
- useCoinListOfferDetails,
102
- useCoinListOffers,
103
- useCoinListRequirements,
104
- useCompleteCoinListOAuth,
105
- useParticipations
1471
+ useCompleteOAuth,
1472
+ useOfferDetails,
1473
+ useOffers,
1474
+ useParticipations,
1475
+ useRequirements
106
1476
  };
107
1477
  //# sourceMappingURL=index.js.map