@coinlist-co/react 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{RequirementItem-BvwsFu9e.d.cts → RequirementItem-B-MvcxSr.d.cts} +3 -3
- package/dist/{RequirementItem-BpkJQep8.d.ts → RequirementItem-DcYot2uC.d.ts} +3 -3
- package/dist/{chunk-IDCROXPS.js → chunk-EHZ6PIGN.js} +6 -4
- package/dist/chunk-EHZ6PIGN.js.map +1 -0
- package/dist/chunk-NC45IO63.js +777 -0
- package/dist/chunk-NC45IO63.js.map +1 -0
- package/dist/chunk-RIFATQB5.js +257 -0
- package/dist/chunk-RIFATQB5.js.map +1 -0
- package/dist/chunk-UGJUTRXC.js +304 -0
- package/dist/chunk-UGJUTRXC.js.map +1 -0
- package/dist/client/components/index.cjs +60 -59
- package/dist/client/components/index.cjs.map +1 -1
- package/dist/client/components/index.d.cts +40 -22
- package/dist/client/components/index.d.ts +40 -22
- package/dist/client/components/index.js +34 -62
- package/dist/client/components/index.js.map +1 -1
- package/dist/client/core/index.cjs +308 -222
- package/dist/client/core/index.cjs.map +1 -1
- package/dist/client/core/index.d.cts +4 -4
- package/dist/client/core/index.d.ts +4 -4
- package/dist/client/core/index.js +14 -14
- package/dist/client/hooks/index.cjs +87 -13
- package/dist/client/hooks/index.cjs.map +1 -1
- package/dist/client/hooks/index.d.cts +102 -9
- package/dist/client/hooks/index.d.ts +102 -9
- package/dist/client/hooks/index.js +9 -9
- package/dist/client/index.cjs +465 -237
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +7 -8
- package/dist/client/index.d.ts +7 -8
- package/dist/client/index.js +24 -18
- package/dist/{coinlist-client-B_dJkbwB.d.ts → coinlist-client-BSxpqylo.d.ts} +13 -138
- package/dist/{coinlist-client-DBKa6j3f.d.cts → coinlist-client-Cah5c_aF.d.cts} +13 -138
- package/dist/participation-DMMEDxON.d.cts +185 -0
- package/dist/participation-E-bT-GXJ.d.ts +185 -0
- package/dist/{requirement-CxbdY8l4.d.cts → requirement-XWT0T_zO.d.cts} +5 -2
- package/dist/{requirement-fJsQ3f9_.d.ts → requirement-XWT0T_zO.d.ts} +5 -2
- package/dist/server/index.cjs +519 -14
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +73 -4
- package/dist/server/index.d.ts +73 -4
- package/dist/server/index.js +116 -20
- package/dist/server/index.js.map +1 -1
- package/dist/useCoinListRequirements-B5-ZPOMz.d.ts +80 -0
- package/dist/useCoinListRequirements-Cuc32Tqw.d.cts +80 -0
- package/package.json +1 -1
- package/dist/chunk-GRVV7NLV.js +0 -282
- package/dist/chunk-GRVV7NLV.js.map +0 -1
- package/dist/chunk-IADBIDY6.js +0 -76
- package/dist/chunk-IADBIDY6.js.map +0 -1
- package/dist/chunk-IDCROXPS.js.map +0 -1
- package/dist/chunk-P4DTIPEX.js +0 -623
- package/dist/chunk-P4DTIPEX.js.map +0 -1
- package/dist/chunk-V5M7SA3C.js +0 -161
- package/dist/chunk-V5M7SA3C.js.map +0 -1
- package/dist/newtype-yKTvFdg_.d.cts +0 -6
- package/dist/newtype-yKTvFdg_.d.ts +0 -6
- package/dist/oauth-session-DgItaMKN.d.cts +0 -56
- package/dist/oauth-session-DnKDI5ou.d.ts +0 -56
- package/dist/useCoinListOffers-C5lBlzcC.d.ts +0 -27
- package/dist/useCoinListOffers-CguwXaCX.d.cts +0 -27
- package/dist/useCoinListRequirements-BECx1cIw.d.ts +0 -31
- package/dist/useCoinListRequirements-CX2gV9gJ.d.cts +0 -31
- package/dist/useCompleteCoinListOAuth-BRhAqD4_.d.cts +0 -55
- package/dist/useCompleteCoinListOAuth-DEZpHjd6.d.ts +0 -55
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createCoinListClient
|
|
3
|
+
} from "./chunk-RIFATQB5.js";
|
|
4
|
+
import {
|
|
5
|
+
NotAuthenticatedError
|
|
6
|
+
} from "./chunk-NC45IO63.js";
|
|
7
|
+
|
|
8
|
+
// src/client/hooks/useCoinList.ts
|
|
9
|
+
import { useContext } from "react";
|
|
10
|
+
|
|
11
|
+
// src/client/CoinListProvider.tsx
|
|
12
|
+
import {
|
|
13
|
+
createContext,
|
|
14
|
+
useEffect,
|
|
15
|
+
useMemo,
|
|
16
|
+
useState
|
|
17
|
+
} from "react";
|
|
18
|
+
import { jsx } from "react/jsx-runtime";
|
|
19
|
+
var CoinListContext = createContext(null);
|
|
20
|
+
function CoinListProvider({ config, children }) {
|
|
21
|
+
const [isReady, setIsReady] = useState(false);
|
|
22
|
+
const coinlist = useMemo(() => createCoinListClient(config), [config]);
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
let isCancelled = false;
|
|
25
|
+
if (coinlist.getAuthState() === "unknown") {
|
|
26
|
+
coinlist.init().then(() => {
|
|
27
|
+
if (!isCancelled) {
|
|
28
|
+
setIsReady(true);
|
|
29
|
+
}
|
|
30
|
+
}).catch(() => {
|
|
31
|
+
if (!isCancelled) {
|
|
32
|
+
setIsReady(true);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
setIsReady(true);
|
|
37
|
+
}
|
|
38
|
+
return () => {
|
|
39
|
+
isCancelled = true;
|
|
40
|
+
};
|
|
41
|
+
}, [coinlist]);
|
|
42
|
+
const value = useMemo(() => ({ coinlist, isReady }), [coinlist, isReady]);
|
|
43
|
+
return /* @__PURE__ */ jsx(CoinListContext.Provider, { value, children });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/client/hooks/useCoinList.ts
|
|
47
|
+
function useCoinList() {
|
|
48
|
+
const context = useContext(CoinListContext);
|
|
49
|
+
if (context === null) {
|
|
50
|
+
throw new CoinListClientInitializationError();
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
isReady: context.isReady,
|
|
54
|
+
coinlist: context.coinlist
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
var CoinListClientInitializationError = class extends Error {
|
|
58
|
+
constructor(message = "useCoinList() must be used within CoinListProvider. Wrap your app with <CoinListProvider config={...}>.") {
|
|
59
|
+
super(message);
|
|
60
|
+
this.name = "CoinListClientInitializationError";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/client/hooks/useCoinListOfferDetails.ts
|
|
65
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
66
|
+
var LOADING_STATE = { type: "LOADING" };
|
|
67
|
+
function useCoinListOfferDetails(offerId, options = {}) {
|
|
68
|
+
const { coinlist, isReady } = useCoinList();
|
|
69
|
+
const { serverData } = options;
|
|
70
|
+
const [offerDetailsState, setOfferDetailsState] = useState2(
|
|
71
|
+
serverData !== void 0 ? { type: "CONTENT", offerDetail: serverData } : LOADING_STATE
|
|
72
|
+
);
|
|
73
|
+
useEffect2(() => {
|
|
74
|
+
let isCancelled = false;
|
|
75
|
+
if (!isReady) {
|
|
76
|
+
if (serverData === void 0) {
|
|
77
|
+
setOfferDetailsState(LOADING_STATE);
|
|
78
|
+
}
|
|
79
|
+
return () => {
|
|
80
|
+
isCancelled = true;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (serverData !== void 0) {
|
|
84
|
+
return () => {
|
|
85
|
+
isCancelled = true;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
setOfferDetailsState(LOADING_STATE);
|
|
89
|
+
coinlist.fetchOfferDetails(offerId).then((offerDetail) => {
|
|
90
|
+
if (!isCancelled) {
|
|
91
|
+
setOfferDetailsState({ type: "CONTENT", offerDetail });
|
|
92
|
+
}
|
|
93
|
+
}).catch((error) => {
|
|
94
|
+
if (!isCancelled) {
|
|
95
|
+
const reason = error instanceof NotAuthenticatedError ? "not-authenticated" : "generic-error";
|
|
96
|
+
setOfferDetailsState({ type: "ERROR", reason });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
return () => {
|
|
100
|
+
isCancelled = true;
|
|
101
|
+
};
|
|
102
|
+
}, [coinlist, isReady, offerId, serverData]);
|
|
103
|
+
return { offerDetailsState };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/client/hooks/useCoinListOffers.ts
|
|
107
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
108
|
+
var LOADING_STATE2 = { type: "LOADING" };
|
|
109
|
+
function useCoinListOffers(options = {}) {
|
|
110
|
+
const { coinlist, isReady } = useCoinList();
|
|
111
|
+
const { serverOffers } = options;
|
|
112
|
+
const [offersState, setOffersState] = useState3(
|
|
113
|
+
serverOffers !== void 0 ? { type: "CONTENT", offers: serverOffers } : LOADING_STATE2
|
|
114
|
+
);
|
|
115
|
+
useEffect3(() => {
|
|
116
|
+
let isCancelled = false;
|
|
117
|
+
if (!isReady) {
|
|
118
|
+
if (serverOffers === void 0) {
|
|
119
|
+
setOffersState(LOADING_STATE2);
|
|
120
|
+
}
|
|
121
|
+
return () => {
|
|
122
|
+
isCancelled = true;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (serverOffers !== void 0) {
|
|
126
|
+
return () => {
|
|
127
|
+
isCancelled = true;
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
setOffersState(LOADING_STATE2);
|
|
131
|
+
coinlist.fetchAllOffers().then((offers) => {
|
|
132
|
+
if (!isCancelled) {
|
|
133
|
+
setOffersState({ type: "CONTENT", offers });
|
|
134
|
+
}
|
|
135
|
+
}).catch((error) => {
|
|
136
|
+
if (!isCancelled) {
|
|
137
|
+
const reason = error instanceof NotAuthenticatedError ? "not-authenticated" : "generic-error";
|
|
138
|
+
setOffersState({ type: "ERROR", reason });
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
return () => {
|
|
142
|
+
isCancelled = true;
|
|
143
|
+
};
|
|
144
|
+
}, [coinlist, isReady, serverOffers]);
|
|
145
|
+
return { offersState };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/client/hooks/useCoinListRequirements.ts
|
|
149
|
+
import { useCallback, useEffect as useEffect4, useState as useState4 } from "react";
|
|
150
|
+
var LOADING_STATE3 = { type: "LOADING" };
|
|
151
|
+
function useCoinListRequirements(offerId, options = {}) {
|
|
152
|
+
const { coinlist, isReady } = useCoinList();
|
|
153
|
+
const { serverData } = options;
|
|
154
|
+
const [requirementsState, setRequirementsState] = useState4(
|
|
155
|
+
serverData !== void 0 ? { type: "CONTENT", ...serverData } : LOADING_STATE3
|
|
156
|
+
);
|
|
157
|
+
const [fetchKey, setFetchKey] = useState4(0);
|
|
158
|
+
const refetch = useCallback(() => setFetchKey((k) => k + 1), []);
|
|
159
|
+
useEffect4(() => {
|
|
160
|
+
let isCancelled = false;
|
|
161
|
+
if (!isReady) {
|
|
162
|
+
if (serverData === void 0) {
|
|
163
|
+
setRequirementsState(LOADING_STATE3);
|
|
164
|
+
}
|
|
165
|
+
return () => {
|
|
166
|
+
isCancelled = true;
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (serverData !== void 0 && fetchKey === 0) {
|
|
170
|
+
return () => {
|
|
171
|
+
isCancelled = true;
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
setRequirementsState(LOADING_STATE3);
|
|
175
|
+
Promise.all([
|
|
176
|
+
coinlist.fetchOfferRequirements(offerId),
|
|
177
|
+
coinlist.fetchRequirementStatuses(offerId)
|
|
178
|
+
]).then(([requirementsByOptionId, statuses]) => {
|
|
179
|
+
if (!isCancelled) {
|
|
180
|
+
setRequirementsState({
|
|
181
|
+
type: "CONTENT",
|
|
182
|
+
requirementsByOptionId,
|
|
183
|
+
statuses
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}).catch((error) => {
|
|
187
|
+
if (!isCancelled) {
|
|
188
|
+
const reason = error instanceof NotAuthenticatedError ? "not-authenticated" : "generic-error";
|
|
189
|
+
setRequirementsState({ type: "ERROR", reason });
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
return () => {
|
|
193
|
+
isCancelled = true;
|
|
194
|
+
};
|
|
195
|
+
}, [coinlist, isReady, offerId, fetchKey]);
|
|
196
|
+
return { requirementsState, refetch };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/client/hooks/useCompleteCoinListOAuth.ts
|
|
200
|
+
import { useEffect as useEffect5, useRef } from "react";
|
|
201
|
+
function useCompleteCoinListOAuth(options) {
|
|
202
|
+
const { coinlist } = useCoinList();
|
|
203
|
+
const postOAuthCompleteRef = useRef(options.postOAuthComplete);
|
|
204
|
+
const onFailureRef = useRef(options.onFailure);
|
|
205
|
+
const onSuccessRef = useRef(options.onSuccess);
|
|
206
|
+
postOAuthCompleteRef.current = options.postOAuthComplete;
|
|
207
|
+
onFailureRef.current = options.onFailure;
|
|
208
|
+
onSuccessRef.current = options.onSuccess;
|
|
209
|
+
const hasStartedExchangeRef = useRef(false);
|
|
210
|
+
useEffect5(() => {
|
|
211
|
+
if (typeof window === "undefined") return;
|
|
212
|
+
const params = new URLSearchParams(window.location.search);
|
|
213
|
+
if (!params.has("code") && !params.has("error")) return;
|
|
214
|
+
if (hasStartedExchangeRef.current) return;
|
|
215
|
+
const oauthResult = coinlist.completeOAuth();
|
|
216
|
+
if (oauthResult.type === "error") {
|
|
217
|
+
void Promise.resolve(onFailureRef.current(oauthResult.reason)).catch(
|
|
218
|
+
() => {
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
hasStartedExchangeRef.current = true;
|
|
224
|
+
let cancelled = false;
|
|
225
|
+
void (async () => {
|
|
226
|
+
try {
|
|
227
|
+
const ok = await postOAuthCompleteRef.current({
|
|
228
|
+
code: oauthResult.code,
|
|
229
|
+
codeVerifier: oauthResult.codeVerifier
|
|
230
|
+
});
|
|
231
|
+
if (cancelled) return;
|
|
232
|
+
if (!ok) {
|
|
233
|
+
hasStartedExchangeRef.current = false;
|
|
234
|
+
await onFailureRef.current("complete_request_failed");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
await coinlist.init();
|
|
238
|
+
if (cancelled) return;
|
|
239
|
+
await onSuccessRef.current();
|
|
240
|
+
} catch {
|
|
241
|
+
if (cancelled) return;
|
|
242
|
+
hasStartedExchangeRef.current = false;
|
|
243
|
+
await onFailureRef.current("complete_request_failed");
|
|
244
|
+
}
|
|
245
|
+
})();
|
|
246
|
+
return () => {
|
|
247
|
+
cancelled = true;
|
|
248
|
+
hasStartedExchangeRef.current = false;
|
|
249
|
+
};
|
|
250
|
+
}, [coinlist]);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/client/hooks/useParticipations.ts
|
|
254
|
+
import { useEffect as useEffect6, useState as useState5 } from "react";
|
|
255
|
+
var LOADING_STATE4 = { type: "LOADING" };
|
|
256
|
+
function useParticipations(offerId, options = {}) {
|
|
257
|
+
const { coinlist, isReady } = useCoinList();
|
|
258
|
+
const { serverData } = options;
|
|
259
|
+
const [participationsState, setParticipationsState] = useState5(
|
|
260
|
+
serverData !== void 0 ? { type: "CONTENT", participations: serverData } : LOADING_STATE4
|
|
261
|
+
);
|
|
262
|
+
useEffect6(() => {
|
|
263
|
+
let isCancelled = false;
|
|
264
|
+
if (!isReady) {
|
|
265
|
+
if (serverData === void 0) {
|
|
266
|
+
setParticipationsState(LOADING_STATE4);
|
|
267
|
+
}
|
|
268
|
+
return () => {
|
|
269
|
+
isCancelled = true;
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
if (serverData !== void 0) {
|
|
273
|
+
return () => {
|
|
274
|
+
isCancelled = true;
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
setParticipationsState(LOADING_STATE4);
|
|
278
|
+
coinlist.fetchAllParticipations(offerId).then((participations) => {
|
|
279
|
+
if (!isCancelled) {
|
|
280
|
+
setParticipationsState({ type: "CONTENT", participations });
|
|
281
|
+
}
|
|
282
|
+
}).catch((error) => {
|
|
283
|
+
if (!isCancelled) {
|
|
284
|
+
const reason = error instanceof NotAuthenticatedError ? "not-authenticated" : "generic-error";
|
|
285
|
+
setParticipationsState({ type: "ERROR", reason });
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
return () => {
|
|
289
|
+
isCancelled = true;
|
|
290
|
+
};
|
|
291
|
+
}, [coinlist, isReady, offerId, serverData]);
|
|
292
|
+
return { participationsState };
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export {
|
|
296
|
+
CoinListProvider,
|
|
297
|
+
useCoinList,
|
|
298
|
+
useCoinListOfferDetails,
|
|
299
|
+
useCoinListOffers,
|
|
300
|
+
useCoinListRequirements,
|
|
301
|
+
useCompleteCoinListOAuth,
|
|
302
|
+
useParticipations
|
|
303
|
+
};
|
|
304
|
+
//# sourceMappingURL=chunk-UGJUTRXC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client/hooks/useCoinList.ts","../src/client/CoinListProvider.tsx","../src/client/hooks/useCoinListOfferDetails.ts","../src/client/hooks/useCoinListOffers.ts","../src/client/hooks/useCoinListRequirements.ts","../src/client/hooks/useCompleteCoinListOAuth.ts","../src/client/hooks/useParticipations.ts"],"sourcesContent":["import { useContext } from 'react';\nimport { CoinListContext } from '@/client/CoinListProvider';\nimport type { CoinListClient } from '@/client/core/coinlist-client';\n\nexport interface UseCoinListResult {\n /**\n * Indicates whether the CoinList client has been initialized and ready to use.\n * While isReady is false, then the current user state will be `unknown`.\n */\n isReady: boolean;\n coinlist: CoinListClient;\n}\n\nexport function useCoinList(): UseCoinListResult {\n const context = useContext(CoinListContext);\n if (context === null) {\n throw new CoinListClientInitializationError();\n }\n\n return {\n isReady: context.isReady,\n coinlist: context.coinlist,\n };\n}\n\nexport class CoinListClientInitializationError extends Error {\n constructor(\n message = 'useCoinList() must be used within CoinListProvider. Wrap your app with <CoinListProvider config={...}>.'\n ) {\n super(message);\n this.name = 'CoinListClientInitializationError';\n }\n}\n","/**\n * Global CoinList initialization for React apps.\n * Wrap your app once at the root; use useCoinList() anywhere below.\n */\n\nimport {\n createContext,\n type ReactNode,\n useEffect,\n useMemo,\n useState,\n} from 'react';\nimport {\n type ClientConfig,\n type CoinListClient,\n createCoinListClient,\n} from '@/client/core/coinlist-client';\n\nexport type CoinListContextValue = {\n coinlist: CoinListClient;\n isReady: boolean;\n};\n\nexport const CoinListContext = createContext<CoinListContextValue | null>(null);\n\nexport type CoinListProviderProps = {\n config: ClientConfig;\n children: ReactNode;\n};\n\n/**\n * Provide CoinList (api + config) to the tree. Initialize once at app root with your config.\n */\nexport function CoinListProvider({ config, children }: CoinListProviderProps) {\n const [isReady, setIsReady] = useState(false);\n const coinlist = useMemo(() => createCoinListClient(config), [config]);\n\n useEffect(() => {\n let isCancelled = false;\n if (coinlist.getAuthState() === 'unknown') {\n coinlist\n .init()\n .then(() => {\n if (!isCancelled) {\n setIsReady(true);\n }\n })\n .catch(() => {\n if (!isCancelled) {\n setIsReady(true);\n }\n });\n } else {\n setIsReady(true);\n }\n\n return () => {\n isCancelled = true;\n };\n }, [coinlist]);\n\n const value = useMemo(() => ({ coinlist, isReady }), [coinlist, isReady]);\n\n return (\n <CoinListContext.Provider value={value}>\n {children}\n </CoinListContext.Provider>\n );\n}\n","import { useEffect, useState } from 'react';\nimport { useCoinList } from '@/client/hooks/useCoinList';\nimport { NotAuthenticatedError } from '@/shared/types/errors';\nimport type { OfferId } from '@/shared/types/offer';\nimport type { OfferDetail } from '@/shared/types/offer-detail';\n\nexport type LoadOfferDetailsReason = 'not-authenticated' | 'generic-error';\n\nexport type LoadOfferDetailsState =\n | {\n type: 'LOADING';\n }\n | {\n type: 'ERROR';\n reason: LoadOfferDetailsReason;\n }\n | {\n type: 'CONTENT';\n offerDetail: OfferDetail;\n };\n\nexport interface UseCoinListOfferDetailsOptions {\n /**\n * Offer detail pre-fetched on the server (e.g. via `CoinListServer.fetchOfferDetails()`).\n * When provided, the hook uses this data as-is and skips the client-side fetch entirely.\n */\n serverData?: OfferDetail;\n}\n\nexport interface UseCoinListOfferDetailsResult {\n offerDetailsState: LoadOfferDetailsState;\n}\n\nconst LOADING_STATE: LoadOfferDetailsState = { type: 'LOADING' };\n\n/**\n * Loads CoinList offer details for a given offer id and exposes a UI-friendly state.\n *\n * Pass `serverData` (pre-fetched server-side) to use that data as-is and skip\n * the client-side fetch entirely.\n *\n * Returns `LOADING` while CoinList is initializing or while details are being fetched.\n * Returns `CONTENT` with the offer when `fetchOfferDetails()` succeeds.\n * Returns `ERROR` with:\n * - `not-authenticated` when fetching fails with {@link NotAuthenticatedError}\n * - `generic-error` for any other failure\n */\nexport function useCoinListOfferDetails(\n offerId: OfferId,\n options: UseCoinListOfferDetailsOptions = {}\n): UseCoinListOfferDetailsResult {\n const { coinlist, isReady } = useCoinList();\n const { serverData } = options;\n const [offerDetailsState, setOfferDetailsState] =\n useState<LoadOfferDetailsState>(\n serverData !== undefined\n ? { type: 'CONTENT', offerDetail: serverData }\n : LOADING_STATE\n );\n\n useEffect(() => {\n let isCancelled = false;\n\n if (!isReady) {\n if (serverData === undefined) {\n setOfferDetailsState(LOADING_STATE);\n }\n return () => {\n isCancelled = true;\n };\n }\n\n if (serverData !== undefined) {\n return () => {\n isCancelled = true;\n };\n }\n\n setOfferDetailsState(LOADING_STATE);\n coinlist\n .fetchOfferDetails(offerId)\n .then((offerDetail) => {\n if (!isCancelled) {\n setOfferDetailsState({ type: 'CONTENT', offerDetail });\n }\n })\n .catch((error: unknown) => {\n if (!isCancelled) {\n const reason: LoadOfferDetailsReason =\n error instanceof NotAuthenticatedError\n ? 'not-authenticated'\n : 'generic-error';\n setOfferDetailsState({ type: 'ERROR', reason });\n }\n });\n\n return () => {\n isCancelled = true;\n };\n }, [coinlist, isReady, offerId, serverData]);\n\n return { offerDetailsState };\n}\n","import { useEffect, useState } from 'react';\nimport { useCoinList } from '@/client/hooks/useCoinList';\nimport { NotAuthenticatedError } from '@/shared/types/errors';\nimport type { Offer } from '@/shared/types/offer';\n\nexport type LoadOffersReason = 'not-authenticated' | 'generic-error';\n\nexport type LoadOffersState =\n | {\n type: 'LOADING';\n }\n | {\n type: 'ERROR';\n reason: LoadOffersReason;\n }\n | {\n type: 'CONTENT';\n offers: Offer[];\n };\n\nexport interface UseCoinListOffersOptions {\n /**\n * Offers pre-fetched on the server (e.g. via `CoinListServer.fetchAllOffers()`).\n * When provided, the hook uses this data as-is and skips the client-side fetch entirely.\n */\n serverOffers?: Offer[];\n}\n\nexport interface UseCoinListOffersResult {\n offersState: LoadOffersState;\n}\n\nconst LOADING_STATE: LoadOffersState = { type: 'LOADING' };\n\n/**\n * Loads all CoinList offers for the current user and exposes a UI-friendly state.\n *\n * Pass `serverOffers` (pre-fetched server-side) to use that data as-is and skip\n * the client-side fetch entirely.\n *\n * Returns `LOADING` while CoinList is initializing or while offers are being fetched.\n * Returns `CONTENT` with all offers when `fetchAllOffers()` succeeds.\n * Returns `ERROR` with:\n * - `not-authenticated` when fetching fails with {@link NotAuthenticatedError}\n * - `generic-error` for any other failure\n */\nexport function useCoinListOffers(\n options: UseCoinListOffersOptions = {}\n): UseCoinListOffersResult {\n const { coinlist, isReady } = useCoinList();\n const { serverOffers } = options;\n const [offersState, setOffersState] = useState<LoadOffersState>(\n serverOffers !== undefined\n ? { type: 'CONTENT', offers: serverOffers }\n : LOADING_STATE\n );\n\n useEffect(() => {\n let isCancelled = false;\n\n // Wait until CoinListProvider finishes auth initialization.\n if (!isReady) {\n if (serverOffers === undefined) {\n setOffersState(LOADING_STATE);\n }\n return () => {\n isCancelled = true;\n };\n }\n\n if (serverOffers !== undefined) {\n return () => {\n isCancelled = true;\n };\n }\n\n setOffersState(LOADING_STATE);\n coinlist\n .fetchAllOffers()\n .then((offers) => {\n if (!isCancelled) {\n setOffersState({ type: 'CONTENT', offers });\n }\n })\n .catch((error: unknown) => {\n if (!isCancelled) {\n const reason: LoadOffersReason =\n error instanceof NotAuthenticatedError\n ? 'not-authenticated'\n : 'generic-error';\n setOffersState({ type: 'ERROR', reason });\n }\n });\n\n return () => {\n isCancelled = true;\n };\n }, [coinlist, isReady, serverOffers]);\n\n return { offersState };\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport { useCoinList } from '@/client/hooks/useCoinList';\nimport { NotAuthenticatedError } from '@/shared/types/errors';\nimport type { OfferId } from '@/shared/types/offer';\nimport type { OfferOptionId } from '@/shared/types/offer-detail';\nimport type {\n Requirement,\n RequirementStatusInfo,\n} from '@/shared/types/requirement';\n\nexport type LoadRequirementsReason = 'not-authenticated' | 'generic-error';\n\nexport type LoadRequirementsState =\n | {\n type: 'LOADING';\n }\n | {\n type: 'ERROR';\n reason: LoadRequirementsReason;\n }\n | {\n type: 'CONTENT';\n requirementsByOptionId: Record<OfferOptionId, Requirement[]>;\n statuses: RequirementStatusInfo[];\n };\n\nexport interface UseCoinListRequirementsOptions {\n /**\n * Requirements data pre-fetched on the server (e.g. via\n * `CoinListServer.fetchOfferRequirements()` + `fetchRequirementStatuses()`).\n * When provided, the hook uses this data as-is and skips the initial client-side fetch.\n * Calling `refetch()` will still trigger a fresh fetch.\n */\n serverData?: {\n requirementsByOptionId: Record<OfferOptionId, Requirement[]>;\n statuses: RequirementStatusInfo[];\n };\n}\n\nexport interface UseCoinListRequirementsResult {\n requirementsState: LoadRequirementsState;\n /** Triggers a re-fetch of requirements and statuses. */\n refetch: () => void;\n}\n\nconst LOADING_STATE: LoadRequirementsState = { type: 'LOADING' };\n\n/**\n * Loads requirements (grouped by option ID) and requirement statuses for a\n * given offer, then exposes them via a state machine.\n *\n * Pass `serverData` (pre-fetched server-side) to use that data as-is and skip\n * the initial client-side fetch. Calling `refetch()` will still trigger a fresh fetch.\n *\n * Returns `LOADING` while CoinList is initializing or while data is being fetched.\n * Returns `CONTENT` with requirementsByOptionId and statuses on success.\n * Returns `ERROR` with:\n * - `not-authenticated` when fetching fails with {@link NotAuthenticatedError}\n * - `generic-error` for any other failure\n */\nexport function useCoinListRequirements(\n offerId: OfferId,\n options: UseCoinListRequirementsOptions = {}\n): UseCoinListRequirementsResult {\n const { coinlist, isReady } = useCoinList();\n const { serverData } = options;\n const [requirementsState, setRequirementsState] =\n useState<LoadRequirementsState>(\n serverData !== undefined\n ? { type: 'CONTENT', ...serverData }\n : LOADING_STATE\n );\n const [fetchKey, setFetchKey] = useState(0);\n const refetch = useCallback(() => setFetchKey((k) => k + 1), []);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: fetchKey is an intentional refetch trigger\n useEffect(() => {\n let isCancelled = false;\n\n if (!isReady) {\n if (serverData === undefined) {\n setRequirementsState(LOADING_STATE);\n }\n return () => {\n isCancelled = true;\n };\n }\n\n if (serverData !== undefined && fetchKey === 0) {\n return () => {\n isCancelled = true;\n };\n }\n\n setRequirementsState(LOADING_STATE);\n Promise.all([\n coinlist.fetchOfferRequirements(offerId),\n coinlist.fetchRequirementStatuses(offerId),\n ])\n .then(([requirementsByOptionId, statuses]) => {\n if (!isCancelled) {\n setRequirementsState({\n type: 'CONTENT',\n requirementsByOptionId,\n statuses,\n });\n }\n })\n .catch((error: unknown) => {\n if (!isCancelled) {\n const reason: LoadRequirementsReason =\n error instanceof NotAuthenticatedError\n ? 'not-authenticated'\n : 'generic-error';\n setRequirementsState({ type: 'ERROR', reason });\n }\n });\n\n return () => {\n isCancelled = true;\n };\n }, [coinlist, isReady, offerId, fetchKey]);\n\n return { requirementsState, refetch };\n}\n","import { useEffect, useRef } from 'react';\nimport type { OauthClientErrorReason } from '@/client/core/types/client-types';\nimport { useCoinList } from '@/client/hooks/useCoinList';\nimport type { AuthorizationCode, CodeVerifier } from '@/shared/types/oauth';\n\n/**\n * Reasons passed to {@link UseCompleteCoinListOAuthOptions.onFailure} when the\n * OAuth callback cannot be completed. Includes PKCE / redirect validation\n * errors from {@link CoinListClient.completeOAuth} and a dedicated value when\n * {@link UseCompleteCoinListOAuthOptions.postOAuthComplete} fails or rejects.\n */\nexport type CompleteCoinListOAuthFailureReason =\n | OauthClientErrorReason\n | 'complete_request_failed';\n\nexport interface UseCompleteCoinListOAuthOptions {\n /**\n * Exchange the authorization code on your backend (e.g. POST to your API).\n * Return `true` when the session was established successfully, `false` when\n * the request completed but signing in failed. Rejections are treated like\n * `false` and reported as {@link CompleteCoinListOAuthFailureReason}\n * `\"complete_request_failed\"`.\n */\n postOAuthComplete: (payload: {\n code: AuthorizationCode;\n codeVerifier: CodeVerifier;\n }) => Promise<boolean>;\n /**\n * Called when the user denied access (`error` query parameter), when\n * {@link CoinListClient.completeOAuth} fails PKCE/state validation, or when\n * {@link UseCompleteCoinListOAuthOptions.postOAuthComplete} fails.\n */\n onFailure: (\n reason: CompleteCoinListOAuthFailureReason\n ) => void | Promise<void>;\n /** Invoked after {@link UseCompleteCoinListOAuthOptions.postOAuthComplete} succeeds and {@link CoinListClient.init} has refreshed the client session. */\n onSuccess: () => void | Promise<void>;\n}\n\n/**\n * Runs the CoinList OAuth callback once on mount: validates the redirect URL via\n * {@link CoinListClient.completeOAuth}, exchanges the code via {@link UseCompleteCoinListOAuthOptions.postOAuthComplete},\n * refreshes tokens with {@link CoinListClient.init}, then calls {@link UseCompleteCoinListOAuthOptions.onSuccess}.\n *\n * Framework-agnostic: reads `window.location` like {@link CoinListClient.completeOAuth}. Supply\n * {@link UseCompleteCoinListOAuthOptions.postOAuthComplete}, {@link UseCompleteCoinListOAuthOptions.onFailure},\n * and {@link UseCompleteCoinListOAuthOptions.onSuccess} to perform redirects or routing in your app.\n *\n * Must be used within {@link CoinListProvider}.\n */\nexport function useCompleteCoinListOAuth(\n options: UseCompleteCoinListOAuthOptions\n): void {\n const { coinlist } = useCoinList();\n\n const postOAuthCompleteRef = useRef(options.postOAuthComplete);\n const onFailureRef = useRef(options.onFailure);\n const onSuccessRef = useRef(options.onSuccess);\n\n postOAuthCompleteRef.current = options.postOAuthComplete;\n onFailureRef.current = options.onFailure;\n onSuccessRef.current = options.onSuccess;\n\n const hasStartedExchangeRef = useRef(false);\n\n useEffect(() => {\n if (typeof window === 'undefined') return;\n\n const params = new URLSearchParams(window.location.search);\n if (!params.has('code') && !params.has('error')) return;\n\n if (hasStartedExchangeRef.current) return;\n\n const oauthResult = coinlist.completeOAuth();\n if (oauthResult.type === 'error') {\n void Promise.resolve(onFailureRef.current(oauthResult.reason)).catch(\n () => {}\n );\n return;\n }\n\n hasStartedExchangeRef.current = true;\n let cancelled = false;\n\n void (async () => {\n try {\n const ok = await postOAuthCompleteRef.current({\n code: oauthResult.code,\n codeVerifier: oauthResult.codeVerifier,\n });\n\n if (cancelled) return;\n\n if (!ok) {\n hasStartedExchangeRef.current = false;\n await onFailureRef.current('complete_request_failed');\n return;\n }\n\n await coinlist.init();\n\n if (cancelled) return;\n\n await onSuccessRef.current();\n } catch {\n if (cancelled) return;\n hasStartedExchangeRef.current = false;\n await onFailureRef.current('complete_request_failed');\n }\n })();\n\n return () => {\n cancelled = true;\n hasStartedExchangeRef.current = false;\n };\n }, [coinlist]);\n}\n","import { useEffect, useState } from 'react';\nimport { useCoinList } from '@/client/hooks/useCoinList';\nimport { NotAuthenticatedError } from '@/shared/types/errors';\nimport type { OfferId } from '@/shared/types/offer';\nimport type { Participation } from '@/shared/types/participation';\n\nexport type LoadParticipationsReason = 'not-authenticated' | 'generic-error';\n\nexport type LoadParticipationsState =\n | {\n type: 'LOADING';\n }\n | {\n type: 'ERROR';\n reason: LoadParticipationsReason;\n }\n | {\n type: 'CONTENT';\n participations: Participation[];\n };\n\nexport interface UseParticipationsOptions {\n /**\n * Participations pre-fetched on the server (e.g. via `CoinListServer.fetchAllParticipations()`).\n * When provided, the hook uses this data as-is and skips the client-side fetch entirely.\n */\n serverData?: Participation[];\n}\n\nexport interface UseParticipationsResult {\n participationsState: LoadParticipationsState;\n}\n\nconst LOADING_STATE: LoadParticipationsState = { type: 'LOADING' };\n\n/**\n * Loads all CoinList participations for the current user and exposes a UI-friendly state.\n * Optionally filters by offer when `offerId` is provided.\n *\n * Pass `serverData` (pre-fetched server-side) to use that data as-is and skip\n * the client-side fetch entirely.\n *\n * Returns `LOADING` while CoinList is initializing or while participations are being fetched.\n * Returns `CONTENT` with all participations when `fetchAllParticipations()` succeeds.\n * Returns `ERROR` with:\n * - `not-authenticated` when fetching fails with {@link NotAuthenticatedError}\n * - `generic-error` for any other failure\n */\nexport function useParticipations(\n offerId?: OfferId,\n options: UseParticipationsOptions = {}\n): UseParticipationsResult {\n const { coinlist, isReady } = useCoinList();\n const { serverData } = options;\n const [participationsState, setParticipationsState] =\n useState<LoadParticipationsState>(\n serverData !== undefined\n ? { type: 'CONTENT', participations: serverData }\n : LOADING_STATE\n );\n\n useEffect(() => {\n let isCancelled = false;\n\n // Wait until CoinListProvider finishes auth initialization.\n if (!isReady) {\n if (serverData === undefined) {\n setParticipationsState(LOADING_STATE);\n }\n return () => {\n isCancelled = true;\n };\n }\n\n if (serverData !== undefined) {\n return () => {\n isCancelled = true;\n };\n }\n\n setParticipationsState(LOADING_STATE);\n coinlist\n .fetchAllParticipations(offerId)\n .then((participations) => {\n if (!isCancelled) {\n setParticipationsState({ type: 'CONTENT', participations });\n }\n })\n .catch((error: unknown) => {\n if (!isCancelled) {\n const reason: LoadParticipationsReason =\n error instanceof NotAuthenticatedError\n ? 'not-authenticated'\n : 'generic-error';\n setParticipationsState({ type: 'ERROR', reason });\n }\n });\n\n return () => {\n isCancelled = true;\n };\n }, [coinlist, isReady, offerId, serverData]);\n\n return { participationsState };\n}\n"],"mappings":"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","names":["useEffect","useState","useState","useEffect","useEffect","useState","LOADING_STATE","useState","useEffect","useEffect","useState","LOADING_STATE","useState","useEffect","useEffect","useEffect","useEffect","useState","LOADING_STATE","useState","useEffect"]}
|
|
@@ -541,13 +541,23 @@ function OfferCard({
|
|
|
541
541
|
// src/client/hooks/useCoinListOffers.ts
|
|
542
542
|
var import_react4 = require("react");
|
|
543
543
|
var LOADING_STATE = { type: "LOADING" };
|
|
544
|
-
function useCoinListOffers() {
|
|
544
|
+
function useCoinListOffers(options = {}) {
|
|
545
545
|
const { coinlist, isReady } = useCoinList();
|
|
546
|
-
const
|
|
546
|
+
const { serverOffers } = options;
|
|
547
|
+
const [offersState, setOffersState] = (0, import_react4.useState)(
|
|
548
|
+
serverOffers !== void 0 ? { type: "CONTENT", offers: serverOffers } : LOADING_STATE
|
|
549
|
+
);
|
|
547
550
|
(0, import_react4.useEffect)(() => {
|
|
548
551
|
let isCancelled = false;
|
|
549
552
|
if (!isReady) {
|
|
550
|
-
|
|
553
|
+
if (serverOffers === void 0) {
|
|
554
|
+
setOffersState(LOADING_STATE);
|
|
555
|
+
}
|
|
556
|
+
return () => {
|
|
557
|
+
isCancelled = true;
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
if (serverOffers !== void 0) {
|
|
551
561
|
return () => {
|
|
552
562
|
isCancelled = true;
|
|
553
563
|
};
|
|
@@ -566,22 +576,32 @@ function useCoinListOffers() {
|
|
|
566
576
|
return () => {
|
|
567
577
|
isCancelled = true;
|
|
568
578
|
};
|
|
569
|
-
}, [coinlist, isReady]);
|
|
579
|
+
}, [coinlist, isReady, serverOffers]);
|
|
570
580
|
return { offersState };
|
|
571
581
|
}
|
|
572
582
|
|
|
573
583
|
// src/client/hooks/useCoinListRequirements.ts
|
|
574
584
|
var import_react5 = require("react");
|
|
575
585
|
var LOADING_STATE2 = { type: "LOADING" };
|
|
576
|
-
function useCoinListRequirements(offerId) {
|
|
586
|
+
function useCoinListRequirements(offerId, options = {}) {
|
|
577
587
|
const { coinlist, isReady } = useCoinList();
|
|
578
|
-
const
|
|
588
|
+
const { serverData } = options;
|
|
589
|
+
const [requirementsState, setRequirementsState] = (0, import_react5.useState)(
|
|
590
|
+
serverData !== void 0 ? { type: "CONTENT", ...serverData } : LOADING_STATE2
|
|
591
|
+
);
|
|
579
592
|
const [fetchKey, setFetchKey] = (0, import_react5.useState)(0);
|
|
580
593
|
const refetch = (0, import_react5.useCallback)(() => setFetchKey((k) => k + 1), []);
|
|
581
594
|
(0, import_react5.useEffect)(() => {
|
|
582
595
|
let isCancelled = false;
|
|
583
596
|
if (!isReady) {
|
|
584
|
-
|
|
597
|
+
if (serverData === void 0) {
|
|
598
|
+
setRequirementsState(LOADING_STATE2);
|
|
599
|
+
}
|
|
600
|
+
return () => {
|
|
601
|
+
isCancelled = true;
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
if (serverData !== void 0 && fetchKey === 0) {
|
|
585
605
|
return () => {
|
|
586
606
|
isCancelled = true;
|
|
587
607
|
};
|
|
@@ -615,7 +635,9 @@ function useCoinListRequirements(offerId) {
|
|
|
615
635
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
616
636
|
var DEFAULT_MAX_COLUMNS = 3;
|
|
617
637
|
function OffersGrid(props) {
|
|
618
|
-
const { offersState } = useCoinListOffers(
|
|
638
|
+
const { offersState } = useCoinListOffers({
|
|
639
|
+
serverOffers: props.serverOffers
|
|
640
|
+
});
|
|
619
641
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(BaseOffersGrid, { ...props, offersState });
|
|
620
642
|
}
|
|
621
643
|
function BaseOffersGrid({
|
|
@@ -786,6 +808,7 @@ function VerificationActionZone({
|
|
|
786
808
|
}) {
|
|
787
809
|
switch (status) {
|
|
788
810
|
case "required":
|
|
811
|
+
if (onAction === null) return null;
|
|
789
812
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ClButton, { size: "sm", onClick: onAction, children: "Continue" });
|
|
790
813
|
case "pending":
|
|
791
814
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
@@ -802,7 +825,7 @@ function VerificationActionZone({
|
|
|
802
825
|
AlertBanner,
|
|
803
826
|
{
|
|
804
827
|
variant: "error",
|
|
805
|
-
content: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
|
|
828
|
+
content: onContactSupport === null ? "Verification rejected." : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
|
|
806
829
|
"Verification rejected.",
|
|
807
830
|
" ",
|
|
808
831
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
@@ -827,7 +850,7 @@ function WalletActionZone({
|
|
|
827
850
|
}) {
|
|
828
851
|
if (status === "completed") {
|
|
829
852
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
|
|
830
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ClButton, { size: "sm", variant: "secondary", onClick: onAction, children: "Change wallet" }),
|
|
853
|
+
onAction !== null && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ClButton, { size: "sm", variant: "secondary", onClick: onAction, children: "Change wallet" }),
|
|
831
854
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
832
855
|
AlertBanner,
|
|
833
856
|
{
|
|
@@ -839,7 +862,7 @@ function WalletActionZone({
|
|
|
839
862
|
}
|
|
840
863
|
if (status === "rejected") {
|
|
841
864
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
|
|
842
|
-
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ClButton, { size: "sm", onClick: onAction, children: "Connect wallet" }),
|
|
865
|
+
onAction !== null && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ClButton, { size: "sm", onClick: onAction, children: "Connect wallet" }),
|
|
843
866
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
844
867
|
AlertBanner,
|
|
845
868
|
{
|
|
@@ -849,6 +872,7 @@ function WalletActionZone({
|
|
|
849
872
|
)
|
|
850
873
|
] });
|
|
851
874
|
}
|
|
875
|
+
if (onAction === null) return null;
|
|
852
876
|
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ClButton, { size: "sm", onClick: onAction, children: "Connect wallet" });
|
|
853
877
|
}
|
|
854
878
|
function RequirementItem({
|
|
@@ -948,65 +972,30 @@ var DEFAULT_DESCRIPTIONS = {
|
|
|
948
972
|
accreditation: "Verify your accreditation status."
|
|
949
973
|
};
|
|
950
974
|
function RequirementsChecklist(props) {
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
const { requirementsState } = useCoinListRequirements(props.offerId);
|
|
975
|
+
const { coinlist } = useCoinList();
|
|
976
|
+
const { requirementsState } = useCoinListRequirements(props.offerId, {
|
|
977
|
+
serverData: props.serverData
|
|
978
|
+
});
|
|
979
|
+
const onRequirementAction = props.onRequirementActionOverride !== void 0 ? props.onRequirementActionOverride : (requirement) => coinlist.handleRequirement(requirement);
|
|
980
|
+
const onContactSupport = props.onContactSupportOverride !== void 0 ? props.onContactSupportOverride : () => coinlist.contactSupport();
|
|
958
981
|
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
959
982
|
BaseRequirementsChecklist,
|
|
960
983
|
{
|
|
961
984
|
...props,
|
|
985
|
+
onRequirementActionOverride: onRequirementAction,
|
|
986
|
+
onContactSupportOverride: onContactSupport,
|
|
962
987
|
requirementsState
|
|
963
988
|
}
|
|
964
989
|
);
|
|
965
990
|
}
|
|
966
|
-
function StaticRequirementsChecklist({
|
|
967
|
-
title,
|
|
968
|
-
description,
|
|
969
|
-
status,
|
|
970
|
-
onContinue,
|
|
971
|
-
children,
|
|
972
|
-
className
|
|
973
|
-
}) {
|
|
974
|
-
const [expandedIndex, setExpandedIndex] = (0, import_react6.useState)(null);
|
|
975
|
-
const accordionChildren = import_react6.Children.map(children, (child, index) => {
|
|
976
|
-
if (!(0, import_react6.isValidElement)(child)) return child;
|
|
977
|
-
return (0, import_react6.cloneElement)(
|
|
978
|
-
child,
|
|
979
|
-
{
|
|
980
|
-
expanded: expandedIndex === index,
|
|
981
|
-
onToggle: () => setExpandedIndex((prev) => prev === index ? null : index)
|
|
982
|
-
}
|
|
983
|
-
);
|
|
984
|
-
});
|
|
985
|
-
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: cn("flex w-full flex-col gap-4", className), children: [
|
|
986
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
987
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h3", { className: cn(typeClasses.h3, "text-text-primary"), children: title }),
|
|
988
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: cn(typeClasses.body, "text-text-secondary"), children: description })
|
|
989
|
-
] }),
|
|
990
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { className: "flex flex-col divide-y divide-divider", children: accordionChildren }),
|
|
991
|
-
status === "ineligible" && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
992
|
-
AlertBanner,
|
|
993
|
-
{
|
|
994
|
-
variant: "error",
|
|
995
|
-
icon: true,
|
|
996
|
-
content: "You are not eligible to participate."
|
|
997
|
-
}
|
|
998
|
-
),
|
|
999
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(ClButton, { fullWidth: true, disabled: status !== "eligible", onClick: onContinue, children: "Continue" })
|
|
1000
|
-
] });
|
|
1001
|
-
}
|
|
1002
991
|
function BaseRequirementsChecklist({
|
|
1003
992
|
requirementsState,
|
|
1004
993
|
optionId,
|
|
1005
994
|
title,
|
|
1006
995
|
description,
|
|
1007
996
|
onContinue,
|
|
1008
|
-
onRequirementAction,
|
|
1009
|
-
onContactSupport,
|
|
997
|
+
onRequirementActionOverride: onRequirementAction,
|
|
998
|
+
onContactSupportOverride: onContactSupport,
|
|
1010
999
|
getLabel,
|
|
1011
1000
|
getDescription,
|
|
1012
1001
|
loading,
|
|
@@ -1053,6 +1042,18 @@ function BaseRequirementsChecklist({
|
|
|
1053
1042
|
const statusInfo = statusByRequirementId.get(req.id);
|
|
1054
1043
|
const label = getLabel ? getLabel(req) : DEFAULT_LABELS[req.type];
|
|
1055
1044
|
const description2 = (getDescription ? getDescription(req) : null) ?? DEFAULT_DESCRIPTIONS[req.type];
|
|
1045
|
+
let onActionBound;
|
|
1046
|
+
if (onRequirementAction === null) {
|
|
1047
|
+
onActionBound = null;
|
|
1048
|
+
} else if (onRequirementAction) {
|
|
1049
|
+
onActionBound = () => onRequirementAction(req);
|
|
1050
|
+
}
|
|
1051
|
+
let onContactSupportBound;
|
|
1052
|
+
if (onContactSupport === null) {
|
|
1053
|
+
onContactSupportBound = null;
|
|
1054
|
+
} else if (onContactSupport) {
|
|
1055
|
+
onContactSupportBound = () => onContactSupport(req);
|
|
1056
|
+
}
|
|
1056
1057
|
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1057
1058
|
RequirementItem,
|
|
1058
1059
|
{
|
|
@@ -1064,8 +1065,8 @@ function BaseRequirementsChecklist({
|
|
|
1064
1065
|
),
|
|
1065
1066
|
expanded: expandedIndex === index,
|
|
1066
1067
|
onToggle: () => setExpandedIndex((prev) => prev === index ? null : index),
|
|
1067
|
-
onAction:
|
|
1068
|
-
onContactSupport:
|
|
1068
|
+
onAction: onActionBound,
|
|
1069
|
+
onContactSupport: onContactSupportBound
|
|
1069
1070
|
},
|
|
1070
1071
|
req.id
|
|
1071
1072
|
);
|
|
@@ -1078,7 +1079,7 @@ function BaseRequirementsChecklist({
|
|
|
1078
1079
|
content: "You are not eligible to participate."
|
|
1079
1080
|
}
|
|
1080
1081
|
),
|
|
1081
|
-
/* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1082
|
+
onContinue && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
|
|
1082
1083
|
ClButton,
|
|
1083
1084
|
{
|
|
1084
1085
|
fullWidth: true,
|