@stamprally/admin-ui 0.8.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,1479 +1,110 @@
1
- import { DEFAULT_SHEET_THEME, THEME_PRESETS, resolveLocalizedText, CURRENT_RALLY_CONFIG_VERSION, stripSensitiveConfig, 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 universalText(value, locale) {
7
- return typeof value === "string" ? value : value?.[locale] ?? "";
8
- }
9
- function updateUniversalText(value, locale, next) {
10
- return { ...typeof value === "string" ? {} : value, [locale]: next };
11
- }
12
- var newUniversalCondition = () => ({ type: "passcode", code: "" });
13
- function defaultUniversalSpot(index) {
14
- return {
15
- id: `spot-${index + 1}`,
16
- orderIndex: index,
17
- name: { ja: `\u30B9\u30DD\u30C3\u30C8 ${index + 1}`, en: `Spot ${index + 1}` },
18
- conditions: [newUniversalCondition()]
19
- };
20
- }
21
- function defaultUniversalReward(index) {
22
- return {
23
- id: `reward-${index + 1}`,
24
- title: { ja: `\u666F\u54C1 ${index + 1}`, en: `Reward ${index + 1}` },
25
- type: "digital",
26
- redemptionMethod: "manual_slide",
27
- requiredStampCount: 1
28
- };
29
- }
30
- function updateUniversalCondition(condition, field, value) {
31
- if (condition.type === "gps") {
32
- return { ...condition, [field]: Number(value) };
33
- }
34
- return { ...condition, [field]: value };
35
- }
36
- function UniversalSpotForm({
37
- spot,
38
- spots,
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({
21
+ config,
39
22
  onChange,
40
- onDelete,
41
- onMoveUp,
42
- onMoveDown
23
+ locale = "en"
43
24
  }) {
44
- const update = (next) => onChange({ ...spot, ...next });
45
- const references = spot.externalReferences ?? [];
46
- const conditions = spot.conditions;
47
- return /* @__PURE__ */ jsxs("fieldset", { className: "stamprally-admin-card", "aria-label": `Spot ${spot.id}`, children: [
48
- /* @__PURE__ */ jsxs("legend", { children: [
49
- "Spot ",
50
- spot.orderIndex + 1
51
- ] }),
52
- /* @__PURE__ */ jsxs("label", { children: [
53
- "ID ",
54
- /* @__PURE__ */ jsx("input", { value: spot.id, onChange: (event) => update({ id: event.target.value }) })
55
- ] }),
56
- /* @__PURE__ */ jsxs("label", { children: [
57
- "Name (ja)",
58
- " ",
59
- /* @__PURE__ */ jsx(
60
- "input",
61
- {
62
- value: universalText(spot.name, "ja"),
63
- onChange: (event) => update({ name: updateUniversalText(spot.name, "ja", event.target.value) })
64
- }
65
- )
66
- ] }),
67
- /* @__PURE__ */ jsxs("label", { children: [
68
- "Name (en)",
69
- " ",
70
- /* @__PURE__ */ jsx(
71
- "input",
72
- {
73
- value: universalText(spot.name, "en"),
74
- onChange: (event) => update({ name: updateUniversalText(spot.name, "en", event.target.value) })
75
- }
76
- )
77
- ] }),
78
- /* @__PURE__ */ jsxs("label", { children: [
79
- "Description (ja)",
80
- " ",
81
- /* @__PURE__ */ jsx(
82
- "textarea",
83
- {
84
- value: universalText(spot.description, "ja"),
85
- onChange: (event) => update({ description: updateUniversalText(spot.description, "ja", event.target.value) })
86
- }
87
- )
88
- ] }),
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) }),
89
28
  /* @__PURE__ */ jsxs("label", { children: [
90
- "Order index",
91
- " ",
29
+ "Title",
92
30
  /* @__PURE__ */ jsx(
93
31
  "input",
94
32
  {
95
- type: "number",
96
- value: spot.orderIndex,
97
- onChange: (event) => update({ orderIndex: Number(event.target.value) })
33
+ value: resolveLocalizedText(config.title, locale),
34
+ onChange: (event) => update({ title: event.target.value })
98
35
  }
99
36
  )
100
37
  ] }),
101
- /* @__PURE__ */ jsxs("label", { children: [
102
- "Prerequisite spot",
103
- /* @__PURE__ */ jsxs(
104
- "select",
105
- {
106
- value: "",
107
- onChange: (event) => {
108
- if (event.target.value !== "")
109
- update({ prerequisites: [...spot.prerequisites ?? [], event.target.value] });
110
- },
111
- children: [
112
- /* @__PURE__ */ jsx("option", { value: "", children: "Select a prerequisite" }),
113
- spots.filter((item) => item.id !== spot.id && !spot.prerequisites?.includes(item.id)).map((item) => /* @__PURE__ */ jsx("option", { value: item.id, children: item.id }, item.id))
114
- ]
115
- }
116
- )
117
- ] }),
118
- (spot.prerequisites ?? []).map((id) => /* @__PURE__ */ jsxs(
38
+ /* @__PURE__ */ jsx(
119
39
  "button",
120
40
  {
121
41
  type: "button",
122
- onClick: () => update({ prerequisites: (spot.prerequisites ?? []).filter((item) => item !== id) }),
123
- children: [
124
- "Remove prerequisite: ",
125
- id
126
- ]
127
- },
128
- id
129
- )),
130
- /* @__PURE__ */ jsxs("div", { children: [
131
- /* @__PURE__ */ jsx("h4", { children: "External references" }),
132
- references.map((reference, index) => /* @__PURE__ */ jsxs("div", { children: [
133
- /* @__PURE__ */ jsxs("label", { children: [
134
- "Reference type",
135
- " ",
136
- /* @__PURE__ */ jsx(
137
- "input",
138
- {
139
- value: reference.type,
140
- onChange: (event) => update({
141
- externalReferences: references.map(
142
- (item, itemIndex) => itemIndex === index ? { ...item, type: event.target.value } : item
143
- )
144
- })
145
- }
146
- )
147
- ] }),
148
- /* @__PURE__ */ jsxs("label", { children: [
149
- "Reference ID",
150
- " ",
151
- /* @__PURE__ */ jsx(
152
- "input",
153
- {
154
- value: reference.id,
155
- onChange: (event) => update({
156
- externalReferences: references.map(
157
- (item, itemIndex) => itemIndex === index ? { ...item, id: event.target.value } : item
158
- )
159
- })
160
- }
161
- )
162
- ] }),
163
- /* @__PURE__ */ jsxs("label", { children: [
164
- "Reference URL",
165
- " ",
166
- /* @__PURE__ */ jsx(
167
- "input",
168
- {
169
- value: reference.url ?? "",
170
- onChange: (event) => update({
171
- externalReferences: references.map(
172
- (item, itemIndex) => itemIndex === index ? { ...item, url: event.target.value } : item
173
- )
174
- })
175
- }
176
- )
177
- ] }),
178
- /* @__PURE__ */ jsxs("label", { children: [
179
- "Reference metadata",
180
- " ",
181
- /* @__PURE__ */ jsx(
182
- "textarea",
183
- {
184
- value: JSON.stringify(reference.metadata ?? {}, null, 2),
185
- onChange: (event) => {
186
- try {
187
- const metadata = JSON.parse(event.target.value);
188
- if (typeof metadata === "object" && metadata !== null && !Array.isArray(metadata))
189
- update({
190
- externalReferences: references.map(
191
- (item, itemIndex) => itemIndex === index ? { ...item, metadata } : item
192
- )
193
- });
194
- } catch {
195
- }
196
- }
197
- }
198
- )
199
- ] }),
200
- /* @__PURE__ */ jsx(
201
- "button",
202
- {
203
- type: "button",
204
- onClick: () => update({
205
- externalReferences: references.filter((_, itemIndex) => itemIndex !== index)
206
- }),
207
- children: "Remove reference"
208
- }
209
- )
210
- ] }, `${reference.type}:${reference.id}`)),
211
- /* @__PURE__ */ jsx(
212
- "button",
213
- {
214
- type: "button",
215
- onClick: () => update({
216
- externalReferences: [...references, { type: "", id: "" }]
217
- }),
218
- children: "Add external reference"
219
- }
220
- )
221
- ] }),
222
- /* @__PURE__ */ jsxs("div", { children: [
223
- /* @__PURE__ */ jsx("h4", { children: "Conditions" }),
224
- conditions.map((condition, index) => /* @__PURE__ */ jsxs("div", { children: [
225
- /* @__PURE__ */ jsxs("label", { children: [
226
- "Condition type",
227
- /* @__PURE__ */ jsxs(
228
- "select",
229
- {
230
- value: condition.type,
231
- onChange: (event) => {
232
- const next = event.target.value === "qr" ? { type: "qr", secretToken: "" } : event.target.value === "gps" ? { type: "gps", latitude: 0, longitude: 0, radiusMeters: 100 } : event.target.value === "custom" ? { type: "custom", validatorName: "" } : { type: "passcode", code: "" };
233
- onChange({
234
- ...spot,
235
- conditions: conditions.map(
236
- (item, itemIndex) => itemIndex === index ? next : item
237
- )
238
- });
239
- },
240
- children: [
241
- /* @__PURE__ */ jsx("option", { value: "qr", children: "QR" }),
242
- /* @__PURE__ */ jsx("option", { value: "passcode", children: "Passcode" }),
243
- /* @__PURE__ */ jsx("option", { value: "gps", children: "GPS" }),
244
- /* @__PURE__ */ jsx("option", { value: "custom", children: "Custom" })
245
- ]
246
- }
247
- )
248
- ] }),
249
- condition.type === "qr" && /* @__PURE__ */ jsxs("label", { children: [
250
- "QR secret token",
251
- " ",
252
- /* @__PURE__ */ jsx(
253
- "input",
254
- {
255
- value: condition.secretToken,
256
- onChange: (event) => onChange({
257
- ...spot,
258
- conditions: conditions.map(
259
- (item, itemIndex) => itemIndex === index ? updateUniversalCondition(item, "secretToken", event.target.value) : item
260
- )
261
- })
262
- }
263
- )
264
- ] }),
265
- condition.type === "passcode" && /* @__PURE__ */ jsxs("label", { children: [
266
- "Passcode",
267
- " ",
268
- /* @__PURE__ */ jsx(
269
- "input",
270
- {
271
- value: condition.code,
272
- onChange: (event) => onChange({
273
- ...spot,
274
- conditions: conditions.map(
275
- (item, itemIndex) => itemIndex === index ? updateUniversalCondition(item, "code", event.target.value) : item
276
- )
277
- })
278
- }
279
- )
280
- ] }),
281
- condition.type === "custom" && /* @__PURE__ */ jsxs("label", { children: [
282
- "Validator name",
283
- " ",
284
- /* @__PURE__ */ jsx(
285
- "input",
286
- {
287
- value: condition.validatorName,
288
- onChange: (event) => onChange({
289
- ...spot,
290
- conditions: conditions.map(
291
- (item, itemIndex) => itemIndex === index ? updateUniversalCondition(item, "validatorName", event.target.value) : item
292
- )
293
- })
294
- }
295
- )
296
- ] }),
297
- condition.type === "gps" && /* @__PURE__ */ jsxs(Fragment, { children: [
298
- /* @__PURE__ */ jsxs("label", { children: [
299
- "Latitude",
300
- " ",
301
- /* @__PURE__ */ jsx(
302
- "input",
303
- {
304
- type: "number",
305
- value: condition.latitude,
306
- onChange: (event) => onChange({
307
- ...spot,
308
- conditions: conditions.map(
309
- (item, itemIndex) => itemIndex === index ? updateUniversalCondition(item, "latitude", event.target.value) : item
310
- )
311
- })
312
- }
313
- )
314
- ] }),
315
- /* @__PURE__ */ jsxs("label", { children: [
316
- "Longitude",
317
- " ",
318
- /* @__PURE__ */ jsx(
319
- "input",
320
- {
321
- type: "number",
322
- value: condition.longitude,
323
- onChange: (event) => onChange({
324
- ...spot,
325
- conditions: conditions.map(
326
- (item, itemIndex) => itemIndex === index ? updateUniversalCondition(item, "longitude", event.target.value) : item
327
- )
328
- })
329
- }
330
- )
331
- ] }),
332
- /* @__PURE__ */ jsxs("label", { children: [
333
- "Radius meters",
334
- " ",
335
- /* @__PURE__ */ jsx(
336
- "input",
337
- {
338
- type: "number",
339
- value: condition.radiusMeters,
340
- onChange: (event) => onChange({
341
- ...spot,
342
- conditions: conditions.map(
343
- (item, itemIndex) => itemIndex === index ? updateUniversalCondition(item, "radiusMeters", event.target.value) : item
344
- )
345
- })
346
- }
347
- )
348
- ] })
349
- ] }),
350
- conditions.length > 1 && /* @__PURE__ */ jsx(
351
- "button",
352
- {
353
- type: "button",
354
- onClick: () => update({ conditions: conditions.filter((_, itemIndex) => itemIndex !== index) }),
355
- children: "Remove condition"
356
- }
357
- )
358
- ] }, JSON.stringify(condition))),
359
- /* @__PURE__ */ jsx(
360
- "button",
361
- {
362
- type: "button",
363
- onClick: () => update({ conditions: [...conditions, newUniversalCondition()] }),
364
- children: "Add condition"
365
- }
366
- )
367
- ] }),
368
- /* @__PURE__ */ jsx("button", { type: "button", onClick: onMoveUp, disabled: spot.orderIndex === 0, children: "Move spot up" }),
369
- /* @__PURE__ */ jsx("button", { type: "button", onClick: onMoveDown, disabled: spot.orderIndex === spots.length - 1, children: "Move spot down" }),
370
- /* @__PURE__ */ jsx("button", { type: "button", onClick: onDelete, children: "Remove spot" })
371
- ] });
372
- }
373
- function UniversalRewardForm({
374
- reward,
375
- spots,
376
- onChange,
377
- onDelete
378
- }) {
379
- const update = (next) => onChange({ ...reward, ...next });
380
- const conditions = reward.conditions ?? [];
381
- return /* @__PURE__ */ jsxs("fieldset", { className: "stamprally-admin-card", "aria-label": `Reward ${reward.id}`, children: [
382
- /* @__PURE__ */ jsx("legend", { children: "Reward" }),
383
- /* @__PURE__ */ jsxs("label", { children: [
384
- "ID ",
385
- /* @__PURE__ */ jsx("input", { value: reward.id, onChange: (event) => update({ id: event.target.value }) })
386
- ] }),
387
- /* @__PURE__ */ jsxs("label", { children: [
388
- "Title (ja)",
389
- " ",
390
- /* @__PURE__ */ jsx(
391
- "input",
392
- {
393
- value: universalText(reward.title, "ja"),
394
- onChange: (event) => update({ title: updateUniversalText(reward.title, "ja", event.target.value) })
395
- }
396
- )
397
- ] }),
398
- /* @__PURE__ */ jsxs("label", { children: [
399
- "Title (en)",
400
- " ",
401
- /* @__PURE__ */ jsx(
402
- "input",
403
- {
404
- value: universalText(reward.title, "en"),
405
- onChange: (event) => update({ title: updateUniversalText(reward.title, "en", event.target.value) })
406
- }
407
- )
408
- ] }),
409
- /* @__PURE__ */ jsxs("label", { children: [
410
- "Required stamps",
411
- " ",
412
- /* @__PURE__ */ jsx(
413
- "input",
414
- {
415
- type: "number",
416
- min: 0,
417
- value: reward.requiredStampCount,
418
- onChange: (event) => update({ requiredStampCount: Number(event.target.value) })
419
- }
420
- )
421
- ] }),
422
- /* @__PURE__ */ jsxs("label", { children: [
423
- "Stock limit",
424
- " ",
425
- /* @__PURE__ */ jsx(
426
- "input",
427
- {
428
- type: "number",
429
- min: 0,
430
- value: reward.stockLimit ?? "",
431
- onChange: (event) => {
432
- if (event.target.value === "") {
433
- const { stockLimit: _removed, ...rest } = reward;
434
- onChange(rest);
435
- } else update({ stockLimit: Number(event.target.value) });
436
- }
437
- }
438
- )
439
- ] }),
440
- /* @__PURE__ */ jsxs("label", { children: [
441
- "User claim limit",
442
- " ",
443
- /* @__PURE__ */ jsx(
444
- "input",
445
- {
446
- type: "number",
447
- min: 0,
448
- value: reward.userClaimLimit ?? "",
449
- onChange: (event) => {
450
- if (event.target.value === "") {
451
- const { userClaimLimit: _removed, ...rest } = reward;
452
- onChange(rest);
453
- } else update({ userClaimLimit: Number(event.target.value) });
454
- }
455
- }
456
- )
457
- ] }),
458
- /* @__PURE__ */ jsxs("label", { children: [
459
- "Staff passcode",
460
- " ",
461
- /* @__PURE__ */ jsx(
462
- "input",
463
- {
464
- type: "password",
465
- value: reward.staffPasscode ?? "",
466
- onChange: (event) => {
467
- if (event.target.value === "") {
468
- const { staffPasscode: _removed, ...rest } = reward;
469
- onChange(rest);
470
- } else update({ staffPasscode: event.target.value });
471
- }
472
- }
473
- )
474
- ] }),
475
- /* @__PURE__ */ jsxs("label", { children: [
476
- "Redemption method",
477
- " ",
478
- /* @__PURE__ */ jsxs(
479
- "select",
480
- {
481
- value: reward.redemptionMethod,
482
- onChange: (event) => update({ redemptionMethod: event.target.value }),
483
- children: [
484
- /* @__PURE__ */ jsx("option", { value: "manual_slide", children: "manual_slide" }),
485
- /* @__PURE__ */ jsx("option", { value: "staff_passcode", children: "staff_passcode" }),
486
- /* @__PURE__ */ jsx("option", { value: "view_only", children: "view_only" }),
487
- /* @__PURE__ */ jsx("option", { value: "server_claim", children: "server_claim" })
488
- ]
489
- }
490
- )
491
- ] }),
492
- /* @__PURE__ */ jsxs("div", { children: [
493
- /* @__PURE__ */ jsx("h4", { children: "Unlock conditions" }),
494
- conditions.map((condition, index) => /* @__PURE__ */ jsxs("div", { children: [
495
- /* @__PURE__ */ jsxs("label", { children: [
496
- "Reward condition type",
497
- " ",
498
- /* @__PURE__ */ jsxs(
499
- "select",
500
- {
501
- value: condition.type,
502
- onChange: (event) => {
503
- const type = event.target.value;
504
- const next = type === "stamps" ? { type: "stamps", stampIds: [] } : type === "all" || type === "any" ? { type, conditions: [{ type: "stamp_count", count: 1 }] } : { type: "stamp_count", count: 1 };
505
- update({
506
- conditions: conditions.map(
507
- (item, itemIndex) => itemIndex === index ? next : item
508
- )
509
- });
510
- },
511
- children: [
512
- /* @__PURE__ */ jsx("option", { value: "stamp_count", children: "Stamp count" }),
513
- /* @__PURE__ */ jsx("option", { value: "stamps", children: "Specific spots" }),
514
- /* @__PURE__ */ jsx("option", { value: "all", children: "All" }),
515
- /* @__PURE__ */ jsx("option", { value: "any", children: "Any" })
516
- ]
517
- }
518
- )
519
- ] }),
520
- condition.type === "stamp_count" && /* @__PURE__ */ jsxs("label", { children: [
521
- "Required stamp count",
522
- " ",
523
- /* @__PURE__ */ jsx(
524
- "input",
525
- {
526
- type: "number",
527
- min: 0,
528
- value: condition.count,
529
- onChange: (event) => update({
530
- conditions: conditions.map(
531
- (item, itemIndex) => itemIndex === index ? { ...condition, count: Number(event.target.value) } : item
532
- )
533
- })
534
- }
535
- )
536
- ] }),
537
- condition.type === "stamps" && /* @__PURE__ */ jsxs("label", { children: [
538
- "Specific spot IDs",
539
- " ",
540
- /* @__PURE__ */ jsx(
541
- "input",
542
- {
543
- value: condition.stampIds.join(", "),
544
- placeholder: spots.map((item) => item.id).join(", "),
545
- onChange: (event) => update({
546
- conditions: conditions.map(
547
- (item, itemIndex) => itemIndex === index ? {
548
- ...condition,
549
- stampIds: event.target.value.split(",").map((id) => id.trim()).filter(Boolean)
550
- } : item
551
- )
552
- })
553
- }
554
- )
555
- ] }),
556
- /* @__PURE__ */ jsx(
557
- "button",
558
- {
559
- type: "button",
560
- onClick: () => update({ conditions: conditions.filter((_, itemIndex) => itemIndex !== index) }),
561
- children: "Remove reward condition"
562
- }
563
- )
564
- ] }, JSON.stringify(condition))),
565
- /* @__PURE__ */ jsx(
566
- "button",
567
- {
568
- type: "button",
569
- onClick: () => update({ conditions: [...conditions, { type: "stamp_count", count: 1 }] }),
570
- children: "Add reward condition"
571
- }
572
- )
573
- ] }),
574
- /* @__PURE__ */ jsx("button", { type: "button", onClick: onDelete, children: "Remove reward" })
575
- ] });
576
- }
577
- function AdminRallyEditor({ config, locale = "ja", onChange }) {
578
- const spots = config.spots;
579
- const rewards = config.rewards;
580
- const theme = config.theme ?? DEFAULT_SHEET_THEME;
581
- const setConfig = (next) => onChange({ ...config, ...next });
582
- return /* @__PURE__ */ jsxs("section", { "aria-label": "Universal rally editor", className: "stamprally-admin-card", children: [
583
- /* @__PURE__ */ jsxs("fieldset", { children: [
584
- /* @__PURE__ */ jsx("legend", { children: "Basic settings" }),
585
- /* @__PURE__ */ jsxs("label", { children: [
586
- "Title (",
587
- locale,
588
- ")",
589
- " ",
590
- /* @__PURE__ */ jsx(
591
- "input",
592
- {
593
- value: universalText(config.title, locale),
594
- onChange: (event) => setConfig({
595
- title: updateUniversalText(
596
- config.title,
597
- locale,
598
- event.target.value
599
- )
600
- })
601
- }
602
- )
603
- ] }),
604
- /* @__PURE__ */ jsxs("label", { children: [
605
- "Description (",
606
- locale,
607
- ")",
608
- " ",
609
- /* @__PURE__ */ jsx(
610
- "textarea",
611
- {
612
- value: universalText(config.description, locale),
613
- onChange: (event) => {
614
- if (event.target.value === "") {
615
- const { description: _removed, ...rest } = config;
616
- onChange(rest);
617
- } else
618
- setConfig({
619
- description: updateUniversalText(config.description, locale, event.target.value)
620
- });
621
- }
622
- }
623
- )
624
- ] }),
625
- /* @__PURE__ */ jsxs("label", { children: [
626
- "Theme preset",
627
- " ",
628
- /* @__PURE__ */ jsxs(
629
- "select",
630
- {
631
- value: THEME_PRESETS.find((preset) => JSON.stringify(preset.theme) === JSON.stringify(theme))?.id ?? "custom",
632
- onChange: (event) => {
633
- const preset = THEME_PRESETS.find((item) => item.id === event.target.value);
634
- if (preset !== void 0) setConfig({ theme: preset.theme });
635
- },
636
- children: [
637
- /* @__PURE__ */ jsx("option", { value: "custom", children: "custom" }),
638
- THEME_PRESETS.map((preset) => /* @__PURE__ */ jsx("option", { value: preset.id, children: resolveLocalizedText(preset.name, locale) }, preset.id))
639
- ]
640
- }
641
- )
642
- ] }),
643
- /* @__PURE__ */ jsxs("label", { children: [
644
- "Primary color",
645
- " ",
646
- /* @__PURE__ */ jsx(
647
- "input",
648
- {
649
- type: "color",
650
- value: theme.primaryColor,
651
- onChange: (event) => setConfig({ theme: { ...theme, primaryColor: event.target.value } })
652
- }
653
- )
654
- ] }),
655
- /* @__PURE__ */ jsxs("label", { children: [
656
- "Font family",
657
- " ",
658
- /* @__PURE__ */ jsxs(
659
- "select",
660
- {
661
- value: theme.fontFamily ?? "serif",
662
- onChange: (event) => setConfig({
663
- theme: {
664
- ...theme,
665
- fontFamily: event.target.value
666
- }
667
- }),
668
- children: [
669
- /* @__PURE__ */ jsx("option", { value: "system-ui", children: "system-ui" }),
670
- /* @__PURE__ */ jsx("option", { value: "serif", children: "serif" }),
671
- /* @__PURE__ */ jsx("option", { value: "rounded-sans", children: "rounded-sans" }),
672
- /* @__PURE__ */ jsx("option", { value: "monospace", children: "monospace" }),
673
- /* @__PURE__ */ jsx("option", { value: "handwritten", children: "handwritten" })
674
- ]
675
- }
676
- )
677
- ] })
678
- ] }),
679
- /* @__PURE__ */ jsxs("section", { "aria-label": "Universal spots", children: [
680
- /* @__PURE__ */ jsx("h2", { children: "Spots" }),
681
- spots.map((spot, index) => /* @__PURE__ */ jsx(
682
- UniversalSpotForm,
683
- {
684
- spot,
685
- spots,
686
- onChange: (next) => setConfig({
687
- spots: spots.map(
688
- (item, itemIndex) => itemIndex === index ? { ...next, orderIndex: next.orderIndex } : item
689
- )
690
- }),
691
- onMoveUp: () => {
692
- if (index === 0) return;
693
- const current = spots[index];
694
- const previous = spots[index - 1];
695
- if (current === void 0 || previous === void 0) return;
696
- const reordered = spots.map(
697
- (item, itemIndex) => itemIndex === index - 1 ? { ...current, orderIndex: index - 1 } : itemIndex === index ? { ...previous, orderIndex: index } : item
698
- );
699
- setConfig({ spots: reordered });
700
- },
701
- onMoveDown: () => {
702
- if (index === spots.length - 1) return;
703
- const current = spots[index];
704
- const next = spots[index + 1];
705
- if (current === void 0 || next === void 0) return;
706
- const reordered = spots.map(
707
- (item, itemIndex) => itemIndex === index ? { ...next, orderIndex: index } : itemIndex === index + 1 ? { ...current, orderIndex: index + 1 } : item
708
- );
709
- setConfig({ spots: reordered });
710
- },
711
- onDelete: () => setConfig({
712
- spots: spots.filter((_, itemIndex) => itemIndex !== index).map((item, itemIndex) => ({ ...item, orderIndex: itemIndex }))
713
- })
714
- },
715
- spot.id
716
- )),
717
- /* @__PURE__ */ jsx(
718
- "button",
719
- {
720
- type: "button",
721
- onClick: () => setConfig({ spots: [...spots, defaultUniversalSpot(spots.length)] }),
722
- children: "Add spot"
723
- }
724
- )
725
- ] }),
726
- /* @__PURE__ */ jsxs("section", { "aria-label": "Universal rewards", children: [
727
- /* @__PURE__ */ jsx("h2", { children: "Rewards" }),
728
- rewards.map((reward, index) => /* @__PURE__ */ jsx(
729
- UniversalRewardForm,
730
- {
731
- reward,
732
- spots,
733
- onChange: (next) => setConfig({
734
- rewards: rewards.map((item, itemIndex) => itemIndex === index ? next : item)
735
- }),
736
- onDelete: () => setConfig({ rewards: rewards.filter((_, itemIndex) => itemIndex !== index) })
737
- },
738
- reward.id
739
- )),
740
- /* @__PURE__ */ jsx(
741
- "button",
742
- {
743
- type: "button",
744
- onClick: () => setConfig({ rewards: [...rewards, defaultUniversalReward(rewards.length)] }),
745
- children: "Add reward"
746
- }
747
- )
748
- ] })
749
- ] });
750
- }
751
- var localePair = (value) => {
752
- if (typeof value === "string") return { ja: value, en: "" };
753
- const result = {};
754
- for (const [key, text] of Object.entries(value ?? {})) {
755
- if (text !== void 0) result[key] = text;
756
- }
757
- return result;
758
- };
759
- function updateText(value, locale, next) {
760
- const current = localePair(value);
761
- return { ...current, [locale]: next };
762
- }
763
- function GeneralSettingsForm({
764
- config,
765
- locale = "ja",
766
- onChange
767
- }) {
768
- const pair = localePair(config.title);
769
- return /* @__PURE__ */ jsxs("fieldset", { className: "stamprally-admin-card", children: [
770
- /* @__PURE__ */ jsx("legend", { children: "General settings" }),
771
- /* @__PURE__ */ jsxs("label", { children: [
772
- "ID ",
773
- /* @__PURE__ */ jsx("input", { value: config.id, readOnly: true })
774
- ] }),
775
- /* @__PURE__ */ jsxs("label", { children: [
776
- "Title (",
777
- locale,
778
- ")",
779
- " ",
780
- /* @__PURE__ */ jsx(
781
- "input",
782
- {
783
- value: pair[locale],
784
- required: true,
785
- onChange: (event) => onChange({ ...config, title: updateText(config.title, locale, event.target.value) })
786
- }
787
- )
788
- ] }),
789
- /* @__PURE__ */ jsxs("label", { children: [
790
- "Start date",
791
- " ",
792
- /* @__PURE__ */ jsx(
793
- "input",
794
- {
795
- type: "datetime-local",
796
- value: config.startDate?.slice(0, 16) ?? "",
797
- onChange: (event) => {
798
- if (event.target.value === "") {
799
- const { startDate: _startDate, ...rest } = config;
800
- onChange(rest);
801
- } else onChange({ ...config, startDate: new Date(event.target.value).toISOString() });
802
- }
803
- }
804
- )
805
- ] }),
806
- /* @__PURE__ */ jsxs("label", { children: [
807
- "End date",
808
- " ",
809
- /* @__PURE__ */ jsx(
810
- "input",
811
- {
812
- type: "datetime-local",
813
- value: config.endDate?.slice(0, 16) ?? "",
814
- onChange: (event) => {
815
- if (event.target.value === "") {
816
- const { endDate: _endDate, ...rest } = config;
817
- onChange(rest);
818
- } else onChange({ ...config, endDate: new Date(event.target.value).toISOString() });
819
- }
820
- }
821
- )
822
- ] }),
823
- /* @__PURE__ */ jsxs("label", { children: [
824
- /* @__PURE__ */ jsx(
825
- "input",
826
- {
827
- type: "checkbox",
828
- checked: config.isSequential === true,
829
- onChange: (event) => onChange({ ...config, isSequential: event.target.checked })
830
- }
831
- ),
832
- " ",
833
- "Sequential check-in"
834
- ] })
835
- ] });
836
- }
837
- function ConditionEditor({ condition, onChange }) {
838
- const type = condition.type;
839
- return /* @__PURE__ */ jsxs("fieldset", { className: "stamprally-admin-card", children: [
840
- /* @__PURE__ */ jsx("legend", { children: "Check-in condition" }),
841
- /* @__PURE__ */ jsxs("label", { children: [
842
- "Type",
843
- " ",
844
- /* @__PURE__ */ jsxs(
845
- "select",
846
- {
847
- value: type,
848
- onChange: (event) => {
849
- const next = event.target.value;
850
- if (next === "token") onChange({ type: "token", token: "" });
851
- else if (next === "geo")
852
- onChange({ type: "geo", latitude: 0, longitude: 0, radiusMeters: 100 });
853
- else onChange({ type: "instant" });
854
- },
855
- children: [
856
- /* @__PURE__ */ jsx("option", { value: "instant", children: "Instant" }),
857
- /* @__PURE__ */ jsx("option", { value: "token", children: "QR / passcode" }),
858
- /* @__PURE__ */ jsx("option", { value: "geo", children: "GPS" })
859
- ]
860
- }
861
- )
862
- ] }),
863
- condition.type === "token" && /* @__PURE__ */ jsxs("label", { children: [
864
- "Token",
865
- " ",
866
- /* @__PURE__ */ jsx(
867
- "input",
868
- {
869
- value: condition.token,
870
- onChange: (event) => onChange({ ...condition, token: event.target.value })
871
- }
872
- )
873
- ] }),
874
- condition.type === "geo" && /* @__PURE__ */ jsxs(Fragment, { children: [
875
- /* @__PURE__ */ jsxs("label", { children: [
876
- "Latitude",
877
- " ",
878
- /* @__PURE__ */ jsx(
879
- "input",
880
- {
881
- type: "number",
882
- min: -90,
883
- max: 90,
884
- value: condition.latitude,
885
- onChange: (event) => onChange({ ...condition, latitude: Number(event.target.value) })
886
- }
887
- )
888
- ] }),
889
- /* @__PURE__ */ jsxs("label", { children: [
890
- "Longitude",
891
- " ",
892
- /* @__PURE__ */ jsx(
893
- "input",
894
- {
895
- type: "number",
896
- min: -180,
897
- max: 180,
898
- value: condition.longitude,
899
- onChange: (event) => onChange({ ...condition, longitude: Number(event.target.value) })
900
- }
901
- )
902
- ] }),
903
- /* @__PURE__ */ jsxs("label", { children: [
904
- "Radius (m)",
905
- " ",
906
- /* @__PURE__ */ jsx(
907
- "input",
908
- {
909
- type: "number",
910
- min: 1,
911
- value: condition.radiusMeters,
912
- onChange: (event) => onChange({ ...condition, radiusMeters: Number(event.target.value) })
913
- }
914
- )
915
- ] })
916
- ] })
42
+ onClick: () => update({ spots: [...config.spots, newSpot(config.spots.length)] }),
43
+ children: "Add spot"
44
+ }
45
+ ),
46
+ /* @__PURE__ */ jsx(
47
+ "button",
48
+ {
49
+ type: "button",
50
+ onClick: () => update({ rewards: [...config.rewards, newReward(config.rewards.length)] }),
51
+ children: "Add reward"
52
+ }
53
+ ),
54
+ /* @__PURE__ */ jsx("ul", { children: config.spots.map((spot) => /* @__PURE__ */ jsx("li", { children: resolveLocalizedText(spot.name, locale) }, spot.id)) })
917
55
  ] });
918
56
  }
57
+ var RallyEditor = AdminRallyEditor;
919
58
  function SpotItemForm({
920
59
  spot,
921
- index = 0,
922
- locale = "ja",
923
- onChange,
924
- onDelete
60
+ onChange
925
61
  }) {
926
- const pair = localePair(spot.name);
927
- const setField = (field, event) => {
928
- if (event.target.value === "") {
929
- const { [field]: _removed, ...rest } = spot;
930
- onChange(rest);
931
- } else onChange({ ...spot, [field]: event.target.value });
932
- };
933
- return /* @__PURE__ */ jsxs("article", { className: "stamprally-admin-card", children: [
934
- /* @__PURE__ */ jsxs("header", { children: [
935
- /* @__PURE__ */ jsxs("strong", { children: [
936
- "Spot ",
937
- index + 1
938
- ] }),
939
- onDelete !== void 0 && /* @__PURE__ */ jsx("button", { type: "button", onClick: onDelete, children: "Delete" })
940
- ] }),
941
- /* @__PURE__ */ jsxs("label", { children: [
942
- "ID",
943
- " ",
944
- /* @__PURE__ */ jsx(
945
- "input",
946
- {
947
- value: spot.id,
948
- onChange: (event) => onChange({ ...spot, id: event.target.value })
949
- }
950
- )
951
- ] }),
952
- /* @__PURE__ */ jsxs("label", { children: [
953
- "Name (",
954
- locale,
955
- ")",
956
- " ",
957
- /* @__PURE__ */ jsx(
958
- "input",
959
- {
960
- value: pair[locale],
961
- required: true,
962
- onChange: (event) => onChange({ ...spot, name: updateText(spot.name, locale, event.target.value) })
963
- }
964
- )
965
- ] }),
966
- /* @__PURE__ */ jsxs("label", { children: [
967
- "Description (",
968
- locale,
969
- ")",
970
- " ",
971
- /* @__PURE__ */ jsx(
972
- "textarea",
973
- {
974
- value: resolveLocalizedText(spot.description, locale),
975
- onChange: (event) => onChange({
976
- ...spot,
977
- description: updateText(spot.description, locale, event.target.value)
978
- })
979
- }
980
- )
981
- ] }),
982
- /* @__PURE__ */ jsxs("label", { children: [
983
- "Hint (",
984
- locale,
985
- ")",
986
- " ",
987
- /* @__PURE__ */ jsx(
988
- "textarea",
989
- {
990
- value: resolveLocalizedText(spot.hint, locale),
991
- onChange: (event) => onChange({ ...spot, hint: updateText(spot.hint, locale, event.target.value) })
992
- }
993
- )
994
- ] }),
995
- /* @__PURE__ */ jsxs("label", { children: [
996
- "Order index",
997
- " ",
998
- /* @__PURE__ */ jsx(
999
- "input",
1000
- {
1001
- type: "number",
1002
- value: spot.orderIndex ?? spot.order ?? index + 1,
1003
- onChange: (event) => onChange({ ...spot, orderIndex: Number(event.target.value) })
1004
- }
1005
- )
1006
- ] }),
1007
- /* @__PURE__ */ jsxs("label", { children: [
1008
- "Icon URL",
1009
- " ",
1010
- /* @__PURE__ */ jsx("input", { value: spot.iconUrl ?? "", onChange: (event) => setField("iconUrl", event) })
1011
- ] }),
1012
- /* @__PURE__ */ jsxs("label", { children: [
1013
- "Image URL",
1014
- " ",
1015
- /* @__PURE__ */ jsx("input", { value: spot.imageUrl ?? "", onChange: (event) => setField("imageUrl", event) })
1016
- ] }),
1017
- /* @__PURE__ */ jsxs("label", { children: [
1018
- "External URL",
1019
- " ",
1020
- /* @__PURE__ */ jsx(
1021
- "input",
1022
- {
1023
- value: spot.externalUrl ?? "",
1024
- onChange: (event) => setField("externalUrl", event)
1025
- }
1026
- )
1027
- ] }),
1028
- /* @__PURE__ */ jsxs("label", { children: [
1029
- "Redirect URL after claim",
1030
- " ",
1031
- /* @__PURE__ */ jsx(
1032
- "input",
1033
- {
1034
- value: spot.redirectUrlAfterClaim ?? "",
1035
- onChange: (event) => setField("redirectUrlAfterClaim", event)
1036
- }
1037
- )
1038
- ] }),
1039
- /* @__PURE__ */ jsxs("label", { children: [
1040
- "Prerequisites (comma-separated stamp IDs)",
1041
- " ",
1042
- /* @__PURE__ */ jsx(
1043
- "input",
1044
- {
1045
- value: (spot.requiresStampIds ?? spot.dependsOn ?? []).join(", "),
1046
- onChange: (event) => {
1047
- const ids = event.target.value.split(",").map((item) => item.trim()).filter(Boolean);
1048
- if (ids.length === 0) {
1049
- const { requiresStampIds: _requiresStampIds, ...rest } = spot;
1050
- onChange(rest);
1051
- } else onChange({ ...spot, requiresStampIds: ids });
1052
- }
1053
- }
1054
- )
1055
- ] }),
1056
- /* @__PURE__ */ jsxs("label", { children: [
1057
- "Metadata (JSON)",
1058
- " ",
1059
- /* @__PURE__ */ jsx(
1060
- "textarea",
1061
- {
1062
- value: JSON.stringify(spot.metadata ?? {}, null, 2),
1063
- onChange: (event) => {
1064
- try {
1065
- const parsed = JSON.parse(event.target.value);
1066
- if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
1067
- onChange({ ...spot, metadata: parsed });
1068
- }
1069
- } catch {
1070
- }
1071
- }
1072
- }
1073
- )
1074
- ] }),
62
+ return /* @__PURE__ */ jsxs("label", { children: [
63
+ "Spot name",
1075
64
  /* @__PURE__ */ jsx(
1076
- ConditionEditor,
65
+ "input",
1077
66
  {
1078
- condition: spot.condition,
1079
- onChange: (condition) => onChange({ ...spot, condition }),
1080
- locale
67
+ value: resolveLocalizedText(spot.name, "en"),
68
+ onChange: (event) => onChange({ ...spot, name: event.target.value })
1081
69
  }
1082
70
  )
1083
71
  ] });
1084
72
  }
1085
- function SpotListEditor({
1086
- spots,
1087
- locale = "ja",
1088
- onChange
1089
- }) {
1090
- return /* @__PURE__ */ jsxs("section", { "aria-label": "Spots", children: [
1091
- /* @__PURE__ */ jsx("h2", { children: "Spots" }),
1092
- spots.map((spot, index) => /* @__PURE__ */ jsx(
1093
- SpotItemForm,
1094
- {
1095
- spot,
1096
- index,
1097
- locale,
1098
- onChange: (next) => onChange(spots.map((item, itemIndex) => itemIndex === index ? next : item)),
1099
- onDelete: () => onChange(spots.filter((_, itemIndex) => itemIndex !== index))
1100
- },
1101
- spot.id
1102
- ))
1103
- ] });
1104
- }
1105
- function RewardListEditor({
1106
- rewards,
1107
- locale = "ja",
1108
- onChange
1109
- }) {
1110
- return /* @__PURE__ */ jsxs("section", { "aria-label": "Rewards", children: [
1111
- /* @__PURE__ */ jsx("h2", { children: "Rewards" }),
1112
- rewards.map((reward, index) => /* @__PURE__ */ jsxs("article", { className: "stamprally-admin-card", children: [
1113
- /* @__PURE__ */ jsxs("label", { children: [
1114
- "ID",
1115
- " ",
1116
- /* @__PURE__ */ jsx(
1117
- "input",
1118
- {
1119
- value: reward.id,
1120
- onChange: (event) => onChange(
1121
- rewards.map(
1122
- (item, itemIndex) => itemIndex === index ? { ...item, id: event.target.value } : item
1123
- )
1124
- )
1125
- }
1126
- )
1127
- ] }),
1128
- /* @__PURE__ */ jsxs("label", { children: [
1129
- "Title (",
1130
- locale,
1131
- ")",
1132
- " ",
1133
- /* @__PURE__ */ jsx(
1134
- "input",
1135
- {
1136
- value: resolveLocalizedText(reward.title, locale),
1137
- onChange: (event) => onChange(
1138
- rewards.map(
1139
- (item, itemIndex) => itemIndex === index ? { ...item, title: updateText(item.title, locale, event.target.value) } : item
1140
- )
1141
- )
1142
- }
1143
- )
1144
- ] }),
1145
- /* @__PURE__ */ jsxs("label", { children: [
1146
- "Description (",
1147
- locale,
1148
- ")",
1149
- " ",
1150
- /* @__PURE__ */ jsx(
1151
- "textarea",
1152
- {
1153
- value: resolveLocalizedText(reward.description, locale),
1154
- onChange: (event) => onChange(
1155
- rewards.map(
1156
- (item, itemIndex) => itemIndex === index ? {
1157
- ...item,
1158
- description: updateText(item.description, locale, event.target.value)
1159
- } : item
1160
- )
1161
- )
1162
- }
1163
- )
1164
- ] }),
1165
- /* @__PURE__ */ jsxs("label", { children: [
1166
- "Required stamps",
1167
- " ",
1168
- /* @__PURE__ */ jsx(
1169
- "input",
1170
- {
1171
- type: "number",
1172
- min: 0,
1173
- value: reward.requiredStampCount,
1174
- onChange: (event) => onChange(
1175
- rewards.map(
1176
- (item, itemIndex) => itemIndex === index ? { ...item, requiredStampCount: Number(event.target.value) } : item
1177
- )
1178
- )
1179
- }
1180
- )
1181
- ] }),
1182
- /* @__PURE__ */ jsxs("label", { children: [
1183
- "Valid until",
1184
- " ",
1185
- /* @__PURE__ */ jsx(
1186
- "input",
1187
- {
1188
- type: "datetime-local",
1189
- value: reward.validUntil?.slice(0, 16) ?? "",
1190
- onChange: (event) => onChange(
1191
- rewards.map((item, itemIndex) => {
1192
- if (itemIndex !== index) return item;
1193
- if (event.target.value === "") {
1194
- const { validUntil: _validUntil, ...rest } = item;
1195
- return rest;
1196
- }
1197
- return { ...item, validUntil: new Date(event.target.value).toISOString() };
1198
- })
1199
- )
1200
- }
1201
- )
1202
- ] }),
1203
- /* @__PURE__ */ jsxs("label", { children: [
1204
- "Stock limit",
1205
- " ",
1206
- /* @__PURE__ */ jsx(
1207
- "input",
1208
- {
1209
- type: "number",
1210
- min: 1,
1211
- value: reward.stockLimit ?? reward.maxStock ?? "",
1212
- onChange: (event) => onChange(
1213
- rewards.map((item, itemIndex) => {
1214
- if (itemIndex !== index) return item;
1215
- if (event.target.value === "") {
1216
- const { stockLimit: _stockLimit, ...rest } = item;
1217
- return rest;
1218
- }
1219
- return { ...item, stockLimit: Number(event.target.value) };
1220
- })
1221
- )
1222
- }
1223
- )
1224
- ] }),
1225
- /* @__PURE__ */ jsxs("label", { children: [
1226
- "User claim limit",
1227
- " ",
1228
- /* @__PURE__ */ jsx(
1229
- "input",
1230
- {
1231
- type: "number",
1232
- min: 1,
1233
- value: reward.userClaimLimit ?? reward.limitPerUser ?? "",
1234
- onChange: (event) => onChange(
1235
- rewards.map((item, itemIndex) => {
1236
- if (itemIndex !== index) return item;
1237
- if (event.target.value === "") {
1238
- const { userClaimLimit: _userClaimLimit, ...rest } = item;
1239
- return rest;
1240
- }
1241
- return { ...item, userClaimLimit: Number(event.target.value) };
1242
- })
1243
- )
1244
- }
1245
- )
1246
- ] }),
1247
- /* @__PURE__ */ jsxs("label", { children: [
1248
- "Redemption method",
1249
- " ",
1250
- /* @__PURE__ */ jsxs(
1251
- "select",
1252
- {
1253
- value: reward.redemptionMethod,
1254
- onChange: (event) => onChange(
1255
- rewards.map(
1256
- (item, itemIndex) => itemIndex === index ? {
1257
- ...item,
1258
- redemptionMethod: event.target.value
1259
- } : item
1260
- )
1261
- ),
1262
- children: [
1263
- /* @__PURE__ */ jsx("option", { value: "manual_slide", children: "manual_slide" }),
1264
- /* @__PURE__ */ jsx("option", { value: "staff_passcode", children: "staff_passcode" }),
1265
- /* @__PURE__ */ jsx("option", { value: "view_only", children: "view_only" }),
1266
- /* @__PURE__ */ jsx("option", { value: "server_claim", children: "server_claim" })
1267
- ]
1268
- }
1269
- )
1270
- ] }),
1271
- reward.redemptionMethod === "staff_passcode" && /* @__PURE__ */ jsxs("label", { children: [
1272
- "Staff passcode",
1273
- " ",
1274
- /* @__PURE__ */ jsx(
1275
- "input",
1276
- {
1277
- type: "password",
1278
- value: reward.staffPasscode ?? "",
1279
- onChange: (event) => onChange(
1280
- rewards.map(
1281
- (item, itemIndex) => itemIndex === index ? { ...item, staffPasscode: event.target.value } : item
1282
- )
1283
- )
1284
- }
1285
- )
1286
- ] }),
1287
- /* @__PURE__ */ jsxs("label", { children: [
1288
- "Conditions (JSON)",
1289
- " ",
1290
- /* @__PURE__ */ jsx(
1291
- "textarea",
1292
- {
1293
- value: JSON.stringify(reward.conditions ?? [], null, 2),
1294
- onChange: (event) => {
1295
- try {
1296
- const parsed = JSON.parse(event.target.value);
1297
- if (Array.isArray(parsed)) {
1298
- onChange(
1299
- rewards.map(
1300
- (item, itemIndex) => itemIndex === index ? { ...item, conditions: parsed } : item
1301
- )
1302
- );
1303
- }
1304
- } catch {
1305
- }
1306
- }
1307
- }
1308
- )
1309
- ] })
1310
- ] }, reward.id))
1311
- ] });
1312
- }
1313
- function ThemePresetSelector({ value, locale = "ja", onChange }) {
1314
- const selected = THEME_PRESETS.find((preset) => JSON.stringify(preset.theme) === JSON.stringify(value))?.id ?? "custom";
73
+ function GeneralSettingsForm({ config, onChange }) {
1315
74
  return /* @__PURE__ */ jsxs("label", { children: [
1316
- "Theme preset",
1317
- " ",
1318
- /* @__PURE__ */ jsxs(
1319
- "select",
75
+ "Rally title",
76
+ /* @__PURE__ */ jsx(
77
+ "input",
1320
78
  {
1321
- value: selected,
1322
- onChange: (event) => {
1323
- const preset = THEME_PRESETS.find((item) => item.id === event.target.value);
1324
- if (preset !== void 0) onChange(preset.theme);
1325
- },
1326
- children: [
1327
- /* @__PURE__ */ jsx("option", { value: "custom", children: "Custom" }),
1328
- THEME_PRESETS.map((preset) => /* @__PURE__ */ jsx("option", { value: preset.id, children: resolveLocalizedText(preset.name, locale) }, preset.id))
1329
- ]
79
+ value: resolveLocalizedText(config.title, "en"),
80
+ onChange: (event) => onChange({ ...config, title: event.target.value })
1330
81
  }
1331
82
  )
1332
83
  ] });
1333
84
  }
1334
- function ThemeEditor({
1335
- theme = DEFAULT_SHEET_THEME,
1336
- locale = "ja",
1337
- onChange
1338
- }) {
1339
- const update = (field, value) => onChange({ ...theme, [field]: value });
1340
- return /* @__PURE__ */ jsxs("fieldset", { className: "stamprally-admin-card", children: [
1341
- /* @__PURE__ */ jsx("legend", { children: "Theme" }),
1342
- /* @__PURE__ */ jsx(ThemePresetSelector, { value: theme, locale, onChange }),
1343
- /* @__PURE__ */ jsxs("label", { children: [
1344
- "Primary color",
1345
- " ",
1346
- /* @__PURE__ */ jsx(
1347
- "input",
1348
- {
1349
- type: "color",
1350
- value: theme.primaryColor,
1351
- onChange: (event) => update("primaryColor", event.target.value)
1352
- }
1353
- )
1354
- ] }),
1355
- /* @__PURE__ */ jsxs("label", { children: [
1356
- "Grid columns",
1357
- " ",
1358
- /* @__PURE__ */ jsx(
1359
- "input",
1360
- {
1361
- type: "number",
1362
- min: 1,
1363
- max: 6,
1364
- value: theme.gridColumns,
1365
- onChange: (event) => update("gridColumns", Number(event.target.value))
1366
- }
1367
- )
1368
- ] })
1369
- ] });
1370
- }
1371
- function QrExportModal({
1372
- open,
1373
- config,
1374
- locale = "ja",
1375
- onClose
1376
- }) {
1377
- if (!open) return null;
1378
- return /* @__PURE__ */ jsxs("div", { role: "dialog", "aria-modal": "true", className: "stamprally-qr-modal", children: [
1379
- /* @__PURE__ */ jsxs("div", { className: "stamprally-qr-modal__toolbar", children: [
1380
- /* @__PURE__ */ jsx("h2", { children: "QR print kit" }),
1381
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => window.print(), children: "Print" }),
1382
- /* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, children: "Close" })
1383
- ] }),
1384
- /* @__PURE__ */ jsx("div", { className: "stamprally-qr-modal__grid", children: config.stamps.map((spot) => /* @__PURE__ */ jsxs("article", { children: [
1385
- /* @__PURE__ */ jsx("h3", { children: resolveLocalizedText(spot.name, locale) }),
1386
- /* @__PURE__ */ jsx("pre", { children: JSON.stringify({
1387
- version: CURRENT_RALLY_CONFIG_VERSION,
1388
- rallyId: config.id,
1389
- stampId: spot.id
1390
- }) })
1391
- ] }, spot.id)) })
1392
- ] });
1393
- }
1394
- function JsonConfigIO({
1395
- config,
1396
- onImport
1397
- }) {
1398
- const [value, setValue] = useState(() => JSON.stringify(stripSensitiveConfig(config), null, 2));
1399
- const [error, setError] = useState(null);
1400
- const importConfig = () => {
1401
- try {
1402
- const migrated = migrateRallyConfig(JSON.parse(value));
1403
- const validation = validateRallyConfig(migrated);
1404
- if (!validation.valid) {
1405
- setError(validation.errors.map((item) => `${item.path}: ${item.message}`).join(" "));
1406
- return;
1407
- }
1408
- onImport(migrated);
1409
- setError(null);
1410
- } catch {
1411
- setError("Invalid rally JSON.");
1412
- }
85
+ function JsonConfigIO({ config, onImport }) {
86
+ const [value, setValue] = useState(() => JSON.stringify(config, null, 2));
87
+ const parse = (event) => {
88
+ setValue(event.target.value);
1413
89
  };
1414
- return /* @__PURE__ */ jsxs("section", { className: "stamprally-admin-card", "aria-label": "JSON configuration", children: [
1415
- /* @__PURE__ */ jsx("textarea", { value, onChange: (event) => setValue(event.target.value) }),
1416
- /* @__PURE__ */ jsx("button", { type: "button", onClick: importConfig, children: "Import and migrate" }),
90
+ return /* @__PURE__ */ jsxs("div", { children: [
91
+ /* @__PURE__ */ jsx("textarea", { value, onChange: parse }),
1417
92
  /* @__PURE__ */ jsx(
1418
93
  "button",
1419
94
  {
1420
95
  type: "button",
1421
- onClick: () => navigator.clipboard?.writeText(JSON.stringify(stripSensitiveConfig(config), null, 2)),
1422
- children: "Copy public JSON"
1423
- }
1424
- ),
1425
- error !== null && /* @__PURE__ */ jsx("p", { role: "alert", children: error })
1426
- ] });
1427
- }
1428
- function RallyEditor({
1429
- config,
1430
- locale = "ja",
1431
- onChange
1432
- }) {
1433
- const [showQr, setShowQr] = useState(false);
1434
- return /* @__PURE__ */ jsxs("main", { className: "stamprally-admin", children: [
1435
- /* @__PURE__ */ jsxs("header", { children: [
1436
- /* @__PURE__ */ jsx("h1", { children: resolveLocalizedText(config.title, locale) || config.id }),
1437
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setShowQr(true), children: "QR export" })
1438
- ] }),
1439
- /* @__PURE__ */ jsx(GeneralSettingsForm, { config, locale, onChange }),
1440
- config.theme === void 0 ? /* @__PURE__ */ jsx(ThemeEditor, { locale, onChange: (theme) => onChange({ ...config, theme }) }) : /* @__PURE__ */ jsx(
1441
- ThemeEditor,
1442
- {
1443
- theme: config.theme,
1444
- locale,
1445
- onChange: (theme) => onChange({ ...config, theme })
1446
- }
1447
- ),
1448
- /* @__PURE__ */ jsx(
1449
- SpotListEditor,
1450
- {
1451
- spots: config.stamps,
1452
- locale,
1453
- onChange: (stamps) => onChange({ ...config, stamps })
1454
- }
1455
- ),
1456
- /* @__PURE__ */ jsx(
1457
- RewardListEditor,
1458
- {
1459
- rewards: config.rewards ?? [],
1460
- locale,
1461
- onChange: (rewards) => onChange({ ...config, rewards })
1462
- }
1463
- ),
1464
- /* @__PURE__ */ jsx(JsonConfigIO, { config, onImport: onChange }),
1465
- /* @__PURE__ */ jsx(
1466
- QrExportModal,
1467
- {
1468
- open: showQr,
1469
- config,
1470
- locale,
1471
- onClose: () => setShowQr(false)
96
+ onClick: () => {
97
+ try {
98
+ onImport(JSON.parse(value));
99
+ } catch {
100
+ }
101
+ },
102
+ children: "Import"
1472
103
  }
1473
104
  )
1474
105
  ] });
1475
106
  }
1476
107
 
1477
- export { AdminRallyEditor, ConditionEditor, GeneralSettingsForm, JsonConfigIO, QrExportModal, RallyEditor, RewardListEditor, SpotItemForm, SpotListEditor, ThemeEditor, ThemePresetSelector };
108
+ export { AdminRallyEditor, GeneralSettingsForm, JsonConfigIO, RallyEditor, SpotItemForm };
1478
109
  //# sourceMappingURL=index.js.map
1479
110
  //# sourceMappingURL=index.js.map