@ampless/admin 1.0.0-alpha.52 → 1.0.0-alpha.54

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.
@@ -1,380 +0,0 @@
1
- 'use client';
2
- import {
3
- invalidateSiteSettingsCache
4
- } from "./chunk-D72XF3Q3.js";
5
- import {
6
- useLocale,
7
- useT
8
- } from "./chunk-2SVJONQ7.js";
9
-
10
- // src/components/plugin-settings-form.tsx
11
- import { useState } from "react";
12
- import {
13
- resolveLocalized
14
- } from "ampless";
15
- import { Button, Input, Label, Textarea } from "@ampless/runtime/ui";
16
-
17
- // src/lib/plugin-settings.ts
18
- import {
19
- getKvStore,
20
- SITE_CONFIG_PK,
21
- isValidPluginKey,
22
- validatePluginSettingValue
23
- } from "ampless";
24
- function pluginSettingKey(instanceId, fieldKey) {
25
- return `plugins.${instanceId}.${fieldKey}`;
26
- }
27
- async function setPluginPublicSetting(instanceId, field, rawValue) {
28
- if (!isValidPluginKey(instanceId)) {
29
- throw new Error(`Invalid plugin instanceId: "${instanceId}"`);
30
- }
31
- if (!isValidPluginKey(field.key)) {
32
- throw new Error(`Invalid plugin field key: "${field.key}"`);
33
- }
34
- const validated = validatePluginSettingValue(field, rawValue);
35
- if (validated === null) {
36
- throw new Error(`Invalid value for plugin field "${field.key}"`);
37
- }
38
- const store = getKvStore();
39
- await store.put(SITE_CONFIG_PK, pluginSettingKey(instanceId, field.key), validated);
40
- }
41
- async function deletePluginPublicSetting(instanceId, field) {
42
- if (!isValidPluginKey(instanceId)) {
43
- throw new Error(`Invalid plugin instanceId: "${instanceId}"`);
44
- }
45
- if (!isValidPluginKey(field.key)) {
46
- throw new Error(`Invalid plugin field key: "${field.key}"`);
47
- }
48
- const store = getKvStore();
49
- await store.remove(SITE_CONFIG_PK, pluginSettingKey(instanceId, field.key));
50
- }
51
-
52
- // src/components/plugin-settings-form.tsx
53
- import { jsx, jsxs } from "react/jsx-runtime";
54
- var CACHE_REBUILD_DELAY_MS = 8e3;
55
- function stringify(field, raw) {
56
- if (raw === void 0 || raw === null) return "";
57
- switch (field.type) {
58
- case "boolean":
59
- return raw === true ? "true" : raw === false ? "false" : String(raw);
60
- case "json":
61
- if (typeof raw === "string") return raw;
62
- try {
63
- return JSON.stringify(raw, null, 2);
64
- } catch {
65
- return "";
66
- }
67
- default:
68
- return typeof raw === "string" ? raw : String(raw);
69
- }
70
- }
71
- function parse(field, raw) {
72
- switch (field.type) {
73
- case "boolean":
74
- if (raw === "true") return true;
75
- if (raw === "false") return false;
76
- return null;
77
- case "number": {
78
- const trimmed = raw.trim();
79
- if (trimmed === "") return null;
80
- const n = Number(trimmed);
81
- return Number.isNaN(n) ? null : n;
82
- }
83
- case "json": {
84
- const trimmed = raw.trim();
85
- if (trimmed === "") return null;
86
- try {
87
- return JSON.parse(trimmed);
88
- } catch {
89
- return null;
90
- }
91
- }
92
- default:
93
- return raw;
94
- }
95
- }
96
- function PluginSettingsForm({
97
- instanceId,
98
- displayName,
99
- fields,
100
- initialValues
101
- }) {
102
- const t = useT();
103
- const locale = useLocale();
104
- function defaultDisplay(field) {
105
- return field.default !== void 0 ? stringify(field, field.default) : "";
106
- }
107
- const [state, setState] = useState(() => {
108
- const values = {};
109
- for (const field of fields) {
110
- const has = Object.prototype.hasOwnProperty.call(initialValues, field.key);
111
- values[field.key] = has ? stringify(field, initialValues[field.key]) : defaultDisplay(field);
112
- }
113
- return { values, touched: {}, invalid: {} };
114
- });
115
- const [storedKeys, setStoredKeys] = useState(
116
- () => new Set(Object.keys(initialValues))
117
- );
118
- const [saving, setSaving] = useState(false);
119
- const [error, setError] = useState(null);
120
- const [info, setInfo] = useState(null);
121
- function update(key, value) {
122
- setState((prev) => ({
123
- values: { ...prev.values, [key]: value },
124
- touched: { ...prev.touched, [key]: true },
125
- invalid: { ...prev.invalid, [key]: false }
126
- }));
127
- setInfo(null);
128
- }
129
- function scheduleCacheInvalidation() {
130
- setTimeout(async () => {
131
- try {
132
- await invalidateSiteSettingsCache();
133
- } catch (err) {
134
- console.warn("[plugin] cache invalidation failed", err);
135
- }
136
- }, CACHE_REBUILD_DELAY_MS);
137
- }
138
- async function reset(field) {
139
- setError(null);
140
- setInfo(null);
141
- try {
142
- await deletePluginPublicSetting(instanceId, field);
143
- setState((prev) => ({
144
- values: { ...prev.values, [field.key]: defaultDisplay(field) },
145
- touched: { ...prev.touched, [field.key]: false },
146
- invalid: { ...prev.invalid, [field.key]: false }
147
- }));
148
- setStoredKeys((prev) => {
149
- if (!prev.has(field.key)) return prev;
150
- const next = new Set(prev);
151
- next.delete(field.key);
152
- return next;
153
- });
154
- setInfo(t("plugins.resetDone"));
155
- scheduleCacheInvalidation();
156
- } catch (err) {
157
- console.error("[plugin] reset failed", err);
158
- setError(err instanceof Error ? err.message : String(err));
159
- }
160
- }
161
- async function save(e) {
162
- e.preventDefault();
163
- setSaving(true);
164
- setError(null);
165
- setInfo(null);
166
- const newInvalid = {};
167
- const writes = [];
168
- const writtenKeys = [];
169
- for (const field of fields) {
170
- if (!state.touched[field.key]) continue;
171
- const raw = state.values[field.key] ?? "";
172
- const parsed = parse(field, raw);
173
- if (parsed === null && raw !== "") {
174
- newInvalid[field.key] = true;
175
- continue;
176
- }
177
- if (parsed === null) continue;
178
- writes.push(setPluginPublicSetting(instanceId, field, parsed));
179
- writtenKeys.push(field.key);
180
- }
181
- if (Object.keys(newInvalid).length > 0) {
182
- setState((prev) => ({ ...prev, invalid: newInvalid }));
183
- setSaving(false);
184
- setError(t("plugins.invalidValue"));
185
- return;
186
- }
187
- try {
188
- await Promise.all(writes);
189
- setInfo(t("plugins.saved"));
190
- setState((prev) => ({ ...prev, touched: {}, invalid: {} }));
191
- if (writtenKeys.length > 0) {
192
- setStoredKeys((prev) => {
193
- const next = new Set(prev);
194
- for (const k of writtenKeys) next.add(k);
195
- return next;
196
- });
197
- }
198
- scheduleCacheInvalidation();
199
- } catch (err) {
200
- console.error("[plugin] save failed", err);
201
- setError(err instanceof Error ? err.message : String(err));
202
- } finally {
203
- setSaving(false);
204
- }
205
- }
206
- return /* @__PURE__ */ jsxs("form", { onSubmit: save, className: "max-w-2xl space-y-5 rounded-md border p-4", children: [
207
- /* @__PURE__ */ jsxs("div", { children: [
208
- /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold", children: displayName ? resolveLocalized(displayName, locale) : instanceId }),
209
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: instanceId })
210
- ] }),
211
- fields.map((field) => /* @__PURE__ */ jsx(
212
- FieldRow,
213
- {
214
- field,
215
- value: state.values[field.key] ?? "",
216
- invalid: !!state.invalid[field.key],
217
- onChange: (v) => update(field.key, v),
218
- onReset: () => void reset(field),
219
- hasStoredValue: storedKeys.has(field.key)
220
- },
221
- field.key
222
- )),
223
- info && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: info }),
224
- error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }),
225
- /* @__PURE__ */ jsx(Button, { type: "submit", disabled: saving, children: saving ? t("plugins.saving") : t("plugins.save") })
226
- ] });
227
- }
228
- function FieldRow({ field, value, invalid, onChange, onReset, hasStoredValue }) {
229
- const t = useT();
230
- const locale = useLocale();
231
- const id = `plugin-${field.key}`;
232
- const labelEl = /* @__PURE__ */ jsxs(Label, { htmlFor: id, className: invalid ? "text-destructive" : void 0, children: [
233
- resolveLocalized(field.label, locale),
234
- field.required && /* @__PURE__ */ jsx("span", { className: "ml-1 text-destructive", children: "*" })
235
- ] });
236
- const description = field.description ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: resolveLocalized(field.description, locale) }) : null;
237
- const input = renderInput(field, id, value, invalid, onChange);
238
- const placeholder = renderDefaultHint(field, locale);
239
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
240
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
241
- labelEl,
242
- hasStoredValue && /* @__PURE__ */ jsx(
243
- "button",
244
- {
245
- type: "button",
246
- className: "text-xs text-muted-foreground underline-offset-2 hover:underline",
247
- onClick: onReset,
248
- children: t("plugins.resetToDefault")
249
- }
250
- )
251
- ] }),
252
- description,
253
- input,
254
- placeholder
255
- ] });
256
- }
257
- function renderDefaultHint(field, _locale) {
258
- if (field.default === void 0) return null;
259
- let preview;
260
- if (typeof field.default === "string") preview = field.default;
261
- else if (typeof field.default === "boolean" || typeof field.default === "number") {
262
- preview = String(field.default);
263
- } else {
264
- try {
265
- preview = JSON.stringify(field.default);
266
- } catch {
267
- return null;
268
- }
269
- }
270
- if (!preview) return null;
271
- return /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: /* @__PURE__ */ jsx("code", { children: preview }) });
272
- }
273
- function renderInput(field, id, value, invalid, onChange) {
274
- switch (field.type) {
275
- case "text":
276
- case "url":
277
- return /* @__PURE__ */ jsx(
278
- Input,
279
- {
280
- id,
281
- value,
282
- maxLength: field.type === "text" ? field.maxLength : void 0,
283
- placeholder: "placeholder" in field ? field.placeholder : void 0,
284
- onChange: (e) => onChange(e.target.value),
285
- "aria-invalid": invalid,
286
- type: field.type === "url" ? "url" : "text"
287
- }
288
- );
289
- case "textarea":
290
- return /* @__PURE__ */ jsx(
291
- Textarea,
292
- {
293
- id,
294
- value,
295
- rows: field.rows ?? 4,
296
- maxLength: field.maxLength,
297
- placeholder: field.placeholder,
298
- onChange: (e) => onChange(e.target.value),
299
- "aria-invalid": invalid
300
- }
301
- );
302
- case "code":
303
- return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
304
- field.language && /* @__PURE__ */ jsx("p", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: field.language }),
305
- /* @__PURE__ */ jsx(
306
- Textarea,
307
- {
308
- id,
309
- value,
310
- rows: field.rows ?? 8,
311
- maxLength: field.maxLength,
312
- placeholder: field.placeholder,
313
- onChange: (e) => onChange(e.target.value),
314
- "aria-invalid": invalid,
315
- className: "font-mono text-xs"
316
- }
317
- )
318
- ] });
319
- case "boolean":
320
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
321
- /* @__PURE__ */ jsx(
322
- "input",
323
- {
324
- id,
325
- type: "checkbox",
326
- checked: value === "true",
327
- onChange: (e) => onChange(e.target.checked ? "true" : "false"),
328
- className: "h-4 w-4"
329
- }
330
- ),
331
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: value === "true" ? "on" : "off" })
332
- ] });
333
- case "number":
334
- return /* @__PURE__ */ jsx(
335
- Input,
336
- {
337
- id,
338
- type: "number",
339
- value,
340
- min: field.min,
341
- max: field.max,
342
- step: field.step,
343
- onChange: (e) => onChange(e.target.value),
344
- "aria-invalid": invalid
345
- }
346
- );
347
- case "select":
348
- return /* @__PURE__ */ jsxs(
349
- "select",
350
- {
351
- id,
352
- className: "w-full rounded-md border bg-background px-2 py-1.5 text-sm",
353
- value,
354
- onChange: (e) => onChange(e.target.value),
355
- "aria-invalid": invalid,
356
- children: [
357
- /* @__PURE__ */ jsx("option", { value: "", children: "\u2014" }),
358
- field.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: typeof opt.label === "string" ? opt.label : opt.value }, opt.value))
359
- ]
360
- }
361
- );
362
- case "json":
363
- return /* @__PURE__ */ jsx(
364
- Textarea,
365
- {
366
- id,
367
- value,
368
- rows: field.rows ?? 8,
369
- placeholder: field.placeholder ?? "{}",
370
- onChange: (e) => onChange(e.target.value),
371
- "aria-invalid": invalid,
372
- className: "font-mono text-xs"
373
- }
374
- );
375
- }
376
- }
377
-
378
- export {
379
- PluginSettingsForm
380
- };