@stamprally/admin-ui 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,765 +1,110 @@
1
- import { toPublicRallyConfig, resolveLocalizedText, THEME_PRESETS, DEFAULT_SHEET_THEME, CURRENT_RALLY_CONFIG_VERSION, stripSensitiveConfig, validateAdminRallyConfig, migrateRallyConfig, validateRallyConfig } from '@stamprally/core';
1
+ import { resolveLocalizedText } from '@stamprally/core';
2
2
  import { useState } from 'react';
3
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
4
 
5
5
  // src/index.tsx
6
- function AdminRallyEditor({ config, onChange }) {
7
- const [value, setValue] = useState(() => JSON.stringify(config, null, 2));
8
- const [error, setError] = useState(null);
9
- const apply = () => {
10
- try {
11
- const candidate = JSON.parse(value);
12
- const result = validateAdminRallyConfig(candidate);
13
- if (!result.valid) {
14
- setError(result.errors.map((item) => `${item.path}: ${item.message}`).join(" "));
15
- return;
16
- }
17
- onChange(candidate);
18
- setError(null);
19
- } catch {
20
- setError("Invalid rally JSON.");
21
- }
22
- };
23
- return /* @__PURE__ */ jsxs("section", { "aria-label": "Universal rally editor", className: "stamprally-admin-card", children: [
24
- /* @__PURE__ */ jsx("textarea", { value, onChange: (event) => setValue(event.target.value) }),
25
- /* @__PURE__ */ jsx("button", { type: "button", onClick: apply, children: "Apply configuration" }),
26
- /* @__PURE__ */ jsx(
27
- "button",
28
- {
29
- type: "button",
30
- onClick: () => navigator.clipboard?.writeText(JSON.stringify(toPublicRallyConfig(config), null, 2)),
31
- children: "Copy public configuration"
32
- }
33
- ),
34
- error !== null && /* @__PURE__ */ jsx("p", { role: "alert", children: error })
35
- ] });
36
- }
37
- var localePair = (value) => {
38
- if (typeof value === "string") return { ja: value, en: "" };
39
- const result = {};
40
- for (const [key, text] of Object.entries(value ?? {})) {
41
- if (text !== void 0) result[key] = text;
42
- }
43
- return result;
44
- };
45
- function updateText(value, locale, next) {
46
- const current = localePair(value);
47
- return { ...current, [locale]: next };
48
- }
49
- function GeneralSettingsForm({
6
+ var condition = () => ({ type: "passcode", code: "" });
7
+ var newSpot = (index) => ({
8
+ id: `spot-${index + 1}`,
9
+ orderIndex: index,
10
+ name: `Spot ${index + 1}`,
11
+ conditions: [condition()]
12
+ });
13
+ var newReward = (index) => ({
14
+ id: `reward-${index + 1}`,
15
+ title: `Reward ${index + 1}`,
16
+ type: "digital",
17
+ redemptionMethod: "server_claim",
18
+ requiredStampCount: index + 1
19
+ });
20
+ function AdminRallyEditor({
50
21
  config,
51
- locale = "ja",
52
- onChange
53
- }) {
54
- const pair = localePair(config.title);
55
- return /* @__PURE__ */ jsxs("fieldset", { className: "stamprally-admin-card", children: [
56
- /* @__PURE__ */ jsx("legend", { children: "General settings" }),
57
- /* @__PURE__ */ jsxs("label", { children: [
58
- "ID ",
59
- /* @__PURE__ */ jsx("input", { value: config.id, readOnly: true })
60
- ] }),
61
- /* @__PURE__ */ jsxs("label", { children: [
62
- "Title (",
63
- locale,
64
- ")",
65
- " ",
66
- /* @__PURE__ */ jsx(
67
- "input",
68
- {
69
- value: pair[locale],
70
- required: true,
71
- onChange: (event) => onChange({ ...config, title: updateText(config.title, locale, event.target.value) })
72
- }
73
- )
74
- ] }),
75
- /* @__PURE__ */ jsxs("label", { children: [
76
- "Start date",
77
- " ",
78
- /* @__PURE__ */ jsx(
79
- "input",
80
- {
81
- type: "datetime-local",
82
- value: config.startDate?.slice(0, 16) ?? "",
83
- onChange: (event) => {
84
- if (event.target.value === "") {
85
- const { startDate: _startDate, ...rest } = config;
86
- onChange(rest);
87
- } else onChange({ ...config, startDate: new Date(event.target.value).toISOString() });
88
- }
89
- }
90
- )
91
- ] }),
92
- /* @__PURE__ */ jsxs("label", { children: [
93
- "End date",
94
- " ",
95
- /* @__PURE__ */ jsx(
96
- "input",
97
- {
98
- type: "datetime-local",
99
- value: config.endDate?.slice(0, 16) ?? "",
100
- onChange: (event) => {
101
- if (event.target.value === "") {
102
- const { endDate: _endDate, ...rest } = config;
103
- onChange(rest);
104
- } else onChange({ ...config, endDate: new Date(event.target.value).toISOString() });
105
- }
106
- }
107
- )
108
- ] }),
109
- /* @__PURE__ */ jsxs("label", { children: [
110
- /* @__PURE__ */ jsx(
111
- "input",
112
- {
113
- type: "checkbox",
114
- checked: config.isSequential === true,
115
- onChange: (event) => onChange({ ...config, isSequential: event.target.checked })
116
- }
117
- ),
118
- " ",
119
- "Sequential check-in"
120
- ] })
121
- ] });
122
- }
123
- function ConditionEditor({ condition, onChange }) {
124
- const type = condition.type;
125
- return /* @__PURE__ */ jsxs("fieldset", { className: "stamprally-admin-card", children: [
126
- /* @__PURE__ */ jsx("legend", { children: "Check-in condition" }),
127
- /* @__PURE__ */ jsxs("label", { children: [
128
- "Type",
129
- " ",
130
- /* @__PURE__ */ jsxs(
131
- "select",
132
- {
133
- value: type,
134
- onChange: (event) => {
135
- const next = event.target.value;
136
- if (next === "token") onChange({ type: "token", token: "" });
137
- else if (next === "geo")
138
- onChange({ type: "geo", latitude: 0, longitude: 0, radiusMeters: 100 });
139
- else onChange({ type: "instant" });
140
- },
141
- children: [
142
- /* @__PURE__ */ jsx("option", { value: "instant", children: "Instant" }),
143
- /* @__PURE__ */ jsx("option", { value: "token", children: "QR / passcode" }),
144
- /* @__PURE__ */ jsx("option", { value: "geo", children: "GPS" })
145
- ]
146
- }
147
- )
148
- ] }),
149
- condition.type === "token" && /* @__PURE__ */ jsxs("label", { children: [
150
- "Token",
151
- " ",
152
- /* @__PURE__ */ jsx(
153
- "input",
154
- {
155
- value: condition.token,
156
- onChange: (event) => onChange({ ...condition, token: event.target.value })
157
- }
158
- )
159
- ] }),
160
- condition.type === "geo" && /* @__PURE__ */ jsxs(Fragment, { children: [
161
- /* @__PURE__ */ jsxs("label", { children: [
162
- "Latitude",
163
- " ",
164
- /* @__PURE__ */ jsx(
165
- "input",
166
- {
167
- type: "number",
168
- min: -90,
169
- max: 90,
170
- value: condition.latitude,
171
- onChange: (event) => onChange({ ...condition, latitude: Number(event.target.value) })
172
- }
173
- )
174
- ] }),
175
- /* @__PURE__ */ jsxs("label", { children: [
176
- "Longitude",
177
- " ",
178
- /* @__PURE__ */ jsx(
179
- "input",
180
- {
181
- type: "number",
182
- min: -180,
183
- max: 180,
184
- value: condition.longitude,
185
- onChange: (event) => onChange({ ...condition, longitude: Number(event.target.value) })
186
- }
187
- )
188
- ] }),
189
- /* @__PURE__ */ jsxs("label", { children: [
190
- "Radius (m)",
191
- " ",
192
- /* @__PURE__ */ jsx(
193
- "input",
194
- {
195
- type: "number",
196
- min: 1,
197
- value: condition.radiusMeters,
198
- onChange: (event) => onChange({ ...condition, radiusMeters: Number(event.target.value) })
199
- }
200
- )
201
- ] })
202
- ] })
203
- ] });
204
- }
205
- function SpotItemForm({
206
- spot,
207
- index = 0,
208
- locale = "ja",
209
22
  onChange,
210
- onDelete
23
+ locale = "en"
211
24
  }) {
212
- const pair = localePair(spot.name);
213
- const setField = (field, event) => {
214
- if (event.target.value === "") {
215
- const { [field]: _removed, ...rest } = spot;
216
- onChange(rest);
217
- } else onChange({ ...spot, [field]: event.target.value });
218
- };
219
- return /* @__PURE__ */ jsxs("article", { className: "stamprally-admin-card", children: [
220
- /* @__PURE__ */ jsxs("header", { children: [
221
- /* @__PURE__ */ jsxs("strong", { children: [
222
- "Spot ",
223
- index + 1
224
- ] }),
225
- onDelete !== void 0 && /* @__PURE__ */ jsx("button", { type: "button", onClick: onDelete, children: "Delete" })
226
- ] }),
227
- /* @__PURE__ */ jsxs("label", { children: [
228
- "ID",
229
- " ",
230
- /* @__PURE__ */ jsx(
231
- "input",
232
- {
233
- value: spot.id,
234
- onChange: (event) => onChange({ ...spot, id: event.target.value })
235
- }
236
- )
237
- ] }),
238
- /* @__PURE__ */ jsxs("label", { children: [
239
- "Name (",
240
- locale,
241
- ")",
242
- " ",
243
- /* @__PURE__ */ jsx(
244
- "input",
245
- {
246
- value: pair[locale],
247
- required: true,
248
- onChange: (event) => onChange({ ...spot, name: updateText(spot.name, locale, event.target.value) })
249
- }
250
- )
251
- ] }),
252
- /* @__PURE__ */ jsxs("label", { children: [
253
- "Description (",
254
- locale,
255
- ")",
256
- " ",
257
- /* @__PURE__ */ jsx(
258
- "textarea",
259
- {
260
- value: resolveLocalizedText(spot.description, locale),
261
- onChange: (event) => onChange({
262
- ...spot,
263
- description: updateText(spot.description, locale, event.target.value)
264
- })
265
- }
266
- )
267
- ] }),
268
- /* @__PURE__ */ jsxs("label", { children: [
269
- "Hint (",
270
- locale,
271
- ")",
272
- " ",
273
- /* @__PURE__ */ jsx(
274
- "textarea",
275
- {
276
- value: resolveLocalizedText(spot.hint, locale),
277
- onChange: (event) => onChange({ ...spot, hint: updateText(spot.hint, locale, event.target.value) })
278
- }
279
- )
280
- ] }),
281
- /* @__PURE__ */ jsxs("label", { children: [
282
- "Order index",
283
- " ",
284
- /* @__PURE__ */ jsx(
285
- "input",
286
- {
287
- type: "number",
288
- value: spot.orderIndex ?? spot.order ?? index + 1,
289
- onChange: (event) => onChange({ ...spot, orderIndex: Number(event.target.value) })
290
- }
291
- )
292
- ] }),
293
- /* @__PURE__ */ jsxs("label", { children: [
294
- "Icon URL",
295
- " ",
296
- /* @__PURE__ */ jsx("input", { value: spot.iconUrl ?? "", onChange: (event) => setField("iconUrl", event) })
297
- ] }),
298
- /* @__PURE__ */ jsxs("label", { children: [
299
- "Image URL",
300
- " ",
301
- /* @__PURE__ */ jsx("input", { value: spot.imageUrl ?? "", onChange: (event) => setField("imageUrl", event) })
302
- ] }),
25
+ const update = (next) => onChange({ ...config, ...next });
26
+ return /* @__PURE__ */ jsxs("section", { "aria-label": "Rally editor", children: [
27
+ /* @__PURE__ */ jsx("h1", { children: resolveLocalizedText(config.title, locale) }),
303
28
  /* @__PURE__ */ jsxs("label", { children: [
304
- "External URL",
305
- " ",
29
+ "Title",
306
30
  /* @__PURE__ */ jsx(
307
31
  "input",
308
32
  {
309
- value: spot.externalUrl ?? "",
310
- onChange: (event) => setField("externalUrl", event)
311
- }
312
- )
313
- ] }),
314
- /* @__PURE__ */ jsxs("label", { children: [
315
- "Redirect URL after claim",
316
- " ",
317
- /* @__PURE__ */ jsx(
318
- "input",
319
- {
320
- value: spot.redirectUrlAfterClaim ?? "",
321
- onChange: (event) => setField("redirectUrlAfterClaim", event)
322
- }
323
- )
324
- ] }),
325
- /* @__PURE__ */ jsxs("label", { children: [
326
- "Prerequisites (comma-separated stamp IDs)",
327
- " ",
328
- /* @__PURE__ */ jsx(
329
- "input",
330
- {
331
- value: (spot.requiresStampIds ?? spot.dependsOn ?? []).join(", "),
332
- onChange: (event) => {
333
- const ids = event.target.value.split(",").map((item) => item.trim()).filter(Boolean);
334
- if (ids.length === 0) {
335
- const { requiresStampIds: _requiresStampIds, ...rest } = spot;
336
- onChange(rest);
337
- } else onChange({ ...spot, requiresStampIds: ids });
338
- }
339
- }
340
- )
341
- ] }),
342
- /* @__PURE__ */ jsxs("label", { children: [
343
- "Metadata (JSON)",
344
- " ",
345
- /* @__PURE__ */ jsx(
346
- "textarea",
347
- {
348
- value: JSON.stringify(spot.metadata ?? {}, null, 2),
349
- onChange: (event) => {
350
- try {
351
- const parsed = JSON.parse(event.target.value);
352
- if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
353
- onChange({ ...spot, metadata: parsed });
354
- }
355
- } catch {
356
- }
357
- }
33
+ value: resolveLocalizedText(config.title, locale),
34
+ onChange: (event) => update({ title: event.target.value })
358
35
  }
359
36
  )
360
37
  ] }),
361
38
  /* @__PURE__ */ jsx(
362
- ConditionEditor,
363
- {
364
- condition: spot.condition,
365
- onChange: (condition) => onChange({ ...spot, condition }),
366
- locale
367
- }
368
- )
369
- ] });
370
- }
371
- function SpotListEditor({
372
- spots,
373
- locale = "ja",
374
- onChange
375
- }) {
376
- return /* @__PURE__ */ jsxs("section", { "aria-label": "Spots", children: [
377
- /* @__PURE__ */ jsx("h2", { children: "Spots" }),
378
- spots.map((spot, index) => /* @__PURE__ */ jsx(
379
- SpotItemForm,
380
- {
381
- spot,
382
- index,
383
- locale,
384
- onChange: (next) => onChange(spots.map((item, itemIndex) => itemIndex === index ? next : item)),
385
- onDelete: () => onChange(spots.filter((_, itemIndex) => itemIndex !== index))
386
- },
387
- spot.id
388
- ))
389
- ] });
390
- }
391
- function RewardListEditor({
392
- rewards,
393
- locale = "ja",
394
- onChange
395
- }) {
396
- return /* @__PURE__ */ jsxs("section", { "aria-label": "Rewards", children: [
397
- /* @__PURE__ */ jsx("h2", { children: "Rewards" }),
398
- rewards.map((reward, index) => /* @__PURE__ */ jsxs("article", { className: "stamprally-admin-card", children: [
399
- /* @__PURE__ */ jsxs("label", { children: [
400
- "ID",
401
- " ",
402
- /* @__PURE__ */ jsx(
403
- "input",
404
- {
405
- value: reward.id,
406
- onChange: (event) => onChange(
407
- rewards.map(
408
- (item, itemIndex) => itemIndex === index ? { ...item, id: event.target.value } : item
409
- )
410
- )
411
- }
412
- )
413
- ] }),
414
- /* @__PURE__ */ jsxs("label", { children: [
415
- "Title (",
416
- locale,
417
- ")",
418
- " ",
419
- /* @__PURE__ */ jsx(
420
- "input",
421
- {
422
- value: resolveLocalizedText(reward.title, locale),
423
- onChange: (event) => onChange(
424
- rewards.map(
425
- (item, itemIndex) => itemIndex === index ? { ...item, title: updateText(item.title, locale, event.target.value) } : item
426
- )
427
- )
428
- }
429
- )
430
- ] }),
431
- /* @__PURE__ */ jsxs("label", { children: [
432
- "Description (",
433
- locale,
434
- ")",
435
- " ",
436
- /* @__PURE__ */ jsx(
437
- "textarea",
438
- {
439
- value: resolveLocalizedText(reward.description, locale),
440
- onChange: (event) => onChange(
441
- rewards.map(
442
- (item, itemIndex) => itemIndex === index ? {
443
- ...item,
444
- description: updateText(item.description, locale, event.target.value)
445
- } : item
446
- )
447
- )
448
- }
449
- )
450
- ] }),
451
- /* @__PURE__ */ jsxs("label", { children: [
452
- "Required stamps",
453
- " ",
454
- /* @__PURE__ */ jsx(
455
- "input",
456
- {
457
- type: "number",
458
- min: 0,
459
- value: reward.requiredStampCount,
460
- onChange: (event) => onChange(
461
- rewards.map(
462
- (item, itemIndex) => itemIndex === index ? { ...item, requiredStampCount: Number(event.target.value) } : item
463
- )
464
- )
465
- }
466
- )
467
- ] }),
468
- /* @__PURE__ */ jsxs("label", { children: [
469
- "Valid until",
470
- " ",
471
- /* @__PURE__ */ jsx(
472
- "input",
473
- {
474
- type: "datetime-local",
475
- value: reward.validUntil?.slice(0, 16) ?? "",
476
- onChange: (event) => onChange(
477
- rewards.map((item, itemIndex) => {
478
- if (itemIndex !== index) return item;
479
- if (event.target.value === "") {
480
- const { validUntil: _validUntil, ...rest } = item;
481
- return rest;
482
- }
483
- return { ...item, validUntil: new Date(event.target.value).toISOString() };
484
- })
485
- )
486
- }
487
- )
488
- ] }),
489
- /* @__PURE__ */ jsxs("label", { children: [
490
- "Stock limit",
491
- " ",
492
- /* @__PURE__ */ jsx(
493
- "input",
494
- {
495
- type: "number",
496
- min: 1,
497
- value: reward.stockLimit ?? reward.maxStock ?? "",
498
- onChange: (event) => onChange(
499
- rewards.map((item, itemIndex) => {
500
- if (itemIndex !== index) return item;
501
- if (event.target.value === "") {
502
- const { stockLimit: _stockLimit, ...rest } = item;
503
- return rest;
504
- }
505
- return { ...item, stockLimit: Number(event.target.value) };
506
- })
507
- )
508
- }
509
- )
510
- ] }),
511
- /* @__PURE__ */ jsxs("label", { children: [
512
- "User claim limit",
513
- " ",
514
- /* @__PURE__ */ jsx(
515
- "input",
516
- {
517
- type: "number",
518
- min: 1,
519
- value: reward.userClaimLimit ?? reward.limitPerUser ?? "",
520
- onChange: (event) => onChange(
521
- rewards.map((item, itemIndex) => {
522
- if (itemIndex !== index) return item;
523
- if (event.target.value === "") {
524
- const { userClaimLimit: _userClaimLimit, ...rest } = item;
525
- return rest;
526
- }
527
- return { ...item, userClaimLimit: Number(event.target.value) };
528
- })
529
- )
530
- }
531
- )
532
- ] }),
533
- /* @__PURE__ */ jsxs("label", { children: [
534
- "Redemption method",
535
- " ",
536
- /* @__PURE__ */ jsxs(
537
- "select",
538
- {
539
- value: reward.redemptionMethod,
540
- onChange: (event) => onChange(
541
- rewards.map(
542
- (item, itemIndex) => itemIndex === index ? {
543
- ...item,
544
- redemptionMethod: event.target.value
545
- } : item
546
- )
547
- ),
548
- children: [
549
- /* @__PURE__ */ jsx("option", { value: "manual_slide", children: "manual_slide" }),
550
- /* @__PURE__ */ jsx("option", { value: "staff_passcode", children: "staff_passcode" }),
551
- /* @__PURE__ */ jsx("option", { value: "view_only", children: "view_only" }),
552
- /* @__PURE__ */ jsx("option", { value: "server_claim", children: "server_claim" })
553
- ]
554
- }
555
- )
556
- ] }),
557
- reward.redemptionMethod === "staff_passcode" && /* @__PURE__ */ jsxs("label", { children: [
558
- "Staff passcode",
559
- " ",
560
- /* @__PURE__ */ jsx(
561
- "input",
562
- {
563
- type: "password",
564
- value: reward.staffPasscode ?? "",
565
- onChange: (event) => onChange(
566
- rewards.map(
567
- (item, itemIndex) => itemIndex === index ? { ...item, staffPasscode: event.target.value } : item
568
- )
569
- )
570
- }
571
- )
572
- ] }),
573
- /* @__PURE__ */ jsxs("label", { children: [
574
- "Conditions (JSON)",
575
- " ",
576
- /* @__PURE__ */ jsx(
577
- "textarea",
578
- {
579
- value: JSON.stringify(reward.conditions ?? [], null, 2),
580
- onChange: (event) => {
581
- try {
582
- const parsed = JSON.parse(event.target.value);
583
- if (Array.isArray(parsed)) {
584
- onChange(
585
- rewards.map(
586
- (item, itemIndex) => itemIndex === index ? { ...item, conditions: parsed } : item
587
- )
588
- );
589
- }
590
- } catch {
591
- }
592
- }
593
- }
594
- )
595
- ] })
596
- ] }, reward.id))
597
- ] });
598
- }
599
- function ThemePresetSelector({ value, locale = "ja", onChange }) {
600
- const selected = THEME_PRESETS.find((preset) => JSON.stringify(preset.theme) === JSON.stringify(value))?.id ?? "custom";
601
- return /* @__PURE__ */ jsxs("label", { children: [
602
- "Theme preset",
603
- " ",
604
- /* @__PURE__ */ jsxs(
605
- "select",
39
+ "button",
606
40
  {
607
- value: selected,
608
- onChange: (event) => {
609
- const preset = THEME_PRESETS.find((item) => item.id === event.target.value);
610
- if (preset !== void 0) onChange(preset.theme);
611
- },
612
- children: [
613
- /* @__PURE__ */ jsx("option", { value: "custom", children: "Custom" }),
614
- THEME_PRESETS.map((preset) => /* @__PURE__ */ jsx("option", { value: preset.id, children: resolveLocalizedText(preset.name, locale) }, preset.id))
615
- ]
616
- }
617
- )
618
- ] });
619
- }
620
- function ThemeEditor({
621
- theme = DEFAULT_SHEET_THEME,
622
- locale = "ja",
623
- onChange
624
- }) {
625
- const update = (field, value) => onChange({ ...theme, [field]: value });
626
- return /* @__PURE__ */ jsxs("fieldset", { className: "stamprally-admin-card", children: [
627
- /* @__PURE__ */ jsx("legend", { children: "Theme" }),
628
- /* @__PURE__ */ jsx(ThemePresetSelector, { value: theme, locale, onChange }),
629
- /* @__PURE__ */ jsxs("label", { children: [
630
- "Primary color",
631
- " ",
632
- /* @__PURE__ */ jsx(
633
- "input",
634
- {
635
- type: "color",
636
- value: theme.primaryColor,
637
- onChange: (event) => update("primaryColor", event.target.value)
638
- }
639
- )
640
- ] }),
641
- /* @__PURE__ */ jsxs("label", { children: [
642
- "Grid columns",
643
- " ",
644
- /* @__PURE__ */ jsx(
645
- "input",
646
- {
647
- type: "number",
648
- min: 1,
649
- max: 6,
650
- value: theme.gridColumns,
651
- onChange: (event) => update("gridColumns", Number(event.target.value))
652
- }
653
- )
654
- ] })
655
- ] });
656
- }
657
- function QrExportModal({
658
- open,
659
- config,
660
- locale = "ja",
661
- onClose
662
- }) {
663
- if (!open) return null;
664
- return /* @__PURE__ */ jsxs("div", { role: "dialog", "aria-modal": "true", className: "stamprally-qr-modal", children: [
665
- /* @__PURE__ */ jsxs("div", { className: "stamprally-qr-modal__toolbar", children: [
666
- /* @__PURE__ */ jsx("h2", { children: "QR print kit" }),
667
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => window.print(), children: "Print" }),
668
- /* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, children: "Close" })
669
- ] }),
670
- /* @__PURE__ */ jsx("div", { className: "stamprally-qr-modal__grid", children: config.stamps.map((spot) => /* @__PURE__ */ jsxs("article", { children: [
671
- /* @__PURE__ */ jsx("h3", { children: resolveLocalizedText(spot.name, locale) }),
672
- /* @__PURE__ */ jsx("pre", { children: JSON.stringify({
673
- version: CURRENT_RALLY_CONFIG_VERSION,
674
- rallyId: config.id,
675
- stampId: spot.id
676
- }) })
677
- ] }, spot.id)) })
678
- ] });
679
- }
680
- function JsonConfigIO({
681
- config,
682
- onImport
683
- }) {
684
- const [value, setValue] = useState(() => JSON.stringify(stripSensitiveConfig(config), null, 2));
685
- const [error, setError] = useState(null);
686
- const importConfig = () => {
687
- try {
688
- const migrated = migrateRallyConfig(JSON.parse(value));
689
- const validation = validateRallyConfig(migrated);
690
- if (!validation.valid) {
691
- setError(validation.errors.map((item) => `${item.path}: ${item.message}`).join(" "));
692
- return;
41
+ type: "button",
42
+ onClick: () => update({ spots: [...config.spots, newSpot(config.spots.length)] }),
43
+ children: "Add spot"
693
44
  }
694
- onImport(migrated);
695
- setError(null);
696
- } catch {
697
- setError("Invalid rally JSON.");
698
- }
699
- };
700
- return /* @__PURE__ */ jsxs("section", { className: "stamprally-admin-card", "aria-label": "JSON configuration", children: [
701
- /* @__PURE__ */ jsx("textarea", { value, onChange: (event) => setValue(event.target.value) }),
702
- /* @__PURE__ */ jsx("button", { type: "button", onClick: importConfig, children: "Import and migrate" }),
45
+ ),
703
46
  /* @__PURE__ */ jsx(
704
47
  "button",
705
48
  {
706
49
  type: "button",
707
- onClick: () => navigator.clipboard?.writeText(JSON.stringify(stripSensitiveConfig(config), null, 2)),
708
- children: "Copy public JSON"
50
+ onClick: () => update({ rewards: [...config.rewards, newReward(config.rewards.length)] }),
51
+ children: "Add reward"
709
52
  }
710
53
  ),
711
- error !== null && /* @__PURE__ */ jsx("p", { role: "alert", children: error })
54
+ /* @__PURE__ */ jsx("ul", { children: config.spots.map((spot) => /* @__PURE__ */ jsx("li", { children: resolveLocalizedText(spot.name, locale) }, spot.id)) })
712
55
  ] });
713
56
  }
714
- function RallyEditor({
715
- config,
716
- locale = "ja",
57
+ var RallyEditor = AdminRallyEditor;
58
+ function SpotItemForm({
59
+ spot,
717
60
  onChange
718
61
  }) {
719
- const [showQr, setShowQr] = useState(false);
720
- return /* @__PURE__ */ jsxs("main", { className: "stamprally-admin", children: [
721
- /* @__PURE__ */ jsxs("header", { children: [
722
- /* @__PURE__ */ jsx("h1", { children: resolveLocalizedText(config.title, locale) || config.id }),
723
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setShowQr(true), children: "QR export" })
724
- ] }),
725
- /* @__PURE__ */ jsx(GeneralSettingsForm, { config, locale, onChange }),
726
- config.theme === void 0 ? /* @__PURE__ */ jsx(ThemeEditor, { locale, onChange: (theme) => onChange({ ...config, theme }) }) : /* @__PURE__ */ jsx(
727
- ThemeEditor,
728
- {
729
- theme: config.theme,
730
- locale,
731
- onChange: (theme) => onChange({ ...config, theme })
732
- }
733
- ),
62
+ return /* @__PURE__ */ jsxs("label", { children: [
63
+ "Spot name",
734
64
  /* @__PURE__ */ jsx(
735
- SpotListEditor,
65
+ "input",
736
66
  {
737
- spots: config.stamps,
738
- locale,
739
- onChange: (stamps) => onChange({ ...config, stamps })
67
+ value: resolveLocalizedText(spot.name, "en"),
68
+ onChange: (event) => onChange({ ...spot, name: event.target.value })
740
69
  }
741
- ),
70
+ )
71
+ ] });
72
+ }
73
+ function GeneralSettingsForm({ config, onChange }) {
74
+ return /* @__PURE__ */ jsxs("label", { children: [
75
+ "Rally title",
742
76
  /* @__PURE__ */ jsx(
743
- RewardListEditor,
77
+ "input",
744
78
  {
745
- rewards: config.rewards ?? [],
746
- locale,
747
- onChange: (rewards) => onChange({ ...config, rewards })
79
+ value: resolveLocalizedText(config.title, "en"),
80
+ onChange: (event) => onChange({ ...config, title: event.target.value })
748
81
  }
749
- ),
750
- /* @__PURE__ */ jsx(JsonConfigIO, { config, onImport: onChange }),
82
+ )
83
+ ] });
84
+ }
85
+ function JsonConfigIO({ config, onImport }) {
86
+ const [value, setValue] = useState(() => JSON.stringify(config, null, 2));
87
+ const parse = (event) => {
88
+ setValue(event.target.value);
89
+ };
90
+ return /* @__PURE__ */ jsxs("div", { children: [
91
+ /* @__PURE__ */ jsx("textarea", { value, onChange: parse }),
751
92
  /* @__PURE__ */ jsx(
752
- QrExportModal,
93
+ "button",
753
94
  {
754
- open: showQr,
755
- config,
756
- locale,
757
- onClose: () => setShowQr(false)
95
+ type: "button",
96
+ onClick: () => {
97
+ try {
98
+ onImport(JSON.parse(value));
99
+ } catch {
100
+ }
101
+ },
102
+ children: "Import"
758
103
  }
759
104
  )
760
105
  ] });
761
106
  }
762
107
 
763
- export { AdminRallyEditor, ConditionEditor, GeneralSettingsForm, JsonConfigIO, QrExportModal, RallyEditor, RewardListEditor, SpotItemForm, SpotListEditor, ThemeEditor, ThemePresetSelector };
108
+ export { AdminRallyEditor, GeneralSettingsForm, JsonConfigIO, RallyEditor, SpotItemForm };
764
109
  //# sourceMappingURL=index.js.map
765
110
  //# sourceMappingURL=index.js.map