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