@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.cjs CHANGED
@@ -6,46 +6,229 @@ 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("");
17
- const submit = (event) => {
18
- event.preventDefault();
19
- onSubmit(
20
- condition.type === "gps" ? { latitude: Number(value.split(",")[0]), longitude: Number(value.split(",")[1]) } : condition.type === "nfc" ? { tagId: value } : value
21
- );
23
+ const [status, setStatus] = react.useState("idle");
24
+ const [error, setError] = react.useState(null);
25
+ const videoRef = react.useRef(null);
26
+ const text = (key, fallback) => label(dictionary, locale, key, fallback);
27
+ const verify = async (proof) => {
28
+ setStatus("loading");
29
+ setError(null);
30
+ try {
31
+ const result = await onSubmit(proof);
32
+ if (result !== void 0 && typeof result === "object" && result !== null && "ok" in result && result.ok === false) {
33
+ setStatus("error");
34
+ setError(text("verificationFailed", "Verification failed."));
35
+ } else setStatus("success");
36
+ } catch {
37
+ setStatus("error");
38
+ setError(text("verificationFailed", "Verification failed."));
39
+ }
40
+ };
41
+ const readLocation = () => {
42
+ setStatus("loading");
43
+ void core.getCurrentGeoContext().then((result) => {
44
+ if (result.ok) void verify(result.value);
45
+ else {
46
+ setStatus("error");
47
+ setError(result.error.message);
48
+ }
49
+ });
50
+ };
51
+ const readNfc = () => {
52
+ setStatus("loading");
53
+ void core.readNfcContext().then((result) => {
54
+ if (result.ok) void verify(result.value);
55
+ else {
56
+ setStatus("error");
57
+ setError(result.error.message);
58
+ }
59
+ });
22
60
  };
23
- return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: submit, children: [
24
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
25
- label(dictionary, locale, "proof", "Proof"),
61
+ const scanQr = () => {
62
+ if (videoRef.current === null) return;
63
+ setStatus("loading");
64
+ void core.readQrContext(videoRef.current).then((result) => {
65
+ if (result.ok) void verify(result.value);
66
+ else {
67
+ setStatus("error");
68
+ setError(result.error.message);
69
+ }
70
+ });
71
+ };
72
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
73
+ condition.type === "qr" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
74
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
75
+ text("qrValue", "QR value"),
76
+ /* @__PURE__ */ jsxRuntime.jsx(
77
+ "input",
78
+ {
79
+ "aria-label": text("qrValue", "QR value"),
80
+ placeholder: condition.qrEntryUrl ?? text("qrPlaceholder", "Paste a QR value"),
81
+ value,
82
+ onChange: (event) => setValue(event.target.value)
83
+ }
84
+ )
85
+ ] }),
86
+ /* @__PURE__ */ jsxRuntime.jsx(
87
+ "video",
88
+ {
89
+ ref: videoRef,
90
+ "aria-label": text("qrCamera", "QR camera"),
91
+ hidden: !core.isQrSupported(),
92
+ children: /* @__PURE__ */ jsxRuntime.jsx("track", { kind: "captions" })
93
+ }
94
+ ),
26
95
  /* @__PURE__ */ jsxRuntime.jsx(
27
- "input",
96
+ "button",
28
97
  {
29
- "aria-label": label(dictionary, locale, "proof", "Proof"),
30
- value,
31
- onChange: (event) => setValue(event.target.value)
98
+ type: "button",
99
+ className: join("sry-action", classNames?.action, classNames?.button),
100
+ style: styles?.button ?? styles?.action,
101
+ disabled: disabled || !core.isQrSupported(),
102
+ onClick: scanQr,
103
+ children: text("scanQr", "Scan QR")
32
104
  }
33
105
  )
34
106
  ] }),
35
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", disabled, children: condition.type === "gps" ? label(dictionary, locale, "checkLocation", "Check location") : label(dictionary, locale, "checkIn", "Check in") })
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
+ ),
118
+ condition.type === "nfc" && /* @__PURE__ */ jsxRuntime.jsx(
119
+ "button",
120
+ {
121
+ type: "button",
122
+ className: join("sry-action", classNames?.action, classNames?.button),
123
+ style: styles?.button ?? styles?.action,
124
+ disabled: disabled || !core.isNfcSupported(),
125
+ onClick: readNfc,
126
+ children: text("readNfc", "Read NFC")
127
+ }
128
+ ),
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
+ ),
164
+ /* @__PURE__ */ jsxRuntime.jsxs(
165
+ "div",
166
+ {
167
+ className: join("sry-feedback", classNames?.feedback),
168
+ style: styles?.feedback,
169
+ "aria-live": "polite",
170
+ role: status === "error" ? "alert" : void 0,
171
+ children: [
172
+ status === "loading" && text("verifying", "Verifying\u2026"),
173
+ status === "success" && text("verified", "Verified"),
174
+ status === "error" && (error ?? text("verificationFailed", "Verification failed."))
175
+ ]
176
+ }
177
+ )
36
178
  ] });
37
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
+ }
38
209
  function RallyViewer({
39
210
  config: providedConfig,
40
211
  client,
41
212
  adapter,
42
213
  locale,
43
214
  dictionary,
44
- customConditionRenderers = {}
215
+ classNames = {},
216
+ styles = {},
217
+ style,
218
+ customConditionRenderers = {},
219
+ headerSlot,
220
+ footerSlot,
221
+ renderSpotCard,
222
+ renderRewardCard,
223
+ renderStatusBadge,
224
+ renderVerifyingState,
225
+ renderSuccessFeedback,
226
+ renderErrorFeedback
45
227
  }) {
46
228
  const config = providedConfig ?? client?.getConfig() ?? adapter?.config;
47
229
  const [state, setState] = react.useState(() => client?.getState() ?? null);
48
230
  const [busy, setBusy] = react.useState(null);
231
+ const [feedback, setFeedback] = react.useState(null);
49
232
  react.useEffect(() => {
50
233
  if (client === void 0) return;
51
234
  const unsubscribe = client.subscribe(setState);
@@ -53,87 +236,110 @@ function RallyViewer({
53
236
  return unsubscribe;
54
237
  }, [client]);
55
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
+ };
56
246
  const progress = react.useMemo(
57
- () => core.calculateProgress(
58
- state ?? { rallyId: config.id, userId: null, records: [], rewards: [], updatedAt: "" },
59
- config
60
- ),
61
- [config, state]
247
+ () => core.calculateProgress(currentState, config),
248
+ [config, currentState]
62
249
  );
63
250
  const checkIn = adapter?.onCheckIn ?? (client === void 0 ? void 0 : (spotId, proof, options) => client.checkIn(spotId, proof, options));
64
251
  const claim = adapter?.onClaimReward ?? (client === void 0 ? void 0 : (rewardId, options) => client.claimReward(rewardId, options));
65
252
  const submit = react.useCallback(
66
- (spotId, proof) => {
67
- if (checkIn === void 0) return;
253
+ async (spotId, proof) => {
254
+ if (checkIn === void 0) return void 0;
68
255
  setBusy(spotId);
69
- void checkIn(spotId, proof).finally(() => setBusy(null));
256
+ const result = await checkIn(spotId, proof).finally(() => setBusy(null));
257
+ setFeedback(result);
258
+ return result;
70
259
  },
71
260
  [checkIn]
72
261
  );
73
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": label(dictionary, locale, "viewer", "Stamp rally"), children: [
74
- /* @__PURE__ */ jsxRuntime.jsx("h1", { children: core.resolveLocalizedText(config.title, locale) || config.id }),
75
- /* @__PURE__ */ jsxRuntime.jsx(
76
- "progress",
77
- {
78
- "aria-label": label(dictionary, locale, "progress", "Progress"),
79
- max: 100,
80
- value: progress.percentage
81
- }
82
- ),
83
- " ",
84
- /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
85
- progress.acquired,
86
- "/",
87
- progress.total
88
- ] }),
89
- /* @__PURE__ */ jsxRuntime.jsx("div", { children: config.spots.map(
90
- (spot) => spot.conditions.map((condition) => {
91
- const Renderer = customConditionRenderers[condition.type] ?? DefaultCondition;
92
- return /* @__PURE__ */ jsxRuntime.jsxs("article", { children: [
93
- /* @__PURE__ */ jsxRuntime.jsx("h2", { children: core.resolveLocalizedText(spot.name, locale) }),
94
- /* @__PURE__ */ jsxRuntime.jsx(
95
- Renderer,
96
- {
97
- spot,
98
- condition,
99
- locale,
100
- ...dictionary === void 0 ? {} : { dictionary },
101
- disabled: busy !== null,
102
- onSubmit: (proof) => submit(spot.id, proof)
103
- }
104
- )
105
- ] }, `${spot.id}-${condition.type}-${JSON.stringify(condition)}`);
106
- })
107
- ) }),
108
- /* @__PURE__ */ jsxRuntime.jsx("section", { "aria-label": label(dictionary, locale, "rewards", "Rewards"), children: config.rewards.map((reward) => /* @__PURE__ */ jsxRuntime.jsx(
109
- RewardButton,
110
- {
111
- reward,
112
- state: state?.rewards.find((item) => item.rewardId === reward.id),
113
- onClaim: claim
114
- },
115
- reward.id
116
- )) })
117
- ] });
118
- }
119
- function RewardButton({
120
- reward,
121
- state,
122
- onClaim
123
- }) {
124
262
  return /* @__PURE__ */ jsxRuntime.jsxs(
125
- "button",
263
+ "section",
126
264
  {
127
- type: "button",
128
- disabled: state?.status !== "AVAILABLE" || onClaim === void 0,
129
- onClick: () => {
130
- if (onClaim !== void 0) void onClaim(reward.id);
131
- },
265
+ className: classNames.root,
266
+ style: styles.root ?? style,
267
+ "aria-label": label(dictionary, locale, "viewer", "Stamp rally"),
132
268
  children: [
133
- core.resolveLocalizedText(reward.title, "en"),
134
- " (",
135
- state?.status ?? "LOCKED",
136
- ")"
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
+ ] })
285
+ ] }),
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) => {
295
+ const Renderer = customConditionRenderers[condition.type] ?? DefaultCondition;
296
+ return /* @__PURE__ */ jsxRuntime.jsx(
297
+ "div",
298
+ {
299
+ className: classNames.condition,
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
+ )
314
+ },
315
+ `${spot.id}-${condition.type}-${JSON.stringify(condition)}`
316
+ );
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 = {
334
+ reward,
335
+ state: currentState.rewards.find((item) => item.rewardId === reward.id),
336
+ locale,
337
+ ...dictionary === void 0 ? {} : { dictionary },
338
+ onClaim: claim
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 })
137
343
  ]
138
344
  }
139
345
  );
@@ -142,17 +348,57 @@ function StampSheet({
142
348
  config,
143
349
  state,
144
350
  title,
145
- progress
351
+ progress,
352
+ locale = "en",
353
+ dictionary,
354
+ classNames = {},
355
+ styles = {},
356
+ style,
357
+ headerSlot,
358
+ footerSlot,
359
+ renderSpotCard,
360
+ renderStatusBadge
146
361
  }) {
147
- const current = progress ?? core.calculateProgress(
148
- state ?? { rallyId: config.id, userId: null, records: [], rewards: [], updatedAt: "" },
149
- config
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);
370
+ return /* @__PURE__ */ jsxRuntime.jsxs(
371
+ "section",
372
+ {
373
+ className: classNames.root,
374
+ style: styles.root ?? style,
375
+ "aria-label": title ?? label(dictionary, locale, "stampSheet", "Stamp sheet"),
376
+ children: [
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 })
399
+ ]
400
+ }
150
401
  );
151
- return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": title ?? "Stamp sheet", children: [
152
- /* @__PURE__ */ jsxRuntime.jsx("h2", { children: title ?? core.resolveLocalizedText(config.title, "en") }),
153
- /* @__PURE__ */ jsxRuntime.jsx("progress", { max: 100, value: current.percentage }),
154
- /* @__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)) })
155
- ] });
156
402
  }
157
403
 
158
404
  exports.RallyViewer = RallyViewer;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx"],"names":["useState","jsxs","jsx","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 { 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":["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"]}
package/dist/index.css CHANGED
@@ -1,3 +1,11 @@
1
+ .sry-feedback {
2
+ min-height: 1.5rem;
3
+ color: var(--stamprally-feedback-color, currentColor);
4
+ }
5
+ .sry-action {
6
+ margin-block: 0.35rem;
7
+ }
8
+
1
9
  .stamp-sheet,
2
10
  :where(.stamprally-sheet, .stamprally-rewards) {
3
11
  color: var(--stamprally-text, #352f25);