@coinlist-co/react 0.0.1 → 0.1.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.
Files changed (53) hide show
  1. package/dist/{chunk-OETRTIQ4.js → chunk-74NFAQYP.js} +202 -7
  2. package/dist/chunk-74NFAQYP.js.map +1 -0
  3. package/dist/chunk-EPWEY23P.js +118 -0
  4. package/dist/chunk-EPWEY23P.js.map +1 -0
  5. package/dist/{chunk-NLJMUHOG.js → chunk-GRVV7NLV.js} +35 -6
  6. package/dist/chunk-GRVV7NLV.js.map +1 -0
  7. package/dist/chunk-Y5AHTOZO.js +76 -0
  8. package/dist/chunk-Y5AHTOZO.js.map +1 -0
  9. package/dist/client/components/index.cjs +188 -2
  10. package/dist/client/components/index.cjs.map +1 -1
  11. package/dist/client/components/index.d.cts +41 -1
  12. package/dist/client/components/index.d.ts +41 -1
  13. package/dist/client/components/index.js +149 -4
  14. package/dist/client/components/index.js.map +1 -1
  15. package/dist/client/core/index.cjs +229 -6
  16. package/dist/client/core/index.cjs.map +1 -1
  17. package/dist/client/core/index.d.cts +4 -3
  18. package/dist/client/core/index.d.ts +4 -3
  19. package/dist/client/core/index.js +2 -2
  20. package/dist/client/hooks/index.cjs +136 -2
  21. package/dist/client/hooks/index.cjs.map +1 -1
  22. package/dist/client/hooks/index.d.cts +52 -11
  23. package/dist/client/hooks/index.d.ts +52 -11
  24. package/dist/client/hooks/index.js +13 -5
  25. package/dist/client/index.cjs +287 -8
  26. package/dist/client/index.cjs.map +1 -1
  27. package/dist/client/index.d.cts +6 -4
  28. package/dist/client/index.d.ts +6 -4
  29. package/dist/client/index.js +7 -5
  30. package/dist/client/styles.css +1 -1
  31. package/dist/coinlist-client-BJx6w3Xb.d.cts +271 -0
  32. package/dist/coinlist-client-CN48gVhi.d.ts +271 -0
  33. package/dist/newtype-yKTvFdg_.d.cts +6 -0
  34. package/dist/newtype-yKTvFdg_.d.ts +6 -0
  35. package/dist/{oauth-session-g0OPJNKn.d.ts → oauth-session-DgItaMKN.d.cts} +2 -5
  36. package/dist/{oauth-session-g0OPJNKn.d.cts → oauth-session-DnKDI5ou.d.ts} +2 -5
  37. package/dist/offer-CkZxbS45.d.ts +30 -0
  38. package/dist/offer-Ds1HdaFD.d.cts +30 -0
  39. package/dist/server/index.cjs +32 -3
  40. package/dist/server/index.cjs.map +1 -1
  41. package/dist/server/index.d.cts +3 -2
  42. package/dist/server/index.d.ts +3 -2
  43. package/dist/server/index.js +3 -3
  44. package/dist/server/index.js.map +1 -1
  45. package/dist/useCompleteCoinListOAuth-B2Pl0vQo.d.cts +55 -0
  46. package/dist/useCompleteCoinListOAuth-wkCa45w3.d.ts +55 -0
  47. package/package.json +1 -1
  48. package/dist/chunk-F56RBKNS.js +0 -63
  49. package/dist/chunk-F56RBKNS.js.map +0 -1
  50. package/dist/chunk-NLJMUHOG.js.map +0 -1
  51. package/dist/chunk-OETRTIQ4.js.map +0 -1
  52. package/dist/coinlist-client-BH_Cn_7u.d.cts +0 -71
  53. package/dist/coinlist-client-fkB3J-2a.d.ts +0 -71
@@ -1,14 +1,55 @@
1
- import { a as CoinListClient } from '../../coinlist-client-fkB3J-2a.js';
2
- import '../../oauth-session-g0OPJNKn.js';
1
+ export { C as CompleteCoinListOAuthFailureReason, U as UseCompleteCoinListOAuthOptions, u as useCoinList, a as useCompleteCoinListOAuth } from '../../useCompleteCoinListOAuth-wkCa45w3.js';
2
+ import { O as OfferId, a as Offer } from '../../offer-CkZxbS45.js';
3
+ import { d as OfferDetail } from '../../coinlist-client-CN48gVhi.js';
4
+ import '../../oauth-session-DnKDI5ou.js';
5
+ import '../../newtype-yKTvFdg_.js';
3
6
 
4
- interface UseCoinListResult {
5
- /**
6
- * Indicates whether the CoinList client has been initialized and ready to use.
7
- * While isReady is false, then the current user state will be `unknown`.
8
- */
9
- isReady: boolean;
10
- coinlist: CoinListClient;
7
+ type LoadOfferDetailsReason = 'not-authenticated' | 'generic-error';
8
+ type LoadOfferDetailsState = {
9
+ type: 'LOADING';
10
+ } | {
11
+ type: 'ERROR';
12
+ reason: LoadOfferDetailsReason;
13
+ } | {
14
+ type: 'CONTENT';
15
+ offerDetail: OfferDetail;
16
+ };
17
+ interface UseCoinListOfferDetailsResult {
18
+ offerDetailsState: LoadOfferDetailsState;
11
19
  }
12
- declare function useCoinList(): UseCoinListResult;
20
+ /**
21
+ * Loads CoinList offer details for a given offer id and exposes a UI-friendly state.
22
+ *
23
+ * Returns `LOADING` while CoinList is initializing or while details are being fetched.
24
+ * Returns `CONTENT` with the offer when `fetchOfferDetails()` succeeds.
25
+ * Returns `ERROR` with:
26
+ * - `not-authenticated` when fetching fails with {@link NotAuthenticatedError}
27
+ * - `generic-error` for any other failure
28
+ */
29
+ declare function useCoinListOfferDetails(offerId: OfferId): UseCoinListOfferDetailsResult;
13
30
 
14
- export { useCoinList };
31
+ type LoadOffersReason = 'not-authenticated' | 'generic-error';
32
+ type LoadOffersState = {
33
+ type: 'LOADING';
34
+ } | {
35
+ type: 'ERROR';
36
+ reason: LoadOffersReason;
37
+ } | {
38
+ type: 'CONTENT';
39
+ offers: Offer[];
40
+ };
41
+ interface UseCoinListOffersResult {
42
+ offersState: LoadOffersState;
43
+ }
44
+ /**
45
+ * Loads all CoinList offers for the current user and exposes a UI-friendly state.
46
+ *
47
+ * Returns `LOADING` while CoinList is initializing or while offers are being fetched.
48
+ * Returns `CONTENT` with all offers when `fetchAllOffers()` succeeds.
49
+ * Returns `ERROR` with:
50
+ * - `not-authenticated` when fetching fails with {@link NotAuthenticatedError}
51
+ * - `generic-error` for any other failure
52
+ */
53
+ declare function useCoinListOffers(): UseCoinListOffersResult;
54
+
55
+ export { useCoinListOfferDetails, useCoinListOffers };
@@ -1,9 +1,17 @@
1
1
  import {
2
- useCoinList
3
- } from "../../chunk-F56RBKNS.js";
4
- import "../../chunk-OETRTIQ4.js";
5
- import "../../chunk-NLJMUHOG.js";
2
+ useCoinListOfferDetails,
3
+ useCoinListOffers
4
+ } from "../../chunk-Y5AHTOZO.js";
5
+ import {
6
+ useCoinList,
7
+ useCompleteCoinListOAuth
8
+ } from "../../chunk-EPWEY23P.js";
9
+ import "../../chunk-74NFAQYP.js";
10
+ import "../../chunk-GRVV7NLV.js";
6
11
  export {
7
- useCoinList
12
+ useCoinList,
13
+ useCoinListOfferDetails,
14
+ useCoinListOffers,
15
+ useCompleteCoinListOAuth
8
16
  };
9
17
  //# sourceMappingURL=index.js.map
@@ -33,7 +33,8 @@ __export(client_exports, {
33
33
  UserEmail: () => UserEmail,
34
34
  UserId: () => UserId,
35
35
  createCoinListClient: () => createCoinListClient,
36
- useCoinList: () => useCoinList
36
+ useCoinList: () => useCoinList,
37
+ useCompleteCoinListOAuth: () => useCompleteCoinListOAuth
37
38
  });
38
39
  module.exports = __toCommonJS(client_exports);
39
40
 
@@ -127,7 +128,10 @@ async function makeRequest(request) {
127
128
  if (request.method === "POST" && request.body !== void 0) {
128
129
  init.body = JSON.stringify(request.body);
129
130
  }
130
- const response = await fetch(request.url, init);
131
+ const response = await fetch(
132
+ buildUrlWithQueryParams(request.url, request.queryParams),
133
+ init
134
+ );
131
135
  const responseHeaders = headersToRecord(response.headers);
132
136
  if (response.status === 204 || response.status === 205) {
133
137
  return {
@@ -152,6 +156,32 @@ async function makeRequest(request) {
152
156
  headers: responseHeaders
153
157
  };
154
158
  }
159
+ function buildUrlWithQueryParams(url, queryParams) {
160
+ if (!queryParams) {
161
+ return url;
162
+ }
163
+ const searchParams = new URLSearchParams();
164
+ for (const [key, value] of Object.entries(queryParams)) {
165
+ if (value === void 0 || value === null) {
166
+ continue;
167
+ }
168
+ if (Array.isArray(value)) {
169
+ for (const item of value) {
170
+ if (item === void 0 || item === null) {
171
+ continue;
172
+ }
173
+ searchParams.append(key, String(item));
174
+ }
175
+ continue;
176
+ }
177
+ searchParams.append(key, String(value));
178
+ }
179
+ const queryString = searchParams.toString();
180
+ if (!queryString) {
181
+ return url;
182
+ }
183
+ return url.includes("?") ? `${url}&${queryString}` : `${url}?${queryString}`;
184
+ }
155
185
  function headersToRecord(headers) {
156
186
  const record = {};
157
187
  headers.forEach((value, key) => {
@@ -241,6 +271,13 @@ function getUUIDv4() {
241
271
  }
242
272
  return `${Date.now()}-${Math.random().toString(36).slice(2, 12)}`;
243
273
  }
274
+ function notBlankStringOrNull(value) {
275
+ if (value?.trim()) {
276
+ return value;
277
+ } else {
278
+ return null;
279
+ }
280
+ }
244
281
 
245
282
  // src/client/core/api/middleware/device-id.ts
246
283
  var DEVICE_ID_KEY = "coinlist.device_id";
@@ -254,12 +291,12 @@ function getDeviceId() {
254
291
  }
255
292
 
256
293
  // src/shared/api/frontline.ts
257
- var BASE_URL = "https://mobile-api.coinlist.co";
294
+ var PUBLIC_API_BASE_URL = "https://api.coinlist.co";
258
295
  var HEADER_API_VERSION = "X-API-Version";
259
296
  var HEADER_USER_AGENT = "User-Agent";
260
297
  var HEADER_IDEMPOTENCY_KEY = "Idempotency-Key";
261
298
  var API_VERSION = "2025-10-17";
262
- var COINLIST_OAUTH_URL = "https://coinlist.co/oauth/authorize";
299
+ var COINLIST_OAUTH_PAGE_URL = "https://coinlist.co/oauth/authorize";
263
300
 
264
301
  // src/client/core/api/middleware/user-agent.ts
265
302
  var userAgentMiddleware = async (request) => {
@@ -434,6 +471,51 @@ var ApiClient = class {
434
471
  }
435
472
  };
436
473
 
474
+ // src/shared/api/pagination.ts
475
+ var Cursor = (value) => value;
476
+ async function fetchAllPages(fetchPage) {
477
+ const items = [];
478
+ let cursor = null;
479
+ do {
480
+ const page = await fetchPage({
481
+ after: cursor ?? void 0
482
+ });
483
+ items.push(...page.data);
484
+ cursor = page.startingAfter;
485
+ } while (cursor);
486
+ return items;
487
+ }
488
+ var PaginatedResponse = {
489
+ fromDto: (dto, itemMapper) => ({
490
+ data: dto.data.map(itemMapper),
491
+ startingAfter: dto.starting_after ? Cursor(dto.starting_after) : null,
492
+ startingBefore: dto.starting_before ? Cursor(dto.starting_before) : null
493
+ })
494
+ };
495
+ var PaginationParams = {
496
+ toQueryParams: (params) => {
497
+ const queryParams = {};
498
+ if (params.after) {
499
+ queryParams.starting_after = params.after;
500
+ }
501
+ if (params.before) {
502
+ queryParams.starting_before = params.before;
503
+ }
504
+ if (params.limit) {
505
+ queryParams.limit = params.limit;
506
+ }
507
+ return queryParams;
508
+ }
509
+ };
510
+
511
+ // src/shared/types/errors.ts
512
+ var NotAuthenticatedError = class extends Error {
513
+ constructor(message = "The user is not authenticated. Go through the OAuth flow first!") {
514
+ super(message);
515
+ this.name = "NotAuthenticatedError";
516
+ }
517
+ };
518
+
437
519
  // src/shared/types/oauth.ts
438
520
  var AuthorizationCode = (value) => value;
439
521
  var CodeVerifier = (value) => value;
@@ -442,6 +524,115 @@ var RedirectUri = (value) => value;
442
524
  var ClientId = (value) => value;
443
525
  var ClientSecret = (value) => value;
444
526
 
527
+ // src/shared/types/offer.ts
528
+ var OfferId = (value) => value;
529
+ var OfferSlug = (value) => value;
530
+ var Offer = {
531
+ fromDto: (dto) => ({
532
+ id: OfferId(dto.id),
533
+ slug: OfferSlug(dto.slug),
534
+ tagline: notBlankStringOrNull(dto.tagline),
535
+ bannerUrl: notBlankStringOrNull(dto.banner_url),
536
+ logoUrl: notBlankStringOrNull(dto.logo_url),
537
+ startsAt: new Date(dto.starts_at),
538
+ endsAt: new Date(dto.ends_at)
539
+ })
540
+ };
541
+
542
+ // src/shared/types/asset.ts
543
+ var AssetId = (value) => value;
544
+ var AssetCode = (value) => value;
545
+ var Asset = {
546
+ fromDto: (dto) => ({
547
+ id: AssetId(dto.id),
548
+ code: AssetCode(dto.code),
549
+ name: dto.name,
550
+ fractionalDigits: dto.fractional_digits
551
+ })
552
+ };
553
+
554
+ // src/shared/types/offer-detail.ts
555
+ var OfferOptionId = (value) => value;
556
+ var OfferOptionSlug = (value) => value;
557
+ var OfferDetail = {
558
+ fromDto: (dto) => {
559
+ if (!Array.isArray(dto.funding_assets)) {
560
+ throw new Error(`funding_assets must be an array`);
561
+ }
562
+ if (!Array.isArray(dto.options)) {
563
+ throw new Error(`options must be an array`);
564
+ }
565
+ if (!Array.isArray(dto.terms)) {
566
+ throw new Error(`terms must be an array`);
567
+ }
568
+ if (!Array.isArray(dto.links)) {
569
+ throw new Error(`links must be an array`);
570
+ }
571
+ if (!Array.isArray(dto.faqs)) {
572
+ throw new Error(`faqs must be an array`);
573
+ }
574
+ if (!Array.isArray(dto.milestones)) {
575
+ throw new Error(`milestones must be an array`);
576
+ }
577
+ return {
578
+ id: OfferId(dto.id),
579
+ slug: OfferSlug(dto.slug),
580
+ name: dto.name,
581
+ asset: Asset.fromDto(dto.asset),
582
+ fundingAssets: dto.funding_assets.map(Asset.fromDto),
583
+ about: notBlankStringOrNull(dto.about),
584
+ tagline: notBlankStringOrNull(dto.tagline),
585
+ bannerUrl: notBlankStringOrNull(dto.banner_url),
586
+ logoUrl: notBlankStringOrNull(dto.logo_url),
587
+ category: notBlankStringOrNull(dto.category),
588
+ startsAt: new Date(dto.starts_at),
589
+ endsAt: new Date(dto.ends_at),
590
+ faqs: dto.faqs.map(FaqItem.fromDto),
591
+ links: dto.links.map(Link.fromDto),
592
+ milestones: dto.milestones.map(Milestone.fromDto),
593
+ options: dto.options.map(OfferOption.fromDto),
594
+ terms: dto.terms.map(TermItem.fromDto)
595
+ };
596
+ }
597
+ };
598
+ var OfferOption = {
599
+ fromDto: (dto) => ({
600
+ id: OfferOptionId(dto.id),
601
+ slug: OfferOptionSlug(dto.slug),
602
+ bidIncrement: dto.bid_increment,
603
+ floorPriceUsd: dto.floor_price_usd,
604
+ minimumPurchaseUsd: dto.minimum_purchase_usd,
605
+ priceUsd: dto.price_usd,
606
+ saleAgreementUrl: notBlankStringOrNull(dto.sale_agreement_url),
607
+ totalTokenSupply: dto.total_token_supply
608
+ })
609
+ };
610
+ var FaqItem = {
611
+ fromDto: (dto) => ({
612
+ question: notBlankStringOrNull(dto.question),
613
+ answer: notBlankStringOrNull(dto.answer)
614
+ })
615
+ };
616
+ var Link = {
617
+ fromDto: (dto) => ({
618
+ label: notBlankStringOrNull(dto.label),
619
+ url: notBlankStringOrNull(dto.url)
620
+ })
621
+ };
622
+ var TermItem = {
623
+ fromDto: (dto) => ({
624
+ key: notBlankStringOrNull(dto.key),
625
+ value: notBlankStringOrNull(dto.value)
626
+ })
627
+ };
628
+ var Milestone = {
629
+ fromDto: (dto) => ({
630
+ name: notBlankStringOrNull(dto.name),
631
+ schedule: notBlankStringOrNull(dto.schedule),
632
+ status: dto.status
633
+ })
634
+ };
635
+
445
636
  // src/client/core/coinlist-client.ts
446
637
  var OAUTH_STATE_KEY = "coinlist.oauth_state";
447
638
  var OAUTH_CODE_VERIFIER_KEY = "coinlist.oauth_code_verifier";
@@ -450,9 +641,9 @@ var CoinListClientImpl = class {
450
641
  this._config = _config;
451
642
  /** Cached access token: undefined = loading, null = no token, AccessToken = logged in */
452
643
  this._accessToken = void 0;
453
- this._api = new ApiClient(
644
+ this.api = new ApiClient(
454
645
  {
455
- baseUrl: _config.baseUrl || BASE_URL,
646
+ baseUrl: _config.baseUrl || PUBLIC_API_BASE_URL,
456
647
  xApiVersion: API_VERSION
457
648
  },
458
649
  this.fetchAccessToken.bind(this)
@@ -494,11 +685,16 @@ var CoinListClientImpl = class {
494
685
  code_challenge_method: "S256",
495
686
  state
496
687
  });
497
- const url = `${COINLIST_OAUTH_URL}?${params.toString()}`;
688
+ const oauthPageUrl = this._config.authorizationPageUrl ?? COINLIST_OAUTH_PAGE_URL;
689
+ const url = `${oauthPageUrl}?${params.toString()}`;
498
690
  window.location.href = url;
499
691
  }
500
692
  completeOAuth() {
501
693
  const params = new URLSearchParams(window.location.search);
694
+ const coinlistError = params.get("error");
695
+ if (coinlistError) {
696
+ return { type: "error", reason: "user_canceled" };
697
+ }
502
698
  const stateParam = params.get("state");
503
699
  if (!stateParam) {
504
700
  return { type: "error", reason: "missing_state" };
@@ -524,6 +720,34 @@ var CoinListClientImpl = class {
524
720
  logout() {
525
721
  this._accessToken = null;
526
722
  }
723
+ async fetchAllOffers() {
724
+ this.ensureAuthenticated();
725
+ return await fetchAllPages(this.fetchOffersPage.bind(this));
726
+ }
727
+ async fetchOffersPage(params) {
728
+ this.ensureAuthenticated();
729
+ const pageDto = await this.api.send({
730
+ method: "GET",
731
+ url: "/v1/offers",
732
+ queryParams: PaginationParams.toQueryParams(params),
733
+ attributes: Attributes.protected()
734
+ });
735
+ return PaginatedResponse.fromDto(pageDto, Offer.fromDto);
736
+ }
737
+ async fetchOfferDetails(id) {
738
+ this.ensureAuthenticated();
739
+ const offerDetailDto = await this.api.send({
740
+ method: "GET",
741
+ url: `/v1/offers/${id}`,
742
+ attributes: Attributes.protected()
743
+ });
744
+ return OfferDetail.fromDto(offerDetailDto);
745
+ }
746
+ ensureAuthenticated() {
747
+ if (this.getAuthState() !== "logged-in") {
748
+ throw new NotAuthenticatedError();
749
+ }
750
+ }
527
751
  };
528
752
  function createCoinListClient(config) {
529
753
  return new CoinListClientImpl(config);
@@ -577,6 +801,60 @@ var CoinListClientInitializationError = class extends Error {
577
801
  }
578
802
  };
579
803
 
804
+ // src/client/hooks/useCompleteCoinListOAuth.ts
805
+ var import_react3 = require("react");
806
+ function useCompleteCoinListOAuth(options) {
807
+ const { coinlist } = useCoinList();
808
+ const postOAuthCompleteRef = (0, import_react3.useRef)(options.postOAuthComplete);
809
+ const onFailureRef = (0, import_react3.useRef)(options.onFailure);
810
+ const onSuccessRef = (0, import_react3.useRef)(options.onSuccess);
811
+ postOAuthCompleteRef.current = options.postOAuthComplete;
812
+ onFailureRef.current = options.onFailure;
813
+ onSuccessRef.current = options.onSuccess;
814
+ const hasStartedExchangeRef = (0, import_react3.useRef)(false);
815
+ (0, import_react3.useEffect)(() => {
816
+ if (typeof window === "undefined") return;
817
+ const params = new URLSearchParams(window.location.search);
818
+ if (!params.has("code") && !params.has("error")) return;
819
+ if (hasStartedExchangeRef.current) return;
820
+ const oauthResult = coinlist.completeOAuth();
821
+ if (oauthResult.type === "error") {
822
+ void Promise.resolve(onFailureRef.current(oauthResult.reason)).catch(
823
+ () => {
824
+ }
825
+ );
826
+ return;
827
+ }
828
+ hasStartedExchangeRef.current = true;
829
+ let cancelled = false;
830
+ void (async () => {
831
+ try {
832
+ const ok = await postOAuthCompleteRef.current({
833
+ code: oauthResult.code,
834
+ codeVerifier: oauthResult.codeVerifier
835
+ });
836
+ if (cancelled) return;
837
+ if (!ok) {
838
+ hasStartedExchangeRef.current = false;
839
+ await onFailureRef.current("complete_request_failed");
840
+ return;
841
+ }
842
+ await coinlist.init();
843
+ if (cancelled) return;
844
+ await onSuccessRef.current();
845
+ } catch {
846
+ if (cancelled) return;
847
+ hasStartedExchangeRef.current = false;
848
+ await onFailureRef.current("complete_request_failed");
849
+ }
850
+ })();
851
+ return () => {
852
+ cancelled = true;
853
+ hasStartedExchangeRef.current = false;
854
+ };
855
+ }, [coinlist]);
856
+ }
857
+
580
858
  // src/shared/types/oauth-session.ts
581
859
  var OAuthRefreshToken = (value) => value;
582
860
  var OAuthSession = {
@@ -616,6 +894,7 @@ var User = {
616
894
  UserEmail,
617
895
  UserId,
618
896
  createCoinListClient,
619
- useCoinList
897
+ useCoinList,
898
+ useCompleteCoinListOAuth
620
899
  });
621
900
  //# sourceMappingURL=index.cjs.map