@burdenoff/microfe-store 2026.1031.1 → 2026.1102.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/StoreAdminRoutes.js +22 -12
- package/dist/StoreAdminRoutes.js.map +1 -1
- package/dist/StoreRoot.js +47 -36
- package/dist/StoreRoot.js.map +1 -1
- package/dist/StoreRoutes.js +72 -59
- package/dist/StoreRoutes.js.map +1 -1
- package/dist/components/governance/ReportListingCard.d.ts +11 -0
- package/dist/components/governance/ReportListingCard.js +100 -0
- package/dist/components/governance/ReportListingCard.js.map +1 -0
- package/dist/pages/AdminDashboardPage.js +48 -27
- package/dist/pages/AdminDashboardPage.js.map +1 -1
- package/dist/pages/AdminGovernancePage.d.ts +3 -0
- package/dist/pages/AdminGovernancePage.js +883 -0
- package/dist/pages/AdminGovernancePage.js.map +1 -0
- package/dist/pages/AdminSubmissionsQueuePage.js +189 -169
- package/dist/pages/AdminSubmissionsQueuePage.js.map +1 -1
- package/dist/pages/ProductDetailPage.js +220 -215
- package/dist/pages/ProductDetailPage.js.map +1 -1
- package/dist/pages/PublisherGovernancePage.d.ts +3 -0
- package/dist/pages/PublisherGovernancePage.js +230 -0
- package/dist/pages/PublisherGovernancePage.js.map +1 -0
- package/dist/utils/governance.d.ts +158 -0
- package/dist/utils/governance.js +129 -0
- package/dist/utils/governance.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { useStore as e } from "../providers/StoreProvider.js";
|
|
2
|
+
import { FILE_APPEAL_MUTATION as t, MY_GOVERNANCE_QUERY as n, TAKEDOWN_CATEGORIES as r, formatGovernanceDate as i, labelFor as a, runGovernanceOperation as o } from "../utils/governance.js";
|
|
3
|
+
import { useTr as s } from "../shared/hooks/useTr.js";
|
|
4
|
+
import { useCallback as c, useEffect as l, useState as u } from "react";
|
|
5
|
+
import { useNavigate as d } from "react-router";
|
|
6
|
+
import { ArrowLeft as f, Ban as p, Clock as m, RefreshCw as h, Scale as g, ShieldAlert as _ } from "lucide-react";
|
|
7
|
+
import { Fragment as v, jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
8
|
+
import { GlassCard as x, IllustratedEmptyState as S, PagePurpose as C } from "@burdenoff/fe-libs/ui";
|
|
9
|
+
//#region src/pages/PublisherGovernancePage.tsx
|
|
10
|
+
function w(e, t) {
|
|
11
|
+
return !e || e.targetType !== t.targetType ? !1 : e.targetType === "PUBLISHER" || t.targetType === "LISTING" && e.productId === t.productId;
|
|
12
|
+
}
|
|
13
|
+
var T = () => {
|
|
14
|
+
let T = d(), E = s(), { authToken: D, basePath: O } = e(), [k, A] = u(null), [j, M] = u(!1), [N, P] = u(null), [F, I] = u(null), [L, R] = u(null), [z, B] = u(""), [V, H] = u(!1), U = c(async () => {
|
|
15
|
+
M(!0), P(null);
|
|
16
|
+
try {
|
|
17
|
+
A(await o(n, void 0, D, "Failed to load store actions"));
|
|
18
|
+
} catch (e) {
|
|
19
|
+
P(e instanceof Error ? e.message : "Failed to load store actions");
|
|
20
|
+
} finally {
|
|
21
|
+
M(!1);
|
|
22
|
+
}
|
|
23
|
+
}, [D]);
|
|
24
|
+
l(() => {
|
|
25
|
+
U();
|
|
26
|
+
}, [U]);
|
|
27
|
+
let W = k?.myGovernanceAppeals.appeals ?? [], G = (e, t) => W.find((n) => n.status === "PENDING" && n.targetType === e && (e === "PUBLISHER" || n.productId === t)), K = async () => {
|
|
28
|
+
if (L) {
|
|
29
|
+
H(!0), P(null);
|
|
30
|
+
try {
|
|
31
|
+
await o(t, { input: {
|
|
32
|
+
targetType: L.targetType,
|
|
33
|
+
productId: L.targetType === "LISTING" ? L.productId : null,
|
|
34
|
+
statement: z.trim()
|
|
35
|
+
} }, D, "Could not file the appeal"), R(null), B(""), I(E("governance.appeal.filed", "Appeal filed. The store team decides within five business days and you will see the decision here.")), await U();
|
|
36
|
+
} catch (e) {
|
|
37
|
+
P(e instanceof Error ? e.message : "Could not file the appeal");
|
|
38
|
+
} finally {
|
|
39
|
+
H(!1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}, q = k?.myPublisher?.verificationStatus === "SUSPENDED", J = k?.myTakenDownListings ?? [], Y = (e) => w(L, e) ? /* @__PURE__ */ b("form", {
|
|
43
|
+
className: "mt-3 space-y-2",
|
|
44
|
+
onSubmit: (e) => {
|
|
45
|
+
e.preventDefault(), K();
|
|
46
|
+
},
|
|
47
|
+
children: [/* @__PURE__ */ y("textarea", {
|
|
48
|
+
"aria-label": E("governance.appeal.statement", "Appeal statement"),
|
|
49
|
+
value: z,
|
|
50
|
+
onChange: (e) => B(e.target.value),
|
|
51
|
+
rows: 4,
|
|
52
|
+
maxLength: 4e3,
|
|
53
|
+
required: !0,
|
|
54
|
+
placeholder: E("governance.appeal.placeholder", "Explain why the action should be reversed and what you changed."),
|
|
55
|
+
className: "w-full resize-none rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-sm text-[var(--color-text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]"
|
|
56
|
+
}), /* @__PURE__ */ b("div", {
|
|
57
|
+
className: "flex justify-end gap-2",
|
|
58
|
+
children: [/* @__PURE__ */ y("button", {
|
|
59
|
+
type: "button",
|
|
60
|
+
onClick: () => R(null),
|
|
61
|
+
className: "rounded-md border border-[var(--color-border-default)] px-3 py-1.5 text-xs text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)]",
|
|
62
|
+
children: E("common.cancel", "Cancel")
|
|
63
|
+
}), /* @__PURE__ */ y("button", {
|
|
64
|
+
type: "submit",
|
|
65
|
+
disabled: V || !z.trim(),
|
|
66
|
+
className: "rounded-md bg-[var(--color-action-primary-bg)] px-3 py-1.5 text-xs font-semibold text-[var(--color-action-primary-text)] hover:opacity-90 disabled:opacity-50",
|
|
67
|
+
children: E("governance.appeal.submit", "File appeal")
|
|
68
|
+
})]
|
|
69
|
+
})]
|
|
70
|
+
}) : null, X = (e, t) => t ? /* @__PURE__ */ b("p", {
|
|
71
|
+
className: "mt-2 flex items-center gap-1 text-xs text-[var(--color-status-warning-text)]",
|
|
72
|
+
children: [/* @__PURE__ */ y(m, { className: "size-3" }), E("governance.appeal.pending", "Appeal pending, decision due {{due}}", { due: i(t.reviewDueAt) })]
|
|
73
|
+
}) : /* @__PURE__ */ b("button", {
|
|
74
|
+
type: "button",
|
|
75
|
+
onClick: () => {
|
|
76
|
+
R(e), B(""), I(null);
|
|
77
|
+
},
|
|
78
|
+
className: "mt-2 inline-flex items-center gap-1.5 text-sm text-[var(--color-text-link)] hover:underline",
|
|
79
|
+
children: [/* @__PURE__ */ y(g, { className: "size-4" }), E("governance.appeal.open", "Appeal this decision")]
|
|
80
|
+
});
|
|
81
|
+
return /* @__PURE__ */ b("div", {
|
|
82
|
+
className: "mx-auto max-w-4xl space-y-6 p-6",
|
|
83
|
+
children: [
|
|
84
|
+
/* @__PURE__ */ b("div", {
|
|
85
|
+
className: "flex items-center justify-between gap-3",
|
|
86
|
+
children: [/* @__PURE__ */ b("div", {
|
|
87
|
+
className: "flex items-center gap-2",
|
|
88
|
+
children: [/* @__PURE__ */ y("button", {
|
|
89
|
+
type: "button",
|
|
90
|
+
"aria-label": E("common.back", "Back"),
|
|
91
|
+
onClick: () => T(`${O}/marketplace`),
|
|
92
|
+
className: "rounded-md p-1.5 text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)]",
|
|
93
|
+
children: /* @__PURE__ */ y(f, { className: "size-4" })
|
|
94
|
+
}), /* @__PURE__ */ y("h1", {
|
|
95
|
+
className: "text-xl font-semibold text-[var(--color-text-primary)]",
|
|
96
|
+
children: E("governance.publisher.title", "Store actions and appeals")
|
|
97
|
+
})]
|
|
98
|
+
}), /* @__PURE__ */ b("button", {
|
|
99
|
+
type: "button",
|
|
100
|
+
onClick: () => void U(),
|
|
101
|
+
disabled: j,
|
|
102
|
+
className: "inline-flex items-center gap-2 rounded-md border border-[var(--color-border-default)] px-3 py-1.5 text-sm text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] disabled:opacity-50",
|
|
103
|
+
children: [/* @__PURE__ */ y(h, { className: `size-4 ${j ? "animate-spin" : ""}` }), E("common.refresh", "Refresh")]
|
|
104
|
+
})]
|
|
105
|
+
}),
|
|
106
|
+
/* @__PURE__ */ y(C, { children: E("governance.publisher.purpose", "When the store team takes one of your listings down or suspends your publisher account, the reason appears here. You can appeal each action once at a time.") }),
|
|
107
|
+
N && /* @__PURE__ */ y("div", {
|
|
108
|
+
role: "alert",
|
|
109
|
+
className: "rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bg-subtle)] px-4 py-3 text-sm text-[var(--color-status-error-text)]",
|
|
110
|
+
children: N
|
|
111
|
+
}),
|
|
112
|
+
F && /* @__PURE__ */ y("div", {
|
|
113
|
+
role: "status",
|
|
114
|
+
className: "rounded-lg bg-[var(--color-status-success-bg-subtle)] px-4 py-3 text-sm text-[var(--color-status-success-text)]",
|
|
115
|
+
children: F
|
|
116
|
+
}),
|
|
117
|
+
q && k?.myPublisher && /* @__PURE__ */ b(x, {
|
|
118
|
+
className: "p-5",
|
|
119
|
+
children: [
|
|
120
|
+
/* @__PURE__ */ b("h2", {
|
|
121
|
+
className: "flex items-center gap-2 font-semibold text-[var(--color-status-error-text)]",
|
|
122
|
+
children: [/* @__PURE__ */ y(_, { className: "size-4" }), E("governance.publisher.suspended", "Your publisher account is suspended")]
|
|
123
|
+
}),
|
|
124
|
+
k.myPublisher.suspensionReason && /* @__PURE__ */ y("p", {
|
|
125
|
+
className: "mt-2 text-sm text-[var(--color-text-secondary)]",
|
|
126
|
+
children: k.myPublisher.suspensionReason
|
|
127
|
+
}),
|
|
128
|
+
X({
|
|
129
|
+
targetType: "PUBLISHER",
|
|
130
|
+
name: k.myPublisher.name
|
|
131
|
+
}, G("PUBLISHER")),
|
|
132
|
+
Y({
|
|
133
|
+
targetType: "PUBLISHER",
|
|
134
|
+
name: k.myPublisher.name
|
|
135
|
+
})
|
|
136
|
+
]
|
|
137
|
+
}),
|
|
138
|
+
/* @__PURE__ */ b("section", {
|
|
139
|
+
"aria-label": E("governance.publisher.takenDown", "Taken-down listings"),
|
|
140
|
+
children: [/* @__PURE__ */ y("h2", {
|
|
141
|
+
className: "mb-3 text-sm font-semibold text-[var(--color-text-primary)]",
|
|
142
|
+
children: E("governance.publisher.takenDown", "Taken-down listings")
|
|
143
|
+
}), J.length === 0 && !j ? /* @__PURE__ */ y(S, {
|
|
144
|
+
illustration: "empty-data",
|
|
145
|
+
title: E("governance.publisher.noneTakenDown", "None of your listings are taken down"),
|
|
146
|
+
description: E("governance.publisher.noneTakenDownHint", "If the store team takes a listing down, it appears here with the reason.")
|
|
147
|
+
}) : /* @__PURE__ */ y("ul", {
|
|
148
|
+
className: "space-y-3",
|
|
149
|
+
children: J.map((e) => {
|
|
150
|
+
let t = e.takedown?.source === "PUBLISHER_SUSPENSION", n = {
|
|
151
|
+
targetType: "LISTING",
|
|
152
|
+
productId: e.id,
|
|
153
|
+
name: e.name
|
|
154
|
+
};
|
|
155
|
+
return /* @__PURE__ */ y("li", { children: /* @__PURE__ */ b(x, {
|
|
156
|
+
className: "p-5",
|
|
157
|
+
children: [
|
|
158
|
+
/* @__PURE__ */ b("div", {
|
|
159
|
+
className: "flex flex-wrap items-center gap-2",
|
|
160
|
+
children: [
|
|
161
|
+
/* @__PURE__ */ y(p, { className: "size-4 text-[var(--color-status-error-text)]" }),
|
|
162
|
+
/* @__PURE__ */ y("span", {
|
|
163
|
+
className: "font-semibold text-[var(--color-text-primary)]",
|
|
164
|
+
children: e.name
|
|
165
|
+
}),
|
|
166
|
+
/* @__PURE__ */ y("span", {
|
|
167
|
+
className: "rounded-full bg-[var(--color-bg-muted)] px-2 py-0.5 text-[11px] text-[var(--color-text-secondary)]",
|
|
168
|
+
children: a(r, e.takedown?.category)
|
|
169
|
+
})
|
|
170
|
+
]
|
|
171
|
+
}),
|
|
172
|
+
e.takedown?.reason && /* @__PURE__ */ y("p", {
|
|
173
|
+
className: "mt-2 text-sm text-[var(--color-text-secondary)]",
|
|
174
|
+
children: e.takedown.reason
|
|
175
|
+
}),
|
|
176
|
+
/* @__PURE__ */ y("p", {
|
|
177
|
+
className: "mt-1 text-xs text-[var(--color-text-muted)]",
|
|
178
|
+
children: E("governance.publisher.takenDownOn", "Taken down {{date}}", { date: i(e.takedown?.takenDownAt) })
|
|
179
|
+
}),
|
|
180
|
+
t ? /* @__PURE__ */ y("p", {
|
|
181
|
+
className: "mt-2 text-xs text-[var(--color-text-muted)]",
|
|
182
|
+
children: E("governance.publisher.viaSuspension", "This listing went down with your publisher suspension and comes back when the suspension is lifted.")
|
|
183
|
+
}) : /* @__PURE__ */ b(v, { children: [X(n, G("LISTING", e.id)), Y(n)] })
|
|
184
|
+
]
|
|
185
|
+
}) }, e.id);
|
|
186
|
+
})
|
|
187
|
+
})]
|
|
188
|
+
}),
|
|
189
|
+
/* @__PURE__ */ b("section", {
|
|
190
|
+
"aria-label": E("governance.publisher.appeals", "Your appeals"),
|
|
191
|
+
children: [/* @__PURE__ */ y("h2", {
|
|
192
|
+
className: "mb-3 text-sm font-semibold text-[var(--color-text-primary)]",
|
|
193
|
+
children: E("governance.publisher.appeals", "Your appeals")
|
|
194
|
+
}), W.length === 0 && !j ? /* @__PURE__ */ y("p", {
|
|
195
|
+
className: "text-sm text-[var(--color-text-muted)]",
|
|
196
|
+
children: E("governance.publisher.noAppeals", "You have not filed any appeals.")
|
|
197
|
+
}) : /* @__PURE__ */ y("ul", {
|
|
198
|
+
className: "space-y-2",
|
|
199
|
+
children: W.map((e) => /* @__PURE__ */ b("li", {
|
|
200
|
+
className: "rounded-lg border border-[var(--color-border-seam)] bg-[var(--color-bg-elevated)] px-4 py-3",
|
|
201
|
+
children: [/* @__PURE__ */ b("div", {
|
|
202
|
+
className: "flex flex-wrap items-center gap-2 text-sm",
|
|
203
|
+
children: [
|
|
204
|
+
/* @__PURE__ */ y("span", {
|
|
205
|
+
className: "font-medium text-[var(--color-text-primary)]",
|
|
206
|
+
children: e.targetType === "LISTING" ? e.product?.name ?? E("governance.publisher.listing", "Listing") : E("governance.publisher.account", "Publisher account")
|
|
207
|
+
}),
|
|
208
|
+
/* @__PURE__ */ y("span", {
|
|
209
|
+
className: "rounded-full bg-[var(--color-bg-muted)] px-2 py-0.5 text-[11px] text-[var(--color-text-secondary)]",
|
|
210
|
+
children: e.status
|
|
211
|
+
}),
|
|
212
|
+
/* @__PURE__ */ y("span", {
|
|
213
|
+
className: "text-xs text-[var(--color-text-muted)]",
|
|
214
|
+
children: E("governance.publisher.filed", "Filed {{date}}", { date: i(e.submittedAt) })
|
|
215
|
+
})
|
|
216
|
+
]
|
|
217
|
+
}), e.decisionNote && /* @__PURE__ */ y("p", {
|
|
218
|
+
className: "mt-1 text-sm text-[var(--color-text-secondary)]",
|
|
219
|
+
children: E("governance.publisher.decision", "Decision: {{note}}", { note: e.decisionNote })
|
|
220
|
+
})]
|
|
221
|
+
}, e.id))
|
|
222
|
+
})]
|
|
223
|
+
})
|
|
224
|
+
]
|
|
225
|
+
});
|
|
226
|
+
};
|
|
227
|
+
//#endregion
|
|
228
|
+
export { T as PublisherGovernancePage };
|
|
229
|
+
|
|
230
|
+
//# sourceMappingURL=PublisherGovernancePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PublisherGovernancePage.js","names":[],"sources":["../../src/pages/PublisherGovernancePage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useCallback, useEffect, useState } from 'react';\nimport { ArrowLeft, Ban, Clock, RefreshCw, Scale, ShieldAlert } from 'lucide-react';\nimport { GlassCard, IllustratedEmptyState, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useNavigate } from 'react-router';\nimport { useStore } from '../providers/StoreProvider';\nimport { useTr } from '../shared/hooks/useTr';\nimport {\n FILE_APPEAL_MUTATION,\n MY_GOVERNANCE_QUERY,\n TAKEDOWN_CATEGORIES,\n formatGovernanceDate,\n labelFor,\n runGovernanceOperation,\n type GovernanceAppeal,\n type TakenDownListing,\n} from '../utils/governance';\n\n/**\n * Publisher view of store governance actions (BOFF-10635): the publisher's\n * listings the store team took down, a suspension of their publisher account,\n * and their appeals. Each action shows its reason, and the publisher can\n * appeal it once at a time; appeals are reviewed within five business days.\n */\n\ntype MyPublisher = {\n id: string;\n name: string;\n verificationStatus: string;\n suspensionReason?: string | null;\n};\n\ntype MyGovernance = {\n myTakenDownListings: TakenDownListing[];\n myGovernanceAppeals: { appeals: GovernanceAppeal[] };\n myPublisher: MyPublisher | null;\n};\n\ntype AppealTarget =\n | { targetType: 'LISTING'; productId: string; name: string }\n | {\n targetType: 'PUBLISHER';\n name: string;\n };\n\n/** Same appeal target: the same listing (by id), or the publisher account. */\nfunction isSameTarget(a: AppealTarget | null, b: AppealTarget): boolean {\n if (!a || a.targetType !== b.targetType) return false;\n return (\n a.targetType === 'PUBLISHER' || (b.targetType === 'LISTING' && a.productId === b.productId)\n );\n}\n\nexport const PublisherGovernancePage: FC = () => {\n const navigate = useNavigate();\n const tr = useTr();\n const { authToken, basePath } = useStore();\n const [data, setData] = useState<MyGovernance | null>(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [notice, setNotice] = useState<string | null>(null);\n const [target, setTarget] = useState<AppealTarget | null>(null);\n const [statement, setStatement] = useState('');\n const [submitting, setSubmitting] = useState(false);\n\n const load = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n setData(\n await runGovernanceOperation<MyGovernance>(\n MY_GOVERNANCE_QUERY,\n undefined,\n authToken,\n 'Failed to load store actions'\n )\n );\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load store actions');\n } finally {\n setLoading(false);\n }\n }, [authToken]);\n\n useEffect(() => {\n void load();\n }, [load]);\n\n const appeals = data?.myGovernanceAppeals.appeals ?? [];\n const pendingFor = (targetType: 'LISTING' | 'PUBLISHER', productId?: string) =>\n appeals.find(\n (a) =>\n a.status === 'PENDING' &&\n a.targetType === targetType &&\n (targetType === 'PUBLISHER' || a.productId === productId)\n );\n\n const submitAppeal = async () => {\n if (!target) return;\n setSubmitting(true);\n setError(null);\n try {\n await runGovernanceOperation(\n FILE_APPEAL_MUTATION,\n {\n input: {\n targetType: target.targetType,\n productId: target.targetType === 'LISTING' ? target.productId : null,\n statement: statement.trim(),\n },\n },\n authToken,\n 'Could not file the appeal'\n );\n setTarget(null);\n setStatement('');\n setNotice(\n tr(\n 'governance.appeal.filed',\n 'Appeal filed. The store team decides within five business days and you will see the decision here.'\n )\n );\n await load();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Could not file the appeal');\n } finally {\n setSubmitting(false);\n }\n };\n\n const suspended = data?.myPublisher?.verificationStatus === 'SUSPENDED';\n const listings = data?.myTakenDownListings ?? [];\n\n const appealForm = (forTarget: AppealTarget) =>\n isSameTarget(target, forTarget) ? (\n <form\n className=\"mt-3 space-y-2\"\n onSubmit={(e) => {\n e.preventDefault();\n void submitAppeal();\n }}\n >\n <textarea\n aria-label={tr('governance.appeal.statement', 'Appeal statement')}\n value={statement}\n onChange={(e) => setStatement(e.target.value)}\n rows={4}\n maxLength={4000}\n required\n placeholder={tr(\n 'governance.appeal.placeholder',\n 'Explain why the action should be reversed and what you changed.'\n )}\n className=\"w-full resize-none rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-sm text-[var(--color-text-primary)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n />\n <div className=\"flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => setTarget(null)}\n className=\"rounded-md border border-[var(--color-border-default)] px-3 py-1.5 text-xs text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)]\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={submitting || !statement.trim()}\n className=\"rounded-md bg-[var(--color-action-primary-bg)] px-3 py-1.5 text-xs font-semibold text-[var(--color-action-primary-text)] hover:opacity-90 disabled:opacity-50\"\n >\n {tr('governance.appeal.submit', 'File appeal')}\n </button>\n </div>\n </form>\n ) : null;\n\n const appealButton = (forTarget: AppealTarget, pending?: GovernanceAppeal) =>\n pending ? (\n <p className=\"mt-2 flex items-center gap-1 text-xs text-[var(--color-status-warning-text)]\">\n <Clock className=\"size-3\" />\n {tr('governance.appeal.pending', 'Appeal pending, decision due {{due}}', {\n due: formatGovernanceDate(pending.reviewDueAt),\n })}\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => {\n setTarget(forTarget);\n setStatement('');\n setNotice(null);\n }}\n className=\"mt-2 inline-flex items-center gap-1.5 text-sm text-[var(--color-text-link)] hover:underline\"\n >\n <Scale className=\"size-4\" />\n {tr('governance.appeal.open', 'Appeal this decision')}\n </button>\n );\n\n return (\n <div className=\"mx-auto max-w-4xl space-y-6 p-6\">\n <div className=\"flex items-center justify-between gap-3\">\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n aria-label={tr('common.back', 'Back')}\n onClick={() => navigate(`${basePath}/marketplace`)}\n className=\"rounded-md p-1.5 text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)]\"\n >\n <ArrowLeft className=\"size-4\" />\n </button>\n <h1 className=\"text-xl font-semibold text-[var(--color-text-primary)]\">\n {tr('governance.publisher.title', 'Store actions and appeals')}\n </h1>\n </div>\n <button\n type=\"button\"\n onClick={() => void load()}\n disabled={loading}\n className=\"inline-flex items-center gap-2 rounded-md border border-[var(--color-border-default)] px-3 py-1.5 text-sm text-[var(--color-text-primary)] hover:bg-[var(--color-surface-hover)] disabled:opacity-50\"\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n {tr('common.refresh', 'Refresh')}\n </button>\n </div>\n\n <PagePurpose>\n {tr(\n 'governance.publisher.purpose',\n 'When the store team takes one of your listings down or suspends your publisher account, the reason appears here. You can appeal each action once at a time.'\n )}\n </PagePurpose>\n\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bg-subtle)] px-4 py-3 text-sm text-[var(--color-status-error-text)]\"\n >\n {error}\n </div>\n )}\n {notice && (\n <div\n role=\"status\"\n className=\"rounded-lg bg-[var(--color-status-success-bg-subtle)] px-4 py-3 text-sm text-[var(--color-status-success-text)]\"\n >\n {notice}\n </div>\n )}\n\n {suspended && data?.myPublisher && (\n <GlassCard className=\"p-5\">\n <h2 className=\"flex items-center gap-2 font-semibold text-[var(--color-status-error-text)]\">\n <ShieldAlert className=\"size-4\" />\n {tr('governance.publisher.suspended', 'Your publisher account is suspended')}\n </h2>\n {data.myPublisher.suspensionReason && (\n <p className=\"mt-2 text-sm text-[var(--color-text-secondary)]\">\n {data.myPublisher.suspensionReason}\n </p>\n )}\n {appealButton(\n { targetType: 'PUBLISHER', name: data.myPublisher.name },\n pendingFor('PUBLISHER')\n )}\n {appealForm({ targetType: 'PUBLISHER', name: data.myPublisher.name })}\n </GlassCard>\n )}\n\n <section aria-label={tr('governance.publisher.takenDown', 'Taken-down listings')}>\n <h2 className=\"mb-3 text-sm font-semibold text-[var(--color-text-primary)]\">\n {tr('governance.publisher.takenDown', 'Taken-down listings')}\n </h2>\n {listings.length === 0 && !loading ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tr('governance.publisher.noneTakenDown', 'None of your listings are taken down')}\n description={tr(\n 'governance.publisher.noneTakenDownHint',\n 'If the store team takes a listing down, it appears here with the reason.'\n )}\n />\n ) : (\n <ul className=\"space-y-3\">\n {listings.map((listing) => {\n const viaSuspension = listing.takedown?.source === 'PUBLISHER_SUSPENSION';\n const forTarget: AppealTarget = {\n targetType: 'LISTING',\n productId: listing.id,\n name: listing.name,\n };\n return (\n <li key={listing.id}>\n <GlassCard className=\"p-5\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Ban className=\"size-4 text-[var(--color-status-error-text)]\" />\n <span className=\"font-semibold text-[var(--color-text-primary)]\">\n {listing.name}\n </span>\n <span className=\"rounded-full bg-[var(--color-bg-muted)] px-2 py-0.5 text-[11px] text-[var(--color-text-secondary)]\">\n {labelFor(TAKEDOWN_CATEGORIES, listing.takedown?.category)}\n </span>\n </div>\n {listing.takedown?.reason && (\n <p className=\"mt-2 text-sm text-[var(--color-text-secondary)]\">\n {listing.takedown.reason}\n </p>\n )}\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr('governance.publisher.takenDownOn', 'Taken down {{date}}', {\n date: formatGovernanceDate(listing.takedown?.takenDownAt),\n })}\n </p>\n {viaSuspension ? (\n <p className=\"mt-2 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'governance.publisher.viaSuspension',\n 'This listing went down with your publisher suspension and comes back when the suspension is lifted.'\n )}\n </p>\n ) : (\n <>\n {appealButton(forTarget, pendingFor('LISTING', listing.id))}\n {appealForm(forTarget)}\n </>\n )}\n </GlassCard>\n </li>\n );\n })}\n </ul>\n )}\n </section>\n\n <section aria-label={tr('governance.publisher.appeals', 'Your appeals')}>\n <h2 className=\"mb-3 text-sm font-semibold text-[var(--color-text-primary)]\">\n {tr('governance.publisher.appeals', 'Your appeals')}\n </h2>\n {appeals.length === 0 && !loading ? (\n <p className=\"text-sm text-[var(--color-text-muted)]\">\n {tr('governance.publisher.noAppeals', 'You have not filed any appeals.')}\n </p>\n ) : (\n <ul className=\"space-y-2\">\n {appeals.map((appeal) => (\n <li\n key={appeal.id}\n className=\"rounded-lg border border-[var(--color-border-seam)] bg-[var(--color-bg-elevated)] px-4 py-3\"\n >\n <div className=\"flex flex-wrap items-center gap-2 text-sm\">\n <span className=\"font-medium text-[var(--color-text-primary)]\">\n {appeal.targetType === 'LISTING'\n ? (appeal.product?.name ?? tr('governance.publisher.listing', 'Listing'))\n : tr('governance.publisher.account', 'Publisher account')}\n </span>\n <span className=\"rounded-full bg-[var(--color-bg-muted)] px-2 py-0.5 text-[11px] text-[var(--color-text-secondary)]\">\n {appeal.status}\n </span>\n <span className=\"text-xs text-[var(--color-text-muted)]\">\n {tr('governance.publisher.filed', 'Filed {{date}}', {\n date: formatGovernanceDate(appeal.submittedAt),\n })}\n </span>\n </div>\n {appeal.decisionNote && (\n <p className=\"mt-1 text-sm text-[var(--color-text-secondary)]\">\n {tr('governance.publisher.decision', 'Decision: {{note}}', {\n note: appeal.decisionNote,\n })}\n </p>\n )}\n </li>\n ))}\n </ul>\n )}\n </section>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Store review SLA and marketplace governance (BOFF-10635): GraphQL documents,
|
|
3
|
+
* types and labels shared by the admin governance console, the buyer report
|
|
4
|
+
* dialog and the publisher appeals page. Backed by global-store-svc.
|
|
5
|
+
*/
|
|
6
|
+
export declare const LISTING_REPORT_REASONS: readonly [{
|
|
7
|
+
readonly value: "BROKEN";
|
|
8
|
+
readonly label: "Does not install or does not work";
|
|
9
|
+
}, {
|
|
10
|
+
readonly value: "MISLEADING";
|
|
11
|
+
readonly label: "Misleading description, screenshots or pricing";
|
|
12
|
+
}, {
|
|
13
|
+
readonly value: "ABUSE";
|
|
14
|
+
readonly label: "Abusive or offensive content";
|
|
15
|
+
}, {
|
|
16
|
+
readonly value: "SECURITY";
|
|
17
|
+
readonly label: "Security, privacy or safety risk";
|
|
18
|
+
}, {
|
|
19
|
+
readonly value: "IP_INFRINGEMENT";
|
|
20
|
+
readonly label: "Infringes intellectual property";
|
|
21
|
+
}, {
|
|
22
|
+
readonly value: "SPAM";
|
|
23
|
+
readonly label: "Spam or duplicate listing";
|
|
24
|
+
}, {
|
|
25
|
+
readonly value: "OTHER";
|
|
26
|
+
readonly label: "Something else";
|
|
27
|
+
}];
|
|
28
|
+
export type ListingReportReason = (typeof LISTING_REPORT_REASONS)[number]['value'];
|
|
29
|
+
export declare const TAKEDOWN_CATEGORIES: readonly [{
|
|
30
|
+
readonly value: "POLICY_VIOLATION";
|
|
31
|
+
readonly label: "Policy violation";
|
|
32
|
+
}, {
|
|
33
|
+
readonly value: "MISLEADING";
|
|
34
|
+
readonly label: "Misleading listing";
|
|
35
|
+
}, {
|
|
36
|
+
readonly value: "BROKEN";
|
|
37
|
+
readonly label: "Broken listing";
|
|
38
|
+
}, {
|
|
39
|
+
readonly value: "SECURITY_RISK";
|
|
40
|
+
readonly label: "Security, privacy or safety risk";
|
|
41
|
+
}, {
|
|
42
|
+
readonly value: "IP_INFRINGEMENT";
|
|
43
|
+
readonly label: "Intellectual-property claim";
|
|
44
|
+
}, {
|
|
45
|
+
readonly value: "PUBLISHER_VERIFICATION";
|
|
46
|
+
readonly label: "Publisher verification";
|
|
47
|
+
}, {
|
|
48
|
+
readonly value: "OTHER";
|
|
49
|
+
readonly label: "Other";
|
|
50
|
+
}];
|
|
51
|
+
export type TakedownCategory = (typeof TAKEDOWN_CATEGORIES)[number]['value'];
|
|
52
|
+
export declare function labelFor(options: ReadonlyArray<{
|
|
53
|
+
value: string;
|
|
54
|
+
label: string;
|
|
55
|
+
}>, value?: string | null): string;
|
|
56
|
+
export type GovernanceStats = {
|
|
57
|
+
pendingSubmissions: number;
|
|
58
|
+
overdueSubmissions: number;
|
|
59
|
+
oldestPendingSubmittedAt?: string | null;
|
|
60
|
+
openListingReports: number;
|
|
61
|
+
openReviewReports: number;
|
|
62
|
+
openRatingDisputes: number;
|
|
63
|
+
pendingAppeals: number;
|
|
64
|
+
overdueAppeals: number;
|
|
65
|
+
takenDownListings: number;
|
|
66
|
+
};
|
|
67
|
+
export type ListingReport = {
|
|
68
|
+
id: string;
|
|
69
|
+
productId: string;
|
|
70
|
+
reportedBy: string;
|
|
71
|
+
reason: ListingReportReason;
|
|
72
|
+
details?: string | null;
|
|
73
|
+
status: 'OPEN' | 'ACTIONED' | 'DISMISSED';
|
|
74
|
+
resolvedBy?: string | null;
|
|
75
|
+
resolvedAt?: string | null;
|
|
76
|
+
resolutionNote?: string | null;
|
|
77
|
+
createdAt: string;
|
|
78
|
+
product?: {
|
|
79
|
+
id: string;
|
|
80
|
+
name: string;
|
|
81
|
+
slug: string;
|
|
82
|
+
status: string;
|
|
83
|
+
} | null;
|
|
84
|
+
};
|
|
85
|
+
export type GovernanceReviewReport = {
|
|
86
|
+
id: string;
|
|
87
|
+
reviewId: string;
|
|
88
|
+
reportedBy: string;
|
|
89
|
+
reason: string;
|
|
90
|
+
details?: string | null;
|
|
91
|
+
status: 'PENDING' | 'ACTIONED' | 'DISMISSED';
|
|
92
|
+
isRatingDispute: boolean;
|
|
93
|
+
reportedByPublisher: boolean;
|
|
94
|
+
reviewedAt?: string | null;
|
|
95
|
+
actionTaken?: string | null;
|
|
96
|
+
resolutionNote?: string | null;
|
|
97
|
+
createdAt: string;
|
|
98
|
+
review?: {
|
|
99
|
+
id: string;
|
|
100
|
+
rating: number;
|
|
101
|
+
title?: string | null;
|
|
102
|
+
comment?: string | null;
|
|
103
|
+
status: string;
|
|
104
|
+
productName?: string | null;
|
|
105
|
+
productSlug?: string | null;
|
|
106
|
+
} | null;
|
|
107
|
+
};
|
|
108
|
+
export type ListingTakedown = {
|
|
109
|
+
takenDownAt: string;
|
|
110
|
+
category?: TakedownCategory | null;
|
|
111
|
+
reason?: string | null;
|
|
112
|
+
source: 'LISTING' | 'PUBLISHER_SUSPENSION';
|
|
113
|
+
statusBeforeTakedown?: string | null;
|
|
114
|
+
};
|
|
115
|
+
export type TakenDownListing = {
|
|
116
|
+
id: string;
|
|
117
|
+
name: string;
|
|
118
|
+
slug: string;
|
|
119
|
+
status: string;
|
|
120
|
+
takedown?: ListingTakedown | null;
|
|
121
|
+
};
|
|
122
|
+
export type GovernanceAppeal = {
|
|
123
|
+
id: string;
|
|
124
|
+
targetType: 'LISTING' | 'PUBLISHER';
|
|
125
|
+
productId?: string | null;
|
|
126
|
+
publisherId: string;
|
|
127
|
+
statement: string;
|
|
128
|
+
actionReason?: string | null;
|
|
129
|
+
actionAt?: string | null;
|
|
130
|
+
status: 'PENDING' | 'UPHELD' | 'REVERSED';
|
|
131
|
+
submittedAt: string;
|
|
132
|
+
reviewDueAt: string;
|
|
133
|
+
isReviewOverdue: boolean;
|
|
134
|
+
decidedAt?: string | null;
|
|
135
|
+
decisionNote?: string | null;
|
|
136
|
+
product?: {
|
|
137
|
+
id: string;
|
|
138
|
+
name: string;
|
|
139
|
+
slug: string;
|
|
140
|
+
} | null;
|
|
141
|
+
};
|
|
142
|
+
export declare const GOVERNANCE_STATS_QUERY = "\n query StoreGovernanceStats {\n storeGovernanceStats {\n pendingSubmissions\n overdueSubmissions\n oldestPendingSubmittedAt\n openListingReports\n openReviewReports\n openRatingDisputes\n pendingAppeals\n overdueAppeals\n takenDownListings\n }\n }\n";
|
|
143
|
+
export declare const LISTING_REPORTS_QUERY = "\n query StoreListingReports($status: ListingReportStatus, $limit: Int, $offset: Int) {\n listingReports(status: $status, limit: $limit, offset: $offset) {\n totalCount\n reports {\n id\n productId\n reportedBy\n reason\n details\n status\n resolvedBy\n resolvedAt\n resolutionNote\n createdAt\n product {\n id\n name\n slug\n status\n }\n }\n }\n }\n";
|
|
144
|
+
export declare const REVIEW_REPORTS_QUERY = "\n query StoreReviewReports(\n $status: ReviewReportStatus\n $ratingDisputesOnly: Boolean\n $limit: Int\n $offset: Int\n ) {\n reviewReports(\n status: $status\n ratingDisputesOnly: $ratingDisputesOnly\n limit: $limit\n offset: $offset\n ) {\n totalCount\n reports {\n id\n reviewId\n reportedBy\n reason\n details\n status\n isRatingDispute\n reportedByPublisher\n reviewedAt\n actionTaken\n resolutionNote\n createdAt\n review {\n id\n rating\n title\n comment\n status\n productName\n productSlug\n }\n }\n }\n }\n";
|
|
145
|
+
export declare const APPEALS_QUERY = "\n query StoreGovernanceAppeals($status: GovernanceAppealStatus, $limit: Int, $offset: Int) {\n governanceAppeals(status: $status, limit: $limit, offset: $offset) {\n totalCount\n appeals { \n id\n targetType\n productId\n publisherId\n statement\n actionReason\n actionAt\n status\n submittedAt\n reviewDueAt\n isReviewOverdue\n decidedAt\n decisionNote\n product {\n id\n name\n slug\n }\n }\n }\n }\n";
|
|
146
|
+
export declare const TAKEN_DOWN_QUERY = "\n query StoreTakenDownListings($limit: Int, $offset: Int) {\n takenDownListings(limit: $limit, offset: $offset) {\n totalCount\n products {\n id\n name\n slug\n status\n \n takedown {\n takenDownAt\n category\n reason\n source\n statusBeforeTakedown\n }\n\n }\n }\n }\n";
|
|
147
|
+
export declare const RESOLVE_LISTING_REPORT_MUTATION = "\n mutation StoreResolveListingReport($input: ResolveListingReportInput!) {\n resolveListingReport(input: $input) {\n id\n status\n }\n }\n";
|
|
148
|
+
export declare const RESOLVE_REVIEW_REPORT_MUTATION = "\n mutation StoreResolveReviewReport($input: ResolveReviewReportInput!) {\n resolveReviewReport(input: $input) {\n id\n status\n actionTaken\n }\n }\n";
|
|
149
|
+
export declare const TAKEDOWN_LISTING_MUTATION = "\n mutation StoreTakedownListing($input: TakedownListingInput!) {\n takedownListing(input: $input) {\n id\n status\n isTakenDown\n }\n }\n";
|
|
150
|
+
export declare const REINSTATE_LISTING_MUTATION = "\n mutation StoreReinstateListing($productId: ID!, $note: String) {\n reinstateListing(productId: $productId, note: $note) {\n id\n status\n isTakenDown\n }\n }\n";
|
|
151
|
+
export declare const DECIDE_APPEAL_MUTATION = "\n mutation StoreDecideGovernanceAppeal($input: DecideGovernanceAppealInput!) {\n decideGovernanceAppeal(input: $input) {\n id\n status\n }\n }\n";
|
|
152
|
+
export declare const REPORT_LISTING_MUTATION = "\n mutation StoreReportListing($input: ReportListingInput!) {\n reportListing(input: $input) {\n id\n status\n reason\n }\n }\n";
|
|
153
|
+
export declare const MY_GOVERNANCE_QUERY = "\n query StoreMyGovernance {\n myTakenDownListings {\n id\n name\n slug\n status\n \n takedown {\n takenDownAt\n category\n reason\n source\n statusBeforeTakedown\n }\n\n }\n myGovernanceAppeals(limit: 50) {\n totalCount\n appeals { \n id\n targetType\n productId\n publisherId\n statement\n actionReason\n actionAt\n status\n submittedAt\n reviewDueAt\n isReviewOverdue\n decidedAt\n decisionNote\n product {\n id\n name\n slug\n }\n }\n }\n myPublisher {\n id\n name\n verificationStatus\n suspensionReason\n }\n }\n";
|
|
154
|
+
export declare const FILE_APPEAL_MUTATION = "\n mutation StoreFileGovernanceAppeal($input: FileGovernanceAppealInput!) {\n fileGovernanceAppeal(input: $input) {\n id\n status\n reviewDueAt\n }\n }\n";
|
|
155
|
+
export declare const SEARCH_LISTINGS_QUERY = "\n query StoreGovernanceSearchListings($query: String!) {\n searchProducts(query: $query, limit: 10) {\n id\n name\n slug\n }\n }\n";
|
|
156
|
+
/** Run a global-gateway operation and surface the first GraphQL error. */
|
|
157
|
+
export declare function runGovernanceOperation<T>(query: string, variables: Record<string, unknown> | undefined, authToken: string | null | undefined, fallbackError: string): Promise<T>;
|
|
158
|
+
export declare function formatGovernanceDate(iso?: string | null): string;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { graphqlFetch as e } from "@burdenoff/fe-libs/shared/graphql";
|
|
2
|
+
//#region src/utils/governance.ts
|
|
3
|
+
var t = [
|
|
4
|
+
{
|
|
5
|
+
value: "BROKEN",
|
|
6
|
+
label: "Does not install or does not work"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
value: "MISLEADING",
|
|
10
|
+
label: "Misleading description, screenshots or pricing"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
value: "ABUSE",
|
|
14
|
+
label: "Abusive or offensive content"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
value: "SECURITY",
|
|
18
|
+
label: "Security, privacy or safety risk"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
value: "IP_INFRINGEMENT",
|
|
22
|
+
label: "Infringes intellectual property"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
value: "SPAM",
|
|
26
|
+
label: "Spam or duplicate listing"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
value: "OTHER",
|
|
30
|
+
label: "Something else"
|
|
31
|
+
}
|
|
32
|
+
], n = [
|
|
33
|
+
{
|
|
34
|
+
value: "POLICY_VIOLATION",
|
|
35
|
+
label: "Policy violation"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
value: "MISLEADING",
|
|
39
|
+
label: "Misleading listing"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
value: "BROKEN",
|
|
43
|
+
label: "Broken listing"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
value: "SECURITY_RISK",
|
|
47
|
+
label: "Security, privacy or safety risk"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
value: "IP_INFRINGEMENT",
|
|
51
|
+
label: "Intellectual-property claim"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
value: "PUBLISHER_VERIFICATION",
|
|
55
|
+
label: "Publisher verification"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
value: "OTHER",
|
|
59
|
+
label: "Other"
|
|
60
|
+
}
|
|
61
|
+
];
|
|
62
|
+
function r(e, t) {
|
|
63
|
+
return t ? e.find((e) => e.value === t)?.label ?? t : "—";
|
|
64
|
+
}
|
|
65
|
+
var i = "\n query StoreGovernanceStats {\n storeGovernanceStats {\n pendingSubmissions\n overdueSubmissions\n oldestPendingSubmittedAt\n openListingReports\n openReviewReports\n openRatingDisputes\n pendingAppeals\n overdueAppeals\n takenDownListings\n }\n }\n", a = "\n query StoreListingReports($status: ListingReportStatus, $limit: Int, $offset: Int) {\n listingReports(status: $status, limit: $limit, offset: $offset) {\n totalCount\n reports {\n id\n productId\n reportedBy\n reason\n details\n status\n resolvedBy\n resolvedAt\n resolutionNote\n createdAt\n product {\n id\n name\n slug\n status\n }\n }\n }\n }\n", o = "\n query StoreReviewReports(\n $status: ReviewReportStatus\n $ratingDisputesOnly: Boolean\n $limit: Int\n $offset: Int\n ) {\n reviewReports(\n status: $status\n ratingDisputesOnly: $ratingDisputesOnly\n limit: $limit\n offset: $offset\n ) {\n totalCount\n reports {\n id\n reviewId\n reportedBy\n reason\n details\n status\n isRatingDispute\n reportedByPublisher\n reviewedAt\n actionTaken\n resolutionNote\n createdAt\n review {\n id\n rating\n title\n comment\n status\n productName\n productSlug\n }\n }\n }\n }\n", s = "\n id\n targetType\n productId\n publisherId\n statement\n actionReason\n actionAt\n status\n submittedAt\n reviewDueAt\n isReviewOverdue\n decidedAt\n decisionNote\n product {\n id\n name\n slug\n }\n", c = `
|
|
66
|
+
query StoreGovernanceAppeals($status: GovernanceAppealStatus, $limit: Int, $offset: Int) {
|
|
67
|
+
governanceAppeals(status: $status, limit: $limit, offset: $offset) {
|
|
68
|
+
totalCount
|
|
69
|
+
appeals { ${s} }
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
`, l = "\n takedown {\n takenDownAt\n category\n reason\n source\n statusBeforeTakedown\n }\n", u = `
|
|
73
|
+
query StoreTakenDownListings($limit: Int, $offset: Int) {
|
|
74
|
+
takenDownListings(limit: $limit, offset: $offset) {
|
|
75
|
+
totalCount
|
|
76
|
+
products {
|
|
77
|
+
id
|
|
78
|
+
name
|
|
79
|
+
slug
|
|
80
|
+
status
|
|
81
|
+
${l}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
`, d = "\n mutation StoreResolveListingReport($input: ResolveListingReportInput!) {\n resolveListingReport(input: $input) {\n id\n status\n }\n }\n", f = "\n mutation StoreResolveReviewReport($input: ResolveReviewReportInput!) {\n resolveReviewReport(input: $input) {\n id\n status\n actionTaken\n }\n }\n", p = "\n mutation StoreTakedownListing($input: TakedownListingInput!) {\n takedownListing(input: $input) {\n id\n status\n isTakenDown\n }\n }\n", m = "\n mutation StoreReinstateListing($productId: ID!, $note: String) {\n reinstateListing(productId: $productId, note: $note) {\n id\n status\n isTakenDown\n }\n }\n", h = "\n mutation StoreDecideGovernanceAppeal($input: DecideGovernanceAppealInput!) {\n decideGovernanceAppeal(input: $input) {\n id\n status\n }\n }\n", g = "\n mutation StoreReportListing($input: ReportListingInput!) {\n reportListing(input: $input) {\n id\n status\n reason\n }\n }\n", _ = `
|
|
86
|
+
query StoreMyGovernance {
|
|
87
|
+
myTakenDownListings {
|
|
88
|
+
id
|
|
89
|
+
name
|
|
90
|
+
slug
|
|
91
|
+
status
|
|
92
|
+
${l}
|
|
93
|
+
}
|
|
94
|
+
myGovernanceAppeals(limit: 50) {
|
|
95
|
+
totalCount
|
|
96
|
+
appeals { ${s} }
|
|
97
|
+
}
|
|
98
|
+
myPublisher {
|
|
99
|
+
id
|
|
100
|
+
name
|
|
101
|
+
verificationStatus
|
|
102
|
+
suspensionReason
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
`, v = "\n mutation StoreFileGovernanceAppeal($input: FileGovernanceAppealInput!) {\n fileGovernanceAppeal(input: $input) {\n id\n status\n reviewDueAt\n }\n }\n", y = "\n query StoreGovernanceSearchListings($query: String!) {\n searchProducts(query: $query, limit: 10) {\n id\n name\n slug\n }\n }\n";
|
|
106
|
+
async function b(t, n, r, i) {
|
|
107
|
+
let a = await e({
|
|
108
|
+
gateway: "global",
|
|
109
|
+
query: t,
|
|
110
|
+
variables: n,
|
|
111
|
+
authToken: r || void 0
|
|
112
|
+
});
|
|
113
|
+
if (a.errors?.length) throw Error(a.errors[0]?.message ?? i);
|
|
114
|
+
if (!a.data) throw Error(i);
|
|
115
|
+
return a.data;
|
|
116
|
+
}
|
|
117
|
+
function x(e) {
|
|
118
|
+
return e ? new Date(e).toLocaleString(void 0, {
|
|
119
|
+
month: "short",
|
|
120
|
+
day: "numeric",
|
|
121
|
+
year: "numeric",
|
|
122
|
+
hour: "2-digit",
|
|
123
|
+
minute: "2-digit"
|
|
124
|
+
}) : "—";
|
|
125
|
+
}
|
|
126
|
+
//#endregion
|
|
127
|
+
export { c as APPEALS_QUERY, h as DECIDE_APPEAL_MUTATION, v as FILE_APPEAL_MUTATION, i as GOVERNANCE_STATS_QUERY, a as LISTING_REPORTS_QUERY, t as LISTING_REPORT_REASONS, _ as MY_GOVERNANCE_QUERY, m as REINSTATE_LISTING_MUTATION, g as REPORT_LISTING_MUTATION, d as RESOLVE_LISTING_REPORT_MUTATION, f as RESOLVE_REVIEW_REPORT_MUTATION, o as REVIEW_REPORTS_QUERY, y as SEARCH_LISTINGS_QUERY, n as TAKEDOWN_CATEGORIES, p as TAKEDOWN_LISTING_MUTATION, u as TAKEN_DOWN_QUERY, x as formatGovernanceDate, r as labelFor, b as runGovernanceOperation };
|
|
128
|
+
|
|
129
|
+
//# sourceMappingURL=governance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"governance.js","names":[],"sources":["../../src/utils/governance.ts"],"sourcesContent":["import { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\n/**\n * Store review SLA and marketplace governance (BOFF-10635): GraphQL documents,\n * types and labels shared by the admin governance console, the buyer report\n * dialog and the publisher appeals page. Backed by global-store-svc.\n */\n\nexport const LISTING_REPORT_REASONS = [\n { value: 'BROKEN', label: 'Does not install or does not work' },\n { value: 'MISLEADING', label: 'Misleading description, screenshots or pricing' },\n { value: 'ABUSE', label: 'Abusive or offensive content' },\n { value: 'SECURITY', label: 'Security, privacy or safety risk' },\n { value: 'IP_INFRINGEMENT', label: 'Infringes intellectual property' },\n { value: 'SPAM', label: 'Spam or duplicate listing' },\n { value: 'OTHER', label: 'Something else' },\n] as const;\n\nexport type ListingReportReason = (typeof LISTING_REPORT_REASONS)[number]['value'];\n\nexport const TAKEDOWN_CATEGORIES = [\n { value: 'POLICY_VIOLATION', label: 'Policy violation' },\n { value: 'MISLEADING', label: 'Misleading listing' },\n { value: 'BROKEN', label: 'Broken listing' },\n { value: 'SECURITY_RISK', label: 'Security, privacy or safety risk' },\n { value: 'IP_INFRINGEMENT', label: 'Intellectual-property claim' },\n { value: 'PUBLISHER_VERIFICATION', label: 'Publisher verification' },\n { value: 'OTHER', label: 'Other' },\n] as const;\n\nexport type TakedownCategory = (typeof TAKEDOWN_CATEGORIES)[number]['value'];\n\nexport function labelFor(\n options: ReadonlyArray<{ value: string; label: string }>,\n value?: string | null\n): string {\n if (!value) return '—';\n return options.find((o) => o.value === value)?.label ?? value;\n}\n\nexport type GovernanceStats = {\n pendingSubmissions: number;\n overdueSubmissions: number;\n oldestPendingSubmittedAt?: string | null;\n openListingReports: number;\n openReviewReports: number;\n openRatingDisputes: number;\n pendingAppeals: number;\n overdueAppeals: number;\n takenDownListings: number;\n};\n\nexport type ListingReport = {\n id: string;\n productId: string;\n reportedBy: string;\n reason: ListingReportReason;\n details?: string | null;\n status: 'OPEN' | 'ACTIONED' | 'DISMISSED';\n resolvedBy?: string | null;\n resolvedAt?: string | null;\n resolutionNote?: string | null;\n createdAt: string;\n product?: { id: string; name: string; slug: string; status: string } | null;\n};\n\nexport type GovernanceReviewReport = {\n id: string;\n reviewId: string;\n reportedBy: string;\n reason: string;\n details?: string | null;\n status: 'PENDING' | 'ACTIONED' | 'DISMISSED';\n isRatingDispute: boolean;\n reportedByPublisher: boolean;\n reviewedAt?: string | null;\n actionTaken?: string | null;\n resolutionNote?: string | null;\n createdAt: string;\n review?: {\n id: string;\n rating: number;\n title?: string | null;\n comment?: string | null;\n status: string;\n productName?: string | null;\n productSlug?: string | null;\n } | null;\n};\n\nexport type ListingTakedown = {\n takenDownAt: string;\n category?: TakedownCategory | null;\n reason?: string | null;\n source: 'LISTING' | 'PUBLISHER_SUSPENSION';\n statusBeforeTakedown?: string | null;\n};\n\nexport type TakenDownListing = {\n id: string;\n name: string;\n slug: string;\n status: string;\n takedown?: ListingTakedown | null;\n};\n\nexport type GovernanceAppeal = {\n id: string;\n targetType: 'LISTING' | 'PUBLISHER';\n productId?: string | null;\n publisherId: string;\n statement: string;\n actionReason?: string | null;\n actionAt?: string | null;\n status: 'PENDING' | 'UPHELD' | 'REVERSED';\n submittedAt: string;\n reviewDueAt: string;\n isReviewOverdue: boolean;\n decidedAt?: string | null;\n decisionNote?: string | null;\n product?: { id: string; name: string; slug: string } | null;\n};\n\nexport const GOVERNANCE_STATS_QUERY = /* GraphQL */ `\n query StoreGovernanceStats {\n storeGovernanceStats {\n pendingSubmissions\n overdueSubmissions\n oldestPendingSubmittedAt\n openListingReports\n openReviewReports\n openRatingDisputes\n pendingAppeals\n overdueAppeals\n takenDownListings\n }\n }\n`;\n\nexport const LISTING_REPORTS_QUERY = /* GraphQL */ `\n query StoreListingReports($status: ListingReportStatus, $limit: Int, $offset: Int) {\n listingReports(status: $status, limit: $limit, offset: $offset) {\n totalCount\n reports {\n id\n productId\n reportedBy\n reason\n details\n status\n resolvedBy\n resolvedAt\n resolutionNote\n createdAt\n product {\n id\n name\n slug\n status\n }\n }\n }\n }\n`;\n\nexport const REVIEW_REPORTS_QUERY = /* GraphQL */ `\n query StoreReviewReports(\n $status: ReviewReportStatus\n $ratingDisputesOnly: Boolean\n $limit: Int\n $offset: Int\n ) {\n reviewReports(\n status: $status\n ratingDisputesOnly: $ratingDisputesOnly\n limit: $limit\n offset: $offset\n ) {\n totalCount\n reports {\n id\n reviewId\n reportedBy\n reason\n details\n status\n isRatingDispute\n reportedByPublisher\n reviewedAt\n actionTaken\n resolutionNote\n createdAt\n review {\n id\n rating\n title\n comment\n status\n productName\n productSlug\n }\n }\n }\n }\n`;\n\nconst APPEAL_FIELDS = `\n id\n targetType\n productId\n publisherId\n statement\n actionReason\n actionAt\n status\n submittedAt\n reviewDueAt\n isReviewOverdue\n decidedAt\n decisionNote\n product {\n id\n name\n slug\n }\n`;\n\nexport const APPEALS_QUERY = /* GraphQL */ `\n query StoreGovernanceAppeals($status: GovernanceAppealStatus, $limit: Int, $offset: Int) {\n governanceAppeals(status: $status, limit: $limit, offset: $offset) {\n totalCount\n appeals { ${APPEAL_FIELDS} }\n }\n }\n`;\n\nconst TAKEDOWN_FIELDS = `\n takedown {\n takenDownAt\n category\n reason\n source\n statusBeforeTakedown\n }\n`;\n\nexport const TAKEN_DOWN_QUERY = /* GraphQL */ `\n query StoreTakenDownListings($limit: Int, $offset: Int) {\n takenDownListings(limit: $limit, offset: $offset) {\n totalCount\n products {\n id\n name\n slug\n status\n ${TAKEDOWN_FIELDS}\n }\n }\n }\n`;\n\nexport const RESOLVE_LISTING_REPORT_MUTATION = /* GraphQL */ `\n mutation StoreResolveListingReport($input: ResolveListingReportInput!) {\n resolveListingReport(input: $input) {\n id\n status\n }\n }\n`;\n\nexport const RESOLVE_REVIEW_REPORT_MUTATION = /* GraphQL */ `\n mutation StoreResolveReviewReport($input: ResolveReviewReportInput!) {\n resolveReviewReport(input: $input) {\n id\n status\n actionTaken\n }\n }\n`;\n\nexport const TAKEDOWN_LISTING_MUTATION = /* GraphQL */ `\n mutation StoreTakedownListing($input: TakedownListingInput!) {\n takedownListing(input: $input) {\n id\n status\n isTakenDown\n }\n }\n`;\n\nexport const REINSTATE_LISTING_MUTATION = /* GraphQL */ `\n mutation StoreReinstateListing($productId: ID!, $note: String) {\n reinstateListing(productId: $productId, note: $note) {\n id\n status\n isTakenDown\n }\n }\n`;\n\nexport const DECIDE_APPEAL_MUTATION = /* GraphQL */ `\n mutation StoreDecideGovernanceAppeal($input: DecideGovernanceAppealInput!) {\n decideGovernanceAppeal(input: $input) {\n id\n status\n }\n }\n`;\n\nexport const REPORT_LISTING_MUTATION = /* GraphQL */ `\n mutation StoreReportListing($input: ReportListingInput!) {\n reportListing(input: $input) {\n id\n status\n reason\n }\n }\n`;\n\nexport const MY_GOVERNANCE_QUERY = /* GraphQL */ `\n query StoreMyGovernance {\n myTakenDownListings {\n id\n name\n slug\n status\n ${TAKEDOWN_FIELDS}\n }\n myGovernanceAppeals(limit: 50) {\n totalCount\n appeals { ${APPEAL_FIELDS} }\n }\n myPublisher {\n id\n name\n verificationStatus\n suspensionReason\n }\n }\n`;\n\nexport const FILE_APPEAL_MUTATION = /* GraphQL */ `\n mutation StoreFileGovernanceAppeal($input: FileGovernanceAppealInput!) {\n fileGovernanceAppeal(input: $input) {\n id\n status\n reviewDueAt\n }\n }\n`;\n\nexport const SEARCH_LISTINGS_QUERY = /* GraphQL */ `\n query StoreGovernanceSearchListings($query: String!) {\n searchProducts(query: $query, limit: 10) {\n id\n name\n slug\n }\n }\n`;\n\n/** Run a global-gateway operation and surface the first GraphQL error. */\nexport async function runGovernanceOperation<T>(\n query: string,\n variables: Record<string, unknown> | undefined,\n authToken: string | null | undefined,\n fallbackError: string\n): Promise<T> {\n const result = await graphqlFetch<T>({\n gateway: 'global',\n query,\n variables,\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? fallbackError);\n }\n if (!result.data) {\n throw new Error(fallbackError);\n }\n return result.data;\n}\n\nexport function formatGovernanceDate(iso?: string | null): string {\n if (!iso) return '—';\n return new Date(iso).toLocaleString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n"],"mappings":";;AAQA,IAAa,IAAyB;CACpC;EAAE,OAAO;EAAU,OAAO;EAAqC;CAC/D;EAAE,OAAO;EAAc,OAAO;EAAkD;CAChF;EAAE,OAAO;EAAS,OAAO;EAAgC;CACzD;EAAE,OAAO;EAAY,OAAO;EAAoC;CAChE;EAAE,OAAO;EAAmB,OAAO;EAAmC;CACtE;EAAE,OAAO;EAAQ,OAAO;EAA6B;CACrD;EAAE,OAAO;EAAS,OAAO;EAAkB;CAC5C,EAIY,IAAsB;CACjC;EAAE,OAAO;EAAoB,OAAO;EAAoB;CACxD;EAAE,OAAO;EAAc,OAAO;EAAsB;CACpD;EAAE,OAAO;EAAU,OAAO;EAAkB;CAC5C;EAAE,OAAO;EAAiB,OAAO;EAAoC;CACrE;EAAE,OAAO;EAAmB,OAAO;EAA+B;CAClE;EAAE,OAAO;EAA0B,OAAO;EAA0B;CACpE;EAAE,OAAO;EAAS,OAAO;EAAS;CACnC;AAID,SAAgB,EACd,GACA,GACQ;AAER,QADK,IACE,EAAQ,MAAM,MAAM,EAAE,UAAU,EAAM,EAAE,SAAS,IADrC;;AAuFrB,IAAa,IAAuC,oTAgBvC,IAAsC,kfA0BtC,IAAqC,quBAyC5C,IAAgB,qOAqBT,IAA8B;;;;kBAIzB,EAAc;;;GAK1B,IAAkB,0GAUX,IAAiC;;;;;;;;;UASpC,EAAgB;;;;GAMb,IAAgD,iKAShD,IAA+C,iLAU/C,IAA0C,qKAU1C,IAA2C,6LAU3C,IAAuC,uKASvC,IAAwC,0JAUxC,IAAoC;;;;;;;QAOzC,EAAgB;;;;kBAIN,EAAc;;;;;;;;;GAWnB,IAAqC,oLAUrC,IAAsC;AAWnD,eAAsB,EACpB,GACA,GACA,GACA,GACY;CACZ,IAAM,IAAS,MAAM,EAAgB;EACnC,SAAS;EACT;EACA;EACA,WAAW,KAAa,KAAA;EACzB,CAAC;AACF,KAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,IAAI,WAAW,EAAc;AAE7D,KAAI,CAAC,EAAO,KACV,OAAU,MAAM,EAAc;AAEhC,QAAO,EAAO;;AAGhB,SAAgB,EAAqB,GAA6B;AAEhE,QADK,IACE,IAAI,KAAK,EAAI,CAAC,eAAe,KAAA,GAAW;EAC7C,OAAO;EACP,KAAK;EACL,MAAM;EACN,MAAM;EACN,QAAQ;EACT,CAAC,GAPe"}
|