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