@stamprally/ui 0.9.0 → 0.10.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 +205 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +8 -0
- package/dist/index.d.cts +12 -5
- package/dist/index.d.ts +12 -5
- package/dist/index.js +208 -67
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -14,25 +14,139 @@ function DefaultCondition({
|
|
|
14
14
|
onSubmit
|
|
15
15
|
}) {
|
|
16
16
|
const [value, setValue] = react.useState("");
|
|
17
|
+
const [status, setStatus] = react.useState("idle");
|
|
18
|
+
const [error, setError] = react.useState(null);
|
|
19
|
+
const videoRef = react.useRef(null);
|
|
20
|
+
const text = (key, fallback) => label(dictionary, locale, key, fallback);
|
|
21
|
+
const verify = async (proof) => {
|
|
22
|
+
setStatus("loading");
|
|
23
|
+
setError(null);
|
|
24
|
+
try {
|
|
25
|
+
const result = await onSubmit(proof);
|
|
26
|
+
if (result !== void 0 && typeof result === "object" && result !== null && "ok" in result && result.ok === false) {
|
|
27
|
+
setStatus("error");
|
|
28
|
+
setError(text("verificationFailed", "Verification failed."));
|
|
29
|
+
} else setStatus("success");
|
|
30
|
+
} catch {
|
|
31
|
+
setStatus("error");
|
|
32
|
+
setError(text("verificationFailed", "Verification failed."));
|
|
33
|
+
}
|
|
34
|
+
};
|
|
17
35
|
const submit = (event) => {
|
|
18
36
|
event.preventDefault();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
);
|
|
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;
|
|
43
|
+
setStatus("loading");
|
|
44
|
+
setError(null);
|
|
45
|
+
void core.readQrContext(videoRef.current).then((result) => {
|
|
46
|
+
if (result.ok) void verify(result.value);
|
|
47
|
+
else {
|
|
48
|
+
setStatus("error");
|
|
49
|
+
setError(result.error.message);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const readLocation = () => {
|
|
54
|
+
setStatus("loading");
|
|
55
|
+
setError(null);
|
|
56
|
+
void core.getCurrentGeoContext().then((result) => {
|
|
57
|
+
if (result.ok) void verify(result.value);
|
|
58
|
+
else {
|
|
59
|
+
setStatus("error");
|
|
60
|
+
setError(result.error.message);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
const readNfc = () => {
|
|
65
|
+
setStatus("loading");
|
|
66
|
+
setError(null);
|
|
67
|
+
void core.readNfcContext().then((result) => {
|
|
68
|
+
if (result.ok) void verify(result.value);
|
|
69
|
+
else {
|
|
70
|
+
setStatus("error");
|
|
71
|
+
setError(result.error.message);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
22
74
|
};
|
|
23
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("
|
|
24
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
25
|
-
|
|
75
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
76
|
+
condition.type === "qr" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
77
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
|
|
78
|
+
text("qrValue", "QR value"),
|
|
79
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
80
|
+
"input",
|
|
81
|
+
{
|
|
82
|
+
"aria-label": text("qrValue", "QR value"),
|
|
83
|
+
placeholder: condition.qrEntryUrl ?? text("qrPlaceholder", "Paste a QR value"),
|
|
84
|
+
value,
|
|
85
|
+
onChange: (event) => setValue(event.target.value)
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
] }),
|
|
26
89
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
27
|
-
"
|
|
90
|
+
"video",
|
|
28
91
|
{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
92
|
+
ref: videoRef,
|
|
93
|
+
"aria-label": text("qrCamera", "QR camera"),
|
|
94
|
+
hidden: !core.isQrSupported(),
|
|
95
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("track", { kind: "captions" })
|
|
96
|
+
}
|
|
97
|
+
),
|
|
98
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
99
|
+
"button",
|
|
100
|
+
{
|
|
101
|
+
type: "button",
|
|
102
|
+
className: "sry-action",
|
|
103
|
+
disabled: disabled || !core.isQrSupported(),
|
|
104
|
+
onClick: scanQr,
|
|
105
|
+
children: text("scanQr", "Scan QR")
|
|
32
106
|
}
|
|
33
107
|
)
|
|
34
108
|
] }),
|
|
35
|
-
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "
|
|
109
|
+
condition.type === "gps" && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "sry-action", disabled, onClick: readLocation, children: text("checkLocation", "Check location") }),
|
|
110
|
+
condition.type === "nfc" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
111
|
+
"button",
|
|
112
|
+
{
|
|
113
|
+
type: "button",
|
|
114
|
+
className: "sry-action",
|
|
115
|
+
disabled: disabled || !core.isNfcSupported(),
|
|
116
|
+
onClick: readNfc,
|
|
117
|
+
children: text("readNfc", "Read NFC")
|
|
118
|
+
}
|
|
119
|
+
),
|
|
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
|
+
] }),
|
|
137
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
138
|
+
"div",
|
|
139
|
+
{
|
|
140
|
+
className: "sry-feedback",
|
|
141
|
+
"aria-live": "polite",
|
|
142
|
+
role: status === "error" ? "alert" : void 0,
|
|
143
|
+
children: [
|
|
144
|
+
status === "loading" && text("verifying", "Verifying\u2026"),
|
|
145
|
+
status === "success" && text("verified", "Verified"),
|
|
146
|
+
status === "error" && (error ?? text("verificationFailed", "Verification failed."))
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
)
|
|
36
150
|
] });
|
|
37
151
|
}
|
|
38
152
|
function RallyViewer({
|
|
@@ -41,6 +155,8 @@ function RallyViewer({
|
|
|
41
155
|
adapter,
|
|
42
156
|
locale,
|
|
43
157
|
dictionary,
|
|
158
|
+
classNames = {},
|
|
159
|
+
style,
|
|
44
160
|
customConditionRenderers = {}
|
|
45
161
|
}) {
|
|
46
162
|
const config = providedConfig ?? client?.getConfig() ?? adapter?.config;
|
|
@@ -64,75 +180,98 @@ function RallyViewer({
|
|
|
64
180
|
const claim = adapter?.onClaimReward ?? (client === void 0 ? void 0 : (rewardId, options) => client.claimReward(rewardId, options));
|
|
65
181
|
const submit = react.useCallback(
|
|
66
182
|
(spotId, proof) => {
|
|
67
|
-
if (checkIn === void 0) return;
|
|
183
|
+
if (checkIn === void 0) return Promise.resolve(void 0);
|
|
68
184
|
setBusy(spotId);
|
|
69
|
-
|
|
185
|
+
return checkIn(spotId, proof).finally(() => setBusy(null));
|
|
70
186
|
},
|
|
71
187
|
[checkIn]
|
|
72
188
|
);
|
|
73
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
189
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
190
|
+
"section",
|
|
191
|
+
{
|
|
192
|
+
className: classNames.root,
|
|
193
|
+
style,
|
|
194
|
+
"aria-label": label(dictionary, locale, "viewer", "Stamp rally"),
|
|
195
|
+
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
|
|
210
|
+
] }),
|
|
211
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { children: config.spots.map(
|
|
212
|
+
(spot) => spot.conditions.map((condition) => {
|
|
213
|
+
const Renderer = customConditionRenderers[condition.type] ?? DefaultCondition;
|
|
214
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
215
|
+
"article",
|
|
216
|
+
{
|
|
217
|
+
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
|
+
]
|
|
232
|
+
},
|
|
233
|
+
`${spot.id}-${condition.type}-${JSON.stringify(condition)}`
|
|
234
|
+
);
|
|
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
|
+
{
|
|
240
|
+
reward,
|
|
241
|
+
state: state?.rewards.find((item) => item.rewardId === reward.id),
|
|
242
|
+
locale,
|
|
243
|
+
...dictionary === void 0 ? {} : { dictionary },
|
|
244
|
+
...classNames.reward === void 0 ? {} : { className: classNames.reward },
|
|
245
|
+
onClaim: claim
|
|
246
|
+
},
|
|
247
|
+
reward.id
|
|
248
|
+
)) })
|
|
249
|
+
]
|
|
250
|
+
}
|
|
251
|
+
);
|
|
118
252
|
}
|
|
119
253
|
function RewardButton({
|
|
120
254
|
reward,
|
|
121
255
|
state,
|
|
256
|
+
locale,
|
|
257
|
+
dictionary,
|
|
258
|
+
className,
|
|
122
259
|
onClaim
|
|
123
260
|
}) {
|
|
261
|
+
const status = state?.status ?? "LOCKED";
|
|
124
262
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
125
263
|
"button",
|
|
126
264
|
{
|
|
127
265
|
type: "button",
|
|
128
|
-
|
|
266
|
+
className,
|
|
267
|
+
disabled: status !== "AVAILABLE" || onClaim === void 0,
|
|
129
268
|
onClick: () => {
|
|
130
269
|
if (onClaim !== void 0) void onClaim(reward.id);
|
|
131
270
|
},
|
|
132
271
|
children: [
|
|
133
|
-
core.resolveLocalizedText(reward.title,
|
|
272
|
+
core.resolveLocalizedText(reward.title, locale),
|
|
134
273
|
" (",
|
|
135
|
-
|
|
274
|
+
label(dictionary, locale, `status.${status.toLowerCase()}`, status),
|
|
136
275
|
")"
|
|
137
276
|
]
|
|
138
277
|
}
|
|
@@ -142,14 +281,16 @@ function StampSheet({
|
|
|
142
281
|
config,
|
|
143
282
|
state,
|
|
144
283
|
title,
|
|
145
|
-
progress
|
|
284
|
+
progress,
|
|
285
|
+
locale = "en",
|
|
286
|
+
dictionary
|
|
146
287
|
}) {
|
|
147
288
|
const current = progress ?? core.calculateProgress(
|
|
148
289
|
state ?? { rallyId: config.id, userId: null, records: [], rewards: [], updatedAt: "" },
|
|
149
290
|
config
|
|
150
291
|
);
|
|
151
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": title ?? "Stamp sheet", children: [
|
|
152
|
-
/* @__PURE__ */ jsxRuntime.jsx("h2", { children: title ?? core.resolveLocalizedText(config.title,
|
|
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) }),
|
|
153
294
|
/* @__PURE__ */ jsxRuntime.jsx("progress", { max: 100, value: current.percentage }),
|
|
154
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)) })
|
|
155
296
|
] });
|
package/dist/index.cjs.map
CHANGED
|
@@ -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","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"]}
|
package/dist/index.css
CHANGED
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PublicSpotItem, PublicCheckInCondition, LocaleDictionary, PublicRallyConfig, StampRallyClient, CheckInOptions, CheckInResult, ClaimOptions, ClaimResult, RallyConfig, StampRallyState, StampRallyProgress } from '@stamprally/core';
|
|
2
|
-
import { ComponentType, ReactElement } from 'react';
|
|
2
|
+
import { ComponentType, CSSProperties, ReactElement } from 'react';
|
|
3
3
|
|
|
4
4
|
interface ConditionRendererProps<TLocale extends string = string> {
|
|
5
5
|
readonly spot: PublicSpotItem<TLocale>;
|
|
@@ -7,9 +7,13 @@ interface ConditionRendererProps<TLocale extends string = string> {
|
|
|
7
7
|
readonly locale: TLocale;
|
|
8
8
|
readonly dictionary?: LocaleDictionary<TLocale>;
|
|
9
9
|
readonly disabled: boolean;
|
|
10
|
-
readonly onSubmit: (proof: unknown) => void
|
|
10
|
+
readonly onSubmit: (proof: unknown) => void | Promise<unknown>;
|
|
11
11
|
}
|
|
12
12
|
type ConditionRenderer<TLocale extends string = string> = ComponentType<ConditionRendererProps<TLocale>>;
|
|
13
|
+
type ViewerClassName = "root" | "condition" | "action" | "feedback" | "reward";
|
|
14
|
+
type ViewerStyle = CSSProperties & {
|
|
15
|
+
readonly [key: `--${string}`]: string | number | undefined;
|
|
16
|
+
};
|
|
13
17
|
interface RallyViewerAdapter<TLocale extends string = string> {
|
|
14
18
|
readonly config: PublicRallyConfig<TLocale>;
|
|
15
19
|
readonly onCheckIn: (spotId: string, proof: unknown, options?: CheckInOptions) => Promise<CheckInResult>;
|
|
@@ -21,16 +25,19 @@ interface RallyViewerProps<TLocale extends string = string> {
|
|
|
21
25
|
readonly adapter?: RallyViewerAdapter<TLocale>;
|
|
22
26
|
readonly locale: TLocale;
|
|
23
27
|
readonly dictionary?: LocaleDictionary<TLocale>;
|
|
28
|
+
readonly classNames?: Partial<Record<ViewerClassName, string>>;
|
|
29
|
+
readonly style?: ViewerStyle;
|
|
24
30
|
readonly customConditionRenderers?: Partial<Record<PublicCheckInCondition["type"], ConditionRenderer<TLocale>>>;
|
|
25
31
|
}
|
|
26
|
-
declare function RallyViewer<TLocale extends string = string>({ config: providedConfig, client, adapter, locale, dictionary, customConditionRenderers, }: RallyViewerProps<TLocale>): ReactElement;
|
|
32
|
+
declare function RallyViewer<TLocale extends string = string>({ config: providedConfig, client, adapter, locale, dictionary, classNames, style, customConditionRenderers, }: RallyViewerProps<TLocale>): ReactElement;
|
|
27
33
|
interface StampSheetProps<TLocale extends string = string> {
|
|
28
34
|
readonly config: RallyConfig<TLocale>;
|
|
29
35
|
readonly state?: StampRallyState | null;
|
|
30
36
|
readonly title?: string;
|
|
31
37
|
readonly progress?: StampRallyProgress;
|
|
32
38
|
readonly locale?: TLocale;
|
|
39
|
+
readonly dictionary?: LocaleDictionary<TLocale>;
|
|
33
40
|
}
|
|
34
|
-
declare function StampSheet<TLocale extends string = string>({ config, state, title, progress, }: StampSheetProps<TLocale>): ReactElement;
|
|
41
|
+
declare function StampSheet<TLocale extends string = string>({ config, state, title, progress, locale, dictionary, }: StampSheetProps<TLocale>): ReactElement;
|
|
35
42
|
|
|
36
|
-
export { type ConditionRenderer, type ConditionRendererProps, RallyViewer, type RallyViewerAdapter, type RallyViewerProps, StampSheet, type StampSheetProps };
|
|
43
|
+
export { type ConditionRenderer, type ConditionRendererProps, RallyViewer, type RallyViewerAdapter, type RallyViewerProps, StampSheet, type StampSheetProps, type ViewerClassName, type ViewerStyle };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PublicSpotItem, PublicCheckInCondition, LocaleDictionary, PublicRallyConfig, StampRallyClient, CheckInOptions, CheckInResult, ClaimOptions, ClaimResult, RallyConfig, StampRallyState, StampRallyProgress } from '@stamprally/core';
|
|
2
|
-
import { ComponentType, ReactElement } from 'react';
|
|
2
|
+
import { ComponentType, CSSProperties, ReactElement } from 'react';
|
|
3
3
|
|
|
4
4
|
interface ConditionRendererProps<TLocale extends string = string> {
|
|
5
5
|
readonly spot: PublicSpotItem<TLocale>;
|
|
@@ -7,9 +7,13 @@ interface ConditionRendererProps<TLocale extends string = string> {
|
|
|
7
7
|
readonly locale: TLocale;
|
|
8
8
|
readonly dictionary?: LocaleDictionary<TLocale>;
|
|
9
9
|
readonly disabled: boolean;
|
|
10
|
-
readonly onSubmit: (proof: unknown) => void
|
|
10
|
+
readonly onSubmit: (proof: unknown) => void | Promise<unknown>;
|
|
11
11
|
}
|
|
12
12
|
type ConditionRenderer<TLocale extends string = string> = ComponentType<ConditionRendererProps<TLocale>>;
|
|
13
|
+
type ViewerClassName = "root" | "condition" | "action" | "feedback" | "reward";
|
|
14
|
+
type ViewerStyle = CSSProperties & {
|
|
15
|
+
readonly [key: `--${string}`]: string | number | undefined;
|
|
16
|
+
};
|
|
13
17
|
interface RallyViewerAdapter<TLocale extends string = string> {
|
|
14
18
|
readonly config: PublicRallyConfig<TLocale>;
|
|
15
19
|
readonly onCheckIn: (spotId: string, proof: unknown, options?: CheckInOptions) => Promise<CheckInResult>;
|
|
@@ -21,16 +25,19 @@ interface RallyViewerProps<TLocale extends string = string> {
|
|
|
21
25
|
readonly adapter?: RallyViewerAdapter<TLocale>;
|
|
22
26
|
readonly locale: TLocale;
|
|
23
27
|
readonly dictionary?: LocaleDictionary<TLocale>;
|
|
28
|
+
readonly classNames?: Partial<Record<ViewerClassName, string>>;
|
|
29
|
+
readonly style?: ViewerStyle;
|
|
24
30
|
readonly customConditionRenderers?: Partial<Record<PublicCheckInCondition["type"], ConditionRenderer<TLocale>>>;
|
|
25
31
|
}
|
|
26
|
-
declare function RallyViewer<TLocale extends string = string>({ config: providedConfig, client, adapter, locale, dictionary, customConditionRenderers, }: RallyViewerProps<TLocale>): ReactElement;
|
|
32
|
+
declare function RallyViewer<TLocale extends string = string>({ config: providedConfig, client, adapter, locale, dictionary, classNames, style, customConditionRenderers, }: RallyViewerProps<TLocale>): ReactElement;
|
|
27
33
|
interface StampSheetProps<TLocale extends string = string> {
|
|
28
34
|
readonly config: RallyConfig<TLocale>;
|
|
29
35
|
readonly state?: StampRallyState | null;
|
|
30
36
|
readonly title?: string;
|
|
31
37
|
readonly progress?: StampRallyProgress;
|
|
32
38
|
readonly locale?: TLocale;
|
|
39
|
+
readonly dictionary?: LocaleDictionary<TLocale>;
|
|
33
40
|
}
|
|
34
|
-
declare function StampSheet<TLocale extends string = string>({ config, state, title, progress, }: StampSheetProps<TLocale>): ReactElement;
|
|
41
|
+
declare function StampSheet<TLocale extends string = string>({ config, state, title, progress, locale, dictionary, }: StampSheetProps<TLocale>): ReactElement;
|
|
35
42
|
|
|
36
|
-
export { type ConditionRenderer, type ConditionRendererProps, RallyViewer, type RallyViewerAdapter, type RallyViewerProps, StampSheet, type StampSheetProps };
|
|
43
|
+
export { type ConditionRenderer, type ConditionRendererProps, RallyViewer, type RallyViewerAdapter, type RallyViewerProps, StampSheet, type StampSheetProps, type ViewerClassName, type ViewerStyle };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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;
|
|
@@ -12,25 +12,139 @@ function DefaultCondition({
|
|
|
12
12
|
onSubmit
|
|
13
13
|
}) {
|
|
14
14
|
const [value, setValue] = useState("");
|
|
15
|
+
const [status, setStatus] = useState("idle");
|
|
16
|
+
const [error, setError] = useState(null);
|
|
17
|
+
const videoRef = useRef(null);
|
|
18
|
+
const text = (key, fallback) => label(dictionary, locale, key, fallback);
|
|
19
|
+
const verify = async (proof) => {
|
|
20
|
+
setStatus("loading");
|
|
21
|
+
setError(null);
|
|
22
|
+
try {
|
|
23
|
+
const result = await onSubmit(proof);
|
|
24
|
+
if (result !== void 0 && typeof result === "object" && result !== null && "ok" in result && result.ok === false) {
|
|
25
|
+
setStatus("error");
|
|
26
|
+
setError(text("verificationFailed", "Verification failed."));
|
|
27
|
+
} else setStatus("success");
|
|
28
|
+
} catch {
|
|
29
|
+
setStatus("error");
|
|
30
|
+
setError(text("verificationFailed", "Verification failed."));
|
|
31
|
+
}
|
|
32
|
+
};
|
|
15
33
|
const submit = (event) => {
|
|
16
34
|
event.preventDefault();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
);
|
|
35
|
+
if (condition.type === "gps") return;
|
|
36
|
+
if (condition.type === "nfc") return;
|
|
37
|
+
void verify(condition.type === "passcode" ? value : value);
|
|
38
|
+
};
|
|
39
|
+
const scanQr = () => {
|
|
40
|
+
if (videoRef.current === null) return;
|
|
41
|
+
setStatus("loading");
|
|
42
|
+
setError(null);
|
|
43
|
+
void readQrContext(videoRef.current).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 readLocation = () => {
|
|
52
|
+
setStatus("loading");
|
|
53
|
+
setError(null);
|
|
54
|
+
void getCurrentGeoContext().then((result) => {
|
|
55
|
+
if (result.ok) void verify(result.value);
|
|
56
|
+
else {
|
|
57
|
+
setStatus("error");
|
|
58
|
+
setError(result.error.message);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
const readNfc = () => {
|
|
63
|
+
setStatus("loading");
|
|
64
|
+
setError(null);
|
|
65
|
+
void readNfcContext().then((result) => {
|
|
66
|
+
if (result.ok) void verify(result.value);
|
|
67
|
+
else {
|
|
68
|
+
setStatus("error");
|
|
69
|
+
setError(result.error.message);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
20
72
|
};
|
|
21
|
-
return /* @__PURE__ */ jsxs("
|
|
22
|
-
/* @__PURE__ */ jsxs(
|
|
23
|
-
|
|
73
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
74
|
+
condition.type === "qr" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
75
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
76
|
+
text("qrValue", "QR value"),
|
|
77
|
+
/* @__PURE__ */ jsx(
|
|
78
|
+
"input",
|
|
79
|
+
{
|
|
80
|
+
"aria-label": text("qrValue", "QR value"),
|
|
81
|
+
placeholder: condition.qrEntryUrl ?? text("qrPlaceholder", "Paste a QR value"),
|
|
82
|
+
value,
|
|
83
|
+
onChange: (event) => setValue(event.target.value)
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
] }),
|
|
24
87
|
/* @__PURE__ */ jsx(
|
|
25
|
-
"
|
|
88
|
+
"video",
|
|
26
89
|
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
90
|
+
ref: videoRef,
|
|
91
|
+
"aria-label": text("qrCamera", "QR camera"),
|
|
92
|
+
hidden: !isQrSupported(),
|
|
93
|
+
children: /* @__PURE__ */ jsx("track", { kind: "captions" })
|
|
94
|
+
}
|
|
95
|
+
),
|
|
96
|
+
/* @__PURE__ */ jsx(
|
|
97
|
+
"button",
|
|
98
|
+
{
|
|
99
|
+
type: "button",
|
|
100
|
+
className: "sry-action",
|
|
101
|
+
disabled: disabled || !isQrSupported(),
|
|
102
|
+
onClick: scanQr,
|
|
103
|
+
children: text("scanQr", "Scan QR")
|
|
30
104
|
}
|
|
31
105
|
)
|
|
32
106
|
] }),
|
|
33
|
-
/* @__PURE__ */ jsx("button", { type: "
|
|
107
|
+
condition.type === "gps" && /* @__PURE__ */ jsx("button", { type: "button", className: "sry-action", disabled, onClick: readLocation, children: text("checkLocation", "Check location") }),
|
|
108
|
+
condition.type === "nfc" && /* @__PURE__ */ jsx(
|
|
109
|
+
"button",
|
|
110
|
+
{
|
|
111
|
+
type: "button",
|
|
112
|
+
className: "sry-action",
|
|
113
|
+
disabled: disabled || !isNfcSupported(),
|
|
114
|
+
onClick: readNfc,
|
|
115
|
+
children: text("readNfc", "Read NFC")
|
|
116
|
+
}
|
|
117
|
+
),
|
|
118
|
+
(condition.type === "passcode" || condition.type === "custom" || condition.type === "qr") && /* @__PURE__ */ jsxs("form", { onSubmit: submit, children: [
|
|
119
|
+
/* @__PURE__ */ jsxs("label", { children: [
|
|
120
|
+
text(
|
|
121
|
+
condition.type === "passcode" ? "passcode" : "proof",
|
|
122
|
+
condition.type === "passcode" ? "Passcode" : "Proof"
|
|
123
|
+
),
|
|
124
|
+
/* @__PURE__ */ jsx(
|
|
125
|
+
"input",
|
|
126
|
+
{
|
|
127
|
+
"aria-label": text("proof", "Proof"),
|
|
128
|
+
value,
|
|
129
|
+
onChange: (event) => setValue(event.target.value)
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
] }),
|
|
133
|
+
/* @__PURE__ */ jsx("button", { type: "submit", className: "sry-action", disabled: disabled || value.trim() === "", children: text("checkIn", "Check in") })
|
|
134
|
+
] }),
|
|
135
|
+
/* @__PURE__ */ jsxs(
|
|
136
|
+
"div",
|
|
137
|
+
{
|
|
138
|
+
className: "sry-feedback",
|
|
139
|
+
"aria-live": "polite",
|
|
140
|
+
role: status === "error" ? "alert" : void 0,
|
|
141
|
+
children: [
|
|
142
|
+
status === "loading" && text("verifying", "Verifying\u2026"),
|
|
143
|
+
status === "success" && text("verified", "Verified"),
|
|
144
|
+
status === "error" && (error ?? text("verificationFailed", "Verification failed."))
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
)
|
|
34
148
|
] });
|
|
35
149
|
}
|
|
36
150
|
function RallyViewer({
|
|
@@ -39,6 +153,8 @@ function RallyViewer({
|
|
|
39
153
|
adapter,
|
|
40
154
|
locale,
|
|
41
155
|
dictionary,
|
|
156
|
+
classNames = {},
|
|
157
|
+
style,
|
|
42
158
|
customConditionRenderers = {}
|
|
43
159
|
}) {
|
|
44
160
|
const config = providedConfig ?? client?.getConfig() ?? adapter?.config;
|
|
@@ -62,75 +178,98 @@ function RallyViewer({
|
|
|
62
178
|
const claim = adapter?.onClaimReward ?? (client === void 0 ? void 0 : (rewardId, options) => client.claimReward(rewardId, options));
|
|
63
179
|
const submit = useCallback(
|
|
64
180
|
(spotId, proof) => {
|
|
65
|
-
if (checkIn === void 0) return;
|
|
181
|
+
if (checkIn === void 0) return Promise.resolve(void 0);
|
|
66
182
|
setBusy(spotId);
|
|
67
|
-
|
|
183
|
+
return checkIn(spotId, proof).finally(() => setBusy(null));
|
|
68
184
|
},
|
|
69
185
|
[checkIn]
|
|
70
186
|
);
|
|
71
|
-
return /* @__PURE__ */ jsxs(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
187
|
+
return /* @__PURE__ */ jsxs(
|
|
188
|
+
"section",
|
|
189
|
+
{
|
|
190
|
+
className: classNames.root,
|
|
191
|
+
style,
|
|
192
|
+
"aria-label": label(dictionary, locale, "viewer", "Stamp rally"),
|
|
193
|
+
children: [
|
|
194
|
+
/* @__PURE__ */ jsx("h1", { children: resolveLocalizedText(config.title, locale) || config.id }),
|
|
195
|
+
/* @__PURE__ */ jsx(
|
|
196
|
+
"progress",
|
|
197
|
+
{
|
|
198
|
+
"aria-label": label(dictionary, locale, "progress", "Progress"),
|
|
199
|
+
max: 100,
|
|
200
|
+
value: progress.percentage
|
|
201
|
+
}
|
|
202
|
+
),
|
|
203
|
+
" ",
|
|
204
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
205
|
+
progress.acquired,
|
|
206
|
+
"/",
|
|
207
|
+
progress.total
|
|
208
|
+
] }),
|
|
209
|
+
/* @__PURE__ */ jsx("div", { children: config.spots.map(
|
|
210
|
+
(spot) => spot.conditions.map((condition) => {
|
|
211
|
+
const Renderer = customConditionRenderers[condition.type] ?? DefaultCondition;
|
|
212
|
+
return /* @__PURE__ */ jsxs(
|
|
213
|
+
"article",
|
|
214
|
+
{
|
|
215
|
+
className: classNames.condition,
|
|
216
|
+
children: [
|
|
217
|
+
/* @__PURE__ */ jsx("h2", { children: resolveLocalizedText(spot.name, locale) }),
|
|
218
|
+
/* @__PURE__ */ jsx(
|
|
219
|
+
Renderer,
|
|
220
|
+
{
|
|
221
|
+
spot,
|
|
222
|
+
condition,
|
|
223
|
+
locale,
|
|
224
|
+
...dictionary === void 0 ? {} : { dictionary },
|
|
225
|
+
disabled: busy !== null,
|
|
226
|
+
onSubmit: (proof) => submit(spot.id, proof)
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
]
|
|
230
|
+
},
|
|
231
|
+
`${spot.id}-${condition.type}-${JSON.stringify(condition)}`
|
|
232
|
+
);
|
|
233
|
+
})
|
|
234
|
+
) }),
|
|
235
|
+
/* @__PURE__ */ jsx("section", { "aria-label": label(dictionary, locale, "rewards", "Rewards"), children: config.rewards.map((reward) => /* @__PURE__ */ jsx(
|
|
236
|
+
RewardButton,
|
|
237
|
+
{
|
|
238
|
+
reward,
|
|
239
|
+
state: state?.rewards.find((item) => item.rewardId === reward.id),
|
|
240
|
+
locale,
|
|
241
|
+
...dictionary === void 0 ? {} : { dictionary },
|
|
242
|
+
...classNames.reward === void 0 ? {} : { className: classNames.reward },
|
|
243
|
+
onClaim: claim
|
|
244
|
+
},
|
|
245
|
+
reward.id
|
|
246
|
+
)) })
|
|
247
|
+
]
|
|
248
|
+
}
|
|
249
|
+
);
|
|
116
250
|
}
|
|
117
251
|
function RewardButton({
|
|
118
252
|
reward,
|
|
119
253
|
state,
|
|
254
|
+
locale,
|
|
255
|
+
dictionary,
|
|
256
|
+
className,
|
|
120
257
|
onClaim
|
|
121
258
|
}) {
|
|
259
|
+
const status = state?.status ?? "LOCKED";
|
|
122
260
|
return /* @__PURE__ */ jsxs(
|
|
123
261
|
"button",
|
|
124
262
|
{
|
|
125
263
|
type: "button",
|
|
126
|
-
|
|
264
|
+
className,
|
|
265
|
+
disabled: status !== "AVAILABLE" || onClaim === void 0,
|
|
127
266
|
onClick: () => {
|
|
128
267
|
if (onClaim !== void 0) void onClaim(reward.id);
|
|
129
268
|
},
|
|
130
269
|
children: [
|
|
131
|
-
resolveLocalizedText(reward.title,
|
|
270
|
+
resolveLocalizedText(reward.title, locale),
|
|
132
271
|
" (",
|
|
133
|
-
|
|
272
|
+
label(dictionary, locale, `status.${status.toLowerCase()}`, status),
|
|
134
273
|
")"
|
|
135
274
|
]
|
|
136
275
|
}
|
|
@@ -140,14 +279,16 @@ function StampSheet({
|
|
|
140
279
|
config,
|
|
141
280
|
state,
|
|
142
281
|
title,
|
|
143
|
-
progress
|
|
282
|
+
progress,
|
|
283
|
+
locale = "en",
|
|
284
|
+
dictionary
|
|
144
285
|
}) {
|
|
145
286
|
const current = progress ?? calculateProgress(
|
|
146
287
|
state ?? { rallyId: config.id, userId: null, records: [], rewards: [], updatedAt: "" },
|
|
147
288
|
config
|
|
148
289
|
);
|
|
149
|
-
return /* @__PURE__ */ jsxs("section", { "aria-label": title ?? "Stamp sheet", children: [
|
|
150
|
-
/* @__PURE__ */ jsx("h2", { children: title ?? resolveLocalizedText(config.title,
|
|
290
|
+
return /* @__PURE__ */ jsxs("section", { "aria-label": title ?? label(dictionary, locale, "stampSheet", "Stamp sheet"), children: [
|
|
291
|
+
/* @__PURE__ */ jsx("h2", { children: title ?? resolveLocalizedText(config.title, locale) }),
|
|
151
292
|
/* @__PURE__ */ jsx("progress", { max: 100, value: current.percentage }),
|
|
152
293
|
/* @__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
294
|
] });
|
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 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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stamprally/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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/
|
|
33
|
-
"@stamprally/
|
|
32
|
+
"@stamprally/core": "0.10.0",
|
|
33
|
+
"@stamprally/react": "0.10.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"react": "^18.0.0 || ^19.0.0",
|