@burdenoff/microfe-devportal 2026.916.7 → 2026.917.1

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.
Files changed (31) hide show
  1. package/dist/devportal/components/ImageUploadField.d.ts +30 -0
  2. package/dist/devportal/components/ImageUploadField.d.ts.map +1 -0
  3. package/dist/devportal/components/ImageUploadField.js +182 -0
  4. package/dist/devportal/components/ImageUploadField.js.map +1 -0
  5. package/dist/devportal/components/ManifestViewer.d.ts +12 -0
  6. package/dist/devportal/components/ManifestViewer.d.ts.map +1 -0
  7. package/dist/devportal/components/ManifestViewer.js +58 -0
  8. package/dist/devportal/components/ManifestViewer.js.map +1 -0
  9. package/dist/devportal/components/index.d.ts +1 -0
  10. package/dist/devportal/components/index.d.ts.map +1 -1
  11. package/dist/devportal/components/index.js +1 -0
  12. package/dist/devportal/pages/AdminReviewPage.d.ts.map +1 -1
  13. package/dist/devportal/pages/AdminReviewPage.js +54 -64
  14. package/dist/devportal/pages/AdminReviewPage.js.map +1 -1
  15. package/dist/devportal/pages/ApplicationDetailPage.d.ts.map +1 -1
  16. package/dist/devportal/pages/ApplicationDetailPage.js +712 -883
  17. package/dist/devportal/pages/ApplicationDetailPage.js.map +1 -1
  18. package/dist/devportal/pages/ApplicationVersionsPage.d.ts.map +1 -1
  19. package/dist/devportal/pages/ApplicationVersionsPage.js +304 -272
  20. package/dist/devportal/pages/ApplicationVersionsPage.js.map +1 -1
  21. package/dist/devportal/pages/CreatePhysicalItemPage.d.ts.map +1 -1
  22. package/dist/devportal/pages/CreatePhysicalItemPage.js +238 -331
  23. package/dist/devportal/pages/CreatePhysicalItemPage.js.map +1 -1
  24. package/dist/devportal/pages/EditPhysicalItemPage.d.ts.map +1 -1
  25. package/dist/devportal/pages/EditPhysicalItemPage.js +352 -408
  26. package/dist/devportal/pages/EditPhysicalItemPage.js.map +1 -1
  27. package/dist/translations/en.d.ts +4 -1
  28. package/dist/translations/en.d.ts.map +1 -1
  29. package/dist/translations/en.js +5 -2
  30. package/dist/translations/en.js.map +1 -1
  31. package/package.json +1 -1
@@ -0,0 +1,30 @@
1
+ import { FC } from 'react';
2
+
3
+ interface ImageUploadFieldProps {
4
+ label: string;
5
+ value: string;
6
+ onChange: (url: string) => void;
7
+ hint?: string;
8
+ accept?: string;
9
+ aspectClass?: string;
10
+ upload: (file: File) => Promise<{
11
+ downloadUrl: string;
12
+ }>;
13
+ uploading: boolean;
14
+ progress: number;
15
+ error: string | null;
16
+ }
17
+ export declare const ImageUploadField: FC<ImageUploadFieldProps>;
18
+ interface ScreenshotGalleryProps {
19
+ urls: string[];
20
+ onChange: (urls: string[]) => void;
21
+ upload: (file: File) => Promise<{
22
+ downloadUrl: string;
23
+ }>;
24
+ uploading: boolean;
25
+ progress: number;
26
+ error: string | null;
27
+ }
28
+ export declare const ScreenshotGallery: FC<ScreenshotGalleryProps>;
29
+ export {};
30
+ //# sourceMappingURL=ImageUploadField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageUploadField.d.ts","sourceRoot":"","sources":["../../../src/devportal/components/ImageUploadField.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAchC,UAAU,qBAAqB;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzD,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC,qBAAqB,CA+FtD,CAAC;AAEF,UAAU,sBAAsB;IAC9B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACnC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzD,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,sBAAsB,CA4HxD,CAAC"}
@@ -0,0 +1,182 @@
1
+ import { cn as e } from "../utils/cn.js";
2
+ import { useCallback as t, useRef as n, useState as r } from "react";
3
+ import { Loader2 as i, Upload as a, X as o } from "lucide-react";
4
+ import { Fragment as s, jsx as c, jsxs as l } from "react/jsx-runtime";
5
+ //#region src/devportal/components/ImageUploadField.tsx
6
+ var u = ({ label: o, value: s, onChange: u, hint: d, accept: f = "image/png,image/jpeg,image/webp,image/gif,image/svg+xml", aspectClass: p = "aspect-square", upload: m, uploading: h, progress: g, error: _ }) => {
7
+ let v = n(null), [y, b] = r(!1), x = n(s);
8
+ x.current !== s && (x.current = s, y && b(!1));
9
+ let S = t(async (e) => {
10
+ try {
11
+ u((await m(e)).downloadUrl);
12
+ } catch {}
13
+ }, [m, u]);
14
+ return /* @__PURE__ */ l("div", {
15
+ className: "space-y-1.5",
16
+ children: [
17
+ /* @__PURE__ */ c("label", {
18
+ className: "text-sm font-medium",
19
+ children: o
20
+ }),
21
+ /* @__PURE__ */ l("div", {
22
+ className: "flex gap-3 items-start",
23
+ children: [/* @__PURE__ */ l("div", {
24
+ className: e("relative shrink-0 w-20 rounded-lg border border-border-subtle bg-bg-sunken overflow-hidden flex items-center justify-center", p),
25
+ children: [s && !y ? /* @__PURE__ */ c("img", {
26
+ src: s,
27
+ alt: o,
28
+ className: "w-full h-full object-cover",
29
+ onError: () => b(!0)
30
+ }) : /* @__PURE__ */ c(a, {
31
+ size: 18,
32
+ className: "text-text-secondary opacity-40"
33
+ }), h && /* @__PURE__ */ l("div", {
34
+ className: "absolute inset-0 bg-bg-overlay flex flex-col items-center justify-center gap-1",
35
+ children: [/* @__PURE__ */ c(i, {
36
+ size: 14,
37
+ className: "animate-spin text-white"
38
+ }), /* @__PURE__ */ l("span", {
39
+ className: "text-white text-xs",
40
+ children: [g, "%"]
41
+ })]
42
+ })]
43
+ }), /* @__PURE__ */ l("div", {
44
+ className: "flex-1 space-y-1.5",
45
+ children: [
46
+ /* @__PURE__ */ c("input", {
47
+ type: "url",
48
+ value: s,
49
+ onChange: (e) => u(e.target.value),
50
+ placeholder: "https://...",
51
+ className: "w-full rounded-md border border-border-subtle bg-bg-input px-3 py-1.5 text-sm placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-primary/30"
52
+ }),
53
+ /* @__PURE__ */ c("input", {
54
+ ref: v,
55
+ type: "file",
56
+ accept: f,
57
+ className: "hidden",
58
+ onChange: (e) => {
59
+ let t = e.target.files?.[0];
60
+ t && S(t), e.target.value = "";
61
+ }
62
+ }),
63
+ /* @__PURE__ */ l("button", {
64
+ type: "button",
65
+ disabled: h,
66
+ onClick: () => v.current?.click(),
67
+ className: "inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs font-medium hover:bg-bg-sunken transition-colors disabled:opacity-50",
68
+ children: [/* @__PURE__ */ c(a, { size: 11 }), h ? `Uploading… ${g}%` : "Upload image"]
69
+ }),
70
+ _ && /* @__PURE__ */ c("p", {
71
+ className: "text-xs text-status-error-text",
72
+ children: _
73
+ })
74
+ ]
75
+ })]
76
+ }),
77
+ d && /* @__PURE__ */ c("p", {
78
+ className: "text-xs text-text-secondary",
79
+ children: d
80
+ })
81
+ ]
82
+ });
83
+ }, d = ({ urls: u, onChange: d, upload: f, uploading: p, progress: m, error: h }) => {
84
+ let g = n(null), [_, v] = r(""), y = t(async (e) => {
85
+ try {
86
+ let t = await f(e);
87
+ d([...u, t.downloadUrl]);
88
+ } catch {}
89
+ }, [
90
+ f,
91
+ d,
92
+ u
93
+ ]), b = () => {
94
+ let e = _.trim();
95
+ e && !u.includes(e) && (d([...u, e]), v(""));
96
+ }, x = (e) => {
97
+ d(u.filter((t, n) => n !== e));
98
+ };
99
+ return /* @__PURE__ */ l("div", {
100
+ className: "space-y-2",
101
+ children: [
102
+ /* @__PURE__ */ l("div", {
103
+ className: "flex flex-col gap-0.5",
104
+ children: [/* @__PURE__ */ c("label", {
105
+ className: "text-sm font-medium",
106
+ children: "Screenshots"
107
+ }), /* @__PURE__ */ c("p", {
108
+ className: "text-xs text-text-muted",
109
+ children: "Shown in the horizontal preview strip below the hero — wide landscape proportions work best (16:9)"
110
+ })]
111
+ }),
112
+ /* @__PURE__ */ l("div", {
113
+ className: "flex flex-wrap gap-3",
114
+ children: [u.map((e, t) => /* @__PURE__ */ l("div", {
115
+ className: "relative group w-36 aspect-video rounded-lg border border-border-subtle overflow-hidden bg-bg-sunken",
116
+ children: [/* @__PURE__ */ c("img", {
117
+ src: e,
118
+ alt: `Screenshot ${t + 1}`,
119
+ className: "w-full h-full object-cover"
120
+ }), /* @__PURE__ */ c("button", {
121
+ type: "button",
122
+ onClick: () => x(t),
123
+ className: "absolute top-1 right-1 rounded-full bg-bg-overlay text-white p-0.5 opacity-0 group-hover:opacity-100 transition-opacity",
124
+ children: /* @__PURE__ */ c(o, { size: 12 })
125
+ })]
126
+ }, t)), /* @__PURE__ */ c("button", {
127
+ type: "button",
128
+ disabled: p,
129
+ onClick: () => g.current?.click(),
130
+ className: e("w-36 aspect-video rounded-lg border-2 border-dashed border-border-subtle flex flex-col items-center justify-center gap-1.5 text-text-secondary hover:border-border-strong hover:bg-bg-sunken transition-colors disabled:opacity-50"),
131
+ children: p ? /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ c(i, {
132
+ size: 18,
133
+ className: "animate-spin text-primary"
134
+ }), /* @__PURE__ */ l("span", {
135
+ className: "text-xs",
136
+ children: [m, "%"]
137
+ })] }) : /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ c(a, { size: 18 }), /* @__PURE__ */ c("span", {
138
+ className: "text-xs",
139
+ children: "Add screenshot"
140
+ })] })
141
+ })]
142
+ }),
143
+ /* @__PURE__ */ l("div", {
144
+ className: "flex gap-2",
145
+ children: [/* @__PURE__ */ c("input", {
146
+ type: "url",
147
+ value: _,
148
+ onChange: (e) => v(e.target.value),
149
+ onKeyDown: (e) => {
150
+ e.key === "Enter" && (e.preventDefault(), b());
151
+ },
152
+ placeholder: "https://... paste a screenshot URL",
153
+ className: "flex-1 rounded-md border border-border-subtle bg-bg-input px-3 py-1.5 text-sm placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-primary/30"
154
+ }), /* @__PURE__ */ c("button", {
155
+ type: "button",
156
+ onClick: b,
157
+ disabled: !_.trim(),
158
+ className: "inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 text-xs font-medium hover:bg-bg-sunken transition-colors disabled:opacity-50",
159
+ children: "Add"
160
+ })]
161
+ }),
162
+ /* @__PURE__ */ c("input", {
163
+ ref: g,
164
+ type: "file",
165
+ accept: "image/png,image/jpeg,image/webp,image/gif",
166
+ className: "hidden",
167
+ onChange: (e) => {
168
+ let t = e.target.files?.[0];
169
+ t && y(t), e.target.value = "";
170
+ }
171
+ }),
172
+ h && /* @__PURE__ */ c("p", {
173
+ className: "text-xs text-status-error-text",
174
+ children: h
175
+ })
176
+ ]
177
+ });
178
+ };
179
+ //#endregion
180
+ export { u as ImageUploadField, d as ScreenshotGallery };
181
+
182
+ //# sourceMappingURL=ImageUploadField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageUploadField.js","names":[],"sources":["../../../src/devportal/components/ImageUploadField.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useCallback, useRef, useState } from \"react\";\nimport { Loader2, Upload, X } from \"lucide-react\";\nimport { cn } from \"../utils/cn\";\n\n// =============================================================================\n// IMAGE UPLOAD COMPONENTS\n//\n// Shared by the devportal App pages (Store Listing tab) and the physical\n// item create/edit pages — a single icon/banner upload field plus a\n// screenshot gallery, both backed by usePackageUpload's\n// upload(file) => Promise<{ downloadUrl }> shape.\n// =============================================================================\n\ninterface ImageUploadFieldProps {\n label: string;\n value: string;\n onChange: (url: string) => void;\n hint?: string;\n accept?: string;\n aspectClass?: string; // tailwind class for the preview container ratio\n upload: (file: File) => Promise<{ downloadUrl: string }>;\n uploading: boolean;\n progress: number;\n error: string | null;\n}\n\nexport const ImageUploadField: FC<ImageUploadFieldProps> = ({\n label,\n value,\n onChange,\n hint,\n accept = \"image/png,image/jpeg,image/webp,image/gif,image/svg+xml\",\n aspectClass = \"aspect-square\",\n upload,\n uploading,\n progress,\n error,\n}) => {\n const inputRef = useRef<HTMLInputElement>(null);\n const [imgBroken, setImgBroken] = useState(false);\n const prevValue = useRef(value);\n if (prevValue.current !== value) {\n prevValue.current = value;\n if (imgBroken) setImgBroken(false);\n }\n\n const handleFile = useCallback(\n async (file: File) => {\n try {\n const result = await upload(file);\n onChange(result.downloadUrl);\n } catch {\n // error shown via error prop\n }\n },\n [upload, onChange],\n );\n\n return (\n <div className=\"space-y-1.5\">\n <label className=\"text-sm font-medium\">{label}</label>\n <div className=\"flex gap-3 items-start\">\n {/* Preview */}\n <div\n className={cn(\n \"relative shrink-0 w-20 rounded-lg border border-border-subtle bg-bg-sunken overflow-hidden flex items-center justify-center\",\n aspectClass,\n )}\n >\n {value && !imgBroken ? (\n <img\n src={value}\n alt={label}\n className=\"w-full h-full object-cover\"\n onError={() => setImgBroken(true)}\n />\n ) : (\n <Upload size={18} className=\"text-text-secondary opacity-40\" />\n )}\n {uploading && (\n <div className=\"absolute inset-0 bg-bg-overlay flex flex-col items-center justify-center gap-1\">\n <Loader2 size={14} className=\"animate-spin text-white\" />\n <span className=\"text-white text-xs\">{progress}%</span>\n </div>\n )}\n </div>\n {/* URL input + upload button */}\n <div className=\"flex-1 space-y-1.5\">\n <input\n type=\"url\"\n value={value}\n onChange={(e) => onChange(e.target.value)}\n placeholder=\"https://...\"\n className=\"w-full rounded-md border border-border-subtle bg-bg-input px-3 py-1.5 text-sm placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-primary/30\"\n />\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n className=\"hidden\"\n onChange={(e) => {\n const f = e.target.files?.[0];\n if (f) void handleFile(f);\n e.target.value = \"\";\n }}\n />\n <button\n type=\"button\"\n disabled={uploading}\n onClick={() => inputRef.current?.click()}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs font-medium hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n <Upload size={11} />\n {uploading ? `Uploading… ${progress}%` : \"Upload image\"}\n </button>\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n </div>\n {hint && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n </div>\n );\n};\n\ninterface ScreenshotGalleryProps {\n urls: string[];\n onChange: (urls: string[]) => void;\n upload: (file: File) => Promise<{ downloadUrl: string }>;\n uploading: boolean;\n progress: number;\n error: string | null;\n}\n\nexport const ScreenshotGallery: FC<ScreenshotGalleryProps> = ({\n urls,\n onChange,\n upload,\n uploading,\n progress,\n error,\n}) => {\n const inputRef = useRef<HTMLInputElement>(null);\n const [urlInput, setUrlInput] = useState(\"\");\n\n const handleFile = useCallback(\n async (file: File) => {\n try {\n const result = await upload(file);\n onChange([...urls, result.downloadUrl]);\n } catch {\n // error shown via error prop\n }\n },\n [upload, onChange, urls],\n );\n\n const addUrl = () => {\n const trimmed = urlInput.trim();\n if (trimmed && !urls.includes(trimmed)) {\n onChange([...urls, trimmed]);\n setUrlInput(\"\");\n }\n };\n\n const remove = (idx: number) => {\n onChange(urls.filter((_, i) => i !== idx));\n };\n\n return (\n <div className=\"space-y-2\">\n <div className=\"flex flex-col gap-0.5\">\n <label className=\"text-sm font-medium\">Screenshots</label>\n <p className=\"text-xs text-text-muted\">\n Shown in the horizontal preview strip below the hero — wide landscape\n proportions work best (16:9)\n </p>\n </div>\n <div className=\"flex flex-wrap gap-3\">\n {urls.map((url, idx) => (\n <div\n key={idx}\n className=\"relative group w-36 aspect-video rounded-lg border border-border-subtle overflow-hidden bg-bg-sunken\"\n >\n <img\n src={url}\n alt={`Screenshot ${idx + 1}`}\n className=\"w-full h-full object-cover\"\n />\n <button\n type=\"button\"\n onClick={() => remove(idx)}\n className=\"absolute top-1 right-1 rounded-full bg-bg-overlay text-white p-0.5 opacity-0 group-hover:opacity-100 transition-opacity\"\n >\n <X size={12} />\n </button>\n </div>\n ))}\n {/* Upload slot */}\n <button\n type=\"button\"\n disabled={uploading}\n onClick={() => inputRef.current?.click()}\n className={cn(\n \"w-36 aspect-video rounded-lg border-2 border-dashed border-border-subtle flex flex-col items-center justify-center gap-1.5 text-text-secondary hover:border-border-strong hover:bg-bg-sunken transition-colors disabled:opacity-50\",\n )}\n >\n {uploading ? (\n <>\n <Loader2 size={18} className=\"animate-spin text-primary\" />\n <span className=\"text-xs\">{progress}%</span>\n </>\n ) : (\n <>\n <Upload size={18} />\n <span className=\"text-xs\">Add screenshot</span>\n </>\n )}\n </button>\n </div>\n {/* URL paste row */}\n <div className=\"flex gap-2\">\n <input\n type=\"url\"\n value={urlInput}\n onChange={(e) => setUrlInput(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n addUrl();\n }\n }}\n placeholder=\"https://... paste a screenshot URL\"\n className=\"flex-1 rounded-md border border-border-subtle bg-bg-input px-3 py-1.5 text-sm placeholder:text-text-placeholder focus:outline-none focus:ring-2 focus:ring-primary/30\"\n />\n <button\n type=\"button\"\n onClick={addUrl}\n disabled={!urlInput.trim()}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1.5 text-xs font-medium hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n Add\n </button>\n </div>\n <input\n ref={inputRef}\n type=\"file\"\n accept=\"image/png,image/jpeg,image/webp,image/gif\"\n className=\"hidden\"\n onChange={(e) => {\n const f = e.target.files?.[0];\n if (f) void handleFile(f);\n e.target.value = \"\";\n }}\n />\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n"],"mappings":";;;;;AA2BA,IAAa,KAA+C,EAC1D,UACA,UACA,aACA,SACA,YAAS,2DACT,iBAAc,iBACd,WACA,cACA,aACA,eACI;CACJ,IAAM,IAAW,EAAyB,KAAK,EACzC,CAAC,GAAW,KAAgB,EAAS,GAAM,EAC3C,IAAY,EAAO,EAAM;AAC/B,CAAI,EAAU,YAAY,MACxB,EAAU,UAAU,GAChB,KAAW,EAAa,GAAM;CAGpC,IAAM,IAAa,EACjB,OAAO,MAAe;AACpB,MAAI;AAEF,MADe,MAAM,EAAO,EAAK,EACjB,YAAY;UACtB;IAIV,CAAC,GAAQ,EAAS,CACnB;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,SAAD;IAAO,WAAU;cAAuB;IAAc,CAAA;GACtD,kBAAC,OAAD;IAAK,WAAU;cAAf,CAEE,kBAAC,OAAD;KACE,WAAW,EACT,+HACA,EACD;eAJH,CAMG,KAAS,CAAC,IACT,kBAAC,OAAD;MACE,KAAK;MACL,KAAK;MACL,WAAU;MACV,eAAe,EAAa,GAAK;MACjC,CAAA,GAEF,kBAAC,GAAD;MAAQ,MAAM;MAAI,WAAU;MAAmC,CAAA,EAEhE,KACC,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAS,MAAM;OAAI,WAAU;OAA4B,CAAA,EACzD,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CAAsC,GAAS,IAAQ;SACnD;QAEJ;QAEN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,SAAD;OACE,MAAK;OACE;OACP,WAAW,MAAM,EAAS,EAAE,OAAO,MAAM;OACzC,aAAY;OACZ,WAAU;OACV,CAAA;MACF,kBAAC,SAAD;OACE,KAAK;OACL,MAAK;OACG;OACR,WAAU;OACV,WAAW,MAAM;QACf,IAAM,IAAI,EAAE,OAAO,QAAQ;AAE3B,QADI,KAAQ,EAAW,EAAE,EACzB,EAAE,OAAO,QAAQ;;OAEnB,CAAA;MACF,kBAAC,UAAD;OACE,MAAK;OACL,UAAU;OACV,eAAe,EAAS,SAAS,OAAO;OACxC,WAAU;iBAJZ,CAME,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA,EACnB,IAAY,cAAc,EAAS,KAAK,eAClC;;MACR,KAAS,kBAAC,KAAD;OAAG,WAAU;iBAAkC;OAAU,CAAA;MAC/D;OACF;;GACL,KAAQ,kBAAC,KAAD;IAAG,WAAU;cAA+B;IAAS,CAAA;GAC1D;;GAaG,KAAiD,EAC5D,SACA,aACA,WACA,cACA,aACA,eACI;CACJ,IAAM,IAAW,EAAyB,KAAK,EACzC,CAAC,GAAU,KAAe,EAAS,GAAG,EAEtC,IAAa,EACjB,OAAO,MAAe;AACpB,MAAI;GACF,IAAM,IAAS,MAAM,EAAO,EAAK;AACjC,KAAS,CAAC,GAAG,GAAM,EAAO,YAAY,CAAC;UACjC;IAIV;EAAC;EAAQ;EAAU;EAAK,CACzB,EAEK,UAAe;EACnB,IAAM,IAAU,EAAS,MAAM;AAC/B,EAAI,KAAW,CAAC,EAAK,SAAS,EAAQ,KACpC,EAAS,CAAC,GAAG,GAAM,EAAQ,CAAC,EAC5B,EAAY,GAAG;IAIb,KAAU,MAAgB;AAC9B,IAAS,EAAK,QAAQ,GAAG,MAAM,MAAM,EAAI,CAAC;;AAG5C,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,SAAD;KAAO,WAAU;eAAsB;KAAmB,CAAA,EAC1D,kBAAC,KAAD;KAAG,WAAU;eAA0B;KAGnC,CAAA,CACA;;GACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAK,KAAK,GAAK,MACd,kBAAC,OAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD;MACE,KAAK;MACL,KAAK,cAAc,IAAM;MACzB,WAAU;MACV,CAAA,EACF,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAO,EAAI;MAC1B,WAAU;gBAEV,kBAAC,GAAD,EAAG,MAAM,IAAM,CAAA;MACR,CAAA,CACL;OAfC,EAeD,CACN,EAEF,kBAAC,UAAD;KACE,MAAK;KACL,UAAU;KACV,eAAe,EAAS,SAAS,OAAO;KACxC,WAAW,EACT,qOACD;eAEA,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;MAAS,MAAM;MAAI,WAAU;MAA8B,CAAA,EAC3D,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CAA2B,GAAS,IAAQ;QAC3C,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA,EACpB,kBAAC,QAAD;MAAM,WAAU;gBAAU;MAAqB,CAAA,CAC9C,EAAA,CAAA;KAEE,CAAA,CACL;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,SAAD;KACE,MAAK;KACL,OAAO;KACP,WAAW,MAAM,EAAY,EAAE,OAAO,MAAM;KAC5C,YAAY,MAAM;AAChB,MAAI,EAAE,QAAQ,YACZ,EAAE,gBAAgB,EAClB,GAAQ;;KAGZ,aAAY;KACZ,WAAU;KACV,CAAA,EACF,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,UAAU,CAAC,EAAS,MAAM;KAC1B,WAAU;eACX;KAEQ,CAAA,CACL;;GACN,kBAAC,SAAD;IACE,KAAK;IACL,MAAK;IACL,QAAO;IACP,WAAU;IACV,WAAW,MAAM;KACf,IAAM,IAAI,EAAE,OAAO,QAAQ;AAE3B,KADI,KAAQ,EAAW,EAAE,EACzB,EAAE,OAAO,QAAQ;;IAEnB,CAAA;GACD,KAAS,kBAAC,KAAD;IAAG,WAAU;cAAkC;IAAU,CAAA;GAC/D"}
@@ -0,0 +1,12 @@
1
+ import { FC } from 'react';
2
+
3
+ export interface ManifestViewerProps {
4
+ manifest: Record<string, unknown>;
5
+ tr: (key: string, fallback: string) => string;
6
+ }
7
+ /**
8
+ * Read-only modal for viewing a version's manifest.json, so developers and
9
+ * reviewers can validate what was uploaded without leaving the page.
10
+ */
11
+ export declare const ManifestViewer: FC<ManifestViewerProps>;
12
+ //# sourceMappingURL=ManifestViewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ManifestViewer.d.ts","sourceRoot":"","sources":["../../../src/devportal/components/ManifestViewer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAIhC,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAAC;CAC/C;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CAwElD,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { useEffect as e, useRef as t, useState as n } from "react";
2
+ import { FileJson as r, X as i } from "lucide-react";
3
+ import { Fragment as a, jsx as o, jsxs as s } from "react/jsx-runtime";
4
+ //#region src/devportal/components/ManifestViewer.tsx
5
+ var c = ({ manifest: c, tr: l }) => {
6
+ let [u, d] = n(!1), f = t(null);
7
+ return e(() => {
8
+ if (!u) return;
9
+ let e = document.body.style.overflow;
10
+ document.body.style.overflow = "hidden", window.requestAnimationFrame(() => f.current?.focus());
11
+ let t = (e) => {
12
+ e.key === "Escape" && (e.preventDefault(), d(!1));
13
+ };
14
+ return document.addEventListener("keydown", t), () => {
15
+ document.body.style.overflow = e, document.removeEventListener("keydown", t);
16
+ };
17
+ }, [u]), /* @__PURE__ */ s(a, { children: [/* @__PURE__ */ s("button", {
18
+ type: "button",
19
+ onClick: () => d(!0),
20
+ className: "inline-flex items-center gap-1.5 text-xs text-text-secondary hover:text-text-primary",
21
+ children: [/* @__PURE__ */ o(r, { size: 13 }), l("devportal.viewManifest", "View manifest.json")]
22
+ }), u && /* @__PURE__ */ s("div", {
23
+ className: "fixed inset-0 z-50 flex items-center justify-center p-4",
24
+ children: [/* @__PURE__ */ o("button", {
25
+ type: "button",
26
+ "aria-label": "Close dialog backdrop",
27
+ className: "absolute inset-0 bg-bg-overlay",
28
+ onClick: () => d(!1)
29
+ }), /* @__PURE__ */ s("div", {
30
+ ref: f,
31
+ role: "dialog",
32
+ "aria-modal": "true",
33
+ "aria-label": l("devportal.manifestJson", "manifest.json"),
34
+ tabIndex: -1,
35
+ className: "relative z-10 flex max-h-[80vh] w-full max-w-2xl flex-col rounded-lg bg-bg-surface p-6 shadow-xl focus:outline-none",
36
+ children: [/* @__PURE__ */ s("div", {
37
+ className: "flex items-center justify-between gap-4",
38
+ children: [/* @__PURE__ */ o("h3", {
39
+ className: "text-lg font-semibold",
40
+ children: l("devportal.manifestJson", "manifest.json")
41
+ }), /* @__PURE__ */ o("button", {
42
+ type: "button",
43
+ onClick: () => d(!1),
44
+ "aria-label": l("devportal.close", "Close"),
45
+ className: "rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary",
46
+ children: /* @__PURE__ */ o(i, { size: 18 })
47
+ })]
48
+ }), /* @__PURE__ */ o("pre", {
49
+ className: "mt-4 flex-1 overflow-auto rounded-md bg-bg-sunken p-3 text-xs font-mono whitespace-pre-wrap break-all",
50
+ children: JSON.stringify(c, null, 2)
51
+ })]
52
+ })]
53
+ })] });
54
+ };
55
+ //#endregion
56
+ export { c as ManifestViewer };
57
+
58
+ //# sourceMappingURL=ManifestViewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ManifestViewer.js","names":[],"sources":["../../../src/devportal/components/ManifestViewer.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { FileJson, X } from \"lucide-react\";\n\nexport interface ManifestViewerProps {\n manifest: Record<string, unknown>;\n tr: (key: string, fallback: string) => string;\n}\n\n/**\n * Read-only modal for viewing a version's manifest.json, so developers and\n * reviewers can validate what was uploaded without leaving the page.\n */\nexport const ManifestViewer: FC<ManifestViewerProps> = ({ manifest, tr }) => {\n const [open, setOpen] = useState(false);\n const dialogRef = useRef<HTMLDivElement | null>(null);\n\n useEffect(() => {\n if (!open) return undefined;\n\n const originalOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n window.requestAnimationFrame(() => dialogRef.current?.focus());\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n event.preventDefault();\n setOpen(false);\n }\n };\n document.addEventListener(\"keydown\", onKeyDown);\n\n return () => {\n document.body.style.overflow = originalOverflow;\n document.removeEventListener(\"keydown\", onKeyDown);\n };\n }, [open]);\n\n return (\n <>\n <button\n type=\"button\"\n onClick={() => setOpen(true)}\n className=\"inline-flex items-center gap-1.5 text-xs text-text-secondary hover:text-text-primary\"\n >\n <FileJson size={13} />\n {tr(\"devportal.viewManifest\", \"View manifest.json\")}\n </button>\n {open && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center p-4\">\n <button\n type=\"button\"\n aria-label=\"Close dialog backdrop\"\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={() => setOpen(false)}\n />\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={tr(\"devportal.manifestJson\", \"manifest.json\")}\n tabIndex={-1}\n className=\"relative z-10 flex max-h-[80vh] w-full max-w-2xl flex-col rounded-lg bg-bg-surface p-6 shadow-xl focus:outline-none\"\n >\n <div className=\"flex items-center justify-between gap-4\">\n <h3 className=\"text-lg font-semibold\">\n {tr(\"devportal.manifestJson\", \"manifest.json\")}\n </h3>\n <button\n type=\"button\"\n onClick={() => setOpen(false)}\n aria-label={tr(\"devportal.close\", \"Close\")}\n className=\"rounded-md p-1 text-text-secondary hover:bg-bg-sunken hover:text-text-primary\"\n >\n <X size={18} />\n </button>\n </div>\n <pre className=\"mt-4 flex-1 overflow-auto rounded-md bg-bg-sunken p-3 text-xs font-mono whitespace-pre-wrap break-all\">\n {JSON.stringify(manifest, null, 2)}\n </pre>\n </div>\n </div>\n )}\n </>\n );\n};\n"],"mappings":";;;;AAaA,IAAa,KAA2C,EAAE,aAAU,YAAS;CAC3E,IAAM,CAAC,GAAM,KAAW,EAAS,GAAM,EACjC,IAAY,EAA8B,KAAK;AAuBrD,QArBA,QAAgB;AACd,MAAI,CAAC,EAAM;EAEX,IAAM,IAAmB,SAAS,KAAK,MAAM;AAE7C,EADA,SAAS,KAAK,MAAM,WAAW,UAC/B,OAAO,4BAA4B,EAAU,SAAS,OAAO,CAAC;EAE9D,IAAM,KAAa,MAAyB;AAC1C,GAAI,EAAM,QAAQ,aAChB,EAAM,gBAAgB,EACtB,EAAQ,GAAM;;AAKlB,SAFA,SAAS,iBAAiB,WAAW,EAAU,QAElC;AAEX,GADA,SAAS,KAAK,MAAM,WAAW,GAC/B,SAAS,oBAAoB,WAAW,EAAU;;IAEnD,CAAC,EAAK,CAAC,EAGR,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,UAAD;EACE,MAAK;EACL,eAAe,EAAQ,GAAK;EAC5B,WAAU;YAHZ,CAKE,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA,EACrB,EAAG,0BAA0B,qBAAqB,CAC5C;KACR,KACC,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,UAAD;GACE,MAAK;GACL,cAAW;GACX,WAAU;GACV,eAAe,EAAQ,GAAM;GAC7B,CAAA,EACF,kBAAC,OAAD;GACE,KAAK;GACL,MAAK;GACL,cAAW;GACX,cAAY,EAAG,0BAA0B,gBAAgB;GACzD,UAAU;GACV,WAAU;aANZ,CAQE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,0BAA0B,gBAAgB;KAC3C,CAAA,EACL,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAQ,GAAM;KAC7B,cAAY,EAAG,mBAAmB,QAAQ;KAC1C,WAAU;eAEV,kBAAC,GAAD,EAAG,MAAM,IAAM,CAAA;KACR,CAAA,CACL;OACN,kBAAC,OAAD;IAAK,WAAU;cACZ,KAAK,UAAU,GAAU,MAAM,EAAE;IAC9B,CAAA,CACF;KACF;IAEP,EAAA,CAAA"}
@@ -21,4 +21,5 @@ export * from './FeatureFlaggedRoute';
21
21
  export * from './TelemetryErrorBoundary';
22
22
  export * from './SectionNav';
23
23
  export * from './MarkdownEditor';
24
+ export * from './ManifestViewer';
24
25
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/devportal/components/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/devportal/components/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC"}
@@ -16,3 +16,4 @@ import "./ReportOrderIssueModal.js";
16
16
  import "./RegisterPickupLocationModal.js";
17
17
  import "./PermissionGuard.js";
18
18
  import "./MarkdownEditor.js";
19
+ import "./ManifestViewer.js";
@@ -1 +1 @@
1
- {"version":3,"file":"AdminReviewPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/AdminReviewPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAkrBhC,eAAO,MAAM,eAAe,EAAE,EA2Y7B,CAAC"}
1
+ {"version":3,"file":"AdminReviewPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/AdminReviewPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAipBhC,eAAO,MAAM,eAAe,EAAE,EA2Y7B,CAAC"}