@stamprally/admin-ui 0.7.0 → 0.9.0

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