@stamprally/admin-ui 0.10.0 → 0.12.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,6 +5,249 @@ var react = require('react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
 
7
7
  // src/index.tsx
8
+ function nextEntityId(prefix, ids) {
9
+ let index = ids.size + 1;
10
+ let candidate = `${prefix}-${index}`;
11
+ while (ids.has(candidate)) {
12
+ index += 1;
13
+ candidate = `${prefix}-${index}`;
14
+ }
15
+ return candidate;
16
+ }
17
+ function pathParts(path) {
18
+ return path.replaceAll("[", ".").replaceAll("]", "").split(".").filter((part) => part !== "");
19
+ }
20
+ function localizedValue(value, locale, nextValue) {
21
+ return core.updateLocalizedField(
22
+ typeof value === "string" || typeof value === "object" && value !== null ? value : "",
23
+ locale,
24
+ nextValue
25
+ );
26
+ }
27
+ function useAdminRallyEditor(initialConfig, options = {}) {
28
+ const initialRef = react.useRef(initialConfig);
29
+ const [history, setHistory] = react.useState({
30
+ past: [],
31
+ present: initialConfig,
32
+ future: []
33
+ });
34
+ const config = history.present;
35
+ const commit = react.useCallback(
36
+ (value) => {
37
+ setHistory((current) => {
38
+ const next = typeof value === "function" ? value(current.present) : value;
39
+ if (next === current.present) return current;
40
+ options.onChange?.(next);
41
+ return { past: [...current.past, current.present], present: next, future: [] };
42
+ });
43
+ },
44
+ [options.onChange]
45
+ );
46
+ const setConfig = commit;
47
+ const update = (patch) => commit((current) => ({ ...current, ...patch }));
48
+ const updateSpot = (spotId, patch) => commit((current) => ({
49
+ ...current,
50
+ spots: current.spots.map((spot) => spot.id === spotId ? { ...spot, ...patch } : spot)
51
+ }));
52
+ const updateReward = (rewardId, patch) => commit((current) => ({
53
+ ...current,
54
+ rewards: current.rewards.map(
55
+ (reward) => reward.id === rewardId ? { ...reward, ...patch } : reward
56
+ )
57
+ }));
58
+ const addSpot = (spotData = {}) => commit((current) => {
59
+ const id = spotData.id ?? nextEntityId("spot", new Set(current.spots.map((spot) => spot.id)));
60
+ const base = newSpot(current.spots.length);
61
+ return {
62
+ ...current,
63
+ spots: [...current.spots, { ...base, ...spotData, id, orderIndex: current.spots.length }]
64
+ };
65
+ });
66
+ const removeSpot = (spotId) => commit((current) => ({
67
+ ...current,
68
+ spots: current.spots.filter((spot) => spot.id !== spotId).map((spot, index) => ({ ...spot, orderIndex: index }))
69
+ }));
70
+ const reorderSpots = (fromIndex, toIndex) => commit((current) => ({ ...current, spots: moveTo(current.spots, fromIndex, toIndex) }));
71
+ const duplicateSpot = (spotId) => commit((current) => {
72
+ const source = current.spots.find((spot) => spot.id === spotId);
73
+ if (source === void 0) return current;
74
+ const id = nextEntityId("spot-copy", new Set(current.spots.map((spot) => spot.id)));
75
+ const copy = { ...structuredClone(source), id, orderIndex: current.spots.length };
76
+ return { ...current, spots: [...current.spots, copy] };
77
+ });
78
+ const addReward = (rewardData = {}) => commit((current) => {
79
+ const id = rewardData.id ?? nextEntityId("reward", new Set(current.rewards.map((item) => item.id)));
80
+ return {
81
+ ...current,
82
+ rewards: [...current.rewards, { ...newReward(current.rewards.length), ...rewardData, id }]
83
+ };
84
+ });
85
+ const removeReward = (rewardId) => commit((current) => ({
86
+ ...current,
87
+ rewards: current.rewards.filter((item) => item.id !== rewardId)
88
+ }));
89
+ const duplicateReward = (rewardId) => commit((current) => {
90
+ const source = current.rewards.find((reward) => reward.id === rewardId);
91
+ if (source === void 0) return current;
92
+ const id = nextEntityId("reward-copy", new Set(current.rewards.map((item) => item.id)));
93
+ return { ...current, rewards: [...current.rewards, { ...structuredClone(source), id }] };
94
+ });
95
+ const addCondition = (spotId, nextCondition) => commit((current) => ({
96
+ ...current,
97
+ spots: current.spots.map(
98
+ (spot) => spot.id !== spotId ? spot : { ...spot, conditions: [...spot.conditions, structuredClone(nextCondition)] }
99
+ )
100
+ }));
101
+ const removeCondition = (spotId, conditionIndex) => commit((current) => ({
102
+ ...current,
103
+ spots: current.spots.map(
104
+ (spot) => spot.id !== spotId ? spot : { ...spot, conditions: spot.conditions.filter((_, index) => index !== conditionIndex) }
105
+ )
106
+ }));
107
+ const updateLocalized = (path, locale, value) => commit((current) => {
108
+ const parts = pathParts(path);
109
+ if (parts[0] === "spots" && parts.length >= 3) {
110
+ const requestedIndex = parts[1];
111
+ const index = requestedIndex !== void 0 && /^\d+$/.test(requestedIndex) ? Number(requestedIndex) : current.spots.findIndex((spot) => spot.id === requestedIndex);
112
+ const field2 = parts[2];
113
+ if (index < 0 || field2 === void 0 || current.spots[index] === void 0) return current;
114
+ return {
115
+ ...current,
116
+ spots: current.spots.map(
117
+ (spot, spotIndex) => spotIndex === index ? {
118
+ ...spot,
119
+ [field2]: localizedValue(spot[field2], locale, value)
120
+ } : spot
121
+ )
122
+ };
123
+ }
124
+ if (parts[0] === "rewards" && parts.length >= 3) {
125
+ const requestedIndex = parts[1];
126
+ const index = requestedIndex !== void 0 && /^\d+$/.test(requestedIndex) ? Number(requestedIndex) : current.rewards.findIndex((reward) => reward.id === requestedIndex);
127
+ const field2 = parts[2];
128
+ if (index < 0 || field2 === void 0 || current.rewards[index] === void 0)
129
+ return current;
130
+ return {
131
+ ...current,
132
+ rewards: current.rewards.map(
133
+ (reward, rewardIndex) => rewardIndex === index ? {
134
+ ...reward,
135
+ [field2]: localizedValue(reward[field2], locale, value)
136
+ } : reward
137
+ )
138
+ };
139
+ }
140
+ const field = parts[0];
141
+ if (field === void 0) return current;
142
+ return {
143
+ ...current,
144
+ [field]: localizedValue(current[field], locale, value)
145
+ };
146
+ });
147
+ const undo = () => setHistory((current) => {
148
+ const previous = current.past.at(-1);
149
+ if (previous === void 0) return current;
150
+ options.onChange?.(previous);
151
+ return {
152
+ past: current.past.slice(0, -1),
153
+ present: previous,
154
+ future: [current.present, ...current.future]
155
+ };
156
+ });
157
+ const redo = () => setHistory((current) => {
158
+ const next = current.future[0];
159
+ if (next === void 0) return current;
160
+ options.onChange?.(next);
161
+ return {
162
+ past: [...current.past, current.present],
163
+ present: next,
164
+ future: current.future.slice(1)
165
+ };
166
+ });
167
+ const resetConfig = (newConfig) => {
168
+ initialRef.current = newConfig;
169
+ setHistory({ past: [], present: newConfig, future: [] });
170
+ options.onChange?.(newConfig);
171
+ };
172
+ return {
173
+ config,
174
+ setConfig,
175
+ update,
176
+ updateSpot,
177
+ updateReward,
178
+ addSpot,
179
+ removeSpot,
180
+ reorderSpots: (fromIndex, toIndex) => reorderSpots(fromIndex, toIndex),
181
+ duplicateSpot,
182
+ addReward,
183
+ removeReward,
184
+ duplicateReward,
185
+ addCondition,
186
+ removeCondition,
187
+ updateLocalizedField: updateLocalized,
188
+ undo,
189
+ redo,
190
+ canUndo: history.past.length > 0,
191
+ canRedo: history.future.length > 0,
192
+ resetConfig,
193
+ reset: () => resetConfig(initialRef.current),
194
+ isDirty: config !== initialRef.current
195
+ };
196
+ }
197
+ function useSpotEditor(spotId, options = {}) {
198
+ const [config, setConfig] = react.useState(
199
+ options.config ?? options.initialConfig
200
+ );
201
+ const commit = (next) => {
202
+ setConfig(next);
203
+ options.onChange?.(next);
204
+ };
205
+ const spot = config?.spots.find((item) => item.id === spotId);
206
+ return {
207
+ config,
208
+ spot,
209
+ setConfig,
210
+ update: (patch) => {
211
+ if (config === void 0 || spot === void 0) return;
212
+ commit({
213
+ ...config,
214
+ spots: config.spots.map((item) => item.id === spotId ? { ...item, ...patch } : item)
215
+ });
216
+ },
217
+ remove: () => {
218
+ if (config === void 0 || spot === void 0) return;
219
+ commit({ ...config, spots: config.spots.filter((item) => item.id !== spotId) });
220
+ }
221
+ };
222
+ }
223
+ function useRewardEditor(rewardId, options = {}) {
224
+ const [config, setConfig] = react.useState(
225
+ options.config ?? options.initialConfig
226
+ );
227
+ const reward = config?.rewards.find((item) => item.id === rewardId);
228
+ const commit = (next) => {
229
+ setConfig(next);
230
+ options.onChange?.(next);
231
+ };
232
+ return {
233
+ config,
234
+ reward,
235
+ setConfig,
236
+ update: (patch) => {
237
+ if (config === void 0 || reward === void 0) return;
238
+ commit({
239
+ ...config,
240
+ rewards: config.rewards.map(
241
+ (item) => item.id === rewardId ? { ...item, ...patch } : item
242
+ )
243
+ });
244
+ },
245
+ remove: () => {
246
+ if (config === void 0 || reward === void 0) return;
247
+ commit({ ...config, rewards: config.rewards.filter((item) => item.id !== rewardId) });
248
+ }
249
+ };
250
+ }
8
251
  function text(dictionary, locale, key, fallback) {
9
252
  return dictionary?.[locale]?.[key] ?? fallback;
10
253
  }
@@ -30,6 +273,16 @@ function move(items, index, direction) {
30
273
  if (item !== void 0) next.splice(target, 0, item);
31
274
  return next;
32
275
  }
276
+ function moveTo(items, fromIndex, toIndex) {
277
+ if (fromIndex < 0 || fromIndex >= items.length || toIndex < 0 || toIndex >= items.length || fromIndex === toIndex)
278
+ return items;
279
+ const next = [...items];
280
+ const [item] = next.splice(fromIndex, 1);
281
+ if (item !== void 0) next.splice(toIndex, 0, item);
282
+ return next.map(
283
+ (entry, index) => typeof entry === "object" && entry !== null && "orderIndex" in entry ? { ...entry, orderIndex: index } : entry
284
+ );
285
+ }
33
286
  function ConditionEditor({
34
287
  condition: condition2,
35
288
  onChange,
@@ -158,6 +411,18 @@ function SpotItemForm({
158
411
  }
159
412
  )
160
413
  ] }),
414
+ ["description", "hint"].map((key) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
415
+ field(key, key),
416
+ /* @__PURE__ */ jsxRuntime.jsx(
417
+ "textarea",
418
+ {
419
+ value: core.resolveLocalizedText(spot[key] ?? "", activeLocale),
420
+ onChange: (event) => update({
421
+ [key]: core.updateLocalizedField(spot[key] ?? "", activeLocale, event.target.value)
422
+ })
423
+ }
424
+ )
425
+ ] }, key)),
161
426
  ["imageUrl", "iconUrl", "redirectUrlAfterClaim"].map((key) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
162
427
  field(key, key),
163
428
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -180,39 +445,23 @@ function SpotItemForm({
180
445
  }
181
446
  )
182
447
  ] }),
183
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
184
- field("externalReferences", "External references"),
185
- /* @__PURE__ */ jsxRuntime.jsx(
186
- "textarea",
187
- {
188
- value: JSON.stringify(spot.externalReferences ?? [], null, 2),
189
- onChange: (event) => {
190
- try {
191
- const parsed = JSON.parse(event.target.value);
192
- if (Array.isArray(parsed)) update({ externalReferences: parsed });
193
- } catch {
194
- }
195
- }
196
- }
197
- )
198
- ] }),
199
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
200
- field("metadata", "Metadata"),
201
- /* @__PURE__ */ jsxRuntime.jsx(
202
- "textarea",
203
- {
204
- value: JSON.stringify(spot.metadata ?? {}, null, 2),
205
- onChange: (event) => {
206
- try {
207
- const parsed = JSON.parse(event.target.value);
208
- if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed))
209
- update({ metadata: parsed });
210
- } catch {
211
- }
212
- }
213
- }
214
- )
215
- ] }),
448
+ /* @__PURE__ */ jsxRuntime.jsx(
449
+ ExternalReferencesEditor,
450
+ {
451
+ references: spot.externalReferences ?? [],
452
+ onChange: (externalReferences) => update({ externalReferences }),
453
+ label: field("externalReferences", "External references")
454
+ }
455
+ ),
456
+ /* @__PURE__ */ jsxRuntime.jsx(
457
+ MetadataSection,
458
+ {
459
+ name: `spot-${spot.id}`,
460
+ values: spot.metadata ?? {},
461
+ onChange: (metadata) => update({ metadata }),
462
+ label: field("metadata", "Metadata")
463
+ }
464
+ ),
216
465
  spot.conditions.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
217
466
  ConditionEditor,
218
467
  {
@@ -243,6 +492,92 @@ function SpotItemForm({
243
492
  onRemove !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: onRemove, children: field("removeSpot", "Remove spot") })
244
493
  ] });
245
494
  }
495
+ function newUnlockCondition(type) {
496
+ if (type === "stamp_count") return { type, count: 1 };
497
+ if (type === "stamps") return { type, stampIds: [] };
498
+ return { type, conditions: [] };
499
+ }
500
+ function RewardUnlockConditionEditor({
501
+ condition: unlock,
502
+ onChange,
503
+ onRemove
504
+ }) {
505
+ return /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { children: [
506
+ /* @__PURE__ */ jsxRuntime.jsx("legend", { children: "Unlock condition" }),
507
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
508
+ "Type",
509
+ /* @__PURE__ */ jsxRuntime.jsxs(
510
+ "select",
511
+ {
512
+ value: unlock.type,
513
+ onChange: (event) => onChange(newUnlockCondition(event.target.value)),
514
+ children: [
515
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "stamp_count", children: "Stamp count" }),
516
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "stamps", children: "Specific stamps" }),
517
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "all", children: "All conditions" }),
518
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "any", children: "Any condition" })
519
+ ]
520
+ }
521
+ )
522
+ ] }),
523
+ unlock.type === "stamp_count" && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
524
+ "Count",
525
+ /* @__PURE__ */ jsxRuntime.jsx(
526
+ "input",
527
+ {
528
+ type: "number",
529
+ min: 0,
530
+ value: unlock.count,
531
+ onChange: (event) => onChange({ ...unlock, count: Number(event.target.value) })
532
+ }
533
+ )
534
+ ] }),
535
+ unlock.type === "stamps" && /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
536
+ "Stamp IDs",
537
+ /* @__PURE__ */ jsxRuntime.jsx(
538
+ "input",
539
+ {
540
+ value: unlock.stampIds.join(", "),
541
+ onChange: (event) => onChange({
542
+ ...unlock,
543
+ stampIds: event.target.value.split(",").map((value) => value.trim()).filter(Boolean)
544
+ })
545
+ }
546
+ )
547
+ ] }),
548
+ (unlock.type === "all" || unlock.type === "any") && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
549
+ unlock.conditions.map((child, index) => /* @__PURE__ */ jsxRuntime.jsx(
550
+ RewardUnlockConditionEditor,
551
+ {
552
+ condition: child,
553
+ onChange: (next) => onChange({
554
+ ...unlock,
555
+ conditions: unlock.conditions.map(
556
+ (current, childIndex) => childIndex === index ? next : current
557
+ )
558
+ }),
559
+ onRemove: () => onChange({
560
+ ...unlock,
561
+ conditions: unlock.conditions.filter((_, childIndex) => childIndex !== index)
562
+ })
563
+ },
564
+ `${unlock.type}-${JSON.stringify(child)}`
565
+ )),
566
+ /* @__PURE__ */ jsxRuntime.jsx(
567
+ "button",
568
+ {
569
+ type: "button",
570
+ onClick: () => onChange({
571
+ ...unlock,
572
+ conditions: [...unlock.conditions, newUnlockCondition("stamp_count")]
573
+ }),
574
+ children: "Add nested condition"
575
+ }
576
+ )
577
+ ] }),
578
+ onRemove !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: onRemove, children: "Remove condition" })
579
+ ] });
580
+ }
246
581
  function RewardItemForm({
247
582
  reward,
248
583
  locale,
@@ -266,6 +601,23 @@ function RewardItemForm({
266
601
  }
267
602
  )
268
603
  ] }),
604
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
605
+ field("description", "Description"),
606
+ /* @__PURE__ */ jsxRuntime.jsx(
607
+ "textarea",
608
+ {
609
+ value: core.resolveLocalizedText(reward.description ?? "", locale),
610
+ onChange: (event) => onChange({
611
+ ...reward,
612
+ description: core.updateLocalizedField(
613
+ reward.description ?? "",
614
+ locale,
615
+ event.target.value
616
+ )
617
+ })
618
+ }
619
+ )
620
+ ] }),
269
621
  /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
270
622
  field("requiredSpotCount", "Required spot count"),
271
623
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -311,20 +663,36 @@ function RewardItemForm({
311
663
  }
312
664
  )
313
665
  ] }),
314
- /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
315
- field("unlockConditions", "Unlock conditions"),
666
+ /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { children: [
667
+ /* @__PURE__ */ jsxRuntime.jsx("legend", { children: field("unlockConditions", "Unlock conditions") }),
668
+ (reward.conditions ?? []).map((unlock, index) => /* @__PURE__ */ jsxRuntime.jsx(
669
+ RewardUnlockConditionEditor,
670
+ {
671
+ condition: unlock,
672
+ onChange: (next) => onChange({
673
+ ...reward,
674
+ conditions: (reward.conditions ?? []).map(
675
+ (current, conditionIndex) => conditionIndex === index ? next : current
676
+ )
677
+ }),
678
+ onRemove: () => onChange({
679
+ ...reward,
680
+ conditions: (reward.conditions ?? []).filter(
681
+ (_, conditionIndex) => conditionIndex !== index
682
+ )
683
+ })
684
+ },
685
+ `${reward.id}-unlock-${JSON.stringify(unlock)}`
686
+ )),
316
687
  /* @__PURE__ */ jsxRuntime.jsx(
317
- "textarea",
688
+ "button",
318
689
  {
319
- value: JSON.stringify(reward.conditions ?? [], null, 2),
320
- onChange: (event) => {
321
- try {
322
- const parsed = JSON.parse(event.target.value);
323
- if (Array.isArray(parsed))
324
- onChange({ ...reward, conditions: parsed });
325
- } catch {
326
- }
327
- }
690
+ type: "button",
691
+ onClick: () => onChange({
692
+ ...reward,
693
+ conditions: [...reward.conditions ?? [], newUnlockCondition("stamp_count")]
694
+ }),
695
+ children: field("addUnlockCondition", "Add unlock condition")
328
696
  }
329
697
  )
330
698
  ] }),
@@ -345,6 +713,16 @@ function RewardItemForm({
345
713
  }
346
714
  )
347
715
  ] }, key)),
716
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
717
+ field("digitalContentUrl", "Digital content URL"),
718
+ /* @__PURE__ */ jsxRuntime.jsx(
719
+ "input",
720
+ {
721
+ value: reward.digitalContentUrl ?? "",
722
+ onChange: (event) => onChange({ ...reward, digitalContentUrl: event.target.value })
723
+ }
724
+ )
725
+ ] }),
348
726
  /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
349
727
  field("staffPasscode", "Staff passcode"),
350
728
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -374,6 +752,257 @@ function RewardItemForm({
374
752
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: onRemove, children: field("removeReward", "Remove reward") })
375
753
  ] });
376
754
  }
755
+ function ThemeEditor({
756
+ theme,
757
+ onChange,
758
+ locale,
759
+ dictionary
760
+ }) {
761
+ const activeLocale = locale ?? "en";
762
+ const field = (key, fallback) => text(dictionary, activeLocale, key, fallback);
763
+ const update = (key, value) => onChange({ ...theme, [key]: value });
764
+ return /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { children: [
765
+ /* @__PURE__ */ jsxRuntime.jsx("legend", { children: field("theme", "Theme") }),
766
+ [
767
+ "primaryColor",
768
+ "backgroundColor",
769
+ "cardBackgroundColor",
770
+ "textColor",
771
+ "backgroundImageUrl",
772
+ "completedStampColor"
773
+ ].map((key) => /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
774
+ field(key, key),
775
+ /* @__PURE__ */ jsxRuntime.jsx(
776
+ "input",
777
+ {
778
+ type: key.toLowerCase().includes("color") ? "color" : "url",
779
+ value: theme[key] ?? "",
780
+ onChange: (event) => update(key, event.target.value)
781
+ }
782
+ )
783
+ ] }, key)),
784
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
785
+ field("slotShape", "Slot shape"),
786
+ /* @__PURE__ */ jsxRuntime.jsx(
787
+ "select",
788
+ {
789
+ value: theme.slotShape,
790
+ onChange: (event) => update("slotShape", event.target.value),
791
+ children: ["circle", "square", "rounded"].map((shape) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: shape, children: field(`slotShape.${shape}`, shape) }, shape))
792
+ }
793
+ )
794
+ ] }),
795
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
796
+ field("gridColumns", "Grid columns"),
797
+ /* @__PURE__ */ jsxRuntime.jsx(
798
+ "input",
799
+ {
800
+ type: "number",
801
+ min: 1,
802
+ max: 12,
803
+ value: theme.gridColumns,
804
+ onChange: (event) => update("gridColumns", Number(event.target.value))
805
+ }
806
+ )
807
+ ] }),
808
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
809
+ field("fontFamily", "Font family"),
810
+ /* @__PURE__ */ jsxRuntime.jsx(
811
+ "select",
812
+ {
813
+ value: theme.fontFamily ?? "system-ui",
814
+ onChange: (event) => update("fontFamily", event.target.value),
815
+ children: ["system-ui", "serif", "rounded-sans", "monospace", "handwritten"].map(
816
+ (font) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: font, children: font }, font)
817
+ )
818
+ }
819
+ )
820
+ ] }),
821
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
822
+ field("unclaimedOpacity", "Unclaimed opacity"),
823
+ /* @__PURE__ */ jsxRuntime.jsx(
824
+ "input",
825
+ {
826
+ type: "number",
827
+ min: 0,
828
+ max: 1,
829
+ step: 0.05,
830
+ value: theme.unclaimedOpacity ?? 1,
831
+ onChange: (event) => update("unclaimedOpacity", Number(event.target.value))
832
+ }
833
+ )
834
+ ] })
835
+ ] });
836
+ }
837
+ function metadataValue(value) {
838
+ return typeof value === "string" ? value : JSON.stringify(value);
839
+ }
840
+ function ExternalReferencesEditor({
841
+ references,
842
+ onChange,
843
+ label = "External references"
844
+ }) {
845
+ return /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { children: [
846
+ /* @__PURE__ */ jsxRuntime.jsx("legend", { children: label }),
847
+ references.map((reference) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
848
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
849
+ "Type",
850
+ /* @__PURE__ */ jsxRuntime.jsx(
851
+ "input",
852
+ {
853
+ value: reference.type,
854
+ onChange: (event) => onChange(
855
+ references.map(
856
+ (current) => current === reference ? { ...current, type: event.target.value } : current
857
+ )
858
+ )
859
+ }
860
+ )
861
+ ] }),
862
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
863
+ "ID",
864
+ /* @__PURE__ */ jsxRuntime.jsx(
865
+ "input",
866
+ {
867
+ value: reference.id,
868
+ onChange: (event) => onChange(
869
+ references.map(
870
+ (current) => current === reference ? { ...current, id: event.target.value } : current
871
+ )
872
+ )
873
+ }
874
+ )
875
+ ] }),
876
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
877
+ "URL",
878
+ /* @__PURE__ */ jsxRuntime.jsx(
879
+ "input",
880
+ {
881
+ type: "url",
882
+ value: reference.url ?? "",
883
+ onChange: (event) => onChange(
884
+ references.map(
885
+ (current) => current === reference ? { ...current, url: event.target.value } : current
886
+ )
887
+ )
888
+ }
889
+ )
890
+ ] }),
891
+ /* @__PURE__ */ jsxRuntime.jsx(
892
+ "button",
893
+ {
894
+ type: "button",
895
+ onClick: () => onChange(references.filter((current) => current !== reference)),
896
+ children: "Remove reference"
897
+ }
898
+ )
899
+ ] }, `${reference.type}-${reference.id}-${reference.url ?? ""}`)),
900
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => onChange([...references, { type: "", id: "" }]), children: "Add reference" })
901
+ ] });
902
+ }
903
+ function MetadataSection({
904
+ name,
905
+ values,
906
+ onChange,
907
+ label
908
+ }) {
909
+ const entries = Object.entries(values);
910
+ return /* @__PURE__ */ jsxRuntime.jsxs("fieldset", { children: [
911
+ /* @__PURE__ */ jsxRuntime.jsx("legend", { children: label }),
912
+ entries.map(([key, value]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
913
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
914
+ "Key",
915
+ /* @__PURE__ */ jsxRuntime.jsx(
916
+ "input",
917
+ {
918
+ value: key,
919
+ onChange: (event) => {
920
+ const next = { ...values };
921
+ delete next[key];
922
+ next[event.target.value] = value;
923
+ onChange(next);
924
+ }
925
+ }
926
+ )
927
+ ] }),
928
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
929
+ "Value",
930
+ /* @__PURE__ */ jsxRuntime.jsx(
931
+ "input",
932
+ {
933
+ value: metadataValue(value),
934
+ onChange: (event) => {
935
+ let nextValue = event.target.value;
936
+ try {
937
+ nextValue = JSON.parse(event.target.value);
938
+ } catch {
939
+ }
940
+ onChange({ ...values, [key]: nextValue });
941
+ }
942
+ }
943
+ )
944
+ ] }),
945
+ /* @__PURE__ */ jsxRuntime.jsx(
946
+ "button",
947
+ {
948
+ type: "button",
949
+ onClick: () => {
950
+ const next = { ...values };
951
+ delete next[key];
952
+ onChange(next);
953
+ },
954
+ children: "Remove"
955
+ }
956
+ )
957
+ ] }, `${name}-${key}`)),
958
+ /* @__PURE__ */ jsxRuntime.jsx(
959
+ "button",
960
+ {
961
+ type: "button",
962
+ onClick: () => {
963
+ let index = entries.length + 1;
964
+ let key = `key${index}`;
965
+ while (key in values) {
966
+ index += 1;
967
+ key = `key${index}`;
968
+ }
969
+ onChange({ ...values, [key]: "" });
970
+ },
971
+ children: "Add field"
972
+ }
973
+ )
974
+ ] });
975
+ }
976
+ function MetadataEditor({
977
+ publicMetadata = {},
978
+ serverMetadata = {},
979
+ onPublicMetadataChange,
980
+ onServerMetadataChange,
981
+ locale,
982
+ dictionary
983
+ }) {
984
+ const activeLocale = locale ?? "en";
985
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { "aria-label": text(dictionary, activeLocale, "metadata", "Metadata"), children: [
986
+ /* @__PURE__ */ jsxRuntime.jsx(
987
+ MetadataSection,
988
+ {
989
+ name: "public",
990
+ values: publicMetadata,
991
+ onChange: onPublicMetadataChange,
992
+ label: "Public metadata"
993
+ }
994
+ ),
995
+ /* @__PURE__ */ jsxRuntime.jsx(
996
+ MetadataSection,
997
+ {
998
+ name: "server",
999
+ values: serverMetadata,
1000
+ onChange: onServerMetadataChange,
1001
+ label: "Server metadata"
1002
+ }
1003
+ )
1004
+ ] });
1005
+ }
377
1006
  function AdminRallyEditor({ config, onChange, locale, dictionary }) {
378
1007
  const activeLocale = locale ?? "en";
379
1008
  const field = (key, fallback) => text(dictionary, activeLocale, key, fallback);
@@ -391,6 +1020,52 @@ function AdminRallyEditor({ config, onChange, locale, dictionary }) {
391
1020
  }
392
1021
  )
393
1022
  ] }),
1023
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1024
+ field("description", "Description"),
1025
+ /* @__PURE__ */ jsxRuntime.jsx(
1026
+ "textarea",
1027
+ {
1028
+ value: core.resolveLocalizedText(config.description ?? "", activeLocale),
1029
+ onChange: (event) => update({
1030
+ description: core.updateLocalizedField(
1031
+ config.description ?? "",
1032
+ activeLocale,
1033
+ event.target.value
1034
+ )
1035
+ })
1036
+ }
1037
+ )
1038
+ ] }),
1039
+ /* @__PURE__ */ jsxRuntime.jsx(
1040
+ ThemeEditor,
1041
+ {
1042
+ theme: config.theme ?? core.DEFAULT_SHEET_THEME,
1043
+ locale: activeLocale,
1044
+ ...dictionary === void 0 ? {} : { dictionary },
1045
+ onChange: (theme) => update({ theme })
1046
+ }
1047
+ ),
1048
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { children: [
1049
+ field("serverEndpoint", "Server endpoint"),
1050
+ /* @__PURE__ */ jsxRuntime.jsx(
1051
+ "input",
1052
+ {
1053
+ value: config.serverEndpoint ?? "",
1054
+ onChange: (event) => update({ serverEndpoint: event.target.value })
1055
+ }
1056
+ )
1057
+ ] }),
1058
+ /* @__PURE__ */ jsxRuntime.jsx(
1059
+ MetadataEditor,
1060
+ {
1061
+ publicMetadata: config.publicMetadata ?? config.metadata ?? {},
1062
+ serverMetadata: config.serverMetadata ?? {},
1063
+ locale: activeLocale,
1064
+ ...dictionary === void 0 ? {} : { dictionary },
1065
+ onPublicMetadataChange: (metadata) => update({ publicMetadata: metadata }),
1066
+ onServerMetadataChange: (metadata) => update({ serverMetadata: metadata })
1067
+ }
1068
+ ),
394
1069
  /* @__PURE__ */ jsxRuntime.jsx(
395
1070
  "button",
396
1071
  {
@@ -525,9 +1200,17 @@ function JsonConfigIO({ config, onImport, locale, dictionary }) {
525
1200
 
526
1201
  exports.AdminRallyEditor = AdminRallyEditor;
527
1202
  exports.ConditionEditor = ConditionEditor;
1203
+ exports.ExternalReferencesEditor = ExternalReferencesEditor;
528
1204
  exports.GeneralSettingsForm = GeneralSettingsForm;
529
1205
  exports.JsonConfigIO = JsonConfigIO;
1206
+ exports.MetadataEditor = MetadataEditor;
530
1207
  exports.RallyEditor = RallyEditor;
1208
+ exports.RewardItemForm = RewardItemForm;
1209
+ exports.RewardUnlockConditionEditor = RewardUnlockConditionEditor;
531
1210
  exports.SpotItemForm = SpotItemForm;
1211
+ exports.ThemeEditor = ThemeEditor;
1212
+ exports.useAdminRallyEditor = useAdminRallyEditor;
1213
+ exports.useRewardEditor = useRewardEditor;
1214
+ exports.useSpotEditor = useSpotEditor;
532
1215
  //# sourceMappingURL=index.cjs.map
533
1216
  //# sourceMappingURL=index.cjs.map