@burdenoff/microfe-devportal 2026.714.2 → 2026.714.4
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.
|
@@ -10,70 +10,108 @@ import { useDevportalPermission as m } from "../hooks/usePermission.js";
|
|
|
10
10
|
import "../hooks/index.js";
|
|
11
11
|
import { useState as h } from "react";
|
|
12
12
|
import { useI18n as g } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
13
|
-
import { AlertCircle as _, CheckCircle as v, ChevronDown as y, ChevronUp as b, Clock as x, Download as S, Eye as C, FileJson as w, GitBranch as T, Loader2 as E,
|
|
14
|
-
import { jsx as
|
|
13
|
+
import { AlertCircle as _, CheckCircle as v, ChevronDown as y, ChevronUp as b, Clock as x, Download as S, Eye as C, FileJson as w, GitBranch as T, Loader2 as E, Package as D, Search as O, XCircle as k } from "lucide-react";
|
|
14
|
+
import { jsx as A, jsxs as j } from "react/jsx-runtime";
|
|
15
15
|
//#region src/devportal/pages/AdminReviewPage.tsx
|
|
16
|
-
var
|
|
16
|
+
var M = ({ manifest: e, tr: t }) => {
|
|
17
17
|
let [n, r] = h(!1);
|
|
18
|
-
return /* @__PURE__ */
|
|
18
|
+
return /* @__PURE__ */ j("div", { children: [/* @__PURE__ */ j("button", {
|
|
19
19
|
type: "button",
|
|
20
20
|
onClick: () => r((e) => !e),
|
|
21
21
|
className: "inline-flex items-center gap-1.5 text-xs text-text-secondary hover:text-text-primary",
|
|
22
|
-
children: [/* @__PURE__ */
|
|
23
|
-
}), n && /* @__PURE__ */
|
|
22
|
+
children: [/* @__PURE__ */ A(w, { size: 13 }), n ? t("devportal.hideManifest", "Hide manifest") : t("devportal.viewManifest", "View manifest.json")]
|
|
23
|
+
}), n && /* @__PURE__ */ A("pre", {
|
|
24
24
|
className: "mt-2 max-h-48 overflow-auto rounded-md bg-bg-sunken p-3 text-xs font-mono whitespace-pre-wrap break-all",
|
|
25
25
|
children: JSON.stringify(e, null, 2)
|
|
26
26
|
})] });
|
|
27
|
-
},
|
|
27
|
+
}, N = {
|
|
28
|
+
NODE: "Node package",
|
|
29
|
+
WORKFLOW: "Workflow bundle"
|
|
30
|
+
}, P = ({ appType: e, packageUrl: t, packageSize: n, tr: r }) => {
|
|
31
|
+
let i = N[e];
|
|
32
|
+
return i ? /* @__PURE__ */ j("div", {
|
|
33
|
+
className: "rounded-lg border border-status-warning-bg bg-status-warning-bg-subtle p-3 space-y-2",
|
|
34
|
+
children: [/* @__PURE__ */ j("div", {
|
|
35
|
+
className: "flex items-center gap-2 text-xs font-medium text-status-warning-text",
|
|
36
|
+
children: [/* @__PURE__ */ A(D, {
|
|
37
|
+
size: 13,
|
|
38
|
+
className: "flex-shrink-0"
|
|
39
|
+
}), r("devportal.zipVerificationRequired", "ZIP verification required — download and test before approving")]
|
|
40
|
+
}), t ? /* @__PURE__ */ j("a", {
|
|
41
|
+
href: t,
|
|
42
|
+
download: !0,
|
|
43
|
+
target: "_blank",
|
|
44
|
+
rel: "noopener noreferrer",
|
|
45
|
+
className: "inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors",
|
|
46
|
+
children: [
|
|
47
|
+
/* @__PURE__ */ A(S, { size: 14 }),
|
|
48
|
+
r("devportal.download", "Download"),
|
|
49
|
+
" ",
|
|
50
|
+
i,
|
|
51
|
+
n && /* @__PURE__ */ j("span", {
|
|
52
|
+
className: "opacity-70 text-xs",
|
|
53
|
+
children: [
|
|
54
|
+
"(",
|
|
55
|
+
n,
|
|
56
|
+
")"
|
|
57
|
+
]
|
|
58
|
+
})
|
|
59
|
+
]
|
|
60
|
+
}) : /* @__PURE__ */ j("p", {
|
|
61
|
+
className: "text-xs text-status-error-text flex items-center gap-1.5",
|
|
62
|
+
children: [/* @__PURE__ */ A(_, { size: 12 }), r("devportal.noZipUploaded", "No ZIP uploaded — developer must upload before this can be approved")]
|
|
63
|
+
})]
|
|
64
|
+
}) : null;
|
|
65
|
+
}, F = ({ app: t, onApprove: n, onReject: r, processing: a, hasPendingVersions: o }) => {
|
|
28
66
|
let { navigate: s, basePath: c } = e(), { t: l } = g(), u = (e, t) => {
|
|
29
67
|
let n = l(e);
|
|
30
68
|
return n === e ? t : n;
|
|
31
|
-
}, [d, f] = h(!1), [_, x] = h(""), w = m(p.APP_APPROVE), D = m(p.APP_REJECT), { data:
|
|
69
|
+
}, [d, f] = h(!1), [_, x] = h(""), w = m(p.APP_APPROVE), D = m(p.APP_REJECT), { data: O } = i({
|
|
32
70
|
variables: { id: t.id },
|
|
33
71
|
skip: !d,
|
|
34
72
|
fetchPolicy: "cache-first"
|
|
35
|
-
}),
|
|
36
|
-
return /* @__PURE__ */
|
|
73
|
+
}), F = O?.application?.versions?.edges?.[0]?.node;
|
|
74
|
+
return /* @__PURE__ */ j("div", {
|
|
37
75
|
className: "rounded-card border border-border-seam bg-bg-surface overflow-hidden",
|
|
38
|
-
children: [/* @__PURE__ */
|
|
76
|
+
children: [/* @__PURE__ */ j("div", {
|
|
39
77
|
className: "flex items-center gap-4 p-4",
|
|
40
78
|
children: [
|
|
41
|
-
t.iconUrl ? /* @__PURE__ */
|
|
79
|
+
t.iconUrl ? /* @__PURE__ */ A("img", {
|
|
42
80
|
src: t.iconUrl,
|
|
43
81
|
alt: t.name,
|
|
44
82
|
className: "h-12 w-12 rounded-lg object-cover flex-shrink-0"
|
|
45
|
-
}) : /* @__PURE__ */
|
|
83
|
+
}) : /* @__PURE__ */ A("div", {
|
|
46
84
|
className: "h-12 w-12 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0",
|
|
47
|
-
children: /* @__PURE__ */
|
|
85
|
+
children: /* @__PURE__ */ A("span", {
|
|
48
86
|
className: "text-lg font-bold text-text-secondary",
|
|
49
87
|
children: t.name.charAt(0).toUpperCase()
|
|
50
88
|
})
|
|
51
89
|
}),
|
|
52
|
-
/* @__PURE__ */
|
|
90
|
+
/* @__PURE__ */ j("div", {
|
|
53
91
|
className: "flex-1 min-w-0",
|
|
54
92
|
children: [
|
|
55
|
-
/* @__PURE__ */
|
|
93
|
+
/* @__PURE__ */ j("div", {
|
|
56
94
|
className: "flex items-center gap-2",
|
|
57
95
|
children: [
|
|
58
|
-
/* @__PURE__ */
|
|
96
|
+
/* @__PURE__ */ A("h3", {
|
|
59
97
|
className: "font-semibold truncate",
|
|
60
98
|
children: t.name
|
|
61
99
|
}),
|
|
62
|
-
/* @__PURE__ */
|
|
100
|
+
/* @__PURE__ */ A("span", {
|
|
63
101
|
className: "inline-flex items-center rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-xs font-medium text-status-warning-text",
|
|
64
102
|
children: t.status
|
|
65
103
|
}),
|
|
66
|
-
/* @__PURE__ */
|
|
104
|
+
/* @__PURE__ */ A("span", {
|
|
67
105
|
className: "inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
|
|
68
106
|
children: t.appType.replace(/_/g, " ")
|
|
69
107
|
})
|
|
70
108
|
]
|
|
71
109
|
}),
|
|
72
|
-
/* @__PURE__ */
|
|
110
|
+
/* @__PURE__ */ A("p", {
|
|
73
111
|
className: "text-sm text-text-secondary truncate",
|
|
74
112
|
children: t.shortDescription ?? t.description ?? u("devportal.noDescription", "No description")
|
|
75
113
|
}),
|
|
76
|
-
/* @__PURE__ */
|
|
114
|
+
/* @__PURE__ */ j("p", {
|
|
77
115
|
className: "text-xs text-text-secondary mt-0.5",
|
|
78
116
|
children: [
|
|
79
117
|
u("devportal.submitted", "Submitted"),
|
|
@@ -88,178 +126,183 @@ var j = ({ manifest: e, tr: t }) => {
|
|
|
88
126
|
})
|
|
89
127
|
]
|
|
90
128
|
}),
|
|
91
|
-
/* @__PURE__ */
|
|
129
|
+
/* @__PURE__ */ j("div", {
|
|
92
130
|
className: "flex items-center gap-2 flex-shrink-0",
|
|
93
|
-
children: [/* @__PURE__ */
|
|
131
|
+
children: [/* @__PURE__ */ j("button", {
|
|
94
132
|
onClick: () => s?.(`${c}/apps/${t.id}`),
|
|
95
133
|
className: "inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken",
|
|
96
134
|
title: u("devportal.viewAppDetails", "View app details"),
|
|
97
|
-
children: [/* @__PURE__ */
|
|
98
|
-
}), /* @__PURE__ */
|
|
135
|
+
children: [/* @__PURE__ */ A(C, { size: 14 }), u("devportal.view", "View")]
|
|
136
|
+
}), /* @__PURE__ */ j("button", {
|
|
99
137
|
onClick: () => f((e) => !e),
|
|
100
138
|
className: "inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken",
|
|
101
|
-
children: [
|
|
139
|
+
children: [A(d ? b : y, { size: 14 }), u("devportal.review", "Review")]
|
|
102
140
|
})]
|
|
103
141
|
})
|
|
104
142
|
]
|
|
105
|
-
}), d && /* @__PURE__ */
|
|
143
|
+
}), d && /* @__PURE__ */ j("div", {
|
|
106
144
|
className: "border-t bg-bg-sunken/30 p-4 space-y-4",
|
|
107
145
|
children: [
|
|
108
|
-
o && /* @__PURE__ */
|
|
146
|
+
o && /* @__PURE__ */ j("div", {
|
|
109
147
|
className: "flex items-start gap-2 rounded-md border border-status-warning-bg bg-status-warning-bg-subtle px-3 py-2.5 text-xs text-status-warning-text",
|
|
110
|
-
children: [/* @__PURE__ */
|
|
148
|
+
children: [/* @__PURE__ */ A(T, {
|
|
111
149
|
size: 14,
|
|
112
150
|
className: "mt-0.5 shrink-0",
|
|
113
151
|
"aria-hidden": "true"
|
|
114
|
-
}), /* @__PURE__ */
|
|
152
|
+
}), /* @__PURE__ */ j("span", { children: [
|
|
115
153
|
"This app has a pending version. Approve the version in the",
|
|
116
154
|
" ",
|
|
117
|
-
/* @__PURE__ */
|
|
155
|
+
/* @__PURE__ */ A("strong", { children: "Version Updates" }),
|
|
118
156
|
" section below before approving the app — the version must be live before the app can go public."
|
|
119
157
|
] })]
|
|
120
158
|
}),
|
|
121
|
-
t.reviewNotes && /* @__PURE__ */
|
|
159
|
+
t.reviewNotes && /* @__PURE__ */ j("div", {
|
|
122
160
|
className: "rounded-md border bg-bg-surface p-3",
|
|
123
|
-
children: [/* @__PURE__ */
|
|
161
|
+
children: [/* @__PURE__ */ A("p", {
|
|
124
162
|
className: "text-xs font-medium text-text-secondary mb-1",
|
|
125
163
|
children: u("devportal.developerNotes", "Developer Notes")
|
|
126
|
-
}), /* @__PURE__ */
|
|
164
|
+
}), /* @__PURE__ */ A("p", {
|
|
127
165
|
className: "text-sm",
|
|
128
166
|
children: t.reviewNotes
|
|
129
167
|
})]
|
|
130
168
|
}),
|
|
131
|
-
/* @__PURE__ */
|
|
169
|
+
/* @__PURE__ */ j("div", {
|
|
132
170
|
className: "grid grid-cols-2 gap-3 text-sm",
|
|
133
171
|
children: [
|
|
134
|
-
/* @__PURE__ */
|
|
172
|
+
/* @__PURE__ */ j("div", { children: [/* @__PURE__ */ j("span", {
|
|
135
173
|
className: "text-text-secondary",
|
|
136
174
|
children: [
|
|
137
175
|
u("devportal.category", "Category"),
|
|
138
176
|
":",
|
|
139
177
|
" "
|
|
140
178
|
]
|
|
141
|
-
}), /* @__PURE__ */
|
|
142
|
-
/* @__PURE__ */
|
|
179
|
+
}), /* @__PURE__ */ A("span", { children: t.category ?? "—" })] }),
|
|
180
|
+
/* @__PURE__ */ j("div", { children: [/* @__PURE__ */ j("span", {
|
|
143
181
|
className: "text-text-secondary",
|
|
144
182
|
children: [
|
|
145
183
|
u("devportal.pricing", "Pricing"),
|
|
146
184
|
":",
|
|
147
185
|
" "
|
|
148
186
|
]
|
|
149
|
-
}), /* @__PURE__ */
|
|
150
|
-
/* @__PURE__ */
|
|
187
|
+
}), /* @__PURE__ */ A("span", { children: t.pricingType ?? "FREE" })] }),
|
|
188
|
+
/* @__PURE__ */ j("div", { children: [/* @__PURE__ */ j("span", {
|
|
151
189
|
className: "text-text-secondary",
|
|
152
190
|
children: [
|
|
153
191
|
u("devportal.website", "Website"),
|
|
154
192
|
":",
|
|
155
193
|
" "
|
|
156
194
|
]
|
|
157
|
-
}), t.websiteUrl ? /* @__PURE__ */
|
|
195
|
+
}), t.websiteUrl ? /* @__PURE__ */ A("a", {
|
|
158
196
|
href: t.websiteUrl,
|
|
159
197
|
target: "_blank",
|
|
160
198
|
rel: "noopener noreferrer",
|
|
161
199
|
className: "text-primary underline hover:no-underline",
|
|
162
200
|
children: u("devportal.link", "Link")
|
|
163
|
-
}) : /* @__PURE__ */
|
|
164
|
-
/* @__PURE__ */
|
|
201
|
+
}) : /* @__PURE__ */ A("span", { children: "—" })] }),
|
|
202
|
+
/* @__PURE__ */ j("div", { children: [/* @__PURE__ */ j("span", {
|
|
165
203
|
className: "text-text-secondary",
|
|
166
204
|
children: [
|
|
167
205
|
u("devportal.privacyPolicy", "Privacy Policy"),
|
|
168
206
|
":",
|
|
169
207
|
" "
|
|
170
208
|
]
|
|
171
|
-
}), t.privacyPolicyUrl ? /* @__PURE__ */
|
|
209
|
+
}), t.privacyPolicyUrl ? /* @__PURE__ */ A("a", {
|
|
172
210
|
href: t.privacyPolicyUrl,
|
|
173
211
|
target: "_blank",
|
|
174
212
|
rel: "noopener noreferrer",
|
|
175
213
|
className: "text-primary underline hover:no-underline",
|
|
176
214
|
children: u("devportal.link", "Link")
|
|
177
|
-
}) : /* @__PURE__ */
|
|
215
|
+
}) : /* @__PURE__ */ A("span", {
|
|
178
216
|
className: "text-status-error-text",
|
|
179
217
|
children: u("devportal.missing", "Missing")
|
|
180
218
|
})] })
|
|
181
219
|
]
|
|
182
220
|
}),
|
|
183
|
-
|
|
221
|
+
F && /* @__PURE__ */ j("div", {
|
|
184
222
|
className: "rounded-md border bg-bg-surface p-3 space-y-3",
|
|
185
223
|
children: [
|
|
186
|
-
/* @__PURE__ */
|
|
224
|
+
/* @__PURE__ */ j("p", {
|
|
187
225
|
className: "text-xs font-medium text-text-secondary",
|
|
188
226
|
children: [
|
|
189
227
|
u("devportal.latestVersion", "Latest Version"),
|
|
190
228
|
":",
|
|
191
229
|
" ",
|
|
192
|
-
/* @__PURE__ */
|
|
230
|
+
/* @__PURE__ */ A("span", {
|
|
193
231
|
className: "font-mono text-text-primary",
|
|
194
|
-
children:
|
|
232
|
+
children: F.version
|
|
195
233
|
})
|
|
196
234
|
]
|
|
197
235
|
}),
|
|
198
|
-
/* @__PURE__ */
|
|
236
|
+
N[t.appType] ? /* @__PURE__ */ A(P, {
|
|
237
|
+
appType: t.appType,
|
|
238
|
+
packageUrl: F.packageUrl,
|
|
239
|
+
packageSize: F.packageSize,
|
|
240
|
+
tr: u
|
|
241
|
+
}) : F.packageUrl && /* @__PURE__ */ A("div", {
|
|
199
242
|
className: "flex flex-wrap gap-2",
|
|
200
|
-
children:
|
|
201
|
-
href:
|
|
243
|
+
children: /* @__PURE__ */ j("a", {
|
|
244
|
+
href: F.packageUrl,
|
|
202
245
|
download: !0,
|
|
203
246
|
target: "_blank",
|
|
204
247
|
rel: "noopener noreferrer",
|
|
205
248
|
className: "inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken",
|
|
206
249
|
children: [
|
|
207
|
-
/* @__PURE__ */
|
|
250
|
+
/* @__PURE__ */ A(S, { size: 14 }),
|
|
208
251
|
u("devportal.downloadPackage", "Download Package"),
|
|
209
|
-
|
|
252
|
+
F.packageSize && /* @__PURE__ */ j("span", {
|
|
210
253
|
className: "text-text-secondary text-xs ml-1",
|
|
211
254
|
children: [
|
|
212
255
|
"(",
|
|
213
|
-
|
|
256
|
+
F.packageSize,
|
|
214
257
|
")"
|
|
215
258
|
]
|
|
216
259
|
})
|
|
217
260
|
]
|
|
218
261
|
})
|
|
219
262
|
}),
|
|
220
|
-
|
|
221
|
-
manifest:
|
|
263
|
+
F.manifest && /* @__PURE__ */ A(M, {
|
|
264
|
+
manifest: F.manifest,
|
|
222
265
|
tr: u
|
|
223
266
|
})
|
|
224
267
|
]
|
|
225
268
|
}),
|
|
226
|
-
/* @__PURE__ */
|
|
269
|
+
/* @__PURE__ */ j("div", { children: [/* @__PURE__ */ A("label", {
|
|
227
270
|
className: "text-sm font-medium",
|
|
228
271
|
children: u("devportal.reviewNotes", "Review Notes")
|
|
229
|
-
}), /* @__PURE__ */
|
|
272
|
+
}), /* @__PURE__ */ A("textarea", {
|
|
230
273
|
value: _,
|
|
231
274
|
onChange: (e) => x(e.target.value),
|
|
232
275
|
placeholder: u("devportal.reviewNotesPlaceholder", "Add feedback for the developer (required for rejection)..."),
|
|
233
276
|
rows: 3,
|
|
234
277
|
className: "mt-1 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background placeholder:text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none"
|
|
235
278
|
})] }),
|
|
236
|
-
/* @__PURE__ */
|
|
279
|
+
/* @__PURE__ */ j("div", {
|
|
237
280
|
className: "flex gap-3",
|
|
238
|
-
children: [/* @__PURE__ */
|
|
281
|
+
children: [/* @__PURE__ */ j("button", {
|
|
239
282
|
onClick: () => n(t.id, _),
|
|
240
283
|
disabled: a || !w.allowed,
|
|
241
284
|
"aria-disabled": a || !w.allowed,
|
|
242
285
|
title: w.allowed ? void 0 : u("devportal.common.noPermission", "You do not have permission to perform this action"),
|
|
243
286
|
className: "inline-flex items-center gap-2 rounded-md bg-status-success-bg px-4 py-2 text-sm font-medium text-white hover:bg-status-success-bg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary",
|
|
244
|
-
children: [a ? /* @__PURE__ */
|
|
287
|
+
children: [a ? /* @__PURE__ */ A(E, {
|
|
245
288
|
size: 14,
|
|
246
289
|
className: "animate-spin",
|
|
247
290
|
"aria-hidden": "true"
|
|
248
|
-
}) : /* @__PURE__ */
|
|
291
|
+
}) : /* @__PURE__ */ A(v, {
|
|
249
292
|
size: 14,
|
|
250
293
|
"aria-hidden": "true"
|
|
251
294
|
}), u("devportal.approve", "Approve")]
|
|
252
|
-
}), /* @__PURE__ */
|
|
295
|
+
}), /* @__PURE__ */ j("button", {
|
|
253
296
|
onClick: () => r(t.id, _),
|
|
254
297
|
disabled: a || !_.trim() || !D.allowed,
|
|
255
298
|
"aria-disabled": a || !_.trim() || !D.allowed,
|
|
256
299
|
className: "inline-flex items-center gap-2 rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary",
|
|
257
300
|
title: D.allowed ? _.trim() ? void 0 : u("devportal.notesRequiredForRejection", "Notes required for rejection") : u("devportal.common.noPermission", "You do not have permission to perform this action"),
|
|
258
|
-
children: [a ? /* @__PURE__ */
|
|
301
|
+
children: [a ? /* @__PURE__ */ A(E, {
|
|
259
302
|
size: 14,
|
|
260
303
|
className: "animate-spin",
|
|
261
304
|
"aria-hidden": "true"
|
|
262
|
-
}) : /* @__PURE__ */ k
|
|
305
|
+
}) : /* @__PURE__ */ A(k, {
|
|
263
306
|
size: 14,
|
|
264
307
|
"aria-hidden": "true"
|
|
265
308
|
}), u("devportal.reject", "Reject")]
|
|
@@ -268,35 +311,35 @@ var j = ({ manifest: e, tr: t }) => {
|
|
|
268
311
|
]
|
|
269
312
|
})]
|
|
270
313
|
});
|
|
271
|
-
},
|
|
314
|
+
}, I = ({ app: t, onApproveVersion: n, onRejectVersion: r, processingVersionIds: i }) => {
|
|
272
315
|
let { navigate: a, basePath: o } = e(), { t: s } = g(), c = (e, t) => {
|
|
273
316
|
let n = s(e);
|
|
274
317
|
return n === e ? t : n;
|
|
275
318
|
}, [l, u] = h(!1), d = t.versions.edges.map((e) => e.node).filter((e) => e.status === "pending_review");
|
|
276
|
-
return d.length === 0 ? null : /* @__PURE__ */
|
|
319
|
+
return d.length === 0 ? null : /* @__PURE__ */ j("div", {
|
|
277
320
|
className: "rounded-card border border-border-seam bg-bg-surface overflow-hidden",
|
|
278
|
-
children: [/* @__PURE__ */
|
|
321
|
+
children: [/* @__PURE__ */ j("div", {
|
|
279
322
|
className: "flex items-center gap-4 p-4",
|
|
280
323
|
children: [
|
|
281
|
-
t.iconUrl ? /* @__PURE__ */
|
|
324
|
+
t.iconUrl ? /* @__PURE__ */ A("img", {
|
|
282
325
|
src: t.iconUrl,
|
|
283
326
|
alt: t.name,
|
|
284
327
|
className: "h-12 w-12 rounded-lg object-cover flex-shrink-0"
|
|
285
|
-
}) : /* @__PURE__ */
|
|
328
|
+
}) : /* @__PURE__ */ A("div", {
|
|
286
329
|
className: "h-12 w-12 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0",
|
|
287
|
-
children: /* @__PURE__ */
|
|
330
|
+
children: /* @__PURE__ */ A("span", {
|
|
288
331
|
className: "text-lg font-bold text-text-secondary",
|
|
289
332
|
children: t.name.charAt(0).toUpperCase()
|
|
290
333
|
})
|
|
291
334
|
}),
|
|
292
|
-
/* @__PURE__ */
|
|
335
|
+
/* @__PURE__ */ j("div", {
|
|
293
336
|
className: "flex-1 min-w-0",
|
|
294
|
-
children: [/* @__PURE__ */
|
|
337
|
+
children: [/* @__PURE__ */ j("div", {
|
|
295
338
|
className: "flex items-center gap-2",
|
|
296
|
-
children: [/* @__PURE__ */
|
|
339
|
+
children: [/* @__PURE__ */ A("h3", {
|
|
297
340
|
className: "font-semibold truncate",
|
|
298
341
|
children: t.name
|
|
299
|
-
}), /* @__PURE__ */
|
|
342
|
+
}), /* @__PURE__ */ j("span", {
|
|
300
343
|
className: "inline-flex items-center rounded-full bg-status-info-bg-subtle px-2 py-0.5 text-xs font-medium text-status-info-text",
|
|
301
344
|
children: [
|
|
302
345
|
d.length,
|
|
@@ -305,29 +348,30 @@ var j = ({ manifest: e, tr: t }) => {
|
|
|
305
348
|
" pending"
|
|
306
349
|
]
|
|
307
350
|
})]
|
|
308
|
-
}), /* @__PURE__ */
|
|
351
|
+
}), /* @__PURE__ */ A("p", {
|
|
309
352
|
className: "text-sm text-text-secondary truncate",
|
|
310
353
|
children: t.shortDescription ?? t.description ?? c("devportal.noDescription", "No description")
|
|
311
354
|
})]
|
|
312
355
|
}),
|
|
313
|
-
/* @__PURE__ */
|
|
356
|
+
/* @__PURE__ */ j("div", {
|
|
314
357
|
className: "flex items-center gap-2 flex-shrink-0",
|
|
315
|
-
children: [/* @__PURE__ */
|
|
358
|
+
children: [/* @__PURE__ */ j("button", {
|
|
316
359
|
onClick: () => a?.(`${o}/apps/${t.id}`),
|
|
317
360
|
className: "inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken",
|
|
318
|
-
children: [/* @__PURE__ */
|
|
319
|
-
}), /* @__PURE__ */
|
|
361
|
+
children: [/* @__PURE__ */ A(C, { size: 14 }), c("devportal.view", "View")]
|
|
362
|
+
}), /* @__PURE__ */ j("button", {
|
|
320
363
|
onClick: () => u((e) => !e),
|
|
321
364
|
className: "inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken",
|
|
322
|
-
children: [
|
|
365
|
+
children: [A(l ? b : y, { size: 14 }), c("devportal.review", "Review")]
|
|
323
366
|
})]
|
|
324
367
|
})
|
|
325
368
|
]
|
|
326
|
-
}), l && /* @__PURE__ */
|
|
369
|
+
}), l && /* @__PURE__ */ A("div", {
|
|
327
370
|
className: "border-t bg-bg-sunken/30 p-4 space-y-4",
|
|
328
|
-
children: d.map((e) => /* @__PURE__ */
|
|
371
|
+
children: d.map((e) => /* @__PURE__ */ A(L, {
|
|
329
372
|
version: e,
|
|
330
373
|
appId: t.id,
|
|
374
|
+
appType: t.appType,
|
|
331
375
|
onApprove: n,
|
|
332
376
|
onReject: r,
|
|
333
377
|
processing: i.has(e.id),
|
|
@@ -335,30 +379,30 @@ var j = ({ manifest: e, tr: t }) => {
|
|
|
335
379
|
}, e.id))
|
|
336
380
|
})]
|
|
337
381
|
});
|
|
338
|
-
},
|
|
339
|
-
let [
|
|
340
|
-
return /* @__PURE__ */
|
|
382
|
+
}, L = ({ version: e, appId: t, appType: n, onApprove: r, onReject: i, processing: a, tr: o }) => {
|
|
383
|
+
let [s, c] = h(""), l = m(p.APP_APPROVE), u = m(p.APP_REJECT);
|
|
384
|
+
return /* @__PURE__ */ j("div", {
|
|
341
385
|
className: "rounded-md border bg-bg-surface p-4 space-y-3",
|
|
342
386
|
children: [
|
|
343
|
-
/* @__PURE__ */
|
|
387
|
+
/* @__PURE__ */ j("div", {
|
|
344
388
|
className: "flex items-center gap-3",
|
|
345
389
|
children: [
|
|
346
|
-
/* @__PURE__ */
|
|
390
|
+
/* @__PURE__ */ A(T, {
|
|
347
391
|
size: 14,
|
|
348
392
|
className: "text-text-secondary flex-shrink-0"
|
|
349
393
|
}),
|
|
350
|
-
/* @__PURE__ */
|
|
394
|
+
/* @__PURE__ */ A("span", {
|
|
351
395
|
className: "font-mono font-semibold text-sm",
|
|
352
396
|
children: e.version
|
|
353
397
|
}),
|
|
354
|
-
e.isLatest && /* @__PURE__ */
|
|
398
|
+
e.isLatest && /* @__PURE__ */ A("span", {
|
|
355
399
|
className: "inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
|
|
356
400
|
children: "Latest"
|
|
357
401
|
}),
|
|
358
|
-
/* @__PURE__ */
|
|
402
|
+
/* @__PURE__ */ j("span", {
|
|
359
403
|
className: "text-xs text-text-secondary ml-auto",
|
|
360
404
|
children: [
|
|
361
|
-
|
|
405
|
+
o("devportal.submitted", "Submitted"),
|
|
362
406
|
":",
|
|
363
407
|
" ",
|
|
364
408
|
new Date(e.createdAt).toLocaleDateString(void 0, {
|
|
@@ -370,22 +414,27 @@ var j = ({ manifest: e, tr: t }) => {
|
|
|
370
414
|
})
|
|
371
415
|
]
|
|
372
416
|
}),
|
|
373
|
-
e.releaseNotes && /* @__PURE__ */
|
|
417
|
+
e.releaseNotes && /* @__PURE__ */ A("p", {
|
|
374
418
|
className: "text-sm text-text-secondary",
|
|
375
419
|
children: e.releaseNotes
|
|
376
420
|
}),
|
|
377
|
-
/* @__PURE__ */
|
|
421
|
+
N[n] ? /* @__PURE__ */ A(P, {
|
|
422
|
+
appType: n,
|
|
423
|
+
packageUrl: e.packageUrl,
|
|
424
|
+
packageSize: e.packageSize,
|
|
425
|
+
tr: o
|
|
426
|
+
}) : e.packageUrl && /* @__PURE__ */ A("div", {
|
|
378
427
|
className: "flex flex-wrap gap-2",
|
|
379
|
-
children:
|
|
428
|
+
children: /* @__PURE__ */ j("a", {
|
|
380
429
|
href: e.packageUrl,
|
|
381
430
|
download: !0,
|
|
382
431
|
target: "_blank",
|
|
383
432
|
rel: "noopener noreferrer",
|
|
384
433
|
className: "inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken",
|
|
385
434
|
children: [
|
|
386
|
-
/* @__PURE__ */
|
|
387
|
-
|
|
388
|
-
e.packageSize && /* @__PURE__ */
|
|
435
|
+
/* @__PURE__ */ A(S, { size: 14 }),
|
|
436
|
+
o("devportal.downloadPackage", "Download Package"),
|
|
437
|
+
e.packageSize && /* @__PURE__ */ j("span", {
|
|
389
438
|
className: "text-text-secondary text-xs ml-1",
|
|
390
439
|
children: [
|
|
391
440
|
"(",
|
|
@@ -396,49 +445,49 @@ var j = ({ manifest: e, tr: t }) => {
|
|
|
396
445
|
]
|
|
397
446
|
})
|
|
398
447
|
}),
|
|
399
|
-
e.manifest && /* @__PURE__ */
|
|
448
|
+
e.manifest && /* @__PURE__ */ A(M, {
|
|
400
449
|
manifest: e.manifest,
|
|
401
|
-
tr:
|
|
450
|
+
tr: o
|
|
402
451
|
}),
|
|
403
|
-
/* @__PURE__ */
|
|
452
|
+
/* @__PURE__ */ j("div", { children: [/* @__PURE__ */ A("label", {
|
|
404
453
|
className: "text-sm font-medium",
|
|
405
|
-
children:
|
|
406
|
-
}), /* @__PURE__ */
|
|
407
|
-
value:
|
|
408
|
-
onChange: (e) =>
|
|
409
|
-
placeholder:
|
|
454
|
+
children: o("devportal.reviewNotes", "Review Notes")
|
|
455
|
+
}), /* @__PURE__ */ A("textarea", {
|
|
456
|
+
value: s,
|
|
457
|
+
onChange: (e) => c(e.target.value),
|
|
458
|
+
placeholder: o("devportal.reviewNotesPlaceholder", "Add feedback for the developer (required for rejection)..."),
|
|
410
459
|
rows: 2,
|
|
411
460
|
className: "mt-1 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background placeholder:text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none"
|
|
412
461
|
})] }),
|
|
413
|
-
/* @__PURE__ */
|
|
462
|
+
/* @__PURE__ */ j("div", {
|
|
414
463
|
className: "flex gap-3",
|
|
415
|
-
children: [/* @__PURE__ */
|
|
416
|
-
onClick: () =>
|
|
417
|
-
disabled:
|
|
418
|
-
"aria-disabled":
|
|
464
|
+
children: [/* @__PURE__ */ j("button", {
|
|
465
|
+
onClick: () => r(e.id, t, s),
|
|
466
|
+
disabled: a || !l.allowed,
|
|
467
|
+
"aria-disabled": a || !l.allowed,
|
|
419
468
|
className: "inline-flex items-center gap-2 rounded-md bg-status-success-bg px-4 py-2 text-sm font-medium text-white hover:bg-status-success-bg/90 disabled:opacity-50 disabled:cursor-not-allowed",
|
|
420
|
-
children: [
|
|
469
|
+
children: [a ? /* @__PURE__ */ A(E, {
|
|
421
470
|
size: 14,
|
|
422
471
|
className: "animate-spin"
|
|
423
|
-
}) : /* @__PURE__ */
|
|
424
|
-
}), /* @__PURE__ */
|
|
425
|
-
onClick: () =>
|
|
426
|
-
disabled:
|
|
427
|
-
"aria-disabled":
|
|
472
|
+
}) : /* @__PURE__ */ A(v, { size: 14 }), o("devportal.approveVersion", "Approve Version")]
|
|
473
|
+
}), /* @__PURE__ */ j("button", {
|
|
474
|
+
onClick: () => i(e.id, t, s),
|
|
475
|
+
disabled: a || !s.trim() || !u.allowed,
|
|
476
|
+
"aria-disabled": a || !s.trim() || !u.allowed,
|
|
428
477
|
className: "inline-flex items-center gap-2 rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50 disabled:cursor-not-allowed",
|
|
429
|
-
children: [
|
|
478
|
+
children: [a ? /* @__PURE__ */ A(E, {
|
|
430
479
|
size: 14,
|
|
431
480
|
className: "animate-spin"
|
|
432
|
-
}) : /* @__PURE__ */ k
|
|
481
|
+
}) : /* @__PURE__ */ A(k, { size: 14 }), o("devportal.rejectVersion", "Reject Version")]
|
|
433
482
|
})]
|
|
434
483
|
})
|
|
435
484
|
]
|
|
436
485
|
});
|
|
437
|
-
},
|
|
486
|
+
}, R = () => {
|
|
438
487
|
let { t: e } = g(), i = (t, n) => {
|
|
439
488
|
let r = e(t);
|
|
440
489
|
return r === t ? n : r;
|
|
441
|
-
}, [m, y] = h(""), [b, S] = h(/* @__PURE__ */ new Set()), [C, w] = h(/* @__PURE__ */ new Set()), [
|
|
490
|
+
}, [m, y] = h(""), [b, S] = h(/* @__PURE__ */ new Set()), [C, w] = h(/* @__PURE__ */ new Set()), [D, k] = h(null), [M, N] = h(null), P = (e) => S((t) => new Set(t).add(e)), L = (e) => S((t) => {
|
|
442
491
|
let n = new Set(t);
|
|
443
492
|
return n.delete(e), n;
|
|
444
493
|
}), R = (e) => w((t) => new Set(t).add(e)), z = (e) => w((t) => {
|
|
@@ -454,98 +503,98 @@ var j = ({ manifest: e, tr: t }) => {
|
|
|
454
503
|
variables: { limit: 100 },
|
|
455
504
|
fetchPolicy: "cache-and-network"
|
|
456
505
|
}), [K] = n(), [q] = s(), [J] = r(), [Y] = c(), X = B?.applications?.edges?.map((e) => e.node) ?? [], Z = W?.applicationsWithPendingVersions?.edges?.map((e) => e.node) ?? [], Q = new Set(Z.map((e) => e.id)), $ = m ? X.filter((e) => e.name.toLowerCase().includes(m.toLowerCase()) || (e.shortDescription ?? "").toLowerCase().includes(m.toLowerCase())) : X, ee = async (e, t) => {
|
|
457
|
-
|
|
506
|
+
P(e), k(null), N(null);
|
|
458
507
|
try {
|
|
459
508
|
let n = await K({ variables: {
|
|
460
509
|
id: e,
|
|
461
510
|
notes: t || void 0
|
|
462
511
|
} });
|
|
463
512
|
if (n.error) throw n.error;
|
|
464
|
-
|
|
513
|
+
N(i("devportal.applicationApprovedSuccess", "Application approved successfully.")), await U();
|
|
465
514
|
} catch (e) {
|
|
466
|
-
|
|
515
|
+
k(e instanceof Error ? e.message : i("devportal.failedToApproveApplication", "Failed to approve application"));
|
|
467
516
|
} finally {
|
|
468
517
|
L(e);
|
|
469
518
|
}
|
|
470
519
|
}, te = async (e, t) => {
|
|
471
520
|
if (t.trim()) {
|
|
472
|
-
|
|
521
|
+
P(e), k(null), N(null);
|
|
473
522
|
try {
|
|
474
523
|
let n = await q({ variables: {
|
|
475
524
|
id: e,
|
|
476
525
|
notes: t
|
|
477
526
|
} });
|
|
478
527
|
if (n.error) throw n.error;
|
|
479
|
-
|
|
528
|
+
N(i("devportal.applicationRejected", "Application rejected.")), await U();
|
|
480
529
|
} catch (e) {
|
|
481
|
-
|
|
530
|
+
k(e instanceof Error ? e.message : i("devportal.failedToRejectApplication", "Failed to reject application"));
|
|
482
531
|
} finally {
|
|
483
532
|
L(e);
|
|
484
533
|
}
|
|
485
534
|
}
|
|
486
535
|
}, ne = async (e, t, n) => {
|
|
487
|
-
R(e),
|
|
536
|
+
R(e), k(null), N(null);
|
|
488
537
|
try {
|
|
489
538
|
let t = await J({ variables: {
|
|
490
539
|
id: e,
|
|
491
540
|
notes: n || void 0
|
|
492
541
|
} });
|
|
493
542
|
if (t.error) throw t.error;
|
|
494
|
-
|
|
543
|
+
N(i("devportal.versionApprovedSuccess", "Version approved and published.")), await G();
|
|
495
544
|
} catch (e) {
|
|
496
|
-
|
|
545
|
+
k(e instanceof Error ? e.message : i("devportal.failedToApproveVersion", "Failed to approve version"));
|
|
497
546
|
} finally {
|
|
498
547
|
z(e);
|
|
499
548
|
}
|
|
500
549
|
}, re = async (e, t, n) => {
|
|
501
550
|
if (n.trim()) {
|
|
502
|
-
R(e),
|
|
551
|
+
R(e), k(null), N(null);
|
|
503
552
|
try {
|
|
504
553
|
let t = await Y({ variables: {
|
|
505
554
|
id: e,
|
|
506
555
|
reason: n
|
|
507
556
|
} });
|
|
508
557
|
if (t.error) throw t.error;
|
|
509
|
-
|
|
558
|
+
N(i("devportal.versionRejectedSuccess", "Version rejected and returned to draft.")), await G();
|
|
510
559
|
} catch (e) {
|
|
511
|
-
|
|
560
|
+
k(e instanceof Error ? e.message : i("devportal.failedToRejectVersion", "Failed to reject version"));
|
|
512
561
|
} finally {
|
|
513
562
|
z(e);
|
|
514
563
|
}
|
|
515
564
|
}
|
|
516
565
|
};
|
|
517
|
-
return V && !B ? /* @__PURE__ */
|
|
566
|
+
return V && !B ? /* @__PURE__ */ A(u, {
|
|
518
567
|
title: i("devportal.appReviewQueue", "App Review Queue"),
|
|
519
568
|
description: i("devportal.appReviewQueueDescription", "Review submitted applications for approval"),
|
|
520
|
-
children: /* @__PURE__ */
|
|
569
|
+
children: /* @__PURE__ */ A("div", {
|
|
521
570
|
className: "flex items-center justify-center py-12",
|
|
522
|
-
children: /* @__PURE__ */
|
|
571
|
+
children: /* @__PURE__ */ A(E, { className: "h-8 w-8 animate-spin text-text-secondary" })
|
|
523
572
|
})
|
|
524
|
-
}) : H ? /* @__PURE__ */
|
|
573
|
+
}) : H ? /* @__PURE__ */ A(u, {
|
|
525
574
|
title: i("devportal.appReviewQueue", "App Review Queue"),
|
|
526
575
|
description: i("devportal.appReviewQueueDescription", "Review submitted applications for approval"),
|
|
527
|
-
children: /* @__PURE__ */
|
|
576
|
+
children: /* @__PURE__ */ j("div", {
|
|
528
577
|
className: "rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center",
|
|
529
578
|
children: [
|
|
530
|
-
/* @__PURE__ */
|
|
531
|
-
/* @__PURE__ */
|
|
579
|
+
/* @__PURE__ */ A(_, { className: "mx-auto h-8 w-8 text-status-error-text" }),
|
|
580
|
+
/* @__PURE__ */ A("p", {
|
|
532
581
|
className: "mt-2 text-sm text-text-secondary",
|
|
533
582
|
children: l(H)
|
|
534
583
|
}),
|
|
535
|
-
/* @__PURE__ */
|
|
584
|
+
/* @__PURE__ */ A("button", {
|
|
536
585
|
onClick: () => U(),
|
|
537
586
|
className: "mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90",
|
|
538
587
|
children: i("devportal.retry", "Retry")
|
|
539
588
|
})
|
|
540
589
|
]
|
|
541
590
|
})
|
|
542
|
-
}) : /* @__PURE__ */
|
|
591
|
+
}) : /* @__PURE__ */ A(f, {
|
|
543
592
|
permission: p.REVIEW_MANAGE,
|
|
544
|
-
children: /* @__PURE__ */
|
|
593
|
+
children: /* @__PURE__ */ j(u, {
|
|
545
594
|
title: i("devportal.appReviewQueue", "App Review Queue"),
|
|
546
595
|
description: i("devportal.appReviewQueueFullDescription", "Review submitted applications for approval or rejection"),
|
|
547
596
|
purpose: i("devportal.appReviewQueuePurpose", "The moderation queue for apps developers have submitted to the marketplace. Open each submission to check its listing, permissions, and behaviour against the guidelines, then approve it to go live or reject it with feedback."),
|
|
548
|
-
actions: /* @__PURE__ */
|
|
597
|
+
actions: /* @__PURE__ */ A("button", {
|
|
549
598
|
onClick: () => {
|
|
550
599
|
U(), G();
|
|
551
600
|
},
|
|
@@ -553,15 +602,15 @@ var j = ({ manifest: e, tr: t }) => {
|
|
|
553
602
|
children: i("devportal.refresh", "Refresh")
|
|
554
603
|
}),
|
|
555
604
|
children: [
|
|
556
|
-
/* @__PURE__ */
|
|
605
|
+
/* @__PURE__ */ j("div", {
|
|
557
606
|
className: "flex items-center gap-6 rounded-card border border-border-seam bg-bg-surface p-4",
|
|
558
607
|
children: [
|
|
559
|
-
/* @__PURE__ */
|
|
608
|
+
/* @__PURE__ */ j("div", {
|
|
560
609
|
className: "flex items-center gap-2",
|
|
561
|
-
children: [/* @__PURE__ */
|
|
610
|
+
children: [/* @__PURE__ */ A(x, {
|
|
562
611
|
size: 18,
|
|
563
612
|
className: "text-status-warning-text"
|
|
564
|
-
}), /* @__PURE__ */
|
|
613
|
+
}), /* @__PURE__ */ j("span", {
|
|
565
614
|
className: "text-sm font-medium",
|
|
566
615
|
children: [
|
|
567
616
|
X.length,
|
|
@@ -570,12 +619,12 @@ var j = ({ manifest: e, tr: t }) => {
|
|
|
570
619
|
]
|
|
571
620
|
})]
|
|
572
621
|
}),
|
|
573
|
-
Z.length > 0 && /* @__PURE__ */
|
|
622
|
+
Z.length > 0 && /* @__PURE__ */ j("div", {
|
|
574
623
|
className: "flex items-center gap-2",
|
|
575
|
-
children: [/* @__PURE__ */
|
|
624
|
+
children: [/* @__PURE__ */ A(T, {
|
|
576
625
|
size: 18,
|
|
577
626
|
className: "text-status-info-text"
|
|
578
|
-
}), /* @__PURE__ */
|
|
627
|
+
}), /* @__PURE__ */ j("span", {
|
|
579
628
|
className: "text-sm font-medium",
|
|
580
629
|
children: [
|
|
581
630
|
Z.length,
|
|
@@ -584,29 +633,29 @@ var j = ({ manifest: e, tr: t }) => {
|
|
|
584
633
|
]
|
|
585
634
|
})]
|
|
586
635
|
}),
|
|
587
|
-
(b.size > 0 || C.size > 0) && /* @__PURE__ */
|
|
636
|
+
(b.size > 0 || C.size > 0) && /* @__PURE__ */ j("div", {
|
|
588
637
|
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
589
|
-
children: [/* @__PURE__ */
|
|
638
|
+
children: [/* @__PURE__ */ A(E, {
|
|
590
639
|
size: 14,
|
|
591
640
|
className: "animate-spin"
|
|
592
641
|
}), i("devportal.processing", "Processing...")]
|
|
593
642
|
})
|
|
594
643
|
]
|
|
595
644
|
}),
|
|
596
|
-
|
|
645
|
+
M && /* @__PURE__ */ j("div", {
|
|
597
646
|
className: "flex items-center gap-2 rounded-lg border border-border-subtle bg-status-success-bg-subtle px-4 py-3 text-sm text-status-success-text",
|
|
598
|
-
children: [/* @__PURE__ */
|
|
647
|
+
children: [/* @__PURE__ */ A(v, { size: 16 }), M]
|
|
599
648
|
}),
|
|
600
|
-
|
|
649
|
+
D && /* @__PURE__ */ j("div", {
|
|
601
650
|
className: "flex items-center gap-2 rounded-lg border border-status-error-border/50 bg-status-error-bg/10 px-4 py-3 text-sm text-status-error-text",
|
|
602
|
-
children: [/* @__PURE__ */
|
|
651
|
+
children: [/* @__PURE__ */ A(_, { size: 16 }), D]
|
|
603
652
|
}),
|
|
604
|
-
X.length > 0 && /* @__PURE__ */
|
|
653
|
+
X.length > 0 && /* @__PURE__ */ j("div", {
|
|
605
654
|
className: "relative max-w-sm",
|
|
606
|
-
children: [/* @__PURE__ */
|
|
655
|
+
children: [/* @__PURE__ */ A(O, {
|
|
607
656
|
size: 14,
|
|
608
657
|
className: "absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary"
|
|
609
|
-
}), /* @__PURE__ */
|
|
658
|
+
}), /* @__PURE__ */ A("input", {
|
|
610
659
|
type: "text",
|
|
611
660
|
placeholder: i("devportal.searchSubmittedApps", "Search submitted apps..."),
|
|
612
661
|
value: m,
|
|
@@ -614,16 +663,16 @@ var j = ({ manifest: e, tr: t }) => {
|
|
|
614
663
|
className: "w-full rounded-md border bg-bg-surface pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
|
615
664
|
})]
|
|
616
665
|
}),
|
|
617
|
-
/* @__PURE__ */
|
|
666
|
+
/* @__PURE__ */ A(d, {
|
|
618
667
|
title: i("devportal.newAppSubmissions", "New App Submissions"),
|
|
619
|
-
children: $.length === 0 ? /* @__PURE__ */
|
|
668
|
+
children: $.length === 0 ? /* @__PURE__ */ A(t, {
|
|
620
669
|
illustration: "empty-data",
|
|
621
|
-
icon: /* @__PURE__ */
|
|
670
|
+
icon: /* @__PURE__ */ A(v, { size: 32 }),
|
|
622
671
|
title: i("devportal.noAppsPendingReview", "No apps pending review"),
|
|
623
672
|
description: m ? i("devportal.noAppsMatchSearch", "No apps match your search") : i("devportal.allApplicationsReviewed", "All submitted applications have been reviewed")
|
|
624
|
-
}) : /* @__PURE__ */
|
|
673
|
+
}) : /* @__PURE__ */ A("div", {
|
|
625
674
|
className: "space-y-3",
|
|
626
|
-
children: $.map((e) => /* @__PURE__ */
|
|
675
|
+
children: $.map((e) => /* @__PURE__ */ A(F, {
|
|
627
676
|
app: e,
|
|
628
677
|
onApprove: ee,
|
|
629
678
|
onReject: te,
|
|
@@ -632,16 +681,16 @@ var j = ({ manifest: e, tr: t }) => {
|
|
|
632
681
|
}, e.id))
|
|
633
682
|
})
|
|
634
683
|
}),
|
|
635
|
-
/* @__PURE__ */
|
|
684
|
+
/* @__PURE__ */ A(d, {
|
|
636
685
|
title: i("devportal.versionUpdates", "Version Updates (Published Apps)"),
|
|
637
|
-
children: Z.length === 0 ? /* @__PURE__ */
|
|
686
|
+
children: Z.length === 0 ? /* @__PURE__ */ A(t, {
|
|
638
687
|
illustration: "empty-data",
|
|
639
|
-
icon: /* @__PURE__ */
|
|
688
|
+
icon: /* @__PURE__ */ A(T, { size: 32 }),
|
|
640
689
|
title: i("devportal.noPendingVersions", "No pending version updates"),
|
|
641
690
|
description: i("devportal.allVersionsReviewed", "All version updates from published apps have been reviewed")
|
|
642
|
-
}) : /* @__PURE__ */
|
|
691
|
+
}) : /* @__PURE__ */ A("div", {
|
|
643
692
|
className: "space-y-3",
|
|
644
|
-
children: Z.map((e) => /* @__PURE__ */
|
|
693
|
+
children: Z.map((e) => /* @__PURE__ */ A(I, {
|
|
645
694
|
app: e,
|
|
646
695
|
onApproveVersion: ne,
|
|
647
696
|
onRejectVersion: re,
|
|
@@ -654,6 +703,6 @@ var j = ({ manifest: e, tr: t }) => {
|
|
|
654
703
|
});
|
|
655
704
|
};
|
|
656
705
|
//#endregion
|
|
657
|
-
export {
|
|
706
|
+
export { R as AdminReviewPage };
|
|
658
707
|
|
|
659
708
|
//# sourceMappingURL=AdminReviewPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminReviewPage.js","names":[],"sources":["../../../src/devportal/pages/AdminReviewPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n CheckCircle,\n XCircle,\n Clock,\n Search,\n Loader2,\n AlertCircle,\n Eye,\n ChevronDown,\n ChevronUp,\n Download,\n FileJson,\n GitBranch,\n} from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport {\n useGetApplicationsQuery,\n useApproveApplicationMutation,\n useRejectApplicationMutation,\n useGetApplicationForReviewQuery,\n useGetApplicationsWithPendingVersionsQuery,\n useApproveApplicationVersionMutation,\n useRejectApplicationVersionMutation,\n getErrorMessage,\n useDevportalPermission,\n type ApplicationFieldsFragment,\n type ApplicationVersionFieldsFragment,\n type GetApplicationsWithPendingVersionsQuery,\n} from \"../hooks\";\n\ntype AppWithPendingVersions = NonNullable<\n GetApplicationsWithPendingVersionsQuery[\"applicationsWithPendingVersions\"][\"edges\"][number][\"node\"]\n>;\n\n// ============================================================================\n// Manifest Viewer\n// ============================================================================\n\ninterface ManifestViewerProps {\n manifest: Record<string, unknown>;\n tr: (key: string, fallback: string) => string;\n}\n\nconst ManifestViewer: FC<ManifestViewerProps> = ({ manifest, tr }) => {\n const [open, setOpen] = useState(false);\n return (\n <div>\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n className=\"inline-flex items-center gap-1.5 text-xs text-text-secondary hover:text-text-primary\"\n >\n <FileJson size={13} />\n {open\n ? tr(\"devportal.hideManifest\", \"Hide manifest\")\n : tr(\"devportal.viewManifest\", \"View manifest.json\")}\n </button>\n {open && (\n <pre className=\"mt-2 max-h-48 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 )}\n </div>\n );\n};\n\n// ============================================================================\n// Review Row Component\n// ============================================================================\n\ninterface ReviewRowProps {\n app: ApplicationFieldsFragment;\n onApprove: (id: string, notes: string) => void;\n onReject: (id: string, notes: string) => void;\n processing: boolean;\n hasPendingVersions: boolean;\n}\n\nconst ReviewRow: FC<ReviewRowProps> = ({\n app,\n onApprove,\n onReject,\n processing,\n hasPendingVersions,\n}) => {\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [expanded, setExpanded] = useState(false);\n const [reviewNotes, setReviewNotes] = useState(\"\");\n const approvePerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_APPROVE);\n const rejectPerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_REJECT);\n\n const { data: versionData } = useGetApplicationForReviewQuery({\n variables: { id: app.id },\n skip: !expanded,\n fetchPolicy: \"cache-first\",\n });\n const latestVersion = versionData?.application?.versions?.edges?.[0]?.node;\n\n return (\n <div className=\"rounded-card border border-border-seam bg-bg-surface overflow-hidden\">\n {/* Header row */}\n <div className=\"flex items-center gap-4 p-4\">\n {app.iconUrl ? (\n <img\n src={app.iconUrl}\n alt={app.name}\n className=\"h-12 w-12 rounded-lg object-cover flex-shrink-0\"\n />\n ) : (\n <div className=\"h-12 w-12 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <span className=\"text-lg font-bold text-text-secondary\">\n {app.name.charAt(0).toUpperCase()}\n </span>\n </div>\n )}\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold truncate\">{app.name}</h3>\n <span className=\"inline-flex items-center rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-xs font-medium text-status-warning-text\">\n {app.status}\n </span>\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {app.appType.replace(/_/g, \" \")}\n </span>\n </div>\n <p className=\"text-sm text-text-secondary truncate\">\n {app.shortDescription ??\n app.description ??\n tr(\"devportal.noDescription\", \"No description\")}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {tr(\"devportal.submitted\", \"Submitted\")}:{\" \"}\n {app.submittedAt\n ? new Date(app.submittedAt).toLocaleDateString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n })\n : tr(\"devportal.unknown\", \"Unknown\")}\n </p>\n </div>\n\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <button\n onClick={() => navigate?.(`${basePath}/apps/${app.id}`)}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n title={tr(\"devportal.viewAppDetails\", \"View app details\")}\n >\n <Eye size={14} />\n {tr(\"devportal.view\", \"View\")}\n </button>\n <button\n onClick={() => setExpanded((v) => !v)}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n {expanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}\n {tr(\"devportal.review\", \"Review\")}\n </button>\n </div>\n </div>\n\n {/* Expanded review panel */}\n {expanded && (\n <div className=\"border-t bg-bg-sunken/30 p-4 space-y-4\">\n {/* Version approval hint — shown when this app also has pending versions */}\n {hasPendingVersions && (\n <div className=\"flex items-start gap-2 rounded-md border border-status-warning-bg bg-status-warning-bg-subtle px-3 py-2.5 text-xs text-status-warning-text\">\n <GitBranch\n size={14}\n className=\"mt-0.5 shrink-0\"\n aria-hidden=\"true\"\n />\n <span>\n This app has a pending version. Approve the version in the{\" \"}\n <strong>Version Updates</strong> section below before approving\n the app — the version must be live before the app can go public.\n </span>\n </div>\n )}\n\n {/* Submission notes from developer */}\n {app.reviewNotes && (\n <div className=\"rounded-md border bg-bg-surface p-3\">\n <p className=\"text-xs font-medium text-text-secondary mb-1\">\n {tr(\"devportal.developerNotes\", \"Developer Notes\")}\n </p>\n <p className=\"text-sm\">{app.reviewNotes}</p>\n </div>\n )}\n\n {/* App details summary */}\n <div className=\"grid grid-cols-2 gap-3 text-sm\">\n <div>\n <span className=\"text-text-secondary\">\n {tr(\"devportal.category\", \"Category\")}:{\" \"}\n </span>\n <span>{app.category ?? \"—\"}</span>\n </div>\n <div>\n <span className=\"text-text-secondary\">\n {tr(\"devportal.pricing\", \"Pricing\")}:{\" \"}\n </span>\n <span>{app.pricingType ?? \"FREE\"}</span>\n </div>\n <div>\n <span className=\"text-text-secondary\">\n {tr(\"devportal.website\", \"Website\")}:{\" \"}\n </span>\n {app.websiteUrl ? (\n <a\n href={app.websiteUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary underline hover:no-underline\"\n >\n {tr(\"devportal.link\", \"Link\")}\n </a>\n ) : (\n <span>—</span>\n )}\n </div>\n <div>\n <span className=\"text-text-secondary\">\n {tr(\"devportal.privacyPolicy\", \"Privacy Policy\")}:{\" \"}\n </span>\n {app.privacyPolicyUrl ? (\n <a\n href={app.privacyPolicyUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary underline hover:no-underline\"\n >\n {tr(\"devportal.link\", \"Link\")}\n </a>\n ) : (\n <span className=\"text-status-error-text\">\n {tr(\"devportal.missing\", \"Missing\")}\n </span>\n )}\n </div>\n </div>\n\n {/* Latest version package for review */}\n {latestVersion && (\n <div className=\"rounded-md border bg-bg-surface p-3 space-y-3\">\n <p className=\"text-xs font-medium text-text-secondary\">\n {tr(\"devportal.latestVersion\", \"Latest Version\")}:{\" \"}\n <span className=\"font-mono text-text-primary\">\n {latestVersion.version}\n </span>\n </p>\n <div className=\"flex flex-wrap gap-2\">\n {latestVersion.packageUrl && (\n <a\n href={latestVersion.packageUrl}\n download\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n <Download size={14} />\n {tr(\"devportal.downloadPackage\", \"Download Package\")}\n {latestVersion.packageSize && (\n <span className=\"text-text-secondary text-xs ml-1\">\n ({latestVersion.packageSize})\n </span>\n )}\n </a>\n )}\n </div>\n {latestVersion.manifest && (\n <ManifestViewer\n manifest={latestVersion.manifest as Record<string, unknown>}\n tr={tr}\n />\n )}\n </div>\n )}\n\n {/* Reviewer notes input */}\n <div>\n <label className=\"text-sm font-medium\">\n {tr(\"devportal.reviewNotes\", \"Review Notes\")}\n </label>\n <textarea\n value={reviewNotes}\n onChange={(e) => setReviewNotes(e.target.value)}\n placeholder={tr(\n \"devportal.reviewNotesPlaceholder\",\n \"Add feedback for the developer (required for rejection)...\",\n )}\n rows={3}\n className=\"mt-1 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background placeholder:text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n\n {/* Action buttons */}\n <div className=\"flex gap-3\">\n <button\n onClick={() => onApprove(app.id, reviewNotes)}\n disabled={processing || !approvePerm.allowed}\n aria-disabled={processing || !approvePerm.allowed}\n title={\n !approvePerm.allowed\n ? tr(\n \"devportal.common.noPermission\",\n \"You do not have permission to perform this action\",\n )\n : undefined\n }\n className=\"inline-flex items-center gap-2 rounded-md bg-status-success-bg px-4 py-2 text-sm font-medium text-white hover:bg-status-success-bg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {processing ? (\n <Loader2\n size={14}\n className=\"animate-spin\"\n aria-hidden=\"true\"\n />\n ) : (\n <CheckCircle size={14} aria-hidden=\"true\" />\n )}\n {tr(\"devportal.approve\", \"Approve\")}\n </button>\n <button\n onClick={() => onReject(app.id, reviewNotes)}\n disabled={\n processing || !reviewNotes.trim() || !rejectPerm.allowed\n }\n aria-disabled={\n processing || !reviewNotes.trim() || !rejectPerm.allowed\n }\n className=\"inline-flex items-center gap-2 rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n title={\n !rejectPerm.allowed\n ? tr(\n \"devportal.common.noPermission\",\n \"You do not have permission to perform this action\",\n )\n : !reviewNotes.trim()\n ? tr(\n \"devportal.notesRequiredForRejection\",\n \"Notes required for rejection\",\n )\n : undefined\n }\n >\n {processing ? (\n <Loader2\n size={14}\n className=\"animate-spin\"\n aria-hidden=\"true\"\n />\n ) : (\n <XCircle size={14} aria-hidden=\"true\" />\n )}\n {tr(\"devportal.reject\", \"Reject\")}\n </button>\n </div>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Version Review Row Component\n// ============================================================================\n\ninterface VersionReviewRowProps {\n app: AppWithPendingVersions;\n onApproveVersion: (versionId: string, appId: string, notes: string) => void;\n onRejectVersion: (versionId: string, appId: string, reason: string) => void;\n processingVersionIds: Set<string>;\n}\n\nconst VersionReviewRow: FC<VersionReviewRowProps> = ({\n app,\n onApproveVersion,\n onRejectVersion,\n processingVersionIds,\n}) => {\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [expanded, setExpanded] = useState(false);\n\n const pendingVersions = app.versions.edges\n .map((e) => e.node)\n .filter((v) => v.status === \"pending_review\");\n\n if (pendingVersions.length === 0) return null;\n\n return (\n <div className=\"rounded-card border border-border-seam bg-bg-surface overflow-hidden\">\n <div className=\"flex items-center gap-4 p-4\">\n {app.iconUrl ? (\n <img\n src={app.iconUrl}\n alt={app.name}\n className=\"h-12 w-12 rounded-lg object-cover flex-shrink-0\"\n />\n ) : (\n <div className=\"h-12 w-12 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <span className=\"text-lg font-bold text-text-secondary\">\n {app.name.charAt(0).toUpperCase()}\n </span>\n </div>\n )}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold truncate\">{app.name}</h3>\n <span className=\"inline-flex items-center rounded-full bg-status-info-bg-subtle px-2 py-0.5 text-xs font-medium text-status-info-text\">\n {pendingVersions.length} version\n {pendingVersions.length !== 1 ? \"s\" : \"\"} pending\n </span>\n </div>\n <p className=\"text-sm text-text-secondary truncate\">\n {app.shortDescription ??\n app.description ??\n tr(\"devportal.noDescription\", \"No description\")}\n </p>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <button\n onClick={() => navigate?.(`${basePath}/apps/${app.id}`)}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n <Eye size={14} />\n {tr(\"devportal.view\", \"View\")}\n </button>\n <button\n onClick={() => setExpanded((v) => !v)}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n {expanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}\n {tr(\"devportal.review\", \"Review\")}\n </button>\n </div>\n </div>\n\n {expanded && (\n <div className=\"border-t bg-bg-sunken/30 p-4 space-y-4\">\n {pendingVersions.map((version) => (\n <VersionCard\n key={version.id}\n version={version}\n appId={app.id}\n onApprove={onApproveVersion}\n onReject={onRejectVersion}\n processing={processingVersionIds.has(version.id)}\n tr={tr}\n />\n ))}\n </div>\n )}\n </div>\n );\n};\n\ntype PendingVersion =\n AppWithPendingVersions[\"versions\"][\"edges\"][number][\"node\"];\n\ninterface VersionCardProps {\n version: PendingVersion;\n appId: string;\n onApprove: (versionId: string, appId: string, notes: string) => void;\n onReject: (versionId: string, appId: string, reason: string) => void;\n processing: boolean;\n tr: (key: string, fallback: string) => string;\n}\n\nconst VersionCard: FC<VersionCardProps> = ({\n version,\n appId,\n onApprove,\n onReject,\n processing,\n tr,\n}) => {\n const [notes, setNotes] = useState(\"\");\n const approvePerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_APPROVE);\n const rejectPerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_REJECT);\n\n return (\n <div className=\"rounded-md border bg-bg-surface p-4 space-y-3\">\n <div className=\"flex items-center gap-3\">\n <GitBranch size={14} className=\"text-text-secondary flex-shrink-0\" />\n <span className=\"font-mono font-semibold text-sm\">\n {version.version}\n </span>\n {version.isLatest && (\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n Latest\n </span>\n )}\n <span className=\"text-xs text-text-secondary ml-auto\">\n {tr(\"devportal.submitted\", \"Submitted\")}:{\" \"}\n {new Date(version.createdAt).toLocaleDateString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n })}\n </span>\n </div>\n\n {version.releaseNotes && (\n <p className=\"text-sm text-text-secondary\">{version.releaseNotes}</p>\n )}\n\n <div className=\"flex flex-wrap gap-2\">\n {version.packageUrl && (\n <a\n href={version.packageUrl}\n download\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n <Download size={14} />\n {tr(\"devportal.downloadPackage\", \"Download Package\")}\n {version.packageSize && (\n <span className=\"text-text-secondary text-xs ml-1\">\n ({version.packageSize})\n </span>\n )}\n </a>\n )}\n </div>\n\n {version.manifest && (\n <ManifestViewer\n manifest={version.manifest as Record<string, unknown>}\n tr={tr}\n />\n )}\n\n <div>\n <label className=\"text-sm font-medium\">\n {tr(\"devportal.reviewNotes\", \"Review Notes\")}\n </label>\n <textarea\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n placeholder={tr(\n \"devportal.reviewNotesPlaceholder\",\n \"Add feedback for the developer (required for rejection)...\",\n )}\n rows={2}\n className=\"mt-1 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background placeholder:text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n\n <div className=\"flex gap-3\">\n <button\n onClick={() => onApprove(version.id, appId, notes)}\n disabled={processing || !approvePerm.allowed}\n aria-disabled={processing || !approvePerm.allowed}\n className=\"inline-flex items-center gap-2 rounded-md bg-status-success-bg px-4 py-2 text-sm font-medium text-white hover:bg-status-success-bg/90 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {processing ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : (\n <CheckCircle size={14} />\n )}\n {tr(\"devportal.approveVersion\", \"Approve Version\")}\n </button>\n <button\n onClick={() => onReject(version.id, appId, notes)}\n disabled={processing || !notes.trim() || !rejectPerm.allowed}\n aria-disabled={processing || !notes.trim() || !rejectPerm.allowed}\n className=\"inline-flex items-center gap-2 rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {processing ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : (\n <XCircle size={14} />\n )}\n {tr(\"devportal.rejectVersion\", \"Reject Version\")}\n </button>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Admin Review Page\n// ============================================================================\n\nexport const AdminReviewPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [processingIds, setProcessingIds] = useState<Set<string>>(new Set());\n const [processingVersionIds, setProcessingVersionIds] = useState<Set<string>>(\n new Set(),\n );\n const [actionError, setActionError] = useState<string | null>(null);\n const [actionSuccess, setActionSuccess] = useState<string | null>(null);\n\n const addProcessing = (id: string) =>\n setProcessingIds((prev) => new Set(prev).add(id));\n const removeProcessing = (id: string) =>\n setProcessingIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n const addProcessingVersion = (id: string) =>\n setProcessingVersionIds((prev) => new Set(prev).add(id));\n const removeProcessingVersion = (id: string) =>\n setProcessingVersionIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n\n const { data, loading, error, refetch } = useGetApplicationsQuery({\n variables: { status: \"SUBMITTED\" as const, limit: 100 },\n fetchPolicy: \"cache-and-network\",\n });\n\n const { data: pendingVersionData, refetch: refetchPendingVersions } =\n useGetApplicationsWithPendingVersionsQuery({\n variables: { limit: 100 },\n fetchPolicy: \"cache-and-network\",\n });\n\n const [approveApplication] = useApproveApplicationMutation();\n const [rejectApplication] = useRejectApplicationMutation();\n const [approveApplicationVersion] = useApproveApplicationVersionMutation();\n const [rejectApplicationVersion] = useRejectApplicationVersionMutation();\n\n const apps =\n data?.applications?.edges?.map(\n (edge: { node: ApplicationFieldsFragment }) => edge.node,\n ) ?? [];\n\n const appsWithPendingVersions =\n pendingVersionData?.applicationsWithPendingVersions?.edges?.map(\n (edge: { node: AppWithPendingVersions }) => edge.node,\n ) ?? [];\n\n const pendingVersionAppIds = new Set(\n appsWithPendingVersions.map((a: AppWithPendingVersions) => a.id),\n );\n\n const filteredApps = searchQuery\n ? apps.filter(\n (app: ApplicationFieldsFragment) =>\n app.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n (app.shortDescription ?? \"\")\n .toLowerCase()\n .includes(searchQuery.toLowerCase()),\n )\n : apps;\n\n const handleApprove = async (id: string, notes: string) => {\n addProcessing(id);\n setActionError(null);\n setActionSuccess(null);\n try {\n const result = await approveApplication({\n variables: { id, notes: notes || undefined },\n });\n if (result.error) {\n throw result.error;\n }\n setActionSuccess(\n tr(\n \"devportal.applicationApprovedSuccess\",\n \"Application approved successfully.\",\n ),\n );\n await refetch();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToApproveApplication\",\n \"Failed to approve application\",\n ),\n );\n } finally {\n removeProcessing(id);\n }\n };\n\n const handleReject = async (id: string, notes: string) => {\n if (!notes.trim()) return;\n addProcessing(id);\n setActionError(null);\n setActionSuccess(null);\n try {\n const result = await rejectApplication({ variables: { id, notes } });\n if (result.error) {\n throw result.error;\n }\n setActionSuccess(\n tr(\"devportal.applicationRejected\", \"Application rejected.\"),\n );\n await refetch();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToRejectApplication\",\n \"Failed to reject application\",\n ),\n );\n } finally {\n removeProcessing(id);\n }\n };\n\n const handleApproveVersion = async (\n versionId: string,\n _appId: string,\n notes: string,\n ) => {\n addProcessingVersion(versionId);\n setActionError(null);\n setActionSuccess(null);\n try {\n const result = await approveApplicationVersion({\n variables: { id: versionId, notes: notes || undefined },\n });\n if (result.error) throw result.error;\n setActionSuccess(\n tr(\n \"devportal.versionApprovedSuccess\",\n \"Version approved and published.\",\n ),\n );\n await refetchPendingVersions();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\"devportal.failedToApproveVersion\", \"Failed to approve version\"),\n );\n } finally {\n removeProcessingVersion(versionId);\n }\n };\n\n const handleRejectVersion = async (\n versionId: string,\n _appId: string,\n reason: string,\n ) => {\n if (!reason.trim()) return;\n addProcessingVersion(versionId);\n setActionError(null);\n setActionSuccess(null);\n try {\n const result = await rejectApplicationVersion({\n variables: { id: versionId, reason },\n });\n if (result.error) throw result.error;\n setActionSuccess(\n tr(\n \"devportal.versionRejectedSuccess\",\n \"Version rejected and returned to draft.\",\n ),\n );\n await refetchPendingVersions();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\"devportal.failedToRejectVersion\", \"Failed to reject version\"),\n );\n } finally {\n removeProcessingVersion(versionId);\n }\n };\n\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.appReviewQueue\", \"App Review Queue\")}\n description={tr(\n \"devportal.appReviewQueueDescription\",\n \"Review submitted applications for approval\",\n )}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n </div>\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.appReviewQueue\", \"App Review Queue\")}\n description={tr(\n \"devportal.appReviewQueueDescription\",\n \"Review submitted applications for approval\",\n )}\n >\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-status-error-text\" />\n <p className=\"mt-2 text-sm text-text-secondary\">\n {getErrorMessage(error)}\n </p>\n <button\n onClick={() => refetch()}\n className=\"mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr(\"devportal.retry\", \"Retry\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.REVIEW_MANAGE}>\n <PageLayout\n title={tr(\"devportal.appReviewQueue\", \"App Review Queue\")}\n description={tr(\n \"devportal.appReviewQueueFullDescription\",\n \"Review submitted applications for approval or rejection\",\n )}\n purpose={tr(\n \"devportal.appReviewQueuePurpose\",\n \"The moderation queue for apps developers have submitted to the marketplace. Open each submission to check its listing, permissions, and behaviour against the guidelines, then approve it to go live or reject it with feedback.\",\n )}\n actions={\n <button\n onClick={() => {\n void refetch();\n void refetchPendingVersions();\n }}\n className=\"rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n {tr(\"devportal.refresh\", \"Refresh\")}\n </button>\n }\n >\n {/* Stats bar */}\n <div className=\"flex items-center gap-6 rounded-card border border-border-seam bg-bg-surface p-4\">\n <div className=\"flex items-center gap-2\">\n <Clock size={18} className=\"text-status-warning-text\" />\n <span className=\"text-sm font-medium\">\n {apps.length} {tr(\"devportal.pendingReview\", \"pending review\")}\n </span>\n </div>\n {appsWithPendingVersions.length > 0 && (\n <div className=\"flex items-center gap-2\">\n <GitBranch size={18} className=\"text-status-info-text\" />\n <span className=\"text-sm font-medium\">\n {appsWithPendingVersions.length}{\" \"}\n {tr(\"devportal.withPendingVersions\", \"with pending versions\")}\n </span>\n </div>\n )}\n {(processingIds.size > 0 || processingVersionIds.size > 0) && (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 size={14} className=\"animate-spin\" />\n {tr(\"devportal.processing\", \"Processing...\")}\n </div>\n )}\n </div>\n\n {/* Feedback messages */}\n {actionSuccess && (\n <div className=\"flex items-center gap-2 rounded-lg border border-border-subtle bg-status-success-bg-subtle px-4 py-3 text-sm text-status-success-text\">\n <CheckCircle size={16} />\n {actionSuccess}\n </div>\n )}\n {actionError && (\n <div className=\"flex items-center gap-2 rounded-lg border border-status-error-border/50 bg-status-error-bg/10 px-4 py-3 text-sm text-status-error-text\">\n <AlertCircle size={16} />\n {actionError}\n </div>\n )}\n\n {/* Search */}\n {apps.length > 0 && (\n <div className=\"relative max-w-sm\">\n <Search\n size={14}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n />\n <input\n type=\"text\"\n placeholder={tr(\n \"devportal.searchSubmittedApps\",\n \"Search submitted apps...\",\n )}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border bg-bg-surface pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n )}\n\n {/* New app submissions */}\n <PageSection\n title={tr(\"devportal.newAppSubmissions\", \"New App Submissions\")}\n >\n {filteredApps.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<CheckCircle size={32} />}\n title={tr(\n \"devportal.noAppsPendingReview\",\n \"No apps pending review\",\n )}\n description={\n searchQuery\n ? tr(\n \"devportal.noAppsMatchSearch\",\n \"No apps match your search\",\n )\n : tr(\n \"devportal.allApplicationsReviewed\",\n \"All submitted applications have been reviewed\",\n )\n }\n />\n ) : (\n <div className=\"space-y-3\">\n {filteredApps.map((app: ApplicationFieldsFragment) => (\n <ReviewRow\n key={app.id}\n app={app}\n onApprove={handleApprove}\n onReject={handleReject}\n processing={processingIds.has(app.id)}\n hasPendingVersions={pendingVersionAppIds.has(app.id)}\n />\n ))}\n </div>\n )}\n </PageSection>\n\n {/* Version updates for published apps */}\n <PageSection\n title={tr(\n \"devportal.versionUpdates\",\n \"Version Updates (Published Apps)\",\n )}\n >\n {appsWithPendingVersions.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<GitBranch size={32} />}\n title={tr(\n \"devportal.noPendingVersions\",\n \"No pending version updates\",\n )}\n description={tr(\n \"devportal.allVersionsReviewed\",\n \"All version updates from published apps have been reviewed\",\n )}\n />\n ) : (\n <div className=\"space-y-3\">\n {appsWithPendingVersions.map((app: AppWithPendingVersions) => (\n <VersionReviewRow\n key={app.id}\n app={app}\n onApproveVersion={handleApproveVersion}\n onRejectVersion={handleRejectVersion}\n processingVersionIds={processingVersionIds}\n />\n ))}\n </div>\n )}\n </PageSection>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AdminReviewPage.js","names":[],"sources":["../../../src/devportal/pages/AdminReviewPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n CheckCircle,\n XCircle,\n Clock,\n Search,\n Loader2,\n AlertCircle,\n Eye,\n ChevronDown,\n ChevronUp,\n Download,\n FileJson,\n GitBranch,\n Package,\n} from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport {\n useGetApplicationsQuery,\n useApproveApplicationMutation,\n useRejectApplicationMutation,\n useGetApplicationForReviewQuery,\n useGetApplicationsWithPendingVersionsQuery,\n useApproveApplicationVersionMutation,\n useRejectApplicationVersionMutation,\n getErrorMessage,\n useDevportalPermission,\n type ApplicationFieldsFragment,\n type ApplicationVersionFieldsFragment,\n type GetApplicationsWithPendingVersionsQuery,\n} from \"../hooks\";\n\ntype AppWithPendingVersions = NonNullable<\n GetApplicationsWithPendingVersionsQuery[\"applicationsWithPendingVersions\"][\"edges\"][number][\"node\"]\n>;\n\n// ============================================================================\n// Manifest Viewer\n// ============================================================================\n\ninterface ManifestViewerProps {\n manifest: Record<string, unknown>;\n tr: (key: string, fallback: string) => string;\n}\n\nconst ManifestViewer: FC<ManifestViewerProps> = ({ manifest, tr }) => {\n const [open, setOpen] = useState(false);\n return (\n <div>\n <button\n type=\"button\"\n onClick={() => setOpen((v) => !v)}\n className=\"inline-flex items-center gap-1.5 text-xs text-text-secondary hover:text-text-primary\"\n >\n <FileJson size={13} />\n {open\n ? tr(\"devportal.hideManifest\", \"Hide manifest\")\n : tr(\"devportal.viewManifest\", \"View manifest.json\")}\n </button>\n {open && (\n <pre className=\"mt-2 max-h-48 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 )}\n </div>\n );\n};\n\n// ============================================================================\n// ZIP verification banner (Node / Workflow apps only)\n// ============================================================================\n\nconst ZIP_TYPE_LABELS: Partial<Record<string, string>> = {\n NODE: \"Node package\",\n WORKFLOW: \"Workflow bundle\",\n};\n\ninterface ZipDownloadProps {\n appType: string;\n packageUrl: string | null | undefined;\n packageSize: string | null | undefined;\n tr: (key: string, fallback: string) => string;\n}\n\nconst ZipDownload: FC<ZipDownloadProps> = ({\n appType,\n packageUrl,\n packageSize,\n tr,\n}) => {\n const label = ZIP_TYPE_LABELS[appType];\n if (!label) return null;\n\n return (\n <div className=\"rounded-lg border border-status-warning-bg bg-status-warning-bg-subtle p-3 space-y-2\">\n <div className=\"flex items-center gap-2 text-xs font-medium text-status-warning-text\">\n <Package size={13} className=\"flex-shrink-0\" />\n {tr(\n \"devportal.zipVerificationRequired\",\n \"ZIP verification required — download and test before approving\",\n )}\n </div>\n {packageUrl ? (\n <a\n href={packageUrl}\n download\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-3 py-1.5 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <Download size={14} />\n {tr(\"devportal.download\", \"Download\")} {label}\n {packageSize && (\n <span className=\"opacity-70 text-xs\">({packageSize})</span>\n )}\n </a>\n ) : (\n <p className=\"text-xs text-status-error-text flex items-center gap-1.5\">\n <AlertCircle size={12} />\n {tr(\n \"devportal.noZipUploaded\",\n \"No ZIP uploaded — developer must upload before this can be approved\",\n )}\n </p>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Review Row Component\n// ============================================================================\n\ninterface ReviewRowProps {\n app: ApplicationFieldsFragment;\n onApprove: (id: string, notes: string) => void;\n onReject: (id: string, notes: string) => void;\n processing: boolean;\n hasPendingVersions: boolean;\n}\n\nconst ReviewRow: FC<ReviewRowProps> = ({\n app,\n onApprove,\n onReject,\n processing,\n hasPendingVersions,\n}) => {\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [expanded, setExpanded] = useState(false);\n const [reviewNotes, setReviewNotes] = useState(\"\");\n const approvePerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_APPROVE);\n const rejectPerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_REJECT);\n\n const { data: versionData } = useGetApplicationForReviewQuery({\n variables: { id: app.id },\n skip: !expanded,\n fetchPolicy: \"cache-first\",\n });\n const latestVersion = versionData?.application?.versions?.edges?.[0]?.node;\n\n return (\n <div className=\"rounded-card border border-border-seam bg-bg-surface overflow-hidden\">\n {/* Header row */}\n <div className=\"flex items-center gap-4 p-4\">\n {app.iconUrl ? (\n <img\n src={app.iconUrl}\n alt={app.name}\n className=\"h-12 w-12 rounded-lg object-cover flex-shrink-0\"\n />\n ) : (\n <div className=\"h-12 w-12 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <span className=\"text-lg font-bold text-text-secondary\">\n {app.name.charAt(0).toUpperCase()}\n </span>\n </div>\n )}\n\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold truncate\">{app.name}</h3>\n <span className=\"inline-flex items-center rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-xs font-medium text-status-warning-text\">\n {app.status}\n </span>\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {app.appType.replace(/_/g, \" \")}\n </span>\n </div>\n <p className=\"text-sm text-text-secondary truncate\">\n {app.shortDescription ??\n app.description ??\n tr(\"devportal.noDescription\", \"No description\")}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {tr(\"devportal.submitted\", \"Submitted\")}:{\" \"}\n {app.submittedAt\n ? new Date(app.submittedAt).toLocaleDateString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n })\n : tr(\"devportal.unknown\", \"Unknown\")}\n </p>\n </div>\n\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <button\n onClick={() => navigate?.(`${basePath}/apps/${app.id}`)}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n title={tr(\"devportal.viewAppDetails\", \"View app details\")}\n >\n <Eye size={14} />\n {tr(\"devportal.view\", \"View\")}\n </button>\n <button\n onClick={() => setExpanded((v) => !v)}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n {expanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}\n {tr(\"devportal.review\", \"Review\")}\n </button>\n </div>\n </div>\n\n {/* Expanded review panel */}\n {expanded && (\n <div className=\"border-t bg-bg-sunken/30 p-4 space-y-4\">\n {/* Version approval hint — shown when this app also has pending versions */}\n {hasPendingVersions && (\n <div className=\"flex items-start gap-2 rounded-md border border-status-warning-bg bg-status-warning-bg-subtle px-3 py-2.5 text-xs text-status-warning-text\">\n <GitBranch\n size={14}\n className=\"mt-0.5 shrink-0\"\n aria-hidden=\"true\"\n />\n <span>\n This app has a pending version. Approve the version in the{\" \"}\n <strong>Version Updates</strong> section below before approving\n the app — the version must be live before the app can go public.\n </span>\n </div>\n )}\n\n {/* Submission notes from developer */}\n {app.reviewNotes && (\n <div className=\"rounded-md border bg-bg-surface p-3\">\n <p className=\"text-xs font-medium text-text-secondary mb-1\">\n {tr(\"devportal.developerNotes\", \"Developer Notes\")}\n </p>\n <p className=\"text-sm\">{app.reviewNotes}</p>\n </div>\n )}\n\n {/* App details summary */}\n <div className=\"grid grid-cols-2 gap-3 text-sm\">\n <div>\n <span className=\"text-text-secondary\">\n {tr(\"devportal.category\", \"Category\")}:{\" \"}\n </span>\n <span>{app.category ?? \"—\"}</span>\n </div>\n <div>\n <span className=\"text-text-secondary\">\n {tr(\"devportal.pricing\", \"Pricing\")}:{\" \"}\n </span>\n <span>{app.pricingType ?? \"FREE\"}</span>\n </div>\n <div>\n <span className=\"text-text-secondary\">\n {tr(\"devportal.website\", \"Website\")}:{\" \"}\n </span>\n {app.websiteUrl ? (\n <a\n href={app.websiteUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary underline hover:no-underline\"\n >\n {tr(\"devportal.link\", \"Link\")}\n </a>\n ) : (\n <span>—</span>\n )}\n </div>\n <div>\n <span className=\"text-text-secondary\">\n {tr(\"devportal.privacyPolicy\", \"Privacy Policy\")}:{\" \"}\n </span>\n {app.privacyPolicyUrl ? (\n <a\n href={app.privacyPolicyUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary underline hover:no-underline\"\n >\n {tr(\"devportal.link\", \"Link\")}\n </a>\n ) : (\n <span className=\"text-status-error-text\">\n {tr(\"devportal.missing\", \"Missing\")}\n </span>\n )}\n </div>\n </div>\n\n {/* Latest version package for review */}\n {latestVersion && (\n <div className=\"rounded-md border bg-bg-surface p-3 space-y-3\">\n <p className=\"text-xs font-medium text-text-secondary\">\n {tr(\"devportal.latestVersion\", \"Latest Version\")}:{\" \"}\n <span className=\"font-mono text-text-primary\">\n {latestVersion.version}\n </span>\n </p>\n\n {/* ZIP download — prominent banner for Node/Workflow, plain link otherwise */}\n {ZIP_TYPE_LABELS[app.appType] ? (\n <ZipDownload\n appType={app.appType}\n packageUrl={latestVersion.packageUrl}\n packageSize={latestVersion.packageSize}\n tr={tr}\n />\n ) : (\n latestVersion.packageUrl && (\n <div className=\"flex flex-wrap gap-2\">\n <a\n href={latestVersion.packageUrl}\n download\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n <Download size={14} />\n {tr(\"devportal.downloadPackage\", \"Download Package\")}\n {latestVersion.packageSize && (\n <span className=\"text-text-secondary text-xs ml-1\">\n ({latestVersion.packageSize})\n </span>\n )}\n </a>\n </div>\n )\n )}\n\n {latestVersion.manifest && (\n <ManifestViewer\n manifest={latestVersion.manifest as Record<string, unknown>}\n tr={tr}\n />\n )}\n </div>\n )}\n\n {/* Reviewer notes input */}\n <div>\n <label className=\"text-sm font-medium\">\n {tr(\"devportal.reviewNotes\", \"Review Notes\")}\n </label>\n <textarea\n value={reviewNotes}\n onChange={(e) => setReviewNotes(e.target.value)}\n placeholder={tr(\n \"devportal.reviewNotesPlaceholder\",\n \"Add feedback for the developer (required for rejection)...\",\n )}\n rows={3}\n className=\"mt-1 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background placeholder:text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n\n {/* Action buttons */}\n <div className=\"flex gap-3\">\n <button\n onClick={() => onApprove(app.id, reviewNotes)}\n disabled={processing || !approvePerm.allowed}\n aria-disabled={processing || !approvePerm.allowed}\n title={\n !approvePerm.allowed\n ? tr(\n \"devportal.common.noPermission\",\n \"You do not have permission to perform this action\",\n )\n : undefined\n }\n className=\"inline-flex items-center gap-2 rounded-md bg-status-success-bg px-4 py-2 text-sm font-medium text-white hover:bg-status-success-bg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {processing ? (\n <Loader2\n size={14}\n className=\"animate-spin\"\n aria-hidden=\"true\"\n />\n ) : (\n <CheckCircle size={14} aria-hidden=\"true\" />\n )}\n {tr(\"devportal.approve\", \"Approve\")}\n </button>\n <button\n onClick={() => onReject(app.id, reviewNotes)}\n disabled={\n processing || !reviewNotes.trim() || !rejectPerm.allowed\n }\n aria-disabled={\n processing || !reviewNotes.trim() || !rejectPerm.allowed\n }\n className=\"inline-flex items-center gap-2 rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n title={\n !rejectPerm.allowed\n ? tr(\n \"devportal.common.noPermission\",\n \"You do not have permission to perform this action\",\n )\n : !reviewNotes.trim()\n ? tr(\n \"devportal.notesRequiredForRejection\",\n \"Notes required for rejection\",\n )\n : undefined\n }\n >\n {processing ? (\n <Loader2\n size={14}\n className=\"animate-spin\"\n aria-hidden=\"true\"\n />\n ) : (\n <XCircle size={14} aria-hidden=\"true\" />\n )}\n {tr(\"devportal.reject\", \"Reject\")}\n </button>\n </div>\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// Version Review Row Component\n// ============================================================================\n\ninterface VersionReviewRowProps {\n app: AppWithPendingVersions;\n onApproveVersion: (versionId: string, appId: string, notes: string) => void;\n onRejectVersion: (versionId: string, appId: string, reason: string) => void;\n processingVersionIds: Set<string>;\n}\n\nconst VersionReviewRow: FC<VersionReviewRowProps> = ({\n app,\n onApproveVersion,\n onRejectVersion,\n processingVersionIds,\n}) => {\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [expanded, setExpanded] = useState(false);\n\n const pendingVersions = app.versions.edges\n .map((e) => e.node)\n .filter((v) => v.status === \"pending_review\");\n\n if (pendingVersions.length === 0) return null;\n\n return (\n <div className=\"rounded-card border border-border-seam bg-bg-surface overflow-hidden\">\n <div className=\"flex items-center gap-4 p-4\">\n {app.iconUrl ? (\n <img\n src={app.iconUrl}\n alt={app.name}\n className=\"h-12 w-12 rounded-lg object-cover flex-shrink-0\"\n />\n ) : (\n <div className=\"h-12 w-12 rounded-lg bg-bg-sunken flex items-center justify-center flex-shrink-0\">\n <span className=\"text-lg font-bold text-text-secondary\">\n {app.name.charAt(0).toUpperCase()}\n </span>\n </div>\n )}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold truncate\">{app.name}</h3>\n <span className=\"inline-flex items-center rounded-full bg-status-info-bg-subtle px-2 py-0.5 text-xs font-medium text-status-info-text\">\n {pendingVersions.length} version\n {pendingVersions.length !== 1 ? \"s\" : \"\"} pending\n </span>\n </div>\n <p className=\"text-sm text-text-secondary truncate\">\n {app.shortDescription ??\n app.description ??\n tr(\"devportal.noDescription\", \"No description\")}\n </p>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n <button\n onClick={() => navigate?.(`${basePath}/apps/${app.id}`)}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n <Eye size={14} />\n {tr(\"devportal.view\", \"View\")}\n </button>\n <button\n onClick={() => setExpanded((v) => !v)}\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n {expanded ? <ChevronUp size={14} /> : <ChevronDown size={14} />}\n {tr(\"devportal.review\", \"Review\")}\n </button>\n </div>\n </div>\n\n {expanded && (\n <div className=\"border-t bg-bg-sunken/30 p-4 space-y-4\">\n {pendingVersions.map((version) => (\n <VersionCard\n key={version.id}\n version={version}\n appId={app.id}\n appType={app.appType}\n onApprove={onApproveVersion}\n onReject={onRejectVersion}\n processing={processingVersionIds.has(version.id)}\n tr={tr}\n />\n ))}\n </div>\n )}\n </div>\n );\n};\n\ntype PendingVersion =\n AppWithPendingVersions[\"versions\"][\"edges\"][number][\"node\"];\n\ninterface VersionCardProps {\n version: PendingVersion;\n appId: string;\n appType: string;\n onApprove: (versionId: string, appId: string, notes: string) => void;\n onReject: (versionId: string, appId: string, reason: string) => void;\n processing: boolean;\n tr: (key: string, fallback: string) => string;\n}\n\nconst VersionCard: FC<VersionCardProps> = ({\n version,\n appId,\n appType,\n onApprove,\n onReject,\n processing,\n tr,\n}) => {\n const [notes, setNotes] = useState(\"\");\n const approvePerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_APPROVE);\n const rejectPerm = useDevportalPermission(DEVPORTAL_PERMISSIONS.APP_REJECT);\n\n return (\n <div className=\"rounded-md border bg-bg-surface p-4 space-y-3\">\n <div className=\"flex items-center gap-3\">\n <GitBranch size={14} className=\"text-text-secondary flex-shrink-0\" />\n <span className=\"font-mono font-semibold text-sm\">\n {version.version}\n </span>\n {version.isLatest && (\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n Latest\n </span>\n )}\n <span className=\"text-xs text-text-secondary ml-auto\">\n {tr(\"devportal.submitted\", \"Submitted\")}:{\" \"}\n {new Date(version.createdAt).toLocaleDateString(undefined, {\n year: \"numeric\",\n month: \"short\",\n day: \"numeric\",\n })}\n </span>\n </div>\n\n {version.releaseNotes && (\n <p className=\"text-sm text-text-secondary\">{version.releaseNotes}</p>\n )}\n\n {ZIP_TYPE_LABELS[appType] ? (\n <ZipDownload\n appType={appType}\n packageUrl={version.packageUrl}\n packageSize={version.packageSize}\n tr={tr}\n />\n ) : (\n version.packageUrl && (\n <div className=\"flex flex-wrap gap-2\">\n <a\n href={version.packageUrl}\n download\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n <Download size={14} />\n {tr(\"devportal.downloadPackage\", \"Download Package\")}\n {version.packageSize && (\n <span className=\"text-text-secondary text-xs ml-1\">\n ({version.packageSize})\n </span>\n )}\n </a>\n </div>\n )\n )}\n\n {version.manifest && (\n <ManifestViewer\n manifest={version.manifest as Record<string, unknown>}\n tr={tr}\n />\n )}\n\n <div>\n <label className=\"text-sm font-medium\">\n {tr(\"devportal.reviewNotes\", \"Review Notes\")}\n </label>\n <textarea\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n placeholder={tr(\n \"devportal.reviewNotesPlaceholder\",\n \"Add feedback for the developer (required for rejection)...\",\n )}\n rows={2}\n className=\"mt-1 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background placeholder:text-text-secondary focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] resize-none\"\n />\n </div>\n\n <div className=\"flex gap-3\">\n <button\n onClick={() => onApprove(version.id, appId, notes)}\n disabled={processing || !approvePerm.allowed}\n aria-disabled={processing || !approvePerm.allowed}\n className=\"inline-flex items-center gap-2 rounded-md bg-status-success-bg px-4 py-2 text-sm font-medium text-white hover:bg-status-success-bg/90 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {processing ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : (\n <CheckCircle size={14} />\n )}\n {tr(\"devportal.approveVersion\", \"Approve Version\")}\n </button>\n <button\n onClick={() => onReject(version.id, appId, notes)}\n disabled={processing || !notes.trim() || !rejectPerm.allowed}\n aria-disabled={processing || !notes.trim() || !rejectPerm.allowed}\n className=\"inline-flex items-center gap-2 rounded-md bg-status-error-bg px-4 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg/90 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {processing ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : (\n <XCircle size={14} />\n )}\n {tr(\"devportal.rejectVersion\", \"Reject Version\")}\n </button>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Admin Review Page\n// ============================================================================\n\nexport const AdminReviewPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [processingIds, setProcessingIds] = useState<Set<string>>(new Set());\n const [processingVersionIds, setProcessingVersionIds] = useState<Set<string>>(\n new Set(),\n );\n const [actionError, setActionError] = useState<string | null>(null);\n const [actionSuccess, setActionSuccess] = useState<string | null>(null);\n\n const addProcessing = (id: string) =>\n setProcessingIds((prev) => new Set(prev).add(id));\n const removeProcessing = (id: string) =>\n setProcessingIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n const addProcessingVersion = (id: string) =>\n setProcessingVersionIds((prev) => new Set(prev).add(id));\n const removeProcessingVersion = (id: string) =>\n setProcessingVersionIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n\n const { data, loading, error, refetch } = useGetApplicationsQuery({\n variables: { status: \"SUBMITTED\" as const, limit: 100 },\n fetchPolicy: \"cache-and-network\",\n });\n\n const { data: pendingVersionData, refetch: refetchPendingVersions } =\n useGetApplicationsWithPendingVersionsQuery({\n variables: { limit: 100 },\n fetchPolicy: \"cache-and-network\",\n });\n\n const [approveApplication] = useApproveApplicationMutation();\n const [rejectApplication] = useRejectApplicationMutation();\n const [approveApplicationVersion] = useApproveApplicationVersionMutation();\n const [rejectApplicationVersion] = useRejectApplicationVersionMutation();\n\n const apps =\n data?.applications?.edges?.map(\n (edge: { node: ApplicationFieldsFragment }) => edge.node,\n ) ?? [];\n\n const appsWithPendingVersions =\n pendingVersionData?.applicationsWithPendingVersions?.edges?.map(\n (edge: { node: AppWithPendingVersions }) => edge.node,\n ) ?? [];\n\n const pendingVersionAppIds = new Set(\n appsWithPendingVersions.map((a: AppWithPendingVersions) => a.id),\n );\n\n const filteredApps = searchQuery\n ? apps.filter(\n (app: ApplicationFieldsFragment) =>\n app.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n (app.shortDescription ?? \"\")\n .toLowerCase()\n .includes(searchQuery.toLowerCase()),\n )\n : apps;\n\n const handleApprove = async (id: string, notes: string) => {\n addProcessing(id);\n setActionError(null);\n setActionSuccess(null);\n try {\n const result = await approveApplication({\n variables: { id, notes: notes || undefined },\n });\n if (result.error) {\n throw result.error;\n }\n setActionSuccess(\n tr(\n \"devportal.applicationApprovedSuccess\",\n \"Application approved successfully.\",\n ),\n );\n await refetch();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToApproveApplication\",\n \"Failed to approve application\",\n ),\n );\n } finally {\n removeProcessing(id);\n }\n };\n\n const handleReject = async (id: string, notes: string) => {\n if (!notes.trim()) return;\n addProcessing(id);\n setActionError(null);\n setActionSuccess(null);\n try {\n const result = await rejectApplication({ variables: { id, notes } });\n if (result.error) {\n throw result.error;\n }\n setActionSuccess(\n tr(\"devportal.applicationRejected\", \"Application rejected.\"),\n );\n await refetch();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToRejectApplication\",\n \"Failed to reject application\",\n ),\n );\n } finally {\n removeProcessing(id);\n }\n };\n\n const handleApproveVersion = async (\n versionId: string,\n _appId: string,\n notes: string,\n ) => {\n addProcessingVersion(versionId);\n setActionError(null);\n setActionSuccess(null);\n try {\n const result = await approveApplicationVersion({\n variables: { id: versionId, notes: notes || undefined },\n });\n if (result.error) throw result.error;\n setActionSuccess(\n tr(\n \"devportal.versionApprovedSuccess\",\n \"Version approved and published.\",\n ),\n );\n await refetchPendingVersions();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\"devportal.failedToApproveVersion\", \"Failed to approve version\"),\n );\n } finally {\n removeProcessingVersion(versionId);\n }\n };\n\n const handleRejectVersion = async (\n versionId: string,\n _appId: string,\n reason: string,\n ) => {\n if (!reason.trim()) return;\n addProcessingVersion(versionId);\n setActionError(null);\n setActionSuccess(null);\n try {\n const result = await rejectApplicationVersion({\n variables: { id: versionId, reason },\n });\n if (result.error) throw result.error;\n setActionSuccess(\n tr(\n \"devportal.versionRejectedSuccess\",\n \"Version rejected and returned to draft.\",\n ),\n );\n await refetchPendingVersions();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\"devportal.failedToRejectVersion\", \"Failed to reject version\"),\n );\n } finally {\n removeProcessingVersion(versionId);\n }\n };\n\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.appReviewQueue\", \"App Review Queue\")}\n description={tr(\n \"devportal.appReviewQueueDescription\",\n \"Review submitted applications for approval\",\n )}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n </div>\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.appReviewQueue\", \"App Review Queue\")}\n description={tr(\n \"devportal.appReviewQueueDescription\",\n \"Review submitted applications for approval\",\n )}\n >\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-status-error-text\" />\n <p className=\"mt-2 text-sm text-text-secondary\">\n {getErrorMessage(error)}\n </p>\n <button\n onClick={() => refetch()}\n className=\"mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr(\"devportal.retry\", \"Retry\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.REVIEW_MANAGE}>\n <PageLayout\n title={tr(\"devportal.appReviewQueue\", \"App Review Queue\")}\n description={tr(\n \"devportal.appReviewQueueFullDescription\",\n \"Review submitted applications for approval or rejection\",\n )}\n purpose={tr(\n \"devportal.appReviewQueuePurpose\",\n \"The moderation queue for apps developers have submitted to the marketplace. Open each submission to check its listing, permissions, and behaviour against the guidelines, then approve it to go live or reject it with feedback.\",\n )}\n actions={\n <button\n onClick={() => {\n void refetch();\n void refetchPendingVersions();\n }}\n className=\"rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken\"\n >\n {tr(\"devportal.refresh\", \"Refresh\")}\n </button>\n }\n >\n {/* Stats bar */}\n <div className=\"flex items-center gap-6 rounded-card border border-border-seam bg-bg-surface p-4\">\n <div className=\"flex items-center gap-2\">\n <Clock size={18} className=\"text-status-warning-text\" />\n <span className=\"text-sm font-medium\">\n {apps.length} {tr(\"devportal.pendingReview\", \"pending review\")}\n </span>\n </div>\n {appsWithPendingVersions.length > 0 && (\n <div className=\"flex items-center gap-2\">\n <GitBranch size={18} className=\"text-status-info-text\" />\n <span className=\"text-sm font-medium\">\n {appsWithPendingVersions.length}{\" \"}\n {tr(\"devportal.withPendingVersions\", \"with pending versions\")}\n </span>\n </div>\n )}\n {(processingIds.size > 0 || processingVersionIds.size > 0) && (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 size={14} className=\"animate-spin\" />\n {tr(\"devportal.processing\", \"Processing...\")}\n </div>\n )}\n </div>\n\n {/* Feedback messages */}\n {actionSuccess && (\n <div className=\"flex items-center gap-2 rounded-lg border border-border-subtle bg-status-success-bg-subtle px-4 py-3 text-sm text-status-success-text\">\n <CheckCircle size={16} />\n {actionSuccess}\n </div>\n )}\n {actionError && (\n <div className=\"flex items-center gap-2 rounded-lg border border-status-error-border/50 bg-status-error-bg/10 px-4 py-3 text-sm text-status-error-text\">\n <AlertCircle size={16} />\n {actionError}\n </div>\n )}\n\n {/* Search */}\n {apps.length > 0 && (\n <div className=\"relative max-w-sm\">\n <Search\n size={14}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n />\n <input\n type=\"text\"\n placeholder={tr(\n \"devportal.searchSubmittedApps\",\n \"Search submitted apps...\",\n )}\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border bg-bg-surface pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n )}\n\n {/* New app submissions */}\n <PageSection\n title={tr(\"devportal.newAppSubmissions\", \"New App Submissions\")}\n >\n {filteredApps.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<CheckCircle size={32} />}\n title={tr(\n \"devportal.noAppsPendingReview\",\n \"No apps pending review\",\n )}\n description={\n searchQuery\n ? tr(\n \"devportal.noAppsMatchSearch\",\n \"No apps match your search\",\n )\n : tr(\n \"devportal.allApplicationsReviewed\",\n \"All submitted applications have been reviewed\",\n )\n }\n />\n ) : (\n <div className=\"space-y-3\">\n {filteredApps.map((app: ApplicationFieldsFragment) => (\n <ReviewRow\n key={app.id}\n app={app}\n onApprove={handleApprove}\n onReject={handleReject}\n processing={processingIds.has(app.id)}\n hasPendingVersions={pendingVersionAppIds.has(app.id)}\n />\n ))}\n </div>\n )}\n </PageSection>\n\n {/* Version updates for published apps */}\n <PageSection\n title={tr(\n \"devportal.versionUpdates\",\n \"Version Updates (Published Apps)\",\n )}\n >\n {appsWithPendingVersions.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<GitBranch size={32} />}\n title={tr(\n \"devportal.noPendingVersions\",\n \"No pending version updates\",\n )}\n description={tr(\n \"devportal.allVersionsReviewed\",\n \"All version updates from published apps have been reviewed\",\n )}\n />\n ) : (\n <div className=\"space-y-3\">\n {appsWithPendingVersions.map((app: AppWithPendingVersions) => (\n <VersionReviewRow\n key={app.id}\n app={app}\n onApproveVersion={handleApproveVersion}\n onRejectVersion={handleRejectVersion}\n processingVersionIds={processingVersionIds}\n />\n ))}\n </div>\n )}\n </PageSection>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|