@burdenoff/microfe-devportal 2026.713.3 → 2026.713.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.
@@ -24,15 +24,15 @@ var j = ({ manifest: e, tr: t }) => {
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
- }, M = ({ app: t, onApprove: n, onReject: r, processing: a }) => {
28
- let { navigate: o, basePath: s } = e(), { t: c } = g(), l = (e, t) => {
29
- let n = c(e);
27
+ }, M = ({ app: t, onApprove: n, onReject: r, processing: a, hasPendingVersions: o }) => {
28
+ let { navigate: s, basePath: c } = e(), { t: l } = g(), u = (e, t) => {
29
+ let n = l(e);
30
30
  return n === e ? t : n;
31
- }, [u, d] = h(!1), [f, _] = h(""), x = m(p.APP_APPROVE), w = m(p.APP_REJECT), { data: T } = i({
31
+ }, [d, f] = h(!1), [_, x] = h(""), w = m(p.APP_APPROVE), D = m(p.APP_REJECT), { data: M } = i({
32
32
  variables: { id: t.id },
33
- skip: !u,
33
+ skip: !d,
34
34
  fetchPolicy: "cache-first"
35
- }), D = T?.application?.versions?.edges?.[0]?.node;
35
+ }), N = M?.application?.versions?.edges?.[0]?.node;
36
36
  return /* @__PURE__ */ A("div", {
37
37
  className: "rounded-card border border-border-seam bg-bg-surface overflow-hidden",
38
38
  children: [/* @__PURE__ */ A("div", {
@@ -71,19 +71,19 @@ var j = ({ manifest: e, tr: t }) => {
71
71
  }),
72
72
  /* @__PURE__ */ k("p", {
73
73
  className: "text-sm text-text-secondary truncate",
74
- children: t.shortDescription ?? t.description ?? l("devportal.noDescription", "No description")
74
+ children: t.shortDescription ?? t.description ?? u("devportal.noDescription", "No description")
75
75
  }),
76
76
  /* @__PURE__ */ A("p", {
77
77
  className: "text-xs text-text-secondary mt-0.5",
78
78
  children: [
79
- l("devportal.submitted", "Submitted"),
79
+ u("devportal.submitted", "Submitted"),
80
80
  ":",
81
81
  " ",
82
82
  t.submittedAt ? new Date(t.submittedAt).toLocaleDateString(void 0, {
83
83
  year: "numeric",
84
84
  month: "short",
85
85
  day: "numeric"
86
- }) : l("devportal.unknown", "Unknown")
86
+ }) : u("devportal.unknown", "Unknown")
87
87
  ]
88
88
  })
89
89
  ]
@@ -91,25 +91,38 @@ var j = ({ manifest: e, tr: t }) => {
91
91
  /* @__PURE__ */ A("div", {
92
92
  className: "flex items-center gap-2 flex-shrink-0",
93
93
  children: [/* @__PURE__ */ A("button", {
94
- onClick: () => o?.(`${s}/apps/${t.id}`),
94
+ onClick: () => s?.(`${c}/apps/${t.id}`),
95
95
  className: "inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken",
96
- title: l("devportal.viewAppDetails", "View app details"),
97
- children: [/* @__PURE__ */ k(C, { size: 14 }), l("devportal.view", "View")]
96
+ title: u("devportal.viewAppDetails", "View app details"),
97
+ children: [/* @__PURE__ */ k(C, { size: 14 }), u("devportal.view", "View")]
98
98
  }), /* @__PURE__ */ A("button", {
99
- onClick: () => d((e) => !e),
99
+ onClick: () => f((e) => !e),
100
100
  className: "inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken",
101
- children: [k(u ? b : y, { size: 14 }), l("devportal.review", "Review")]
101
+ children: [k(d ? b : y, { size: 14 }), u("devportal.review", "Review")]
102
102
  })]
103
103
  })
104
104
  ]
105
- }), u && /* @__PURE__ */ A("div", {
105
+ }), d && /* @__PURE__ */ A("div", {
106
106
  className: "border-t bg-bg-sunken/30 p-4 space-y-4",
107
107
  children: [
108
+ o && /* @__PURE__ */ A("div", {
109
+ 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__ */ k(T, {
111
+ size: 14,
112
+ className: "mt-0.5 shrink-0",
113
+ "aria-hidden": "true"
114
+ }), /* @__PURE__ */ A("span", { children: [
115
+ "This app has a pending version. Approve the version in the",
116
+ " ",
117
+ /* @__PURE__ */ k("strong", { children: "Version Updates" }),
118
+ " section below before approving the app — the version must be live before the app can go public."
119
+ ] })]
120
+ }),
108
121
  t.reviewNotes && /* @__PURE__ */ A("div", {
109
122
  className: "rounded-md border bg-bg-surface p-3",
110
123
  children: [/* @__PURE__ */ k("p", {
111
124
  className: "text-xs font-medium text-text-secondary mb-1",
112
- children: l("devportal.developerNotes", "Developer Notes")
125
+ children: u("devportal.developerNotes", "Developer Notes")
113
126
  }), /* @__PURE__ */ k("p", {
114
127
  className: "text-sm",
115
128
  children: t.reviewNotes
@@ -121,7 +134,7 @@ var j = ({ manifest: e, tr: t }) => {
121
134
  /* @__PURE__ */ A("div", { children: [/* @__PURE__ */ A("span", {
122
135
  className: "text-text-secondary",
123
136
  children: [
124
- l("devportal.category", "Category"),
137
+ u("devportal.category", "Category"),
125
138
  ":",
126
139
  " "
127
140
  ]
@@ -129,7 +142,7 @@ var j = ({ manifest: e, tr: t }) => {
129
142
  /* @__PURE__ */ A("div", { children: [/* @__PURE__ */ A("span", {
130
143
  className: "text-text-secondary",
131
144
  children: [
132
- l("devportal.pricing", "Pricing"),
145
+ u("devportal.pricing", "Pricing"),
133
146
  ":",
134
147
  " "
135
148
  ]
@@ -137,7 +150,7 @@ var j = ({ manifest: e, tr: t }) => {
137
150
  /* @__PURE__ */ A("div", { children: [/* @__PURE__ */ A("span", {
138
151
  className: "text-text-secondary",
139
152
  children: [
140
- l("devportal.website", "Website"),
153
+ u("devportal.website", "Website"),
141
154
  ":",
142
155
  " "
143
156
  ]
@@ -146,12 +159,12 @@ var j = ({ manifest: e, tr: t }) => {
146
159
  target: "_blank",
147
160
  rel: "noopener noreferrer",
148
161
  className: "text-primary underline hover:no-underline",
149
- children: l("devportal.link", "Link")
162
+ children: u("devportal.link", "Link")
150
163
  }) : /* @__PURE__ */ k("span", { children: "—" })] }),
151
164
  /* @__PURE__ */ A("div", { children: [/* @__PURE__ */ A("span", {
152
165
  className: "text-text-secondary",
153
166
  children: [
154
- l("devportal.privacyPolicy", "Privacy Policy"),
167
+ u("devportal.privacyPolicy", "Privacy Policy"),
155
168
  ":",
156
169
  " "
157
170
  ]
@@ -160,73 +173,73 @@ var j = ({ manifest: e, tr: t }) => {
160
173
  target: "_blank",
161
174
  rel: "noopener noreferrer",
162
175
  className: "text-primary underline hover:no-underline",
163
- children: l("devportal.link", "Link")
176
+ children: u("devportal.link", "Link")
164
177
  }) : /* @__PURE__ */ k("span", {
165
178
  className: "text-status-error-text",
166
- children: l("devportal.missing", "Missing")
179
+ children: u("devportal.missing", "Missing")
167
180
  })] })
168
181
  ]
169
182
  }),
170
- D && /* @__PURE__ */ A("div", {
183
+ N && /* @__PURE__ */ A("div", {
171
184
  className: "rounded-md border bg-bg-surface p-3 space-y-3",
172
185
  children: [
173
186
  /* @__PURE__ */ A("p", {
174
187
  className: "text-xs font-medium text-text-secondary",
175
188
  children: [
176
- l("devportal.latestVersion", "Latest Version"),
189
+ u("devportal.latestVersion", "Latest Version"),
177
190
  ":",
178
191
  " ",
179
192
  /* @__PURE__ */ k("span", {
180
193
  className: "font-mono text-text-primary",
181
- children: D.version
194
+ children: N.version
182
195
  })
183
196
  ]
184
197
  }),
185
198
  /* @__PURE__ */ k("div", {
186
199
  className: "flex flex-wrap gap-2",
187
- children: D.packageUrl && /* @__PURE__ */ A("a", {
188
- href: D.packageUrl,
200
+ children: N.packageUrl && /* @__PURE__ */ A("a", {
201
+ href: N.packageUrl,
189
202
  download: !0,
190
203
  target: "_blank",
191
204
  rel: "noopener noreferrer",
192
205
  className: "inline-flex items-center gap-1.5 rounded-md border px-3 py-1.5 text-sm hover:bg-bg-sunken",
193
206
  children: [
194
207
  /* @__PURE__ */ k(S, { size: 14 }),
195
- l("devportal.downloadPackage", "Download Package"),
196
- D.packageSize && /* @__PURE__ */ A("span", {
208
+ u("devportal.downloadPackage", "Download Package"),
209
+ N.packageSize && /* @__PURE__ */ A("span", {
197
210
  className: "text-text-secondary text-xs ml-1",
198
211
  children: [
199
212
  "(",
200
- D.packageSize,
213
+ N.packageSize,
201
214
  ")"
202
215
  ]
203
216
  })
204
217
  ]
205
218
  })
206
219
  }),
207
- D.manifest && /* @__PURE__ */ k(j, {
208
- manifest: D.manifest,
209
- tr: l
220
+ N.manifest && /* @__PURE__ */ k(j, {
221
+ manifest: N.manifest,
222
+ tr: u
210
223
  })
211
224
  ]
212
225
  }),
213
226
  /* @__PURE__ */ A("div", { children: [/* @__PURE__ */ k("label", {
214
227
  className: "text-sm font-medium",
215
- children: l("devportal.reviewNotes", "Review Notes")
228
+ children: u("devportal.reviewNotes", "Review Notes")
216
229
  }), /* @__PURE__ */ k("textarea", {
217
- value: f,
218
- onChange: (e) => _(e.target.value),
219
- placeholder: l("devportal.reviewNotesPlaceholder", "Add feedback for the developer (required for rejection)..."),
230
+ value: _,
231
+ onChange: (e) => x(e.target.value),
232
+ placeholder: u("devportal.reviewNotesPlaceholder", "Add feedback for the developer (required for rejection)..."),
220
233
  rows: 3,
221
234
  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"
222
235
  })] }),
223
236
  /* @__PURE__ */ A("div", {
224
237
  className: "flex gap-3",
225
238
  children: [/* @__PURE__ */ A("button", {
226
- onClick: () => n(t.id, f),
227
- disabled: a || !x.allowed,
228
- "aria-disabled": a || !x.allowed,
229
- title: x.allowed ? void 0 : l("devportal.common.noPermission", "You do not have permission to perform this action"),
239
+ onClick: () => n(t.id, _),
240
+ disabled: a || !w.allowed,
241
+ "aria-disabled": a || !w.allowed,
242
+ title: w.allowed ? void 0 : u("devportal.common.noPermission", "You do not have permission to perform this action"),
230
243
  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",
231
244
  children: [a ? /* @__PURE__ */ k(E, {
232
245
  size: 14,
@@ -235,13 +248,13 @@ var j = ({ manifest: e, tr: t }) => {
235
248
  }) : /* @__PURE__ */ k(v, {
236
249
  size: 14,
237
250
  "aria-hidden": "true"
238
- }), l("devportal.approve", "Approve")]
251
+ }), u("devportal.approve", "Approve")]
239
252
  }), /* @__PURE__ */ A("button", {
240
- onClick: () => r(t.id, f),
241
- disabled: a || !f.trim() || !w.allowed,
242
- "aria-disabled": a || !f.trim() || !w.allowed,
253
+ onClick: () => r(t.id, _),
254
+ disabled: a || !_.trim() || !D.allowed,
255
+ "aria-disabled": a || !_.trim() || !D.allowed,
243
256
  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",
244
- title: w.allowed ? f.trim() ? void 0 : l("devportal.notesRequiredForRejection", "Notes required for rejection") : l("devportal.common.noPermission", "You do not have permission to perform this action"),
257
+ 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"),
245
258
  children: [a ? /* @__PURE__ */ k(E, {
246
259
  size: 14,
247
260
  className: "animate-spin",
@@ -249,7 +262,7 @@ var j = ({ manifest: e, tr: t }) => {
249
262
  }) : /* @__PURE__ */ k(O, {
250
263
  size: 14,
251
264
  "aria-hidden": "true"
252
- }), l("devportal.reject", "Reject")]
265
+ }), u("devportal.reject", "Reject")]
253
266
  })]
254
267
  })
255
268
  ]
@@ -440,7 +453,7 @@ var j = ({ manifest: e, tr: t }) => {
440
453
  }), { data: W, refetch: G } = o({
441
454
  variables: { limit: 100 },
442
455
  fetchPolicy: "cache-and-network"
443
- }), [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 = m ? X.filter((e) => e.name.toLowerCase().includes(m.toLowerCase()) || (e.shortDescription ?? "").toLowerCase().includes(m.toLowerCase())) : X, $ = async (e, t) => {
456
+ }), [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) => {
444
457
  I(e), j(null), F(null);
445
458
  try {
446
459
  let n = await K({ variables: {
@@ -454,7 +467,7 @@ var j = ({ manifest: e, tr: t }) => {
454
467
  } finally {
455
468
  L(e);
456
469
  }
457
- }, ee = async (e, t) => {
470
+ }, te = async (e, t) => {
458
471
  if (t.trim()) {
459
472
  I(e), j(null), F(null);
460
473
  try {
@@ -470,7 +483,7 @@ var j = ({ manifest: e, tr: t }) => {
470
483
  L(e);
471
484
  }
472
485
  }
473
- }, te = async (e, t, n) => {
486
+ }, ne = async (e, t, n) => {
474
487
  R(e), j(null), F(null);
475
488
  try {
476
489
  let t = await J({ variables: {
@@ -484,7 +497,7 @@ var j = ({ manifest: e, tr: t }) => {
484
497
  } finally {
485
498
  z(e);
486
499
  }
487
- }, ne = async (e, t, n) => {
500
+ }, re = async (e, t, n) => {
488
501
  if (n.trim()) {
489
502
  R(e), j(null), F(null);
490
503
  try {
@@ -603,18 +616,19 @@ var j = ({ manifest: e, tr: t }) => {
603
616
  }),
604
617
  /* @__PURE__ */ k(d, {
605
618
  title: i("devportal.newAppSubmissions", "New App Submissions"),
606
- children: Q.length === 0 ? /* @__PURE__ */ k(t, {
619
+ children: $.length === 0 ? /* @__PURE__ */ k(t, {
607
620
  illustration: "empty-data",
608
621
  icon: /* @__PURE__ */ k(v, { size: 32 }),
609
622
  title: i("devportal.noAppsPendingReview", "No apps pending review"),
610
623
  description: m ? i("devportal.noAppsMatchSearch", "No apps match your search") : i("devportal.allApplicationsReviewed", "All submitted applications have been reviewed")
611
624
  }) : /* @__PURE__ */ k("div", {
612
625
  className: "space-y-3",
613
- children: Q.map((e) => /* @__PURE__ */ k(M, {
626
+ children: $.map((e) => /* @__PURE__ */ k(M, {
614
627
  app: e,
615
- onApprove: $,
616
- onReject: ee,
617
- processing: b.has(e.id)
628
+ onApprove: ee,
629
+ onReject: te,
630
+ processing: b.has(e.id),
631
+ hasPendingVersions: Q.has(e.id)
618
632
  }, e.id))
619
633
  })
620
634
  }),
@@ -629,8 +643,8 @@ var j = ({ manifest: e, tr: t }) => {
629
643
  className: "space-y-3",
630
644
  children: Z.map((e) => /* @__PURE__ */ k(N, {
631
645
  app: e,
632
- onApproveVersion: te,
633
- onRejectVersion: ne,
646
+ onApproveVersion: ne,
647
+ onRejectVersion: re,
634
648
  processingVersionIds: C
635
649
  }, e.id))
636
650
  })
@@ -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}\n\nconst ReviewRow: FC<ReviewRowProps> = ({\n app,\n onApprove,\n onReject,\n processing,\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 {/* 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 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 />\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} 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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"}