@stamprally/admin-ui 0.2.1

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.
@@ -0,0 +1,68 @@
1
+ import * as react from 'react';
2
+ import { StampCondition, SupportedLocale, RallyConfig, RewardItem, SpotItem, SheetTheme } from '@stamprally/core';
3
+
4
+ interface GeneralSettingsFormProps {
5
+ readonly config: RallyConfig;
6
+ readonly locale?: SupportedLocale;
7
+ readonly onChange: (config: RallyConfig) => void;
8
+ }
9
+ declare function GeneralSettingsForm({ config, locale, onChange }: GeneralSettingsFormProps): react.JSX.Element;
10
+ interface ConditionEditorProps {
11
+ readonly condition: StampCondition;
12
+ readonly onChange: (condition: StampCondition) => void;
13
+ readonly locale?: SupportedLocale;
14
+ }
15
+ declare function ConditionEditor({ condition, onChange }: ConditionEditorProps): react.JSX.Element;
16
+ interface SpotItemFormProps {
17
+ readonly spot: SpotItem;
18
+ readonly index?: number;
19
+ readonly locale?: SupportedLocale;
20
+ readonly onChange: (spot: SpotItem) => void;
21
+ readonly onDelete?: () => void;
22
+ }
23
+ declare function SpotItemForm({ spot, index, locale, onChange, onDelete, }: SpotItemFormProps): react.JSX.Element;
24
+ interface SpotListEditorProps {
25
+ readonly spots: ReadonlyArray<SpotItem>;
26
+ readonly locale?: SupportedLocale;
27
+ readonly onChange: (spots: ReadonlyArray<SpotItem>) => void;
28
+ }
29
+ declare function SpotListEditor({ spots, locale, onChange }: SpotListEditorProps): react.JSX.Element;
30
+ interface RewardListEditorProps {
31
+ readonly rewards: ReadonlyArray<RewardItem>;
32
+ readonly locale?: SupportedLocale;
33
+ readonly onChange: (rewards: ReadonlyArray<RewardItem>) => void;
34
+ }
35
+ declare function RewardListEditor({ rewards, locale, onChange }: RewardListEditorProps): react.JSX.Element;
36
+ interface ThemePresetSelectorProps {
37
+ readonly value?: SheetTheme;
38
+ readonly locale?: SupportedLocale;
39
+ readonly onChange: (theme: SheetTheme) => void;
40
+ }
41
+ declare function ThemePresetSelector({ value, locale, onChange }: ThemePresetSelectorProps): react.JSX.Element;
42
+ interface ThemeEditorProps {
43
+ readonly theme?: SheetTheme;
44
+ readonly locale?: SupportedLocale;
45
+ readonly onChange: (theme: SheetTheme) => void;
46
+ }
47
+ declare function ThemeEditor({ theme, locale, onChange, }: ThemeEditorProps): react.JSX.Element;
48
+ interface QrExportModalProps {
49
+ readonly open: boolean;
50
+ readonly config: RallyConfig;
51
+ readonly locale?: SupportedLocale;
52
+ readonly onClose: () => void;
53
+ }
54
+ declare function QrExportModal({ open, config, locale, onClose }: QrExportModalProps): react.JSX.Element | null;
55
+ interface JsonConfigIOProps {
56
+ readonly config: RallyConfig;
57
+ readonly onImport: (config: RallyConfig) => void;
58
+ readonly locale?: SupportedLocale;
59
+ }
60
+ declare function JsonConfigIO({ config, onImport }: JsonConfigIOProps): react.JSX.Element;
61
+ interface RallyEditorProps {
62
+ readonly config: RallyConfig;
63
+ readonly locale?: SupportedLocale;
64
+ readonly onChange: (config: RallyConfig) => void;
65
+ }
66
+ declare function RallyEditor({ config, locale, onChange }: RallyEditorProps): react.JSX.Element;
67
+
68
+ export { ConditionEditor, type ConditionEditorProps, GeneralSettingsForm, type GeneralSettingsFormProps, JsonConfigIO, type JsonConfigIOProps, QrExportModal, type QrExportModalProps, RallyEditor, type RallyEditorProps, RewardListEditor, type RewardListEditorProps, SpotItemForm, type SpotItemFormProps, SpotListEditor, type SpotListEditorProps, ThemeEditor, type ThemeEditorProps, ThemePresetSelector, type ThemePresetSelectorProps };
package/dist/index.js ADDED
@@ -0,0 +1,491 @@
1
+ import { resolveLocalizedText, THEME_PRESETS, DEFAULT_SHEET_THEME, CURRENT_RALLY_CONFIG_VERSION, stripSensitiveConfig, migrateRallyConfig, validateRallyConfig } from '@stamprally/core';
2
+ import { useState } from 'react';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+
5
+ // src/index.tsx
6
+ var localePair = (value) => ({
7
+ ja: resolveLocalizedText(value, "ja"),
8
+ en: resolveLocalizedText(value, "en")
9
+ });
10
+ function updateText(value, locale, next) {
11
+ const current = localePair(value);
12
+ return { ...current, [locale]: next };
13
+ }
14
+ function GeneralSettingsForm({ config, locale = "ja", onChange }) {
15
+ const pair = localePair(config.title);
16
+ return /* @__PURE__ */ jsxs("fieldset", { className: "stamprally-admin-card", children: [
17
+ /* @__PURE__ */ jsx("legend", { children: "General settings" }),
18
+ /* @__PURE__ */ jsxs("label", { children: [
19
+ "ID ",
20
+ /* @__PURE__ */ jsx("input", { value: config.id, readOnly: true })
21
+ ] }),
22
+ /* @__PURE__ */ jsxs("label", { children: [
23
+ "Title (",
24
+ locale,
25
+ ")",
26
+ " ",
27
+ /* @__PURE__ */ jsx(
28
+ "input",
29
+ {
30
+ value: pair[locale],
31
+ required: true,
32
+ onChange: (event) => onChange({ ...config, title: updateText(config.title, locale, event.target.value) })
33
+ }
34
+ )
35
+ ] }),
36
+ /* @__PURE__ */ jsxs("label", { children: [
37
+ "Start date",
38
+ " ",
39
+ /* @__PURE__ */ jsx(
40
+ "input",
41
+ {
42
+ type: "datetime-local",
43
+ value: config.startDate?.slice(0, 16) ?? "",
44
+ onChange: (event) => {
45
+ if (event.target.value === "") {
46
+ const { startDate: _startDate, ...rest } = config;
47
+ onChange(rest);
48
+ } else onChange({ ...config, startDate: new Date(event.target.value).toISOString() });
49
+ }
50
+ }
51
+ )
52
+ ] }),
53
+ /* @__PURE__ */ jsxs("label", { children: [
54
+ "End date",
55
+ " ",
56
+ /* @__PURE__ */ jsx(
57
+ "input",
58
+ {
59
+ type: "datetime-local",
60
+ value: config.endDate?.slice(0, 16) ?? "",
61
+ onChange: (event) => {
62
+ if (event.target.value === "") {
63
+ const { endDate: _endDate, ...rest } = config;
64
+ onChange(rest);
65
+ } else onChange({ ...config, endDate: new Date(event.target.value).toISOString() });
66
+ }
67
+ }
68
+ )
69
+ ] }),
70
+ /* @__PURE__ */ jsxs("label", { children: [
71
+ /* @__PURE__ */ jsx(
72
+ "input",
73
+ {
74
+ type: "checkbox",
75
+ checked: config.isSequential === true,
76
+ onChange: (event) => onChange({ ...config, isSequential: event.target.checked })
77
+ }
78
+ ),
79
+ " ",
80
+ "Sequential check-in"
81
+ ] })
82
+ ] });
83
+ }
84
+ function ConditionEditor({ condition, onChange }) {
85
+ const type = condition.type;
86
+ return /* @__PURE__ */ jsxs("fieldset", { className: "stamprally-admin-card", children: [
87
+ /* @__PURE__ */ jsx("legend", { children: "Check-in condition" }),
88
+ /* @__PURE__ */ jsxs("label", { children: [
89
+ "Type",
90
+ " ",
91
+ /* @__PURE__ */ jsxs(
92
+ "select",
93
+ {
94
+ value: type,
95
+ onChange: (event) => {
96
+ const next = event.target.value;
97
+ if (next === "token") onChange({ type: "token", token: "" });
98
+ else if (next === "geo")
99
+ onChange({ type: "geo", latitude: 0, longitude: 0, radiusMeters: 100 });
100
+ else onChange({ type: "instant" });
101
+ },
102
+ children: [
103
+ /* @__PURE__ */ jsx("option", { value: "instant", children: "Instant" }),
104
+ /* @__PURE__ */ jsx("option", { value: "token", children: "QR / passcode" }),
105
+ /* @__PURE__ */ jsx("option", { value: "geo", children: "GPS" })
106
+ ]
107
+ }
108
+ )
109
+ ] }),
110
+ condition.type === "token" && /* @__PURE__ */ jsxs("label", { children: [
111
+ "Token",
112
+ " ",
113
+ /* @__PURE__ */ jsx(
114
+ "input",
115
+ {
116
+ value: condition.token,
117
+ onChange: (event) => onChange({ ...condition, token: event.target.value })
118
+ }
119
+ )
120
+ ] }),
121
+ condition.type === "geo" && /* @__PURE__ */ jsxs(Fragment, { children: [
122
+ /* @__PURE__ */ jsxs("label", { children: [
123
+ "Latitude",
124
+ " ",
125
+ /* @__PURE__ */ jsx(
126
+ "input",
127
+ {
128
+ type: "number",
129
+ min: -90,
130
+ max: 90,
131
+ value: condition.latitude,
132
+ onChange: (event) => onChange({ ...condition, latitude: Number(event.target.value) })
133
+ }
134
+ )
135
+ ] }),
136
+ /* @__PURE__ */ jsxs("label", { children: [
137
+ "Longitude",
138
+ " ",
139
+ /* @__PURE__ */ jsx(
140
+ "input",
141
+ {
142
+ type: "number",
143
+ min: -180,
144
+ max: 180,
145
+ value: condition.longitude,
146
+ onChange: (event) => onChange({ ...condition, longitude: Number(event.target.value) })
147
+ }
148
+ )
149
+ ] }),
150
+ /* @__PURE__ */ jsxs("label", { children: [
151
+ "Radius (m)",
152
+ " ",
153
+ /* @__PURE__ */ jsx(
154
+ "input",
155
+ {
156
+ type: "number",
157
+ min: 1,
158
+ value: condition.radiusMeters,
159
+ onChange: (event) => onChange({ ...condition, radiusMeters: Number(event.target.value) })
160
+ }
161
+ )
162
+ ] })
163
+ ] })
164
+ ] });
165
+ }
166
+ function SpotItemForm({
167
+ spot,
168
+ index = 0,
169
+ locale = "ja",
170
+ onChange,
171
+ onDelete
172
+ }) {
173
+ const pair = localePair(spot.name);
174
+ const setField = (field, event) => {
175
+ if (event.target.value === "") {
176
+ const { [field]: _removed, ...rest } = spot;
177
+ onChange(rest);
178
+ } else onChange({ ...spot, [field]: event.target.value });
179
+ };
180
+ return /* @__PURE__ */ jsxs("article", { className: "stamprally-admin-card", children: [
181
+ /* @__PURE__ */ jsxs("header", { children: [
182
+ /* @__PURE__ */ jsxs("strong", { children: [
183
+ "Spot ",
184
+ index + 1
185
+ ] }),
186
+ onDelete !== void 0 && /* @__PURE__ */ jsx("button", { type: "button", onClick: onDelete, children: "Delete" })
187
+ ] }),
188
+ /* @__PURE__ */ jsxs("label", { children: [
189
+ "ID",
190
+ " ",
191
+ /* @__PURE__ */ jsx(
192
+ "input",
193
+ {
194
+ value: spot.id,
195
+ onChange: (event) => onChange({ ...spot, id: event.target.value })
196
+ }
197
+ )
198
+ ] }),
199
+ /* @__PURE__ */ jsxs("label", { children: [
200
+ "Name (",
201
+ locale,
202
+ ")",
203
+ " ",
204
+ /* @__PURE__ */ jsx(
205
+ "input",
206
+ {
207
+ value: pair[locale],
208
+ required: true,
209
+ onChange: (event) => onChange({ ...spot, name: updateText(spot.name, locale, event.target.value) })
210
+ }
211
+ )
212
+ ] }),
213
+ /* @__PURE__ */ jsxs("label", { children: [
214
+ "Icon URL",
215
+ " ",
216
+ /* @__PURE__ */ jsx("input", { value: spot.iconUrl ?? "", onChange: (event) => setField("iconUrl", event) })
217
+ ] }),
218
+ /* @__PURE__ */ jsxs("label", { children: [
219
+ "Image URL",
220
+ " ",
221
+ /* @__PURE__ */ jsx("input", { value: spot.imageUrl ?? "", onChange: (event) => setField("imageUrl", event) })
222
+ ] }),
223
+ /* @__PURE__ */ jsxs("label", { children: [
224
+ "External URL",
225
+ " ",
226
+ /* @__PURE__ */ jsx(
227
+ "input",
228
+ {
229
+ value: spot.externalUrl ?? "",
230
+ onChange: (event) => setField("externalUrl", event)
231
+ }
232
+ )
233
+ ] }),
234
+ /* @__PURE__ */ jsx(
235
+ ConditionEditor,
236
+ {
237
+ condition: spot.condition,
238
+ onChange: (condition) => onChange({ ...spot, condition }),
239
+ locale
240
+ }
241
+ )
242
+ ] });
243
+ }
244
+ function SpotListEditor({ spots, locale = "ja", onChange }) {
245
+ return /* @__PURE__ */ jsxs("section", { "aria-label": "Spots", children: [
246
+ /* @__PURE__ */ jsx("h2", { children: "Spots" }),
247
+ spots.map((spot, index) => /* @__PURE__ */ jsx(
248
+ SpotItemForm,
249
+ {
250
+ spot,
251
+ index,
252
+ locale,
253
+ onChange: (next) => onChange(spots.map((item, itemIndex) => itemIndex === index ? next : item)),
254
+ onDelete: () => onChange(spots.filter((_, itemIndex) => itemIndex !== index))
255
+ },
256
+ spot.id
257
+ ))
258
+ ] });
259
+ }
260
+ function RewardListEditor({ rewards, locale = "ja", onChange }) {
261
+ return /* @__PURE__ */ jsxs("section", { "aria-label": "Rewards", children: [
262
+ /* @__PURE__ */ jsx("h2", { children: "Rewards" }),
263
+ rewards.map((reward, index) => /* @__PURE__ */ jsxs("article", { className: "stamprally-admin-card", children: [
264
+ /* @__PURE__ */ jsxs("label", { children: [
265
+ "ID",
266
+ " ",
267
+ /* @__PURE__ */ jsx(
268
+ "input",
269
+ {
270
+ value: reward.id,
271
+ onChange: (event) => onChange(
272
+ rewards.map(
273
+ (item, itemIndex) => itemIndex === index ? { ...item, id: event.target.value } : item
274
+ )
275
+ )
276
+ }
277
+ )
278
+ ] }),
279
+ /* @__PURE__ */ jsxs("label", { children: [
280
+ "Title (",
281
+ locale,
282
+ ")",
283
+ " ",
284
+ /* @__PURE__ */ jsx(
285
+ "input",
286
+ {
287
+ value: resolveLocalizedText(reward.title, locale),
288
+ onChange: (event) => onChange(
289
+ rewards.map(
290
+ (item, itemIndex) => itemIndex === index ? { ...item, title: updateText(item.title, locale, event.target.value) } : item
291
+ )
292
+ )
293
+ }
294
+ )
295
+ ] }),
296
+ /* @__PURE__ */ jsxs("label", { children: [
297
+ "Required stamps",
298
+ " ",
299
+ /* @__PURE__ */ jsx(
300
+ "input",
301
+ {
302
+ type: "number",
303
+ min: 0,
304
+ value: reward.requiredStampCount,
305
+ onChange: (event) => onChange(
306
+ rewards.map(
307
+ (item, itemIndex) => itemIndex === index ? { ...item, requiredStampCount: Number(event.target.value) } : item
308
+ )
309
+ )
310
+ }
311
+ )
312
+ ] }),
313
+ /* @__PURE__ */ jsxs("label", { children: [
314
+ "Valid until",
315
+ " ",
316
+ /* @__PURE__ */ jsx(
317
+ "input",
318
+ {
319
+ type: "datetime-local",
320
+ value: reward.validUntil?.slice(0, 16) ?? "",
321
+ onChange: (event) => onChange(
322
+ rewards.map((item, itemIndex) => {
323
+ if (itemIndex !== index) return item;
324
+ if (event.target.value === "") {
325
+ const { validUntil: _validUntil, ...rest } = item;
326
+ return rest;
327
+ }
328
+ return { ...item, validUntil: new Date(event.target.value).toISOString() };
329
+ })
330
+ )
331
+ }
332
+ )
333
+ ] })
334
+ ] }, reward.id))
335
+ ] });
336
+ }
337
+ function ThemePresetSelector({ value, locale = "ja", onChange }) {
338
+ const selected = THEME_PRESETS.find((preset) => JSON.stringify(preset.theme) === JSON.stringify(value))?.id ?? "custom";
339
+ return /* @__PURE__ */ jsxs("label", { children: [
340
+ "Theme preset",
341
+ " ",
342
+ /* @__PURE__ */ jsxs(
343
+ "select",
344
+ {
345
+ value: selected,
346
+ onChange: (event) => {
347
+ const preset = THEME_PRESETS.find((item) => item.id === event.target.value);
348
+ if (preset !== void 0) onChange(preset.theme);
349
+ },
350
+ children: [
351
+ /* @__PURE__ */ jsx("option", { value: "custom", children: "Custom" }),
352
+ THEME_PRESETS.map((preset) => /* @__PURE__ */ jsx("option", { value: preset.id, children: resolveLocalizedText(preset.name, locale) }, preset.id))
353
+ ]
354
+ }
355
+ )
356
+ ] });
357
+ }
358
+ function ThemeEditor({
359
+ theme = DEFAULT_SHEET_THEME,
360
+ locale = "ja",
361
+ onChange
362
+ }) {
363
+ const update = (field, value) => onChange({ ...theme, [field]: value });
364
+ return /* @__PURE__ */ jsxs("fieldset", { className: "stamprally-admin-card", children: [
365
+ /* @__PURE__ */ jsx("legend", { children: "Theme" }),
366
+ /* @__PURE__ */ jsx(ThemePresetSelector, { value: theme, locale, onChange }),
367
+ /* @__PURE__ */ jsxs("label", { children: [
368
+ "Primary color",
369
+ " ",
370
+ /* @__PURE__ */ jsx(
371
+ "input",
372
+ {
373
+ type: "color",
374
+ value: theme.primaryColor,
375
+ onChange: (event) => update("primaryColor", event.target.value)
376
+ }
377
+ )
378
+ ] }),
379
+ /* @__PURE__ */ jsxs("label", { children: [
380
+ "Grid columns",
381
+ " ",
382
+ /* @__PURE__ */ jsx(
383
+ "input",
384
+ {
385
+ type: "number",
386
+ min: 1,
387
+ max: 6,
388
+ value: theme.gridColumns,
389
+ onChange: (event) => update("gridColumns", Number(event.target.value))
390
+ }
391
+ )
392
+ ] })
393
+ ] });
394
+ }
395
+ function QrExportModal({ open, config, locale = "ja", onClose }) {
396
+ if (!open) return null;
397
+ return /* @__PURE__ */ jsxs("div", { role: "dialog", "aria-modal": "true", className: "stamprally-qr-modal", children: [
398
+ /* @__PURE__ */ jsxs("div", { className: "stamprally-qr-modal__toolbar", children: [
399
+ /* @__PURE__ */ jsx("h2", { children: "QR print kit" }),
400
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => window.print(), children: "Print" }),
401
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, children: "Close" })
402
+ ] }),
403
+ /* @__PURE__ */ jsx("div", { className: "stamprally-qr-modal__grid", children: config.stamps.map((spot) => /* @__PURE__ */ jsxs("article", { children: [
404
+ /* @__PURE__ */ jsx("h3", { children: resolveLocalizedText(spot.name, locale) }),
405
+ /* @__PURE__ */ jsx("pre", { children: JSON.stringify({
406
+ version: CURRENT_RALLY_CONFIG_VERSION,
407
+ rallyId: config.id,
408
+ stampId: spot.id
409
+ }) })
410
+ ] }, spot.id)) })
411
+ ] });
412
+ }
413
+ function JsonConfigIO({ config, onImport }) {
414
+ const [value, setValue] = useState(() => JSON.stringify(stripSensitiveConfig(config), null, 2));
415
+ const [error, setError] = useState(null);
416
+ const importConfig = () => {
417
+ try {
418
+ const migrated = migrateRallyConfig(JSON.parse(value));
419
+ const validation = validateRallyConfig(migrated);
420
+ if (!validation.valid) {
421
+ setError(validation.errors.map((item) => `${item.path}: ${item.message}`).join(" "));
422
+ return;
423
+ }
424
+ onImport(migrated);
425
+ setError(null);
426
+ } catch {
427
+ setError("Invalid rally JSON.");
428
+ }
429
+ };
430
+ return /* @__PURE__ */ jsxs("section", { className: "stamprally-admin-card", "aria-label": "JSON configuration", children: [
431
+ /* @__PURE__ */ jsx("textarea", { value, onChange: (event) => setValue(event.target.value) }),
432
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: importConfig, children: "Import and migrate" }),
433
+ /* @__PURE__ */ jsx(
434
+ "button",
435
+ {
436
+ type: "button",
437
+ onClick: () => navigator.clipboard?.writeText(JSON.stringify(stripSensitiveConfig(config), null, 2)),
438
+ children: "Copy public JSON"
439
+ }
440
+ ),
441
+ error !== null && /* @__PURE__ */ jsx("p", { role: "alert", children: error })
442
+ ] });
443
+ }
444
+ function RallyEditor({ config, locale = "ja", onChange }) {
445
+ const [showQr, setShowQr] = useState(false);
446
+ return /* @__PURE__ */ jsxs("main", { className: "stamprally-admin", children: [
447
+ /* @__PURE__ */ jsxs("header", { children: [
448
+ /* @__PURE__ */ jsx("h1", { children: resolveLocalizedText(config.title, locale) || config.id }),
449
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setShowQr(true), children: "QR export" })
450
+ ] }),
451
+ /* @__PURE__ */ jsx(GeneralSettingsForm, { config, locale, onChange }),
452
+ config.theme === void 0 ? /* @__PURE__ */ jsx(ThemeEditor, { locale, onChange: (theme) => onChange({ ...config, theme }) }) : /* @__PURE__ */ jsx(
453
+ ThemeEditor,
454
+ {
455
+ theme: config.theme,
456
+ locale,
457
+ onChange: (theme) => onChange({ ...config, theme })
458
+ }
459
+ ),
460
+ /* @__PURE__ */ jsx(
461
+ SpotListEditor,
462
+ {
463
+ spots: config.stamps,
464
+ locale,
465
+ onChange: (stamps) => onChange({ ...config, stamps })
466
+ }
467
+ ),
468
+ /* @__PURE__ */ jsx(
469
+ RewardListEditor,
470
+ {
471
+ rewards: config.rewards ?? [],
472
+ locale,
473
+ onChange: (rewards) => onChange({ ...config, rewards })
474
+ }
475
+ ),
476
+ /* @__PURE__ */ jsx(JsonConfigIO, { config, onImport: onChange }),
477
+ /* @__PURE__ */ jsx(
478
+ QrExportModal,
479
+ {
480
+ open: showQr,
481
+ config,
482
+ locale,
483
+ onClose: () => setShowQr(false)
484
+ }
485
+ )
486
+ ] });
487
+ }
488
+
489
+ export { ConditionEditor, GeneralSettingsForm, JsonConfigIO, QrExportModal, RallyEditor, RewardListEditor, SpotItemForm, SpotListEditor, ThemeEditor, ThemePresetSelector };
490
+ //# sourceMappingURL=index.js.map
491
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import {\n CURRENT_RALLY_CONFIG_VERSION,\n DEFAULT_SHEET_THEME,\n type LocalizedText,\n migrateRallyConfig,\n type RallyConfig,\n type RewardItem,\n resolveLocalizedText,\n type SheetTheme,\n type SpotItem,\n type StampCondition,\n type SupportedLocale,\n stripSensitiveConfig,\n THEME_PRESETS,\n validateRallyConfig,\n} from \"@stamprally/core\";\nimport type { ChangeEvent } from \"react\";\nimport { useState } from \"react\";\n\nconst localePair = (value: LocalizedText | undefined): { ja: string; en: string } => ({\n ja: resolveLocalizedText(value, \"ja\"),\n en: resolveLocalizedText(value, \"en\"),\n});\n\nfunction updateText(\n value: LocalizedText | undefined,\n locale: \"ja\" | \"en\",\n next: string,\n): LocalizedText {\n const current = localePair(value);\n return { ...current, [locale]: next };\n}\n\nexport interface GeneralSettingsFormProps {\n readonly config: RallyConfig;\n readonly locale?: SupportedLocale;\n readonly onChange: (config: RallyConfig) => void;\n}\nexport function GeneralSettingsForm({ config, locale = \"ja\", onChange }: GeneralSettingsFormProps) {\n const pair = localePair(config.title);\n return (\n <fieldset className=\"stamprally-admin-card\">\n <legend>General settings</legend>\n <label>\n ID <input value={config.id} readOnly />\n </label>\n <label>\n Title ({locale}){\" \"}\n <input\n value={pair[locale]}\n required\n onChange={(event) =>\n onChange({ ...config, title: updateText(config.title, locale, event.target.value) })\n }\n />\n </label>\n <label>\n Start date{\" \"}\n <input\n type=\"datetime-local\"\n value={config.startDate?.slice(0, 16) ?? \"\"}\n onChange={(event) => {\n if (event.target.value === \"\") {\n const { startDate: _startDate, ...rest } = config;\n onChange(rest);\n } else onChange({ ...config, startDate: new Date(event.target.value).toISOString() });\n }}\n />\n </label>\n <label>\n End date{\" \"}\n <input\n type=\"datetime-local\"\n value={config.endDate?.slice(0, 16) ?? \"\"}\n onChange={(event) => {\n if (event.target.value === \"\") {\n const { endDate: _endDate, ...rest } = config;\n onChange(rest);\n } else onChange({ ...config, endDate: new Date(event.target.value).toISOString() });\n }}\n />\n </label>\n <label>\n <input\n type=\"checkbox\"\n checked={config.isSequential === true}\n onChange={(event) => onChange({ ...config, isSequential: event.target.checked })}\n />{\" \"}\n Sequential check-in\n </label>\n </fieldset>\n );\n}\n\nexport interface ConditionEditorProps {\n readonly condition: StampCondition;\n readonly onChange: (condition: StampCondition) => void;\n readonly locale?: SupportedLocale;\n}\nexport function ConditionEditor({ condition, onChange }: ConditionEditorProps) {\n const type = condition.type;\n return (\n <fieldset className=\"stamprally-admin-card\">\n <legend>Check-in condition</legend>\n <label>\n Type{\" \"}\n <select\n value={type}\n onChange={(event) => {\n const next = event.target.value;\n if (next === \"token\") onChange({ type: \"token\", token: \"\" });\n else if (next === \"geo\")\n onChange({ type: \"geo\", latitude: 0, longitude: 0, radiusMeters: 100 });\n else onChange({ type: \"instant\" });\n }}\n >\n <option value=\"instant\">Instant</option>\n <option value=\"token\">QR / passcode</option>\n <option value=\"geo\">GPS</option>\n </select>\n </label>\n {condition.type === \"token\" && (\n <label>\n Token{\" \"}\n <input\n value={condition.token}\n onChange={(event) => onChange({ ...condition, token: event.target.value })}\n />\n </label>\n )}\n {condition.type === \"geo\" && (\n <>\n <label>\n Latitude{\" \"}\n <input\n type=\"number\"\n min={-90}\n max={90}\n value={condition.latitude}\n onChange={(event) => onChange({ ...condition, latitude: Number(event.target.value) })}\n />\n </label>\n <label>\n Longitude{\" \"}\n <input\n type=\"number\"\n min={-180}\n max={180}\n value={condition.longitude}\n onChange={(event) =>\n onChange({ ...condition, longitude: Number(event.target.value) })\n }\n />\n </label>\n <label>\n Radius (m){\" \"}\n <input\n type=\"number\"\n min={1}\n value={condition.radiusMeters}\n onChange={(event) =>\n onChange({ ...condition, radiusMeters: Number(event.target.value) })\n }\n />\n </label>\n </>\n )}\n </fieldset>\n );\n}\n\nexport interface SpotItemFormProps {\n readonly spot: SpotItem;\n readonly index?: number;\n readonly locale?: SupportedLocale;\n readonly onChange: (spot: SpotItem) => void;\n readonly onDelete?: () => void;\n}\nexport function SpotItemForm({\n spot,\n index = 0,\n locale = \"ja\",\n onChange,\n onDelete,\n}: SpotItemFormProps) {\n const pair = localePair(spot.name);\n const setField = (\n field: \"iconUrl\" | \"imageUrl\" | \"externalUrl\" | \"guideId\" | \"groupId\" | \"deckId\",\n event: ChangeEvent<HTMLInputElement>,\n ) => {\n if (event.target.value === \"\") {\n const { [field]: _removed, ...rest } = spot;\n onChange(rest);\n } else onChange({ ...spot, [field]: event.target.value });\n };\n return (\n <article className=\"stamprally-admin-card\">\n <header>\n <strong>Spot {index + 1}</strong>\n {onDelete !== undefined && (\n <button type=\"button\" onClick={onDelete}>\n Delete\n </button>\n )}\n </header>\n <label>\n ID{\" \"}\n <input\n value={spot.id}\n onChange={(event) => onChange({ ...spot, id: event.target.value })}\n />\n </label>\n <label>\n Name ({locale}){\" \"}\n <input\n value={pair[locale]}\n required\n onChange={(event) =>\n onChange({ ...spot, name: updateText(spot.name, locale, event.target.value) })\n }\n />\n </label>\n <label>\n Icon URL{\" \"}\n <input value={spot.iconUrl ?? \"\"} onChange={(event) => setField(\"iconUrl\", event)} />\n </label>\n <label>\n Image URL{\" \"}\n <input value={spot.imageUrl ?? \"\"} onChange={(event) => setField(\"imageUrl\", event)} />\n </label>\n <label>\n External URL{\" \"}\n <input\n value={spot.externalUrl ?? \"\"}\n onChange={(event) => setField(\"externalUrl\", event)}\n />\n </label>\n <ConditionEditor\n condition={spot.condition}\n onChange={(condition) => onChange({ ...spot, condition })}\n locale={locale}\n />\n </article>\n );\n}\n\nexport interface SpotListEditorProps {\n readonly spots: ReadonlyArray<SpotItem>;\n readonly locale?: SupportedLocale;\n readonly onChange: (spots: ReadonlyArray<SpotItem>) => void;\n}\nexport function SpotListEditor({ spots, locale = \"ja\", onChange }: SpotListEditorProps) {\n return (\n <section aria-label=\"Spots\">\n <h2>Spots</h2>\n {spots.map((spot, index) => (\n <SpotItemForm\n key={spot.id}\n spot={spot}\n index={index}\n locale={locale}\n onChange={(next) =>\n onChange(spots.map((item, itemIndex) => (itemIndex === index ? next : item)))\n }\n onDelete={() => onChange(spots.filter((_, itemIndex) => itemIndex !== index))}\n />\n ))}\n </section>\n );\n}\n\nexport interface RewardListEditorProps {\n readonly rewards: ReadonlyArray<RewardItem>;\n readonly locale?: SupportedLocale;\n readonly onChange: (rewards: ReadonlyArray<RewardItem>) => void;\n}\nexport function RewardListEditor({ rewards, locale = \"ja\", onChange }: RewardListEditorProps) {\n return (\n <section aria-label=\"Rewards\">\n <h2>Rewards</h2>\n {rewards.map((reward, index) => (\n <article className=\"stamprally-admin-card\" key={reward.id}>\n <label>\n ID{\" \"}\n <input\n value={reward.id}\n onChange={(event) =>\n onChange(\n rewards.map((item, itemIndex) =>\n itemIndex === index ? { ...item, id: event.target.value } : item,\n ),\n )\n }\n />\n </label>\n <label>\n Title ({locale}){\" \"}\n <input\n value={resolveLocalizedText(reward.title, locale)}\n onChange={(event) =>\n onChange(\n rewards.map((item, itemIndex) =>\n itemIndex === index\n ? { ...item, title: updateText(item.title, locale, event.target.value) }\n : item,\n ),\n )\n }\n />\n </label>\n <label>\n Required stamps{\" \"}\n <input\n type=\"number\"\n min={0}\n value={reward.requiredStampCount}\n onChange={(event) =>\n onChange(\n rewards.map((item, itemIndex) =>\n itemIndex === index\n ? { ...item, requiredStampCount: Number(event.target.value) }\n : item,\n ),\n )\n }\n />\n </label>\n <label>\n Valid until{\" \"}\n <input\n type=\"datetime-local\"\n value={reward.validUntil?.slice(0, 16) ?? \"\"}\n onChange={(event) =>\n onChange(\n rewards.map((item, itemIndex) => {\n if (itemIndex !== index) return item;\n if (event.target.value === \"\") {\n const { validUntil: _validUntil, ...rest } = item;\n return rest;\n }\n return { ...item, validUntil: new Date(event.target.value).toISOString() };\n }),\n )\n }\n />\n </label>\n </article>\n ))}\n </section>\n );\n}\n\nexport interface ThemePresetSelectorProps {\n readonly value?: SheetTheme;\n readonly locale?: SupportedLocale;\n readonly onChange: (theme: SheetTheme) => void;\n}\nexport function ThemePresetSelector({ value, locale = \"ja\", onChange }: ThemePresetSelectorProps) {\n const selected =\n THEME_PRESETS.find((preset) => JSON.stringify(preset.theme) === JSON.stringify(value))?.id ??\n \"custom\";\n return (\n <label>\n Theme preset{\" \"}\n <select\n value={selected}\n onChange={(event) => {\n const preset = THEME_PRESETS.find((item) => item.id === event.target.value);\n if (preset !== undefined) onChange(preset.theme);\n }}\n >\n <option value=\"custom\">Custom</option>\n {THEME_PRESETS.map((preset) => (\n <option key={preset.id} value={preset.id}>\n {resolveLocalizedText(preset.name, locale)}\n </option>\n ))}\n </select>\n </label>\n );\n}\n\nexport interface ThemeEditorProps {\n readonly theme?: SheetTheme;\n readonly locale?: SupportedLocale;\n readonly onChange: (theme: SheetTheme) => void;\n}\nexport function ThemeEditor({\n theme = DEFAULT_SHEET_THEME,\n locale = \"ja\",\n onChange,\n}: ThemeEditorProps) {\n const update = (field: keyof SheetTheme, value: string | number) =>\n onChange({ ...theme, [field]: value });\n return (\n <fieldset className=\"stamprally-admin-card\">\n <legend>Theme</legend>\n <ThemePresetSelector value={theme} locale={locale} onChange={onChange} />\n <label>\n Primary color{\" \"}\n <input\n type=\"color\"\n value={theme.primaryColor}\n onChange={(event) => update(\"primaryColor\", event.target.value)}\n />\n </label>\n <label>\n Grid columns{\" \"}\n <input\n type=\"number\"\n min={1}\n max={6}\n value={theme.gridColumns}\n onChange={(event) => update(\"gridColumns\", Number(event.target.value))}\n />\n </label>\n </fieldset>\n );\n}\n\nexport interface QrExportModalProps {\n readonly open: boolean;\n readonly config: RallyConfig;\n readonly locale?: SupportedLocale;\n readonly onClose: () => void;\n}\nexport function QrExportModal({ open, config, locale = \"ja\", onClose }: QrExportModalProps) {\n if (!open) return null;\n return (\n <div role=\"dialog\" aria-modal=\"true\" className=\"stamprally-qr-modal\">\n <div className=\"stamprally-qr-modal__toolbar\">\n <h2>QR print kit</h2>\n <button type=\"button\" onClick={() => window.print()}>\n Print\n </button>\n <button type=\"button\" onClick={onClose}>\n Close\n </button>\n </div>\n <div className=\"stamprally-qr-modal__grid\">\n {config.stamps.map((spot) => (\n <article key={spot.id}>\n <h3>{resolveLocalizedText(spot.name, locale)}</h3>\n <pre>\n {JSON.stringify({\n version: CURRENT_RALLY_CONFIG_VERSION,\n rallyId: config.id,\n stampId: spot.id,\n })}\n </pre>\n </article>\n ))}\n </div>\n </div>\n );\n}\n\nexport interface JsonConfigIOProps {\n readonly config: RallyConfig;\n readonly onImport: (config: RallyConfig) => void;\n readonly locale?: SupportedLocale;\n}\nexport function JsonConfigIO({ config, onImport }: JsonConfigIOProps) {\n const [value, setValue] = useState(() => JSON.stringify(stripSensitiveConfig(config), null, 2));\n const [error, setError] = useState<string | null>(null);\n const importConfig = (): void => {\n try {\n const migrated = migrateRallyConfig(JSON.parse(value) as unknown);\n const validation = validateRallyConfig(migrated);\n if (!validation.valid) {\n setError(validation.errors.map((item) => `${item.path}: ${item.message}`).join(\" \"));\n return;\n }\n onImport(migrated);\n setError(null);\n } catch {\n setError(\"Invalid rally JSON.\");\n }\n };\n return (\n <section className=\"stamprally-admin-card\" aria-label=\"JSON configuration\">\n <textarea value={value} onChange={(event) => setValue(event.target.value)} />\n <button type=\"button\" onClick={importConfig}>\n Import and migrate\n </button>\n <button\n type=\"button\"\n onClick={() =>\n navigator.clipboard?.writeText(JSON.stringify(stripSensitiveConfig(config), null, 2))\n }\n >\n Copy public JSON\n </button>\n {error !== null && <p role=\"alert\">{error}</p>}\n </section>\n );\n}\n\nexport interface RallyEditorProps {\n readonly config: RallyConfig;\n readonly locale?: SupportedLocale;\n readonly onChange: (config: RallyConfig) => void;\n}\nexport function RallyEditor({ config, locale = \"ja\", onChange }: RallyEditorProps) {\n const [showQr, setShowQr] = useState(false);\n return (\n <main className=\"stamprally-admin\">\n <header>\n <h1>{resolveLocalizedText(config.title, locale) || config.id}</h1>\n <button type=\"button\" onClick={() => setShowQr(true)}>\n QR export\n </button>\n </header>\n <GeneralSettingsForm config={config} locale={locale} onChange={onChange} />\n {config.theme === undefined ? (\n <ThemeEditor locale={locale} onChange={(theme) => onChange({ ...config, theme })} />\n ) : (\n <ThemeEditor\n theme={config.theme}\n locale={locale}\n onChange={(theme) => onChange({ ...config, theme })}\n />\n )}\n <SpotListEditor\n spots={config.stamps}\n locale={locale}\n onChange={(stamps) => onChange({ ...config, stamps })}\n />\n <RewardListEditor\n rewards={config.rewards ?? []}\n locale={locale}\n onChange={(rewards) => onChange({ ...config, rewards })}\n />\n <JsonConfigIO config={config} onImport={onChange} />\n <QrExportModal\n open={showQr}\n config={config}\n locale={locale}\n onClose={() => setShowQr(false)}\n />\n </main>\n );\n}\n"]}