@stamprally/ui 0.9.0 → 0.11.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/index.js CHANGED
@@ -1,49 +1,232 @@
1
- import { calculateProgress, resolveLocalizedText } from '@stamprally/core';
2
- import { useState, useEffect, useMemo, useCallback } from 'react';
3
- import { jsxs, jsx } from 'react/jsx-runtime';
1
+ import { calculateProgress, resolveLocalizedText, isQrSupported, isNfcSupported, readQrContext, getCurrentGeoContext, readNfcContext } from '@stamprally/core';
2
+ import { useState, useEffect, useMemo, useCallback, useRef } from 'react';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
 
5
5
  // src/index.tsx
6
6
  var label = (dictionary, locale, key, fallback) => dictionary?.[locale]?.[key] ?? fallback;
7
+ var join = (...names) => {
8
+ const value = names.filter((name) => name !== void 0 && name !== "").join(" ");
9
+ return value === "" ? void 0 : value;
10
+ };
7
11
  function DefaultCondition({
8
12
  condition,
9
13
  dictionary,
10
14
  locale,
11
15
  disabled,
16
+ classNames,
17
+ styles,
12
18
  onSubmit
13
19
  }) {
14
20
  const [value, setValue] = useState("");
15
- const submit = (event) => {
16
- event.preventDefault();
17
- onSubmit(
18
- condition.type === "gps" ? { latitude: Number(value.split(",")[0]), longitude: Number(value.split(",")[1]) } : condition.type === "nfc" ? { tagId: value } : value
19
- );
21
+ const [status, setStatus] = useState("idle");
22
+ const [error, setError] = useState(null);
23
+ const videoRef = useRef(null);
24
+ const text = (key, fallback) => label(dictionary, locale, key, fallback);
25
+ const verify = async (proof) => {
26
+ setStatus("loading");
27
+ setError(null);
28
+ try {
29
+ const result = await onSubmit(proof);
30
+ if (result !== void 0 && typeof result === "object" && result !== null && "ok" in result && result.ok === false) {
31
+ setStatus("error");
32
+ setError(text("verificationFailed", "Verification failed."));
33
+ } else setStatus("success");
34
+ } catch {
35
+ setStatus("error");
36
+ setError(text("verificationFailed", "Verification failed."));
37
+ }
38
+ };
39
+ const readLocation = () => {
40
+ setStatus("loading");
41
+ void getCurrentGeoContext().then((result) => {
42
+ if (result.ok) void verify(result.value);
43
+ else {
44
+ setStatus("error");
45
+ setError(result.error.message);
46
+ }
47
+ });
48
+ };
49
+ const readNfc = () => {
50
+ setStatus("loading");
51
+ void readNfcContext().then((result) => {
52
+ if (result.ok) void verify(result.value);
53
+ else {
54
+ setStatus("error");
55
+ setError(result.error.message);
56
+ }
57
+ });
20
58
  };
21
- return /* @__PURE__ */ jsxs("form", { onSubmit: submit, children: [
22
- /* @__PURE__ */ jsxs("label", { children: [
23
- label(dictionary, locale, "proof", "Proof"),
59
+ const scanQr = () => {
60
+ if (videoRef.current === null) return;
61
+ setStatus("loading");
62
+ void readQrContext(videoRef.current).then((result) => {
63
+ if (result.ok) void verify(result.value);
64
+ else {
65
+ setStatus("error");
66
+ setError(result.error.message);
67
+ }
68
+ });
69
+ };
70
+ return /* @__PURE__ */ jsxs("div", { children: [
71
+ condition.type === "qr" && /* @__PURE__ */ jsxs(Fragment, { children: [
72
+ /* @__PURE__ */ jsxs("label", { children: [
73
+ text("qrValue", "QR value"),
74
+ /* @__PURE__ */ jsx(
75
+ "input",
76
+ {
77
+ "aria-label": text("qrValue", "QR value"),
78
+ placeholder: condition.qrEntryUrl ?? text("qrPlaceholder", "Paste a QR value"),
79
+ value,
80
+ onChange: (event) => setValue(event.target.value)
81
+ }
82
+ )
83
+ ] }),
84
+ /* @__PURE__ */ jsx(
85
+ "video",
86
+ {
87
+ ref: videoRef,
88
+ "aria-label": text("qrCamera", "QR camera"),
89
+ hidden: !isQrSupported(),
90
+ children: /* @__PURE__ */ jsx("track", { kind: "captions" })
91
+ }
92
+ ),
24
93
  /* @__PURE__ */ jsx(
25
- "input",
94
+ "button",
26
95
  {
27
- "aria-label": label(dictionary, locale, "proof", "Proof"),
28
- value,
29
- onChange: (event) => setValue(event.target.value)
96
+ type: "button",
97
+ className: join("sry-action", classNames?.action, classNames?.button),
98
+ style: styles?.button ?? styles?.action,
99
+ disabled: disabled || !isQrSupported(),
100
+ onClick: scanQr,
101
+ children: text("scanQr", "Scan QR")
30
102
  }
31
103
  )
32
104
  ] }),
33
- /* @__PURE__ */ jsx("button", { type: "submit", disabled, children: condition.type === "gps" ? label(dictionary, locale, "checkLocation", "Check location") : label(dictionary, locale, "checkIn", "Check in") })
105
+ condition.type === "gps" && /* @__PURE__ */ jsx(
106
+ "button",
107
+ {
108
+ type: "button",
109
+ className: join("sry-action", classNames?.action, classNames?.button),
110
+ style: styles?.button ?? styles?.action,
111
+ disabled,
112
+ onClick: readLocation,
113
+ children: text("checkLocation", "Check location")
114
+ }
115
+ ),
116
+ condition.type === "nfc" && /* @__PURE__ */ jsx(
117
+ "button",
118
+ {
119
+ type: "button",
120
+ className: join("sry-action", classNames?.action, classNames?.button),
121
+ style: styles?.button ?? styles?.action,
122
+ disabled: disabled || !isNfcSupported(),
123
+ onClick: readNfc,
124
+ children: text("readNfc", "Read NFC")
125
+ }
126
+ ),
127
+ (condition.type === "passcode" || condition.type === "custom" || condition.type === "qr") && /* @__PURE__ */ jsxs(
128
+ "form",
129
+ {
130
+ onSubmit: (event) => {
131
+ event.preventDefault();
132
+ void verify(value);
133
+ },
134
+ children: [
135
+ /* @__PURE__ */ jsxs("label", { children: [
136
+ text(
137
+ condition.type === "passcode" ? "passcode" : "proof",
138
+ condition.type === "passcode" ? "Passcode" : "Proof"
139
+ ),
140
+ /* @__PURE__ */ jsx(
141
+ "input",
142
+ {
143
+ "aria-label": text("proof", "Proof"),
144
+ value,
145
+ onChange: (event) => setValue(event.target.value)
146
+ }
147
+ )
148
+ ] }),
149
+ /* @__PURE__ */ jsx(
150
+ "button",
151
+ {
152
+ type: "submit",
153
+ className: join("sry-action", classNames?.action, classNames?.button),
154
+ style: styles?.button ?? styles?.action,
155
+ disabled: disabled || value.trim() === "",
156
+ children: text("checkIn", "Check in")
157
+ }
158
+ )
159
+ ]
160
+ }
161
+ ),
162
+ /* @__PURE__ */ jsxs(
163
+ "div",
164
+ {
165
+ className: join("sry-feedback", classNames?.feedback),
166
+ style: styles?.feedback,
167
+ "aria-live": "polite",
168
+ role: status === "error" ? "alert" : void 0,
169
+ children: [
170
+ status === "loading" && text("verifying", "Verifying\u2026"),
171
+ status === "success" && text("verified", "Verified"),
172
+ status === "error" && (error ?? text("verificationFailed", "Verification failed."))
173
+ ]
174
+ }
175
+ )
34
176
  ] });
35
177
  }
178
+ function DefaultRewardCard({
179
+ reward,
180
+ state,
181
+ locale,
182
+ dictionary,
183
+ onClaim,
184
+ classNames,
185
+ styles
186
+ }) {
187
+ const status = state?.status ?? "LOCKED";
188
+ return /* @__PURE__ */ jsxs(
189
+ "button",
190
+ {
191
+ type: "button",
192
+ className: join(classNames?.reward, classNames?.button),
193
+ style: styles?.reward ?? styles?.button,
194
+ disabled: status !== "AVAILABLE" || onClaim === void 0,
195
+ onClick: () => {
196
+ if (onClaim !== void 0) void onClaim(reward.id);
197
+ },
198
+ children: [
199
+ resolveLocalizedText(reward.title, locale),
200
+ " (",
201
+ label(dictionary, locale, `status.${status.toLowerCase()}`, status),
202
+ ")"
203
+ ]
204
+ }
205
+ );
206
+ }
36
207
  function RallyViewer({
37
208
  config: providedConfig,
38
209
  client,
39
210
  adapter,
40
211
  locale,
41
212
  dictionary,
42
- customConditionRenderers = {}
213
+ classNames = {},
214
+ styles = {},
215
+ style,
216
+ customConditionRenderers = {},
217
+ headerSlot,
218
+ footerSlot,
219
+ renderSpotCard,
220
+ renderRewardCard,
221
+ renderStatusBadge,
222
+ renderVerifyingState,
223
+ renderSuccessFeedback,
224
+ renderErrorFeedback
43
225
  }) {
44
226
  const config = providedConfig ?? client?.getConfig() ?? adapter?.config;
45
227
  const [state, setState] = useState(() => client?.getState() ?? null);
46
228
  const [busy, setBusy] = useState(null);
229
+ const [feedback, setFeedback] = useState(null);
47
230
  useEffect(() => {
48
231
  if (client === void 0) return;
49
232
  const unsubscribe = client.subscribe(setState);
@@ -51,87 +234,110 @@ function RallyViewer({
51
234
  return unsubscribe;
52
235
  }, [client]);
53
236
  if (config === void 0) throw new Error("RallyViewer requires config, client, or adapter.");
237
+ const currentState = state ?? {
238
+ rallyId: config.id,
239
+ userId: null,
240
+ records: [],
241
+ rewards: [],
242
+ updatedAt: ""
243
+ };
54
244
  const progress = useMemo(
55
- () => calculateProgress(
56
- state ?? { rallyId: config.id, userId: null, records: [], rewards: [], updatedAt: "" },
57
- config
58
- ),
59
- [config, state]
245
+ () => calculateProgress(currentState, config),
246
+ [config, currentState]
60
247
  );
61
248
  const checkIn = adapter?.onCheckIn ?? (client === void 0 ? void 0 : (spotId, proof, options) => client.checkIn(spotId, proof, options));
62
249
  const claim = adapter?.onClaimReward ?? (client === void 0 ? void 0 : (rewardId, options) => client.claimReward(rewardId, options));
63
250
  const submit = useCallback(
64
- (spotId, proof) => {
65
- if (checkIn === void 0) return;
251
+ async (spotId, proof) => {
252
+ if (checkIn === void 0) return void 0;
66
253
  setBusy(spotId);
67
- void checkIn(spotId, proof).finally(() => setBusy(null));
254
+ const result = await checkIn(spotId, proof).finally(() => setBusy(null));
255
+ setFeedback(result);
256
+ return result;
68
257
  },
69
258
  [checkIn]
70
259
  );
71
- return /* @__PURE__ */ jsxs("section", { "aria-label": label(dictionary, locale, "viewer", "Stamp rally"), children: [
72
- /* @__PURE__ */ jsx("h1", { children: resolveLocalizedText(config.title, locale) || config.id }),
73
- /* @__PURE__ */ jsx(
74
- "progress",
75
- {
76
- "aria-label": label(dictionary, locale, "progress", "Progress"),
77
- max: 100,
78
- value: progress.percentage
79
- }
80
- ),
81
- " ",
82
- /* @__PURE__ */ jsxs("span", { children: [
83
- progress.acquired,
84
- "/",
85
- progress.total
86
- ] }),
87
- /* @__PURE__ */ jsx("div", { children: config.spots.map(
88
- (spot) => spot.conditions.map((condition) => {
89
- const Renderer = customConditionRenderers[condition.type] ?? DefaultCondition;
90
- return /* @__PURE__ */ jsxs("article", { children: [
91
- /* @__PURE__ */ jsx("h2", { children: resolveLocalizedText(spot.name, locale) }),
92
- /* @__PURE__ */ jsx(
93
- Renderer,
94
- {
95
- spot,
96
- condition,
97
- locale,
98
- ...dictionary === void 0 ? {} : { dictionary },
99
- disabled: busy !== null,
100
- onSubmit: (proof) => submit(spot.id, proof)
101
- }
102
- )
103
- ] }, `${spot.id}-${condition.type}-${JSON.stringify(condition)}`);
104
- })
105
- ) }),
106
- /* @__PURE__ */ jsx("section", { "aria-label": label(dictionary, locale, "rewards", "Rewards"), children: config.rewards.map((reward) => /* @__PURE__ */ jsx(
107
- RewardButton,
108
- {
109
- reward,
110
- state: state?.rewards.find((item) => item.rewardId === reward.id),
111
- onClaim: claim
112
- },
113
- reward.id
114
- )) })
115
- ] });
116
- }
117
- function RewardButton({
118
- reward,
119
- state,
120
- onClaim
121
- }) {
122
260
  return /* @__PURE__ */ jsxs(
123
- "button",
261
+ "section",
124
262
  {
125
- type: "button",
126
- disabled: state?.status !== "AVAILABLE" || onClaim === void 0,
127
- onClick: () => {
128
- if (onClaim !== void 0) void onClaim(reward.id);
129
- },
263
+ className: classNames.root,
264
+ style: styles.root ?? style,
265
+ "aria-label": label(dictionary, locale, "viewer", "Stamp rally"),
130
266
  children: [
131
- resolveLocalizedText(reward.title, "en"),
132
- " (",
133
- state?.status ?? "LOCKED",
134
- ")"
267
+ /* @__PURE__ */ jsxs("header", { className: classNames.header, style: styles.header, children: [
268
+ typeof headerSlot === "function" ? headerSlot({ config, state: currentState }) : headerSlot,
269
+ headerSlot === void 0 && /* @__PURE__ */ jsx("h1", { children: resolveLocalizedText(config.title, locale) || config.id }),
270
+ /* @__PURE__ */ jsx(
271
+ "progress",
272
+ {
273
+ "aria-label": label(dictionary, locale, "progress", "Progress"),
274
+ max: 100,
275
+ value: progress.percentage
276
+ }
277
+ ),
278
+ /* @__PURE__ */ jsxs("span", { children: [
279
+ progress.acquired,
280
+ "/",
281
+ progress.total
282
+ ] })
283
+ ] }),
284
+ busy !== null && renderVerifyingState?.(),
285
+ feedback?.ok === true && renderSuccessFeedback?.(feedback),
286
+ feedback?.ok === false && renderErrorFeedback?.(
287
+ "message" in feedback.error ? feedback.error.message : feedback.error.code
288
+ ),
289
+ /* @__PURE__ */ jsx("div", { children: config.spots.map((spot) => {
290
+ const claimed = currentState.records.some((record) => record.stampId === spot.id);
291
+ const status = busy === spot.id ? "verifying" : claimed ? "claimed" : "available";
292
+ const children = spot.conditions.map((condition) => {
293
+ const Renderer = customConditionRenderers[condition.type] ?? DefaultCondition;
294
+ return /* @__PURE__ */ jsx(
295
+ "div",
296
+ {
297
+ className: classNames.condition,
298
+ style: styles.condition,
299
+ children: /* @__PURE__ */ jsx(
300
+ Renderer,
301
+ {
302
+ spot,
303
+ condition,
304
+ locale,
305
+ ...dictionary === void 0 ? {} : { dictionary },
306
+ disabled: busy !== null || claimed,
307
+ ...Object.keys(classNames).length === 0 ? {} : { classNames },
308
+ ...Object.keys(styles).length === 0 ? {} : { styles },
309
+ onSubmit: (proof) => submit(spot.id, proof)
310
+ }
311
+ )
312
+ },
313
+ `${spot.id}-${condition.type}-${JSON.stringify(condition)}`
314
+ );
315
+ });
316
+ const props = {
317
+ spot,
318
+ state: currentState,
319
+ status,
320
+ locale,
321
+ ...dictionary === void 0 ? {} : { dictionary },
322
+ children
323
+ };
324
+ return /* @__PURE__ */ jsx("div", { className: classNames.card, style: styles.card, children: renderSpotCard?.(props) ?? /* @__PURE__ */ jsxs("article", { children: [
325
+ /* @__PURE__ */ jsx("h2", { children: resolveLocalizedText(spot.name, locale) }),
326
+ renderStatusBadge?.({ status }) ?? /* @__PURE__ */ jsx("span", { className: classNames.badge, style: styles.badge, children: status }),
327
+ children
328
+ ] }) }, spot.id);
329
+ }) }),
330
+ /* @__PURE__ */ jsx("section", { "aria-label": label(dictionary, locale, "rewards", "Rewards"), children: config.rewards.map((reward) => {
331
+ const props = {
332
+ reward,
333
+ state: currentState.rewards.find((item) => item.rewardId === reward.id),
334
+ locale,
335
+ ...dictionary === void 0 ? {} : { dictionary },
336
+ onClaim: claim
337
+ };
338
+ return /* @__PURE__ */ jsx("div", { className: classNames.reward, style: styles.reward, children: renderRewardCard?.(props) ?? /* @__PURE__ */ jsx(DefaultRewardCard, { ...props, classNames, styles }) }, reward.id);
339
+ }) }),
340
+ /* @__PURE__ */ jsx("footer", { className: classNames.footer, style: styles.footer, children: footerSlot })
135
341
  ]
136
342
  }
137
343
  );
@@ -140,17 +346,57 @@ function StampSheet({
140
346
  config,
141
347
  state,
142
348
  title,
143
- progress
349
+ progress,
350
+ locale = "en",
351
+ dictionary,
352
+ classNames = {},
353
+ styles = {},
354
+ style,
355
+ headerSlot,
356
+ footerSlot,
357
+ renderSpotCard,
358
+ renderStatusBadge
144
359
  }) {
145
- const current = progress ?? calculateProgress(
146
- state ?? { rallyId: config.id, userId: null, records: [], rewards: [], updatedAt: "" },
147
- config
360
+ const currentState = state ?? {
361
+ rallyId: config.id,
362
+ userId: null,
363
+ records: [],
364
+ rewards: [],
365
+ updatedAt: ""
366
+ };
367
+ const current = progress ?? calculateProgress(currentState, config);
368
+ return /* @__PURE__ */ jsxs(
369
+ "section",
370
+ {
371
+ className: classNames.root,
372
+ style: styles.root ?? style,
373
+ "aria-label": title ?? label(dictionary, locale, "stampSheet", "Stamp sheet"),
374
+ children: [
375
+ /* @__PURE__ */ jsxs("header", { className: classNames.header, style: styles.header, children: [
376
+ typeof headerSlot === "function" ? headerSlot({ config, state: currentState }) : headerSlot ?? /* @__PURE__ */ jsx("h2", { children: title ?? resolveLocalizedText(config.title, locale) }),
377
+ /* @__PURE__ */ jsx("progress", { max: 100, value: current.percentage })
378
+ ] }),
379
+ /* @__PURE__ */ jsx("div", { children: config.spots.map((spot) => {
380
+ const claimed = currentState.records.some((record) => record.stampId === spot.id);
381
+ const status = claimed ? "claimed" : "available";
382
+ const props = {
383
+ spot,
384
+ state: currentState,
385
+ status,
386
+ locale,
387
+ ...dictionary === void 0 ? {} : { dictionary },
388
+ children: /* @__PURE__ */ jsx("span", { className: classNames.slot, style: styles.slot, children: claimed ? "\u2713" : "\u25CB" })
389
+ };
390
+ return /* @__PURE__ */ jsx("div", { className: classNames.card, style: styles.card, children: renderSpotCard?.(props) ?? /* @__PURE__ */ jsxs("span", { className: classNames.slot, style: styles.slot, children: [
391
+ resolveLocalizedText(spot.name, locale),
392
+ " ",
393
+ renderStatusBadge?.({ status }) ?? (claimed ? "\u2713" : "\u25CB")
394
+ ] }) }, spot.id);
395
+ }) }),
396
+ /* @__PURE__ */ jsx("footer", { className: classNames.footer, style: styles.footer, children: footerSlot })
397
+ ]
398
+ }
148
399
  );
149
- return /* @__PURE__ */ jsxs("section", { "aria-label": title ?? "Stamp sheet", children: [
150
- /* @__PURE__ */ jsx("h2", { children: title ?? resolveLocalizedText(config.title, "en") }),
151
- /* @__PURE__ */ jsx("progress", { max: 100, value: current.percentage }),
152
- /* @__PURE__ */ jsx("div", { children: config.spots.map((spot) => /* @__PURE__ */ jsx("span", { children: state?.records.some((record) => record.stampId === spot.id) ? "\u2713" : "\u25CB" }, spot.id)) })
153
- ] });
154
400
  }
155
401
 
156
402
  export { RallyViewer, StampSheet };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type {\n CheckInOptions,\n CheckInResult,\n ClaimOptions,\n ClaimResult,\n LocaleDictionary,\n PublicCheckInCondition,\n PublicRallyConfig,\n PublicSpotItem,\n RallyConfig,\n Reward,\n RewardState,\n StampRallyClient,\n StampRallyProgress,\n StampRallyState,\n UserRallyState,\n} from \"@stamprally/core\";\nimport { calculateProgress, resolveLocalizedText } from \"@stamprally/core\";\nimport {\n type ComponentType,\n type FormEvent,\n type ReactElement,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\n\nexport interface ConditionRendererProps<TLocale extends string = string> {\n readonly spot: PublicSpotItem<TLocale>;\n readonly condition: PublicCheckInCondition;\n readonly locale: TLocale;\n readonly dictionary?: LocaleDictionary<TLocale>;\n readonly disabled: boolean;\n readonly onSubmit: (proof: unknown) => void;\n}\nexport type ConditionRenderer<TLocale extends string = string> = ComponentType<\n ConditionRendererProps<TLocale>\n>;\nexport interface RallyViewerAdapter<TLocale extends string = string> {\n readonly config: PublicRallyConfig<TLocale>;\n readonly onCheckIn: (\n spotId: string,\n proof: unknown,\n options?: CheckInOptions,\n ) => Promise<CheckInResult>;\n readonly onClaimReward?: (rewardId: string, options?: ClaimOptions) => Promise<ClaimResult>;\n}\nexport interface RallyViewerProps<TLocale extends string = string> {\n readonly config?: PublicRallyConfig<TLocale>;\n readonly client?: StampRallyClient;\n readonly adapter?: RallyViewerAdapter<TLocale>;\n readonly locale: TLocale;\n readonly dictionary?: LocaleDictionary<TLocale>;\n readonly customConditionRenderers?: Partial<\n Record<PublicCheckInCondition[\"type\"], ConditionRenderer<TLocale>>\n >;\n}\nconst label = (\n dictionary: LocaleDictionary<string> | undefined,\n locale: string,\n key: string,\n fallback: string,\n): string => dictionary?.[locale]?.[key] ?? fallback;\nfunction DefaultCondition<TLocale extends string>({\n condition,\n dictionary,\n locale,\n disabled,\n onSubmit,\n}: ConditionRendererProps<TLocale>): ReactElement {\n const [value, setValue] = useState(\"\");\n const submit = (event: FormEvent): void => {\n event.preventDefault();\n onSubmit(\n condition.type === \"gps\"\n ? { latitude: Number(value.split(\",\")[0]), longitude: Number(value.split(\",\")[1]) }\n : condition.type === \"nfc\"\n ? { tagId: value }\n : value,\n );\n };\n return (\n <form onSubmit={submit}>\n <label>\n {label(dictionary, locale, \"proof\", \"Proof\")}\n <input\n aria-label={label(dictionary, locale, \"proof\", \"Proof\")}\n value={value}\n onChange={(event) => setValue(event.target.value)}\n />\n </label>\n <button type=\"submit\" disabled={disabled}>\n {condition.type === \"gps\"\n ? label(dictionary, locale, \"checkLocation\", \"Check location\")\n : label(dictionary, locale, \"checkIn\", \"Check in\")}\n </button>\n </form>\n );\n}\nexport function RallyViewer<TLocale extends string = string>({\n config: providedConfig,\n client,\n adapter,\n locale,\n dictionary,\n customConditionRenderers = {},\n}: RallyViewerProps<TLocale>): ReactElement {\n const config = providedConfig ?? client?.getConfig() ?? adapter?.config;\n const [state, setState] = useState<UserRallyState | null>(() => client?.getState() ?? null);\n const [busy, setBusy] = useState<string | null>(null);\n useEffect(() => {\n if (client === undefined) return;\n const unsubscribe = client.subscribe(setState);\n void client.init().then(setState);\n return unsubscribe;\n }, [client]);\n if (config === undefined) throw new Error(\"RallyViewer requires config, client, or adapter.\");\n const progress = useMemo<StampRallyProgress>(\n () =>\n calculateProgress(\n state ?? { rallyId: config.id, userId: null, records: [], rewards: [], updatedAt: \"\" },\n config,\n ),\n [config, state],\n );\n const checkIn =\n adapter?.onCheckIn ??\n (client === undefined\n ? undefined\n : (spotId: string, proof: unknown, options?: CheckInOptions) =>\n client.checkIn(spotId, proof, options));\n const claim =\n adapter?.onClaimReward ??\n (client === undefined\n ? undefined\n : (rewardId: string, options?: ClaimOptions) => client.claimReward(rewardId, options));\n const submit = useCallback(\n (spotId: string, proof: unknown) => {\n if (checkIn === undefined) return;\n setBusy(spotId);\n void checkIn(spotId, proof).finally(() => setBusy(null));\n },\n [checkIn],\n );\n return (\n <section aria-label={label(dictionary, locale, \"viewer\", \"Stamp rally\")}>\n <h1>{resolveLocalizedText(config.title, locale) || config.id}</h1>\n <progress\n aria-label={label(dictionary, locale, \"progress\", \"Progress\")}\n max={100}\n value={progress.percentage}\n />{\" \"}\n <span>\n {progress.acquired}/{progress.total}\n </span>\n <div>\n {config.spots.map((spot) =>\n spot.conditions.map((condition) => {\n const Renderer = customConditionRenderers[condition.type] ?? DefaultCondition;\n return (\n <article key={`${spot.id}-${condition.type}-${JSON.stringify(condition)}`}>\n <h2>{resolveLocalizedText(spot.name, locale)}</h2>\n <Renderer\n spot={spot}\n condition={condition}\n locale={locale}\n {...(dictionary === undefined ? {} : { dictionary })}\n disabled={busy !== null}\n onSubmit={(proof) => submit(spot.id, proof)}\n />\n </article>\n );\n }),\n )}\n </div>\n <section aria-label={label(dictionary, locale, \"rewards\", \"Rewards\")}>\n {config.rewards.map((reward) => (\n <RewardButton\n key={reward.id}\n reward={reward}\n state={state?.rewards.find((item) => item.rewardId === reward.id)}\n onClaim={claim}\n />\n ))}\n </section>\n </section>\n );\n}\nfunction RewardButton({\n reward,\n state,\n onClaim,\n}: {\n readonly reward: Reward;\n readonly state: RewardState | undefined;\n readonly onClaim:\n | ((rewardId: string, options?: ClaimOptions) => Promise<ClaimResult>)\n | undefined;\n}): ReactElement {\n return (\n <button\n type=\"button\"\n disabled={state?.status !== \"AVAILABLE\" || onClaim === undefined}\n onClick={() => {\n if (onClaim !== undefined) void onClaim(reward.id);\n }}\n >\n {resolveLocalizedText(reward.title, \"en\")} ({state?.status ?? \"LOCKED\"})\n </button>\n );\n}\nexport interface StampSheetProps<TLocale extends string = string> {\n readonly config: RallyConfig<TLocale>;\n readonly state?: StampRallyState | null;\n readonly title?: string;\n readonly progress?: StampRallyProgress;\n readonly locale?: TLocale;\n}\nexport function StampSheet<TLocale extends string = string>({\n config,\n state,\n title,\n progress,\n}: StampSheetProps<TLocale>): ReactElement {\n const current =\n progress ??\n calculateProgress(\n state ?? { rallyId: config.id, userId: null, records: [], rewards: [], updatedAt: \"\" },\n config,\n );\n return (\n <section aria-label={title ?? \"Stamp sheet\"}>\n <h2>{title ?? resolveLocalizedText(config.title, \"en\")}</h2>\n <progress max={100} value={current.percentage} />\n <div>\n {config.spots.map((spot) => (\n <span key={spot.id}>\n {state?.records.some((record) => record.stampId === spot.id) ? \"✓\" : \"○\"}\n </span>\n ))}\n </div>\n </section>\n );\n}\n"]}
1
+ {"version":3,"sources":["../src/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type {\n CheckInOptions,\n CheckInResult,\n ClaimOptions,\n ClaimResult,\n LocaleDictionary,\n PublicCheckInCondition,\n PublicRallyConfig,\n PublicReward,\n PublicSpotItem,\n RallyConfig,\n RewardState,\n StampRallyClient,\n StampRallyProgress,\n StampRallyState,\n UserRallyState,\n} from \"@stamprally/core\";\nimport {\n calculateProgress,\n getCurrentGeoContext,\n isNfcSupported,\n isQrSupported,\n readNfcContext,\n readQrContext,\n resolveLocalizedText,\n} from \"@stamprally/core\";\nimport {\n type ComponentType,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nexport type ViewerClassName =\n | \"root\"\n | \"header\"\n | \"card\"\n | \"condition\"\n | \"slot\"\n | \"badge\"\n | \"modal\"\n | \"button\"\n | \"action\"\n | \"feedback\"\n | \"reward\"\n | \"footer\";\nexport type ViewerStyle = CSSProperties & {\n readonly [key: `--${string}`]: string | number | undefined;\n};\nexport type ViewerStyles = Partial<Record<ViewerClassName, ViewerStyle>>;\nexport type ViewerClassNames = Partial<Record<ViewerClassName, string>>;\nexport type SpotStatus = \"locked\" | \"available\" | \"claimed\" | \"verifying\" | \"error\";\n\nexport interface ConditionRendererProps<TLocale extends string = string> {\n readonly spot: PublicSpotItem<TLocale>;\n readonly condition: PublicCheckInCondition;\n readonly locale: TLocale;\n readonly dictionary?: LocaleDictionary<TLocale>;\n readonly disabled: boolean;\n readonly classNames?: ViewerClassNames;\n readonly styles?: ViewerStyles;\n // biome-ignore lint/suspicious/noConfusingVoidType: custom renderers may fire-and-forget while built-ins await feedback.\n readonly onSubmit: (proof: unknown) => void | Promise<unknown>;\n}\nexport type ConditionRenderer<TLocale extends string = string> = ComponentType<\n ConditionRendererProps<TLocale>\n>;\n\nexport interface SpotCardProps<TLocale extends string = string> {\n readonly spot: PublicSpotItem<TLocale>;\n readonly state: UserRallyState;\n readonly status: SpotStatus;\n readonly locale: TLocale;\n readonly dictionary?: LocaleDictionary<TLocale>;\n readonly children: ReactNode;\n}\nexport interface RewardCardProps<TLocale extends string = string> {\n readonly reward: PublicReward<TLocale>;\n readonly state: RewardState | undefined;\n readonly locale: TLocale;\n readonly dictionary?: LocaleDictionary<TLocale>;\n readonly onClaim:\n | ((rewardId: string, options?: ClaimOptions) => Promise<ClaimResult>)\n | undefined;\n}\nexport interface RallyViewerSlots<TLocale extends string = string> {\n readonly headerSlot?:\n | ReactNode\n | ((props: {\n readonly config: PublicRallyConfig<TLocale>;\n readonly state: UserRallyState;\n }) => ReactNode);\n readonly footerSlot?: ReactNode;\n readonly renderSpotCard?: (props: SpotCardProps<TLocale>) => ReactNode;\n readonly renderRewardCard?: (props: RewardCardProps<TLocale>) => ReactNode;\n readonly renderStatusBadge?: (props: { readonly status: SpotStatus }) => ReactNode;\n readonly renderVerifyingState?: () => ReactNode;\n readonly renderSuccessFeedback?: (result: CheckInResult) => ReactNode;\n readonly renderErrorFeedback?: (error: string) => ReactNode;\n}\n\nexport interface RallyViewerAdapter<TLocale extends string = string> {\n readonly config: PublicRallyConfig<TLocale>;\n readonly onCheckIn: (\n spotId: string,\n proof: unknown,\n options?: CheckInOptions,\n ) => Promise<CheckInResult>;\n readonly onClaimReward?: (rewardId: string, options?: ClaimOptions) => Promise<ClaimResult>;\n}\nexport interface RallyViewerProps<TLocale extends string = string>\n extends RallyViewerSlots<TLocale> {\n readonly config?: PublicRallyConfig<TLocale>;\n readonly client?: StampRallyClient;\n readonly adapter?: RallyViewerAdapter<TLocale>;\n readonly locale: TLocale;\n readonly dictionary?: LocaleDictionary<TLocale>;\n readonly classNames?: ViewerClassNames;\n readonly styles?: ViewerStyles;\n readonly style?: ViewerStyle;\n readonly customConditionRenderers?: Partial<\n Record<PublicCheckInCondition[\"type\"], ConditionRenderer<TLocale>>\n >;\n}\n\nconst label = <TLocale extends string>(\n dictionary: LocaleDictionary<TLocale> | undefined,\n locale: TLocale,\n key: string,\n fallback: string,\n): string => dictionary?.[locale]?.[key] ?? fallback;\nconst join = (...names: ReadonlyArray<string | undefined>): string | undefined => {\n const value = names.filter((name): name is string => name !== undefined && name !== \"\").join(\" \");\n return value === \"\" ? undefined : value;\n};\ntype VerificationStatus = \"idle\" | \"loading\" | \"success\" | \"error\";\n\nfunction DefaultCondition<TLocale extends string>({\n condition,\n dictionary,\n locale,\n disabled,\n classNames,\n styles,\n onSubmit,\n}: ConditionRendererProps<TLocale>): ReactElement {\n const [value, setValue] = useState(\"\");\n const [status, setStatus] = useState<VerificationStatus>(\"idle\");\n const [error, setError] = useState<string | null>(null);\n const videoRef = useRef<HTMLVideoElement>(null);\n const text = (key: string, fallback: string): string => label(dictionary, locale, key, fallback);\n const verify = async (proof: unknown): Promise<void> => {\n setStatus(\"loading\");\n setError(null);\n try {\n const result = await onSubmit(proof);\n if (\n result !== undefined &&\n typeof result === \"object\" &&\n result !== null &&\n \"ok\" in result &&\n result.ok === false\n ) {\n setStatus(\"error\");\n setError(text(\"verificationFailed\", \"Verification failed.\"));\n } else setStatus(\"success\");\n } catch {\n setStatus(\"error\");\n setError(text(\"verificationFailed\", \"Verification failed.\"));\n }\n };\n const readLocation = (): void => {\n setStatus(\"loading\");\n void getCurrentGeoContext().then((result) => {\n if (result.ok) void verify(result.value);\n else {\n setStatus(\"error\");\n setError(result.error.message);\n }\n });\n };\n const readNfc = (): void => {\n setStatus(\"loading\");\n void readNfcContext().then((result) => {\n if (result.ok) void verify(result.value);\n else {\n setStatus(\"error\");\n setError(result.error.message);\n }\n });\n };\n const scanQr = (): void => {\n if (videoRef.current === null) return;\n setStatus(\"loading\");\n void readQrContext(videoRef.current).then((result) => {\n if (result.ok) void verify(result.value);\n else {\n setStatus(\"error\");\n setError(result.error.message);\n }\n });\n };\n return (\n <div>\n {condition.type === \"qr\" && (\n <>\n <label>\n {text(\"qrValue\", \"QR value\")}\n <input\n aria-label={text(\"qrValue\", \"QR value\")}\n placeholder={condition.qrEntryUrl ?? text(\"qrPlaceholder\", \"Paste a QR value\")}\n value={value}\n onChange={(event) => setValue(event.target.value)}\n />\n </label>\n <video\n ref={videoRef}\n aria-label={text(\"qrCamera\", \"QR camera\")}\n hidden={!isQrSupported()}\n >\n <track kind=\"captions\" />\n </video>\n <button\n type=\"button\"\n className={join(\"sry-action\", classNames?.action, classNames?.button)}\n style={styles?.button ?? styles?.action}\n disabled={disabled || !isQrSupported()}\n onClick={scanQr}\n >\n {text(\"scanQr\", \"Scan QR\")}\n </button>\n </>\n )}\n {condition.type === \"gps\" && (\n <button\n type=\"button\"\n className={join(\"sry-action\", classNames?.action, classNames?.button)}\n style={styles?.button ?? styles?.action}\n disabled={disabled}\n onClick={readLocation}\n >\n {text(\"checkLocation\", \"Check location\")}\n </button>\n )}\n {condition.type === \"nfc\" && (\n <button\n type=\"button\"\n className={join(\"sry-action\", classNames?.action, classNames?.button)}\n style={styles?.button ?? styles?.action}\n disabled={disabled || !isNfcSupported()}\n onClick={readNfc}\n >\n {text(\"readNfc\", \"Read NFC\")}\n </button>\n )}\n {(condition.type === \"passcode\" ||\n condition.type === \"custom\" ||\n condition.type === \"qr\") && (\n <form\n onSubmit={(event) => {\n event.preventDefault();\n void verify(value);\n }}\n >\n <label>\n {text(\n condition.type === \"passcode\" ? \"passcode\" : \"proof\",\n condition.type === \"passcode\" ? \"Passcode\" : \"Proof\",\n )}\n <input\n aria-label={text(\"proof\", \"Proof\")}\n value={value}\n onChange={(event) => setValue(event.target.value)}\n />\n </label>\n <button\n type=\"submit\"\n className={join(\"sry-action\", classNames?.action, classNames?.button)}\n style={styles?.button ?? styles?.action}\n disabled={disabled || value.trim() === \"\"}\n >\n {text(\"checkIn\", \"Check in\")}\n </button>\n </form>\n )}\n <div\n className={join(\"sry-feedback\", classNames?.feedback)}\n style={styles?.feedback}\n aria-live=\"polite\"\n role={status === \"error\" ? \"alert\" : undefined}\n >\n {status === \"loading\" && text(\"verifying\", \"Verifying…\")}\n {status === \"success\" && text(\"verified\", \"Verified\")}\n {status === \"error\" && (error ?? text(\"verificationFailed\", \"Verification failed.\"))}\n </div>\n </div>\n );\n}\n\nfunction DefaultRewardCard<TLocale extends string>({\n reward,\n state,\n locale,\n dictionary,\n onClaim,\n classNames,\n styles,\n}: RewardCardProps<TLocale> & {\n readonly classNames?: ViewerClassNames;\n readonly styles?: ViewerStyles;\n}): ReactElement {\n const status = state?.status ?? \"LOCKED\";\n return (\n <button\n type=\"button\"\n className={join(classNames?.reward, classNames?.button)}\n style={styles?.reward ?? styles?.button}\n disabled={status !== \"AVAILABLE\" || onClaim === undefined}\n onClick={() => {\n if (onClaim !== undefined) void onClaim(reward.id);\n }}\n >\n {resolveLocalizedText(reward.title, locale)} (\n {label(dictionary, locale, `status.${status.toLowerCase()}`, status)})\n </button>\n );\n}\n\nexport function RallyViewer<TLocale extends string = string>({\n config: providedConfig,\n client,\n adapter,\n locale,\n dictionary,\n classNames = {},\n styles = {},\n style,\n customConditionRenderers = {},\n headerSlot,\n footerSlot,\n renderSpotCard,\n renderRewardCard,\n renderStatusBadge,\n renderVerifyingState,\n renderSuccessFeedback,\n renderErrorFeedback,\n}: RallyViewerProps<TLocale>): ReactElement {\n const config = providedConfig ?? client?.getConfig() ?? adapter?.config;\n const [state, setState] = useState<UserRallyState | null>(() => client?.getState() ?? null);\n const [busy, setBusy] = useState<string | null>(null);\n const [feedback, setFeedback] = useState<CheckInResult | null>(null);\n useEffect(() => {\n if (client === undefined) return;\n const unsubscribe = client.subscribe(setState);\n void client.init().then(setState);\n return unsubscribe;\n }, [client]);\n if (config === undefined) throw new Error(\"RallyViewer requires config, client, or adapter.\");\n const currentState = state ?? {\n rallyId: config.id,\n userId: null,\n records: [],\n rewards: [],\n updatedAt: \"\",\n };\n const progress = useMemo<StampRallyProgress>(\n () => calculateProgress(currentState, config),\n [config, currentState],\n );\n const checkIn =\n adapter?.onCheckIn ??\n (client === undefined\n ? undefined\n : (spotId: string, proof: unknown, options?: CheckInOptions) =>\n client.checkIn(spotId, proof, options));\n const claim =\n adapter?.onClaimReward ??\n (client === undefined\n ? undefined\n : (rewardId: string, options?: ClaimOptions) => client.claimReward(rewardId, options));\n const submit = useCallback(\n async (spotId: string, proof: unknown): Promise<CheckInResult | undefined> => {\n if (checkIn === undefined) return undefined;\n setBusy(spotId);\n const result = await checkIn(spotId, proof).finally(() => setBusy(null));\n setFeedback(result);\n return result;\n },\n [checkIn],\n );\n return (\n <section\n className={classNames.root}\n style={styles.root ?? style}\n aria-label={label(dictionary, locale, \"viewer\", \"Stamp rally\")}\n >\n <header className={classNames.header} style={styles.header}>\n {typeof headerSlot === \"function\"\n ? headerSlot({ config, state: currentState })\n : headerSlot}\n {headerSlot === undefined && (\n <h1>{resolveLocalizedText(config.title, locale) || config.id}</h1>\n )}\n <progress\n aria-label={label(dictionary, locale, \"progress\", \"Progress\")}\n max={100}\n value={progress.percentage}\n />\n <span>\n {progress.acquired}/{progress.total}\n </span>\n </header>\n {busy !== null && renderVerifyingState?.()}\n {feedback?.ok === true && renderSuccessFeedback?.(feedback)}\n {feedback?.ok === false &&\n renderErrorFeedback?.(\n \"message\" in feedback.error ? feedback.error.message : feedback.error.code,\n )}\n <div>\n {config.spots.map((spot) => {\n const claimed = currentState.records.some((record) => record.stampId === spot.id);\n const status: SpotStatus =\n busy === spot.id ? \"verifying\" : claimed ? \"claimed\" : \"available\";\n const children = spot.conditions.map((condition) => {\n const Renderer = customConditionRenderers[condition.type] ?? DefaultCondition;\n return (\n <div\n className={classNames.condition}\n style={styles.condition}\n key={`${spot.id}-${condition.type}-${JSON.stringify(condition)}`}\n >\n <Renderer\n spot={spot}\n condition={condition}\n locale={locale}\n {...(dictionary === undefined ? {} : { dictionary })}\n disabled={busy !== null || claimed}\n {...(Object.keys(classNames).length === 0 ? {} : { classNames })}\n {...(Object.keys(styles).length === 0 ? {} : { styles })}\n onSubmit={(proof) => submit(spot.id, proof)}\n />\n </div>\n );\n });\n const props: SpotCardProps<TLocale> = {\n spot,\n state: currentState,\n status,\n locale,\n ...(dictionary === undefined ? {} : { dictionary }),\n children,\n };\n return (\n <div key={spot.id} className={classNames.card} style={styles.card}>\n {renderSpotCard?.(props) ?? (\n <article>\n <h2>{resolveLocalizedText(spot.name, locale)}</h2>\n {renderStatusBadge?.({ status }) ?? (\n <span className={classNames.badge} style={styles.badge}>\n {status}\n </span>\n )}\n {children}\n </article>\n )}\n </div>\n );\n })}\n </div>\n <section aria-label={label(dictionary, locale, \"rewards\", \"Rewards\")}>\n {config.rewards.map((reward) => {\n const props: RewardCardProps<TLocale> = {\n reward,\n state: currentState.rewards.find((item) => item.rewardId === reward.id),\n locale,\n ...(dictionary === undefined ? {} : { dictionary }),\n onClaim: claim,\n };\n return (\n <div key={reward.id} className={classNames.reward} style={styles.reward}>\n {renderRewardCard?.(props) ?? (\n <DefaultRewardCard {...props} classNames={classNames} styles={styles} />\n )}\n </div>\n );\n })}\n </section>\n <footer className={classNames.footer} style={styles.footer}>\n {footerSlot}\n </footer>\n </section>\n );\n}\n\nexport interface StampSheetProps<TLocale extends string = string>\n extends RallyViewerSlots<TLocale> {\n readonly config: RallyConfig<TLocale>;\n readonly state?: StampRallyState | null;\n readonly title?: string;\n readonly progress?: StampRallyProgress;\n readonly locale?: TLocale;\n readonly dictionary?: LocaleDictionary<TLocale>;\n readonly classNames?: ViewerClassNames;\n readonly styles?: ViewerStyles;\n readonly style?: ViewerStyle;\n}\nexport function StampSheet<TLocale extends string = string>({\n config,\n state,\n title,\n progress,\n locale = \"en\" as TLocale,\n dictionary,\n classNames = {},\n styles = {},\n style,\n headerSlot,\n footerSlot,\n renderSpotCard,\n renderStatusBadge,\n}: StampSheetProps<TLocale>): ReactElement {\n const currentState = state ?? {\n rallyId: config.id,\n userId: null,\n records: [],\n rewards: [],\n updatedAt: \"\",\n };\n const current = progress ?? calculateProgress(currentState, config);\n return (\n <section\n className={classNames.root}\n style={styles.root ?? style}\n aria-label={title ?? label(dictionary, locale, \"stampSheet\", \"Stamp sheet\")}\n >\n <header className={classNames.header} style={styles.header}>\n {typeof headerSlot === \"function\"\n ? headerSlot({ config, state: currentState })\n : (headerSlot ?? <h2>{title ?? resolveLocalizedText(config.title, locale)}</h2>)}\n <progress max={100} value={current.percentage} />\n </header>\n <div>\n {config.spots.map((spot) => {\n const claimed = currentState.records.some((record) => record.stampId === spot.id);\n const status: SpotStatus = claimed ? \"claimed\" : \"available\";\n const props: SpotCardProps<TLocale> = {\n spot,\n state: currentState,\n status,\n locale,\n ...(dictionary === undefined ? {} : { dictionary }),\n children: (\n <span className={classNames.slot} style={styles.slot}>\n {claimed ? \"✓\" : \"○\"}\n </span>\n ),\n };\n return (\n <div key={spot.id} className={classNames.card} style={styles.card}>\n {renderSpotCard?.(props) ?? (\n <span className={classNames.slot} style={styles.slot}>\n {resolveLocalizedText(spot.name, locale)}{\" \"}\n {renderStatusBadge?.({ status }) ?? (claimed ? \"✓\" : \"○\")}\n </span>\n )}\n </div>\n );\n })}\n </div>\n <footer className={classNames.footer} style={styles.footer}>\n {footerSlot}\n </footer>\n </section>\n );\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stamprally/ui",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Accessible participant UI components for stamp rallies.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,8 +29,8 @@
29
29
  "./styles.css": "./dist/index.css"
30
30
  },
31
31
  "dependencies": {
32
- "@stamprally/react": "0.9.0",
33
- "@stamprally/core": "0.9.0"
32
+ "@stamprally/react": "0.11.0",
33
+ "@stamprally/core": "0.11.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "react": "^18.0.0 || ^19.0.0",