@echoxyz/sonar-react 0.4.2 → 0.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @echoxyz/sonar-react
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1ccce38: Remove redundant saleUUID from constructor/Provider
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [1ccce38]
12
+ - @echoxyz/sonar-core@0.4.0
13
+
14
+ ## 0.5.0
15
+
16
+ ### Minor Changes
17
+
18
+ - 2e6e793: Introduce useSonarPurchase hook
19
+
3
20
  ## 0.4.2
4
21
 
5
22
  ### Patch Changes
package/README.md CHANGED
@@ -4,7 +4,7 @@ React provider and hooks for Echo’s Sonar APIs, built on `@echoxyz/sonar-core`
4
4
 
5
5
  - Framework/router agnostic (works with React Router, Next.js, etc.).
6
6
  - Handles PKCE OAuth redirect flow and token storage for the browser.
7
- - Exposes a ready-to-use API client bound to a single `saleUUID`.
7
+ - Exposes a ready-to-use API client bound to a single Sonar oauth client.
8
8
 
9
9
  ## Install
10
10
 
@@ -25,7 +25,6 @@ export function AppRoot({ children }: { children: React.ReactNode }) {
25
25
  return (
26
26
  <SonarProvider
27
27
  config={{
28
- saleUUID: "<your-sale-uuid>",
29
28
  clientUUID: "<your-oauth-client-id>",
30
29
  redirectURI: window.location.origin + "/oauth/callback",
31
30
  // Optional:
@@ -118,52 +117,76 @@ const ExampleEntityPanel = () => {
118
117
 
119
118
  ```
120
119
 
121
- 5. Optional: call APIs with the low level client to implement the purchase flow:
120
+ 5. Implement the purchase flow
122
121
 
123
122
  ```tsx
124
- import { useEffect } from "react";
125
- import { useSonarEntity } from "./hooks/useSonarEntity";
126
- import { useAccount } from "wagmi";
127
-
128
- export function Example() {
129
- const { address, isConnected } = useAccount();
130
- // Would normally need to handle loading and error states like above
131
- const { entity } = useSonarEntity({
132
- saleUUID: "<your-sale-uuid>",
133
- wallet: { address, isConnected },
134
- });
135
- const client = useSonarClient();
123
+ function Example({
124
+ entityUUID,
125
+ walletAddress,
126
+ }: {
127
+ entityUUID: string;
128
+ walletAddress: string;
129
+ }) {
130
+ const sonarPurchaser = useSonarPurchase({
131
+ saleUUID: sonarConfig.saleUUID,
132
+ entityUUID,
133
+ entityType,
134
+ walletAddress,
135
+ });
136
+
137
+ if (sonarPurchaser.loading) {
138
+ return <p>Loading...</p>;
139
+ }
140
+
141
+ if (sonarPurchaser.error) {
142
+ return <p>Error: {error.message}</p>;
143
+ }
144
+
145
+ return (
146
+ <div>
147
+ {sonarPurchaser.readyToPurchase && (
148
+ <PurchaseButton
149
+ generatePurchasePermit={sonarPurchaser.generatePurchasePermit}
150
+ />
151
+ )}
152
+
153
+ {!sonarPurchaser.readyToPurchase &&
154
+ sonarPurchaser.failureReason ===
155
+ PrePurchaseFailureReason.REQUIRES_LIVENESS && (
156
+ <button
157
+ onClick={() => {
158
+ window.open(prePurchaseCheckResult.LivenessCheckURL, "_blank");
159
+ }}
160
+ >
161
+ Complete liveness check to purchase
162
+ </button>
163
+ )}
164
+ </div>
165
+ );
166
+ }
136
167
 
137
- useEffect(() => {
138
- if (!entity) {
139
- return;
140
- }
168
+ function PurchaseButton({
169
+ generatePurchasePermit,
170
+ }: {
171
+ generatePurchasePermit: () => Promise<GeneratePurchasePermitResponse>;
172
+ }) {
173
+ const purchase = async () => {
174
+ const response = await generatePurchasePermit();
175
+ const r = response as unknown as {
176
+ Signature: string;
177
+ PermitJSON: AllocationPermit;
178
+ };
179
+ if (r.Signature && r.PermitJSON) {
180
+ console.log(permit.Signature, permit.Permit);
181
+ return;
182
+ }
183
+ };
141
184
 
142
- (async () => {
143
- const pre = await client.prePurchaseCheck({
144
- saleUUID: "<your-sale-uuid>",
145
- entityUUID: entity.EntityUUID,
146
- walletAddress: "0x1234...abcd" as `0x${string}`,
147
- });
148
-
149
- if (pre.ReadyToPurchase) {
150
- const permit = await client.generatePurchasePermit({
151
- saleUUID: "<your-sale-uuid>",
152
- entityUUID: entity.EntityUUID,
153
- walletAddress: "0x1234...abcd" as `0x${string}`,
154
- });
155
- console.log(permit.Signature, permit.Permit);
156
- }
157
-
158
- const alloc = await client.fetchAllocation({
159
- saleUUID: "<your-sale-uuid>",
160
- walletAddress,
161
- });
162
- console.log(alloc);
163
- })();
164
- }, [entity]);
165
-
166
- return null;
185
+ return (
186
+ <button onClick={purchase}>
187
+ Purchase
188
+ </button>
189
+ );
167
190
  }
168
191
  ```
169
192
 
@@ -171,7 +194,6 @@ export function Example() {
171
194
 
172
195
  - `SonarProvider`
173
196
  - Props `config`:
174
- - `saleUUID: string` (required) – Sale to scope API calls against.
175
197
  - `clientUUID: string` (required) – Echo OAuth Client ID.
176
198
  - `redirectURI: string` (required) – Your OAuth callback URI.
177
199
  - `apiURL?: string` (default: `https://api.echo.xyz`) – API base URL.
package/dist/index.cjs CHANGED
@@ -23,7 +23,8 @@ __export(index_exports, {
23
23
  SonarProvider: () => SonarProvider,
24
24
  useSonarAuth: () => useSonarAuth,
25
25
  useSonarClient: () => useSonarClient,
26
- useSonarEntity: () => useSonarEntity
26
+ useSonarEntity: () => useSonarEntity,
27
+ useSonarPurchase: () => useSonarPurchase
27
28
  });
28
29
  module.exports = __toCommonJS(index_exports);
29
30
 
@@ -37,14 +38,13 @@ function SonarProvider({ children, config }) {
37
38
  const [authState, setAuthState] = (0, import_react.useState)({ token: void 0, ready: false });
38
39
  const client = (0, import_react.useMemo)(() => {
39
40
  return (0, import_sonar_core.createClient)({
40
- saleUUID: config.saleUUID,
41
41
  apiURL: config.apiURL,
42
42
  tokenKey: config.tokenStorageKey,
43
43
  onTokenChange: (nextToken) => {
44
44
  setAuthState({ token: nextToken ?? void 0, ready: true });
45
45
  }
46
46
  });
47
- }, [config.apiURL, config.saleUUID, config.tokenStorageKey]);
47
+ }, [config.apiURL, config.tokenStorageKey]);
48
48
  (0, import_react.useEffect)(() => {
49
49
  setAuthState({ token: client.getToken() ?? void 0, ready: true });
50
50
  }, [client]);
@@ -56,7 +56,6 @@ function SonarProvider({ children, config }) {
56
56
  window.sessionStorage.setItem("sonar:oauth:state", state);
57
57
  window.sessionStorage.setItem("sonar:oauth:verifier", codeVerifier);
58
58
  const url = (0, import_sonar_core.buildAuthorizationUrl)({
59
- saleUUID: config.saleUUID,
60
59
  clientUUID: config.clientUUID,
61
60
  redirectURI: config.redirectURI,
62
61
  state,
@@ -64,7 +63,7 @@ function SonarProvider({ children, config }) {
64
63
  frontendURL: config.frontendURL
65
64
  });
66
65
  window.location.href = url.toString();
67
- }, [config.saleUUID, config.clientUUID, config.redirectURI, config.frontendURL]);
66
+ }, [config.clientUUID, config.redirectURI, config.frontendURL]);
68
67
  const completeOAuth = (0, import_react.useCallback)(
69
68
  async ({ code, state }) => {
70
69
  if (typeof window === "undefined") {
@@ -192,10 +191,70 @@ function useSonarEntity(args) {
192
191
  error: state.error
193
192
  };
194
193
  }
194
+ function useSonarPurchase(args) {
195
+ const saleUUID = args.saleUUID;
196
+ const entityUUID = args.entityUUID;
197
+ const walletAddress = args.walletAddress;
198
+ const client = useSonarClient();
199
+ const [state, setState] = (0, import_react2.useState)({
200
+ loading: true,
201
+ readyToPurchase: false,
202
+ error: void 0
203
+ });
204
+ const generatePurchasePermit = (0, import_react2.useCallback)(() => {
205
+ return client.generatePurchasePermit({
206
+ saleUUID,
207
+ entityUUID,
208
+ walletAddress
209
+ });
210
+ }, [client, saleUUID, entityUUID, walletAddress]);
211
+ (0, import_react2.useEffect)(() => {
212
+ const fetchPurchaseData = async () => {
213
+ setState({
214
+ loading: true,
215
+ readyToPurchase: false,
216
+ error: void 0
217
+ });
218
+ try {
219
+ const response = await client.prePurchaseCheck({
220
+ saleUUID,
221
+ entityUUID,
222
+ walletAddress
223
+ });
224
+ if (response.ReadyToPurchase) {
225
+ setState({
226
+ loading: false,
227
+ readyToPurchase: true,
228
+ generatePurchasePermit,
229
+ error: void 0
230
+ });
231
+ } else {
232
+ setState({
233
+ loading: false,
234
+ readyToPurchase: false,
235
+ failureReason: response.FailureReason,
236
+ livenessCheckURL: response.LivenessCheckURL,
237
+ error: void 0
238
+ });
239
+ }
240
+ } catch (err) {
241
+ const error = err instanceof Error ? err : new Error(String(err));
242
+ setState({
243
+ loading: false,
244
+ readyToPurchase: false,
245
+ error
246
+ });
247
+ }
248
+ };
249
+ fetchPurchaseData();
250
+ }, [saleUUID, entityUUID, walletAddress, client, generatePurchasePermit]);
251
+ return state;
252
+ }
195
253
  // Annotate the CommonJS export names for ESM import in node:
196
254
  0 && (module.exports = {
197
255
  SonarProvider,
198
256
  useSonarAuth,
199
257
  useSonarClient,
200
- useSonarEntity
258
+ useSonarEntity,
259
+ useSonarPurchase
201
260
  });
package/dist/index.d.cts CHANGED
@@ -1,13 +1,12 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React from 'react';
3
- import { SonarClient, EntityDetails } from '@echoxyz/sonar-core';
3
+ import { SonarClient, EntityDetails, GeneratePurchasePermitResponse, PrePurchaseFailureReason } from '@echoxyz/sonar-core';
4
4
 
5
5
  type SonarProviderProps = {
6
6
  children: React.ReactNode;
7
7
  config: SonarProviderConfig;
8
8
  };
9
9
  type SonarProviderConfig = {
10
- saleUUID: string;
11
10
  clientUUID: string;
12
11
  redirectURI: string;
13
12
  apiURL?: string;
@@ -39,5 +38,34 @@ declare function useSonarEntity(args: {
39
38
  saleUUID: string;
40
39
  walletAddress?: string;
41
40
  }): UseSonarEntityResult;
41
+ type UseSonarPurchaseResultReadyToPurchase = {
42
+ loading: false;
43
+ readyToPurchase: true;
44
+ error: undefined;
45
+ generatePurchasePermit: () => Promise<GeneratePurchasePermitResponse>;
46
+ };
47
+ type UseSonarPurchaseResultNotReadyToPurchase = {
48
+ loading: false;
49
+ readyToPurchase: false;
50
+ error: undefined;
51
+ failureReason: PrePurchaseFailureReason;
52
+ livenessCheckURL: string;
53
+ };
54
+ type UseSonarPurchaseResultError = {
55
+ loading: false;
56
+ readyToPurchase: false;
57
+ error: Error;
58
+ };
59
+ type UseSonarPurchaseResultLoading = {
60
+ loading: true;
61
+ readyToPurchase: false;
62
+ error: undefined;
63
+ };
64
+ type UseSonarPurchaseResult = UseSonarPurchaseResultLoading | UseSonarPurchaseResultReadyToPurchase | UseSonarPurchaseResultNotReadyToPurchase | UseSonarPurchaseResultError;
65
+ declare function useSonarPurchase(args: {
66
+ saleUUID: string;
67
+ entityUUID: string;
68
+ walletAddress: string;
69
+ }): UseSonarPurchaseResult;
42
70
 
43
- export { SonarProvider, type SonarProviderConfig, type UseSonarEntityResult, useSonarAuth, useSonarClient, useSonarEntity };
71
+ export { SonarProvider, type SonarProviderConfig, type UseSonarEntityResult, type UseSonarPurchaseResult, type UseSonarPurchaseResultError, type UseSonarPurchaseResultLoading, type UseSonarPurchaseResultNotReadyToPurchase, type UseSonarPurchaseResultReadyToPurchase, useSonarAuth, useSonarClient, useSonarEntity, useSonarPurchase };
package/dist/index.d.ts CHANGED
@@ -1,13 +1,12 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React from 'react';
3
- import { SonarClient, EntityDetails } from '@echoxyz/sonar-core';
3
+ import { SonarClient, EntityDetails, GeneratePurchasePermitResponse, PrePurchaseFailureReason } from '@echoxyz/sonar-core';
4
4
 
5
5
  type SonarProviderProps = {
6
6
  children: React.ReactNode;
7
7
  config: SonarProviderConfig;
8
8
  };
9
9
  type SonarProviderConfig = {
10
- saleUUID: string;
11
10
  clientUUID: string;
12
11
  redirectURI: string;
13
12
  apiURL?: string;
@@ -39,5 +38,34 @@ declare function useSonarEntity(args: {
39
38
  saleUUID: string;
40
39
  walletAddress?: string;
41
40
  }): UseSonarEntityResult;
41
+ type UseSonarPurchaseResultReadyToPurchase = {
42
+ loading: false;
43
+ readyToPurchase: true;
44
+ error: undefined;
45
+ generatePurchasePermit: () => Promise<GeneratePurchasePermitResponse>;
46
+ };
47
+ type UseSonarPurchaseResultNotReadyToPurchase = {
48
+ loading: false;
49
+ readyToPurchase: false;
50
+ error: undefined;
51
+ failureReason: PrePurchaseFailureReason;
52
+ livenessCheckURL: string;
53
+ };
54
+ type UseSonarPurchaseResultError = {
55
+ loading: false;
56
+ readyToPurchase: false;
57
+ error: Error;
58
+ };
59
+ type UseSonarPurchaseResultLoading = {
60
+ loading: true;
61
+ readyToPurchase: false;
62
+ error: undefined;
63
+ };
64
+ type UseSonarPurchaseResult = UseSonarPurchaseResultLoading | UseSonarPurchaseResultReadyToPurchase | UseSonarPurchaseResultNotReadyToPurchase | UseSonarPurchaseResultError;
65
+ declare function useSonarPurchase(args: {
66
+ saleUUID: string;
67
+ entityUUID: string;
68
+ walletAddress: string;
69
+ }): UseSonarPurchaseResult;
42
70
 
43
- export { SonarProvider, type SonarProviderConfig, type UseSonarEntityResult, useSonarAuth, useSonarClient, useSonarEntity };
71
+ export { SonarProvider, type SonarProviderConfig, type UseSonarEntityResult, type UseSonarPurchaseResult, type UseSonarPurchaseResultError, type UseSonarPurchaseResultLoading, type UseSonarPurchaseResultNotReadyToPurchase, type UseSonarPurchaseResultReadyToPurchase, useSonarAuth, useSonarClient, useSonarEntity, useSonarPurchase };
package/dist/index.js CHANGED
@@ -8,14 +8,13 @@ function SonarProvider({ children, config }) {
8
8
  const [authState, setAuthState] = useState({ token: void 0, ready: false });
9
9
  const client = useMemo(() => {
10
10
  return createClient({
11
- saleUUID: config.saleUUID,
12
11
  apiURL: config.apiURL,
13
12
  tokenKey: config.tokenStorageKey,
14
13
  onTokenChange: (nextToken) => {
15
14
  setAuthState({ token: nextToken ?? void 0, ready: true });
16
15
  }
17
16
  });
18
- }, [config.apiURL, config.saleUUID, config.tokenStorageKey]);
17
+ }, [config.apiURL, config.tokenStorageKey]);
19
18
  useEffect(() => {
20
19
  setAuthState({ token: client.getToken() ?? void 0, ready: true });
21
20
  }, [client]);
@@ -27,7 +26,6 @@ function SonarProvider({ children, config }) {
27
26
  window.sessionStorage.setItem("sonar:oauth:state", state);
28
27
  window.sessionStorage.setItem("sonar:oauth:verifier", codeVerifier);
29
28
  const url = buildAuthorizationUrl({
30
- saleUUID: config.saleUUID,
31
29
  clientUUID: config.clientUUID,
32
30
  redirectURI: config.redirectURI,
33
31
  state,
@@ -35,7 +33,7 @@ function SonarProvider({ children, config }) {
35
33
  frontendURL: config.frontendURL
36
34
  });
37
35
  window.location.href = url.toString();
38
- }, [config.saleUUID, config.clientUUID, config.redirectURI, config.frontendURL]);
36
+ }, [config.clientUUID, config.redirectURI, config.frontendURL]);
39
37
  const completeOAuth = useCallback(
40
38
  async ({ code, state }) => {
41
39
  if (typeof window === "undefined") {
@@ -76,17 +74,19 @@ function SonarProvider({ children, config }) {
76
74
  }
77
75
 
78
76
  // src/hooks.ts
79
- import { APIError } from "@echoxyz/sonar-core";
80
- import { useCallback as useCallback2, useContext as useContext2, useEffect as useEffect2, useState as useState2 } from "react";
77
+ import {
78
+ APIError
79
+ } from "@echoxyz/sonar-core";
80
+ import { useCallback as useCallback2, useContext, useEffect as useEffect2, useState as useState2 } from "react";
81
81
  function useSonarAuth() {
82
- const ctx = useContext2(AuthContext);
82
+ const ctx = useContext(AuthContext);
83
83
  if (!ctx) {
84
84
  throw new Error("useSonarAuth must be used within a SonarProvider");
85
85
  }
86
86
  return ctx;
87
87
  }
88
88
  function useSonarClient() {
89
- const ctx = useContext2(ClientContext);
89
+ const ctx = useContext(ClientContext);
90
90
  if (!ctx) {
91
91
  throw new Error("useSonarClient must be used within a SonarProvider");
92
92
  }
@@ -163,9 +163,69 @@ function useSonarEntity(args) {
163
163
  error: state.error
164
164
  };
165
165
  }
166
+ function useSonarPurchase(args) {
167
+ const saleUUID = args.saleUUID;
168
+ const entityUUID = args.entityUUID;
169
+ const walletAddress = args.walletAddress;
170
+ const client = useSonarClient();
171
+ const [state, setState] = useState2({
172
+ loading: true,
173
+ readyToPurchase: false,
174
+ error: void 0
175
+ });
176
+ const generatePurchasePermit = useCallback2(() => {
177
+ return client.generatePurchasePermit({
178
+ saleUUID,
179
+ entityUUID,
180
+ walletAddress
181
+ });
182
+ }, [client, saleUUID, entityUUID, walletAddress]);
183
+ useEffect2(() => {
184
+ const fetchPurchaseData = async () => {
185
+ setState({
186
+ loading: true,
187
+ readyToPurchase: false,
188
+ error: void 0
189
+ });
190
+ try {
191
+ const response = await client.prePurchaseCheck({
192
+ saleUUID,
193
+ entityUUID,
194
+ walletAddress
195
+ });
196
+ if (response.ReadyToPurchase) {
197
+ setState({
198
+ loading: false,
199
+ readyToPurchase: true,
200
+ generatePurchasePermit,
201
+ error: void 0
202
+ });
203
+ } else {
204
+ setState({
205
+ loading: false,
206
+ readyToPurchase: false,
207
+ failureReason: response.FailureReason,
208
+ livenessCheckURL: response.LivenessCheckURL,
209
+ error: void 0
210
+ });
211
+ }
212
+ } catch (err) {
213
+ const error = err instanceof Error ? err : new Error(String(err));
214
+ setState({
215
+ loading: false,
216
+ readyToPurchase: false,
217
+ error
218
+ });
219
+ }
220
+ };
221
+ fetchPurchaseData();
222
+ }, [saleUUID, entityUUID, walletAddress, client, generatePurchasePermit]);
223
+ return state;
224
+ }
166
225
  export {
167
226
  SonarProvider,
168
227
  useSonarAuth,
169
228
  useSonarClient,
170
- useSonarEntity
229
+ useSonarEntity,
230
+ useSonarPurchase
171
231
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@echoxyz/sonar-react",
3
- "version": "0.4.2",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -16,7 +16,7 @@
16
16
  "react": ">=18"
17
17
  },
18
18
  "dependencies": {
19
- "@echoxyz/sonar-core": "0.3.0"
19
+ "@echoxyz/sonar-core": "0.4.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@testing-library/react": "^16.0.0",