@burdenoff/microfe-store 2026.624.1 → 2026.624.3
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/components/AccessDenied.js +1 -1
- package/dist/components/AccessDenied.js.map +1 -1
- package/dist/components/InstallAppModal.js +106 -119
- package/dist/components/InstallAppModal.js.map +1 -1
- package/dist/components/cart/CartDrawer.js +5 -5
- package/dist/components/cart/CartDrawer.js.map +1 -1
- package/dist/pages/AdminDashboardPage.js +1 -1
- package/dist/pages/AdminDashboardPage.js.map +1 -1
- package/dist/pages/AdminStoresPage.js +390 -372
- package/dist/pages/AdminStoresPage.js.map +1 -1
- package/dist/pages/AdminSubmissionsQueuePage.js +1 -1
- package/dist/pages/AdminSubmissionsQueuePage.js.map +1 -1
- package/dist/pages/InstalledAppsPage.js +110 -133
- package/dist/pages/InstalledAppsPage.js.map +1 -1
- package/dist/pages/MarketplaceHomePage.js +1 -1
- package/dist/pages/MarketplaceHomePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminStoresPage.js","names":[],"sources":["../../src/pages/AdminStoresPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useCallback, useEffect, useState } from 'react';\nimport { ArrowLeft, Plus, Pencil, Trash2, RefreshCw, Store as StoreIcon } from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { useNavigate } from 'react-router-dom';\nimport { useStore } from '../providers/StoreProvider';\n\ntype StoreRecord = {\n id: string;\n slug: string;\n name: string;\n type: 'PUBLIC' | 'PRIVATE' | 'ENTERPRISE';\n tenantId?: string | null;\n isPublic: boolean;\n requiresApproval: boolean;\n allowUnverifiedPublishers: boolean;\n currency: string;\n logoUrl?: string | null;\n description?: string | null;\n createdAt: string;\n};\n\nconst STORES_QUERY = /* GraphQL */ `\n query AdminStores {\n stores {\n id\n slug\n name\n type\n tenantId\n isPublic\n requiresApproval\n allowUnverifiedPublishers\n currency\n logoUrl\n description\n createdAt\n }\n }\n`;\n\nconst CREATE_STORE_MUTATION = /* GraphQL */ `\n mutation CreateStore($input: CreateStoreInput!) {\n createStore(input: $input) {\n id\n slug\n name\n type\n }\n }\n`;\n\nconst UPDATE_STORE_MUTATION = /* GraphQL */ `\n mutation UpdateStore($id: ID!, $input: UpdateStoreInput!) {\n updateStore(id: $id, input: $input) {\n id\n name\n isPublic\n requiresApproval\n }\n }\n`;\n\nconst DELETE_STORE_MUTATION = /* GraphQL */ `\n mutation DeleteStore($id: ID!) {\n deleteStore(id: $id)\n }\n`;\n\ntype FormState = {\n slug: string;\n name: string;\n type: StoreRecord['type'];\n tenantId: string;\n isPublic: boolean;\n requiresApproval: boolean;\n allowUnverifiedPublishers: boolean;\n currency: string;\n description: string;\n};\n\nconst emptyForm: FormState = {\n slug: '',\n name: '',\n type: 'PRIVATE',\n tenantId: '',\n isPublic: false,\n requiresApproval: false,\n allowUnverifiedPublishers: true,\n currency: 'USD',\n description: '',\n};\n\nexport const AdminStoresPage: FC = () => {\n const navigate = useNavigate();\n const { authToken, basePath } = useStore();\n const [stores, setStores] = useState<StoreRecord[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [showForm, setShowForm] = useState(false);\n const [form, setForm] = useState<FormState>(emptyForm);\n const [editingId, setEditingId] = useState<string | null>(null);\n\n const load = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const result = await graphqlFetch<{ stores: StoreRecord[] }>({\n gateway: 'global',\n query: STORES_QUERY,\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to load stores');\n }\n setStores(result.data?.stores ?? []);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load stores');\n } finally {\n setLoading(false);\n }\n }, [authToken]);\n\n useEffect(() => {\n void load();\n }, [load]);\n\n const openCreate = () => {\n setEditingId(null);\n setForm(emptyForm);\n setShowForm(true);\n };\n\n const openEdit = (s: StoreRecord) => {\n setEditingId(s.id);\n setForm({\n slug: s.slug,\n name: s.name,\n type: s.type,\n tenantId: s.tenantId ?? '',\n isPublic: s.isPublic,\n requiresApproval: s.requiresApproval,\n allowUnverifiedPublishers: s.allowUnverifiedPublishers,\n currency: s.currency,\n description: s.description ?? '',\n });\n setShowForm(true);\n };\n\n const submit = async () => {\n if (!form.slug.trim() || !form.name.trim()) {\n setError('Slug and name are required');\n return;\n }\n setError(null);\n try {\n if (editingId) {\n await graphqlFetch({\n gateway: 'global',\n query: UPDATE_STORE_MUTATION,\n variables: {\n id: editingId,\n input: {\n name: form.name,\n isPublic: form.isPublic,\n requiresApproval: form.requiresApproval,\n allowUnverifiedPublishers: form.allowUnverifiedPublishers,\n currency: form.currency,\n description: form.description || null,\n },\n },\n authToken: authToken || undefined,\n });\n } else {\n await graphqlFetch({\n gateway: 'global',\n query: CREATE_STORE_MUTATION,\n variables: {\n input: {\n slug: form.slug,\n name: form.name,\n type: form.type,\n tenantId: form.tenantId || null,\n isPublic: form.isPublic,\n requiresApproval: form.requiresApproval,\n allowUnverifiedPublishers: form.allowUnverifiedPublishers,\n currency: form.currency,\n description: form.description || null,\n },\n },\n authToken: authToken || undefined,\n });\n }\n setShowForm(false);\n setEditingId(null);\n setForm(emptyForm);\n await load();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Save failed');\n }\n };\n\n const remove = async (id: string) => {\n if (!confirm('Delete this store? This is a soft delete.')) return;\n try {\n await graphqlFetch({\n gateway: 'global',\n query: DELETE_STORE_MUTATION,\n variables: { id },\n authToken: authToken || undefined,\n });\n await load();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Delete failed');\n }\n };\n\n return (\n <div className=\"min-h-screen bg-surface-default\">\n <header className=\"border-b border-border-default bg-surface-raised\">\n <div className=\"mx-auto max-w-7xl px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/dashboard`)}\n className=\"inline-flex items-center gap-2 rounded-md px-3 py-1.5 text-sm text-text-secondary hover:bg-surface-hover hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-4\" />\n Back\n </button>\n <div className=\"flex items-center gap-2\">\n <StoreIcon className=\"size-5 text-text-primary\" />\n <h1 className=\"text-xl font-semibold text-text-primary\">Store registry</h1>\n <span className=\"rounded-full bg-surface-accent-muted px-2 py-0.5 text-xs font-medium text-text-accent\">\n {stores.length}\n </span>\n </div>\n </div>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => void load()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary hover:bg-surface-hover\"\n disabled={loading}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n Refresh\n </button>\n <button\n type=\"button\"\n onClick={openCreate}\n className=\"inline-flex items-center gap-2 rounded-md bg-surface-accent px-3 py-1.5 text-sm font-medium text-text-on-accent hover:bg-surface-accent-hover\"\n >\n <Plus className=\"size-4\" />\n Create store\n </button>\n </div>\n </div>\n </div>\n </header>\n\n <main className=\"mx-auto max-w-7xl p-6\">\n {error && (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-md border border-border-danger bg-surface-danger-muted px-4 py-3 text-sm text-text-danger\"\n >\n {error}\n </div>\n )}\n\n {showForm && (\n <div className=\"mb-6 rounded-lg border border-border-default bg-surface-raised p-4\">\n <h2 className=\"mb-3 text-sm font-semibold text-text-primary\">\n {editingId ? 'Edit store' : 'Create store'}\n </h2>\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\n <label className=\"block\">\n <span className=\"text-xs text-text-secondary\">Slug</span>\n <input\n type=\"text\"\n aria-label=\"Slug\"\n value={form.slug}\n onChange={(e) => setForm({ ...form, slug: e.target.value })}\n disabled={!!editingId}\n required\n aria-required=\"true\"\n className=\"mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary disabled:opacity-60\"\n />\n </label>\n <label className=\"block\">\n <span className=\"text-xs text-text-secondary\">Name</span>\n <input\n type=\"text\"\n aria-label=\"Name\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n required\n aria-required=\"true\"\n className=\"mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary\"\n />\n </label>\n <label className=\"block\">\n <span className=\"text-xs text-text-secondary\">Type</span>\n <select\n aria-label=\"Store type\"\n value={form.type}\n onChange={(e) => setForm({ ...form, type: e.target.value as FormState['type'] })}\n disabled={!!editingId}\n className=\"mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary disabled:opacity-60\"\n >\n <option value=\"PUBLIC\">Public</option>\n <option value=\"PRIVATE\">Private (opt-in)</option>\n <option value=\"ENTERPRISE\">Enterprise (tenant-scoped)</option>\n </select>\n </label>\n <label className=\"block\">\n <span className=\"text-xs text-text-secondary\">Currency</span>\n <input\n type=\"text\"\n aria-label=\"Currency\"\n value={form.currency}\n onChange={(e) => setForm({ ...form, currency: e.target.value })}\n className=\"mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary\"\n />\n </label>\n {form.type !== 'PUBLIC' && (\n <label className=\"block md:col-span-2\">\n <span className=\"text-xs text-text-secondary\">Tenant ID (optional)</span>\n <input\n type=\"text\"\n aria-label=\"Tenant ID\"\n value={form.tenantId}\n onChange={(e) => setForm({ ...form, tenantId: e.target.value })}\n disabled={!!editingId}\n className=\"mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary disabled:opacity-60\"\n />\n </label>\n )}\n <label className=\"block md:col-span-2\">\n <span className=\"text-xs text-text-secondary\">Description</span>\n <textarea\n aria-label=\"Description\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n rows={2}\n className=\"mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary\"\n />\n </label>\n <label className=\"flex items-center gap-2\">\n <input\n type=\"checkbox\"\n aria-label=\"Publicly visible\"\n checked={form.isPublic}\n onChange={(e) => setForm({ ...form, isPublic: e.target.checked })}\n />\n <span className=\"text-sm text-text-primary\">Publicly visible</span>\n </label>\n <label className=\"flex items-center gap-2\">\n <input\n type=\"checkbox\"\n aria-label=\"Requires admin approval\"\n checked={form.requiresApproval}\n onChange={(e) => setForm({ ...form, requiresApproval: e.target.checked })}\n />\n <span className=\"text-sm text-text-primary\">Requires admin approval</span>\n </label>\n <label className=\"flex items-center gap-2 md:col-span-2\">\n <input\n type=\"checkbox\"\n aria-label=\"Allow unverified publishers to submit\"\n checked={form.allowUnverifiedPublishers}\n onChange={(e) =>\n setForm({ ...form, allowUnverifiedPublishers: e.target.checked })\n }\n />\n <span className=\"text-sm text-text-primary\">\n Allow unverified publishers to submit\n </span>\n </label>\n </div>\n <div className=\"mt-4 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n setShowForm(false);\n setEditingId(null);\n setForm(emptyForm);\n }}\n className=\"rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary hover:bg-surface-hover\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void submit()}\n className=\"rounded-md bg-surface-accent px-3 py-1.5 text-sm font-medium text-text-on-accent hover:bg-surface-accent-hover\"\n >\n {editingId ? 'Save changes' : 'Create'}\n </button>\n </div>\n </div>\n )}\n\n {loading && stores.length === 0 ? (\n <output className=\"block py-16 text-center text-text-secondary\">Loading stores…</output>\n ) : stores.length === 0 ? (\n <div className=\"py-16 text-center text-text-secondary\">No stores yet.</div>\n ) : (\n <div className=\"overflow-hidden rounded-lg border border-border-default\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-surface-muted text-left text-xs uppercase tracking-wide text-text-secondary\">\n <tr>\n <th className=\"px-4 py-2\">Slug</th>\n <th className=\"px-4 py-2\">Name</th>\n <th className=\"px-4 py-2\">Type</th>\n <th className=\"px-4 py-2\">Public</th>\n <th className=\"px-4 py-2\">Approval</th>\n <th className=\"px-4 py-2\">Currency</th>\n <th className=\"px-4 py-2 text-right\">Actions</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-default bg-surface-raised\">\n {stores.map((s) => (\n <tr key={s.id} className=\"text-text-primary\">\n <td className=\"px-4 py-2 font-mono text-xs\">{s.slug}</td>\n <td className=\"px-4 py-2\">{s.name}</td>\n <td className=\"px-4 py-2\">\n <span className=\"rounded-full bg-surface-accent-muted px-2 py-0.5 text-xs font-medium text-text-accent\">\n {s.type}\n </span>\n </td>\n <td className=\"px-4 py-2 text-xs\">{s.isPublic ? 'Yes' : 'No'}</td>\n <td className=\"px-4 py-2 text-xs\">\n {s.requiresApproval ? 'Required' : 'Auto-publish'}\n </td>\n <td className=\"px-4 py-2 text-xs\">{s.currency}</td>\n <td className=\"px-4 py-2 text-right\">\n <button\n type=\"button\"\n onClick={() => openEdit(s)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-default px-2 py-1 text-xs text-text-primary hover:bg-surface-hover\"\n >\n <Pencil className=\"size-3\" />\n Edit\n </button>\n {s.id !== 'store_public_default' && (\n <button\n type=\"button\"\n onClick={() => void remove(s.id)}\n className=\"ml-2 inline-flex items-center gap-1 rounded-md border border-border-danger px-2 py-1 text-xs text-text-danger hover:bg-surface-danger-muted\"\n >\n <Trash2 className=\"size-3\" />\n Delete\n </button>\n )}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AdminStoresPage.js","names":[],"sources":["../../src/pages/AdminStoresPage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useCallback, useEffect, useState } from 'react';\nimport { ArrowLeft, Plus, Pencil, Trash2, RefreshCw, Store as StoreIcon } from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { useNavigate } from 'react-router-dom';\nimport { useStore } from '../providers/StoreProvider';\nimport {\n AlertDialog,\n AlertDialogContent,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogCancel,\n AlertDialogAction,\n} from '@burdenoff/fe-libs/ui';\n\ntype StoreRecord = {\n id: string;\n slug: string;\n name: string;\n type: 'PUBLIC' | 'PRIVATE' | 'ENTERPRISE';\n tenantId?: string | null;\n isPublic: boolean;\n requiresApproval: boolean;\n allowUnverifiedPublishers: boolean;\n currency: string;\n logoUrl?: string | null;\n description?: string | null;\n createdAt: string;\n};\n\nconst STORES_QUERY = /* GraphQL */ `\n query AdminStores {\n stores {\n id\n slug\n name\n type\n tenantId\n isPublic\n requiresApproval\n allowUnverifiedPublishers\n currency\n logoUrl\n description\n createdAt\n }\n }\n`;\n\nconst CREATE_STORE_MUTATION = /* GraphQL */ `\n mutation CreateStore($input: CreateStoreInput!) {\n createStore(input: $input) {\n id\n slug\n name\n type\n }\n }\n`;\n\nconst UPDATE_STORE_MUTATION = /* GraphQL */ `\n mutation UpdateStore($id: ID!, $input: UpdateStoreInput!) {\n updateStore(id: $id, input: $input) {\n id\n name\n isPublic\n requiresApproval\n }\n }\n`;\n\nconst DELETE_STORE_MUTATION = /* GraphQL */ `\n mutation DeleteStore($id: ID!) {\n deleteStore(id: $id)\n }\n`;\n\ntype FormState = {\n slug: string;\n name: string;\n type: StoreRecord['type'];\n tenantId: string;\n isPublic: boolean;\n requiresApproval: boolean;\n allowUnverifiedPublishers: boolean;\n currency: string;\n description: string;\n};\n\nconst emptyForm: FormState = {\n slug: '',\n name: '',\n type: 'PRIVATE',\n tenantId: '',\n isPublic: false,\n requiresApproval: false,\n allowUnverifiedPublishers: true,\n currency: 'USD',\n description: '',\n};\n\nexport const AdminStoresPage: FC = () => {\n const navigate = useNavigate();\n const { authToken, basePath } = useStore();\n const [stores, setStores] = useState<StoreRecord[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [showForm, setShowForm] = useState(false);\n const [form, setForm] = useState<FormState>(emptyForm);\n const [editingId, setEditingId] = useState<string | null>(null);\n const [pendingDeleteId, setPendingDeleteId] = useState<string | null>(null);\n\n const load = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const result = await graphqlFetch<{ stores: StoreRecord[] }>({\n gateway: 'global',\n query: STORES_QUERY,\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to load stores');\n }\n setStores(result.data?.stores ?? []);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load stores');\n } finally {\n setLoading(false);\n }\n }, [authToken]);\n\n useEffect(() => {\n void load();\n }, [load]);\n\n const openCreate = () => {\n setEditingId(null);\n setForm(emptyForm);\n setShowForm(true);\n };\n\n const openEdit = (s: StoreRecord) => {\n setEditingId(s.id);\n setForm({\n slug: s.slug,\n name: s.name,\n type: s.type,\n tenantId: s.tenantId ?? '',\n isPublic: s.isPublic,\n requiresApproval: s.requiresApproval,\n allowUnverifiedPublishers: s.allowUnverifiedPublishers,\n currency: s.currency,\n description: s.description ?? '',\n });\n setShowForm(true);\n };\n\n const submit = async () => {\n if (!form.slug.trim() || !form.name.trim()) {\n setError('Slug and name are required');\n return;\n }\n setError(null);\n try {\n if (editingId) {\n await graphqlFetch({\n gateway: 'global',\n query: UPDATE_STORE_MUTATION,\n variables: {\n id: editingId,\n input: {\n name: form.name,\n isPublic: form.isPublic,\n requiresApproval: form.requiresApproval,\n allowUnverifiedPublishers: form.allowUnverifiedPublishers,\n currency: form.currency,\n description: form.description || null,\n },\n },\n authToken: authToken || undefined,\n });\n } else {\n await graphqlFetch({\n gateway: 'global',\n query: CREATE_STORE_MUTATION,\n variables: {\n input: {\n slug: form.slug,\n name: form.name,\n type: form.type,\n tenantId: form.tenantId || null,\n isPublic: form.isPublic,\n requiresApproval: form.requiresApproval,\n allowUnverifiedPublishers: form.allowUnverifiedPublishers,\n currency: form.currency,\n description: form.description || null,\n },\n },\n authToken: authToken || undefined,\n });\n }\n setShowForm(false);\n setEditingId(null);\n setForm(emptyForm);\n await load();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Save failed');\n }\n };\n\n const remove = async (id: string) => {\n try {\n await graphqlFetch({\n gateway: 'global',\n query: DELETE_STORE_MUTATION,\n variables: { id },\n authToken: authToken || undefined,\n });\n await load();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Delete failed');\n }\n };\n\n return (\n <div className=\"min-h-screen bg-surface-default\">\n <header className=\"border-b border-border-default bg-surface-raised\">\n <div className=\"mx-auto max-w-7xl px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/dashboard`)}\n className=\"inline-flex items-center gap-2 rounded-md px-3 py-1.5 text-sm text-text-secondary hover:bg-surface-hover hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-4\" />\n Back\n </button>\n <div className=\"flex items-center gap-2\">\n <StoreIcon className=\"size-5 text-text-primary\" />\n <h1 className=\"text-xl font-semibold text-text-primary\">Store registry</h1>\n <span className=\"rounded-full bg-surface-accent-muted px-2 py-0.5 text-xs font-medium text-text-accent\">\n {stores.length}\n </span>\n </div>\n </div>\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={() => void load()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary hover:bg-surface-hover\"\n disabled={loading}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n Refresh\n </button>\n <button\n type=\"button\"\n onClick={openCreate}\n className=\"inline-flex items-center gap-2 rounded-md bg-surface-accent px-3 py-1.5 text-sm font-medium text-text-on-accent hover:bg-surface-accent-hover\"\n >\n <Plus className=\"size-4\" />\n Create store\n </button>\n </div>\n </div>\n </div>\n </header>\n\n <main className=\"mx-auto max-w-7xl p-6\">\n {error && (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-md border border-border-danger bg-surface-danger-muted px-4 py-3 text-sm text-text-danger\"\n >\n {error}\n </div>\n )}\n\n {showForm && (\n <div className=\"mb-6 rounded-lg border border-border-default bg-surface-raised p-4\">\n <h2 className=\"mb-3 text-sm font-semibold text-text-primary\">\n {editingId ? 'Edit store' : 'Create store'}\n </h2>\n <div className=\"grid grid-cols-1 gap-3 md:grid-cols-2\">\n <label className=\"block\">\n <span className=\"text-xs text-text-secondary\">Slug</span>\n <input\n type=\"text\"\n aria-label=\"Slug\"\n value={form.slug}\n onChange={(e) => setForm({ ...form, slug: e.target.value })}\n disabled={!!editingId}\n required\n aria-required=\"true\"\n className=\"mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary disabled:opacity-60\"\n />\n </label>\n <label className=\"block\">\n <span className=\"text-xs text-text-secondary\">Name</span>\n <input\n type=\"text\"\n aria-label=\"Name\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n required\n aria-required=\"true\"\n className=\"mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary\"\n />\n </label>\n <label className=\"block\">\n <span className=\"text-xs text-text-secondary\">Type</span>\n <select\n aria-label=\"Store type\"\n value={form.type}\n onChange={(e) => setForm({ ...form, type: e.target.value as FormState['type'] })}\n disabled={!!editingId}\n className=\"mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary disabled:opacity-60\"\n >\n <option value=\"PUBLIC\">Public</option>\n <option value=\"PRIVATE\">Private (opt-in)</option>\n <option value=\"ENTERPRISE\">Enterprise (tenant-scoped)</option>\n </select>\n </label>\n <label className=\"block\">\n <span className=\"text-xs text-text-secondary\">Currency</span>\n <input\n type=\"text\"\n aria-label=\"Currency\"\n value={form.currency}\n onChange={(e) => setForm({ ...form, currency: e.target.value })}\n className=\"mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary\"\n />\n </label>\n {form.type !== 'PUBLIC' && (\n <label className=\"block md:col-span-2\">\n <span className=\"text-xs text-text-secondary\">Tenant ID (optional)</span>\n <input\n type=\"text\"\n aria-label=\"Tenant ID\"\n value={form.tenantId}\n onChange={(e) => setForm({ ...form, tenantId: e.target.value })}\n disabled={!!editingId}\n className=\"mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary disabled:opacity-60\"\n />\n </label>\n )}\n <label className=\"block md:col-span-2\">\n <span className=\"text-xs text-text-secondary\">Description</span>\n <textarea\n aria-label=\"Description\"\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n rows={2}\n className=\"mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary\"\n />\n </label>\n <label className=\"flex items-center gap-2\">\n <input\n type=\"checkbox\"\n aria-label=\"Publicly visible\"\n checked={form.isPublic}\n onChange={(e) => setForm({ ...form, isPublic: e.target.checked })}\n />\n <span className=\"text-sm text-text-primary\">Publicly visible</span>\n </label>\n <label className=\"flex items-center gap-2\">\n <input\n type=\"checkbox\"\n aria-label=\"Requires admin approval\"\n checked={form.requiresApproval}\n onChange={(e) => setForm({ ...form, requiresApproval: e.target.checked })}\n />\n <span className=\"text-sm text-text-primary\">Requires admin approval</span>\n </label>\n <label className=\"flex items-center gap-2 md:col-span-2\">\n <input\n type=\"checkbox\"\n aria-label=\"Allow unverified publishers to submit\"\n checked={form.allowUnverifiedPublishers}\n onChange={(e) =>\n setForm({ ...form, allowUnverifiedPublishers: e.target.checked })\n }\n />\n <span className=\"text-sm text-text-primary\">\n Allow unverified publishers to submit\n </span>\n </label>\n </div>\n <div className=\"mt-4 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n setShowForm(false);\n setEditingId(null);\n setForm(emptyForm);\n }}\n className=\"rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary hover:bg-surface-hover\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void submit()}\n className=\"rounded-md bg-surface-accent px-3 py-1.5 text-sm font-medium text-text-on-accent hover:bg-surface-accent-hover\"\n >\n {editingId ? 'Save changes' : 'Create'}\n </button>\n </div>\n </div>\n )}\n\n {loading && stores.length === 0 ? (\n <output className=\"block py-16 text-center text-text-secondary\">Loading stores…</output>\n ) : stores.length === 0 ? (\n <div className=\"py-16 text-center text-text-secondary\">No stores yet.</div>\n ) : (\n <div className=\"overflow-hidden rounded-lg border border-border-default\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-surface-muted text-left text-xs uppercase tracking-wide text-text-secondary\">\n <tr>\n <th className=\"px-4 py-2\">Slug</th>\n <th className=\"px-4 py-2\">Name</th>\n <th className=\"px-4 py-2\">Type</th>\n <th className=\"px-4 py-2\">Public</th>\n <th className=\"px-4 py-2\">Approval</th>\n <th className=\"px-4 py-2\">Currency</th>\n <th className=\"px-4 py-2 text-right\">Actions</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-default bg-surface-raised\">\n {stores.map((s) => (\n <tr key={s.id} className=\"text-text-primary\">\n <td className=\"px-4 py-2 font-mono text-xs\">{s.slug}</td>\n <td className=\"px-4 py-2\">{s.name}</td>\n <td className=\"px-4 py-2\">\n <span className=\"rounded-full bg-surface-accent-muted px-2 py-0.5 text-xs font-medium text-text-accent\">\n {s.type}\n </span>\n </td>\n <td className=\"px-4 py-2 text-xs\">{s.isPublic ? 'Yes' : 'No'}</td>\n <td className=\"px-4 py-2 text-xs\">\n {s.requiresApproval ? 'Required' : 'Auto-publish'}\n </td>\n <td className=\"px-4 py-2 text-xs\">{s.currency}</td>\n <td className=\"px-4 py-2 text-right\">\n <button\n type=\"button\"\n onClick={() => openEdit(s)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-default px-2 py-1 text-xs text-text-primary hover:bg-surface-hover\"\n >\n <Pencil className=\"size-3\" />\n Edit\n </button>\n {s.id !== 'store_public_default' && (\n <button\n type=\"button\"\n onClick={() => setPendingDeleteId(s.id)}\n className=\"ml-2 inline-flex items-center gap-1 rounded-md border border-border-danger px-2 py-1 text-xs text-text-danger hover:bg-surface-danger-muted\"\n >\n <Trash2 className=\"size-3\" />\n Delete\n </button>\n )}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </main>\n\n <AlertDialog\n open={pendingDeleteId !== null}\n onOpenChange={(open) => !open && setPendingDeleteId(null)}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete this store?</AlertDialogTitle>\n <AlertDialogDescription>This is a soft delete.</AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel onClick={() => setPendingDeleteId(null)}>Cancel</AlertDialogCancel>\n <AlertDialogAction\n className=\"bg-surface-danger text-text-on-danger hover:bg-surface-danger-hover\"\n onClick={() => {\n if (pendingDeleteId) void remove(pendingDeleteId);\n setPendingDeleteId(null);\n }}\n >\n Delete\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -351,7 +351,7 @@ var g = "\n query PendingReviewProducts($limit: Int, $offset: Int) {\n pendi
|
|
|
351
351
|
rows: 3,
|
|
352
352
|
required: !0,
|
|
353
353
|
"aria-required": "true",
|
|
354
|
-
className: "mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-2 text-sm text-text-primary focus:border-border-accent focus:outline-none",
|
|
354
|
+
className: "mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-2 text-sm text-text-primary focus:border-border-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
355
355
|
placeholder: "Explain why this submission is being rejected…"
|
|
356
356
|
}),
|
|
357
357
|
/* @__PURE__ */ p("div", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminSubmissionsQueuePage.js","names":[],"sources":["../../src/pages/AdminSubmissionsQueuePage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useCallback, useEffect, useState } from 'react';\nimport { ArrowLeft, Check, X, RefreshCw, PackageCheck, ChevronDown, ChevronUp } from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { useNavigate } from 'react-router-dom';\nimport { useStore } from '../providers/StoreProvider';\nimport { formatPriceForModel } from '../utils';\n\ntype PendingProduct = {\n id: string;\n slug: string;\n name: string;\n type: string;\n nature: string;\n status: string;\n pricingModel: string;\n price: number;\n currency: string;\n description?: string | null;\n submittedAt?: string | null;\n submittedBy?: string | null;\n category?: string | null;\n icon?: string | null;\n publisher?: { id: string; name: string; email: string } | null;\n};\n\ntype ProductStatus = 'PENDING_REVIEW' | 'PUBLISHED' | 'REJECTED' | 'ALL';\n\nconst PENDING_PRODUCTS_QUERY = /* GraphQL */ `\n query PendingReviewProducts($limit: Int, $offset: Int) {\n pendingReviewProducts(limit: $limit, offset: $offset) {\n totalCount\n products {\n id\n slug\n name\n type\n nature\n status\n pricingModel\n price\n currency\n description\n submittedAt\n submittedBy\n category\n icon\n publisher {\n id\n name\n email\n }\n }\n }\n }\n`;\n\nconst ALL_PRODUCTS_QUERY = /* GraphQL */ `\n query AdminProducts($limit: Int, $offset: Int, $status: ProductStatus) {\n products(filter: { status: $status }, limit: $limit, offset: $offset) {\n id\n slug\n name\n type\n nature\n status\n pricingModel\n price\n currency\n description\n submittedAt\n submittedBy\n category\n icon\n publisher {\n id\n name\n email\n }\n }\n }\n`;\n\nconst APPROVE_PRODUCT_MUTATION = /* GraphQL */ `\n mutation ApproveProduct($id: ID!, $notes: String) {\n approveProduct(id: $id, notes: $notes) {\n id\n status\n publishedAt\n }\n }\n`;\n\nconst REJECT_PRODUCT_MUTATION = /* GraphQL */ `\n mutation RejectProduct($id: ID!, $reason: String!) {\n rejectProduct(id: $id, reason: $reason) {\n id\n status\n rejectedReason\n }\n }\n`;\n\nconst STATUS_FILTERS: Array<{ label: string; value: ProductStatus }> = [\n { label: 'Pending review', value: 'PENDING_REVIEW' },\n { label: 'Published', value: 'PUBLISHED' },\n { label: 'Rejected', value: 'REJECTED' },\n { label: 'All', value: 'ALL' },\n];\n\nexport const AdminSubmissionsQueuePage: FC = () => {\n const navigate = useNavigate();\n const { authToken, basePath } = useStore();\n const [products, setProducts] = useState<PendingProduct[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [actingId, setActingId] = useState<string | null>(null);\n const [statusFilter, setStatusFilter] = useState<ProductStatus>('PENDING_REVIEW');\n const [rejectingId, setRejectingId] = useState<string | null>(null);\n const [rejectReason, setRejectReason] = useState('');\n const [viewingId, setViewingId] = useState<string | null>(null);\n\n const loadProducts = useCallback(\n async (filter: ProductStatus) => {\n setLoading(true);\n setError(null);\n try {\n if (filter === 'PENDING_REVIEW') {\n const result = await graphqlFetch<{\n pendingReviewProducts: { totalCount: number; products: PendingProduct[] };\n }>({\n gateway: 'global',\n query: PENDING_PRODUCTS_QUERY,\n variables: { limit: 100, offset: 0 },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to load pending products');\n }\n setProducts(result.data?.pendingReviewProducts.products ?? []);\n setTotalCount(result.data?.pendingReviewProducts.totalCount ?? 0);\n } else {\n const result = await graphqlFetch<{ products: PendingProduct[] }>({\n gateway: 'global',\n query: ALL_PRODUCTS_QUERY,\n variables: {\n limit: 100,\n offset: 0,\n ...(filter !== 'ALL' && { status: filter }),\n },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to load products');\n }\n const list = result.data?.products ?? [];\n setProducts(list);\n setTotalCount(list.length);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load products');\n } finally {\n setLoading(false);\n }\n },\n [authToken]\n );\n\n useEffect(() => {\n void loadProducts(statusFilter);\n }, [loadProducts, statusFilter]);\n\n const approve = async (id: string) => {\n setActingId(id);\n setError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: APPROVE_PRODUCT_MUTATION,\n variables: { id, notes: null },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Approval failed');\n }\n await loadProducts(statusFilter);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Approval failed');\n } finally {\n setActingId(null);\n }\n };\n\n const reject = async (id: string) => {\n if (!rejectReason.trim()) {\n setError('Rejection reason is required');\n return;\n }\n setActingId(id);\n setError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: REJECT_PRODUCT_MUTATION,\n variables: { id, reason: rejectReason.trim() },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Rejection failed');\n }\n setRejectingId(null);\n setRejectReason('');\n await loadProducts(statusFilter);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Rejection failed');\n } finally {\n setActingId(null);\n }\n };\n\n return (\n <div className=\"min-h-screen bg-surface-default\">\n {/* Header */}\n <header className=\"border-b border-border-default bg-surface-raised\">\n <div className=\"mx-auto max-w-7xl px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/dashboard`)}\n className=\"inline-flex items-center gap-2 rounded-md px-3 py-1.5 text-sm text-text-secondary hover:bg-surface-hover hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-4\" />\n Back\n </button>\n <div className=\"flex items-center gap-2\">\n <PackageCheck className=\"size-5 text-text-primary\" />\n <h1 className=\"text-xl font-semibold text-text-primary\">Submission queue</h1>\n <span className=\"rounded-full bg-surface-accent-muted px-2 py-0.5 text-xs font-medium text-text-accent\">\n {totalCount}\n </span>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={() => void loadProducts(statusFilter)}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary hover:bg-surface-hover\"\n disabled={loading}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n Refresh\n </button>\n </div>\n\n {/* Status filter pills */}\n <div className=\"mt-4 flex gap-2\">\n {STATUS_FILTERS.map((f) => (\n <button\n key={f.value}\n type=\"button\"\n onClick={() => setStatusFilter(f.value)}\n className={`rounded-full px-3 py-1 text-xs font-medium transition-colors ${\n statusFilter === f.value\n ? 'bg-surface-accent text-text-on-accent'\n : 'bg-surface-muted text-text-secondary hover:bg-surface-hover'\n }`}\n >\n {f.label}\n </button>\n ))}\n </div>\n </div>\n </header>\n\n <main className=\"mx-auto max-w-7xl p-6\">\n {error && (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-md border border-border-danger bg-surface-danger-muted px-4 py-3 text-sm text-text-danger\"\n >\n {error}\n </div>\n )}\n\n {loading && products.length === 0 ? (\n <output className=\"block py-16 text-center text-text-secondary\">\n Loading submissions…\n </output>\n ) : products.length === 0 ? (\n <div className=\"py-16 text-center text-text-secondary\">\n No products matching the current filter.\n </div>\n ) : (\n <ul className=\"space-y-3\">\n {products.map((p) => (\n <li\n key={p.id}\n className=\"overflow-hidden rounded-lg border border-border-default bg-surface-raised\"\n >\n <div className=\"flex items-start gap-4 p-4\">\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-14 flex-shrink-0 rounded-md object-cover\"\n />\n ) : (\n <div className=\"flex size-14 flex-shrink-0 items-center justify-center rounded-md bg-surface-muted text-xs text-text-secondary\">\n {p.type}\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">\n {p.name}\n </h3>\n <span className=\"rounded-full bg-surface-accent-muted px-2 py-0.5 text-xs font-medium text-text-accent\">\n {p.type}\n </span>\n <span className=\"rounded-full bg-surface-muted px-2 py-0.5 text-xs font-medium text-text-secondary\">\n {p.nature}\n </span>\n </div>\n <div className=\"mt-0.5 text-xs text-text-tertiary\">{p.slug}</div>\n <div className=\"mt-1 flex items-center gap-3 text-xs text-text-secondary\">\n <span>Publisher: {p.publisher?.name ?? 'Unknown'}</span>\n <span>Price: {formatPriceForModel(p.price, p.currency, p.pricingModel)}</span>\n {p.submittedAt && (\n <span>Submitted {new Date(p.submittedAt).toLocaleString()}</span>\n )}\n </div>\n {p.description && (\n <p className=\"mt-2 line-clamp-2 text-sm text-text-secondary\">\n {p.description}\n </p>\n )}\n </div>\n <div className=\"flex flex-shrink-0 flex-col gap-2\">\n <button\n type=\"button\"\n onClick={() => setViewingId(viewingId === p.id ? null : p.id)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-default px-3 py-1.5 text-xs text-text-secondary hover:bg-surface-hover\"\n >\n {viewingId === p.id ? (\n <ChevronUp className=\"size-3.5\" />\n ) : (\n <ChevronDown className=\"size-3.5\" />\n )}\n Details\n </button>\n <button\n type=\"button\"\n onClick={() => void approve(p.id)}\n disabled={actingId === p.id}\n className=\"inline-flex items-center gap-1 rounded-md bg-surface-success px-3 py-1.5 text-xs font-medium text-text-on-success hover:bg-surface-success-hover disabled:opacity-60\"\n >\n <Check className=\"size-3.5\" />\n Approve\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setRejectingId(p.id);\n setRejectReason('');\n }}\n disabled={actingId === p.id}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-danger bg-surface-danger-muted px-3 py-1.5 text-xs font-medium text-text-danger hover:bg-surface-danger hover:text-text-on-danger disabled:opacity-60\"\n >\n <X className=\"size-3.5\" />\n Reject\n </button>\n </div>\n </div>\n\n {viewingId === p.id && (\n <div className=\"border-t border-border-default bg-surface-muted p-4\">\n <dl className=\"grid grid-cols-2 gap-x-6 gap-y-3 text-xs sm:grid-cols-3\">\n <div>\n <dt className=\"font-medium text-text-secondary\">Slug</dt>\n <dd className=\"mt-0.5 font-mono text-text-primary\">{p.slug}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-text-secondary\">Type</dt>\n <dd className=\"mt-0.5 text-text-primary\">{p.type}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-text-secondary\">Nature</dt>\n <dd className=\"mt-0.5 text-text-primary\">{p.nature}</dd>\n </div>\n {p.category && (\n <div>\n <dt className=\"font-medium text-text-secondary\">Category</dt>\n <dd className=\"mt-0.5 text-text-primary\">{p.category}</dd>\n </div>\n )}\n <div>\n <dt className=\"font-medium text-text-secondary\">Pricing</dt>\n <dd className=\"mt-0.5 text-text-primary\">\n {formatPriceForModel(p.price, p.currency, p.pricingModel)}{' '}\n <span className=\"text-text-tertiary\">({p.pricingModel})</span>\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-text-secondary\">Publisher</dt>\n <dd className=\"mt-0.5 text-text-primary\">\n {p.publisher?.name ?? '—'}\n {p.publisher?.email && (\n <span className=\"block text-text-tertiary\">{p.publisher.email}</span>\n )}\n </dd>\n </div>\n {p.submittedBy && (\n <div>\n <dt className=\"font-medium text-text-secondary\">Submitted by</dt>\n <dd className=\"mt-0.5 font-mono text-text-primary\">{p.submittedBy}</dd>\n </div>\n )}\n {p.submittedAt && (\n <div>\n <dt className=\"font-medium text-text-secondary\">Submitted at</dt>\n <dd className=\"mt-0.5 text-text-primary\">\n {new Date(p.submittedAt).toLocaleString()}\n </dd>\n </div>\n )}\n {p.description && (\n <div className=\"col-span-2 sm:col-span-3\">\n <dt className=\"font-medium text-text-secondary\">Description</dt>\n <dd className=\"mt-0.5 whitespace-pre-wrap text-text-primary\">\n {p.description}\n </dd>\n </div>\n )}\n </dl>\n </div>\n )}\n\n {rejectingId === p.id && (\n <div className=\"border-t border-border-default bg-surface-muted px-4 py-3\">\n <label\n htmlFor={`reject-reason-${p.id}`}\n className=\"block text-xs font-medium text-text-secondary\"\n >\n Rejection reason (required)\n </label>\n <textarea\n id={`reject-reason-${p.id}`}\n aria-label=\"Rejection reason\"\n value={rejectReason}\n onChange={(e) => setRejectReason(e.target.value)}\n rows={3}\n required\n aria-required=\"true\"\n className=\"mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-2 text-sm text-text-primary focus:border-border-accent focus:outline-none\"\n placeholder=\"Explain why this submission is being rejected…\"\n />\n <div className=\"mt-2 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n setRejectingId(null);\n setRejectReason('');\n }}\n className=\"rounded-md border border-border-default bg-surface-default px-3 py-1 text-xs text-text-primary hover:bg-surface-hover\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void reject(p.id)}\n disabled={!rejectReason.trim() || actingId === p.id}\n className=\"rounded-md bg-surface-danger px-3 py-1 text-xs font-medium text-text-on-danger hover:bg-surface-danger-hover disabled:opacity-60\"\n >\n Submit rejection\n </button>\n </div>\n </div>\n )}\n </li>\n ))}\n </ul>\n )}\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AdminSubmissionsQueuePage.js","names":[],"sources":["../../src/pages/AdminSubmissionsQueuePage.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useCallback, useEffect, useState } from 'react';\nimport { ArrowLeft, Check, X, RefreshCw, PackageCheck, ChevronDown, ChevronUp } from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { useNavigate } from 'react-router-dom';\nimport { useStore } from '../providers/StoreProvider';\nimport { formatPriceForModel } from '../utils';\n\ntype PendingProduct = {\n id: string;\n slug: string;\n name: string;\n type: string;\n nature: string;\n status: string;\n pricingModel: string;\n price: number;\n currency: string;\n description?: string | null;\n submittedAt?: string | null;\n submittedBy?: string | null;\n category?: string | null;\n icon?: string | null;\n publisher?: { id: string; name: string; email: string } | null;\n};\n\ntype ProductStatus = 'PENDING_REVIEW' | 'PUBLISHED' | 'REJECTED' | 'ALL';\n\nconst PENDING_PRODUCTS_QUERY = /* GraphQL */ `\n query PendingReviewProducts($limit: Int, $offset: Int) {\n pendingReviewProducts(limit: $limit, offset: $offset) {\n totalCount\n products {\n id\n slug\n name\n type\n nature\n status\n pricingModel\n price\n currency\n description\n submittedAt\n submittedBy\n category\n icon\n publisher {\n id\n name\n email\n }\n }\n }\n }\n`;\n\nconst ALL_PRODUCTS_QUERY = /* GraphQL */ `\n query AdminProducts($limit: Int, $offset: Int, $status: ProductStatus) {\n products(filter: { status: $status }, limit: $limit, offset: $offset) {\n id\n slug\n name\n type\n nature\n status\n pricingModel\n price\n currency\n description\n submittedAt\n submittedBy\n category\n icon\n publisher {\n id\n name\n email\n }\n }\n }\n`;\n\nconst APPROVE_PRODUCT_MUTATION = /* GraphQL */ `\n mutation ApproveProduct($id: ID!, $notes: String) {\n approveProduct(id: $id, notes: $notes) {\n id\n status\n publishedAt\n }\n }\n`;\n\nconst REJECT_PRODUCT_MUTATION = /* GraphQL */ `\n mutation RejectProduct($id: ID!, $reason: String!) {\n rejectProduct(id: $id, reason: $reason) {\n id\n status\n rejectedReason\n }\n }\n`;\n\nconst STATUS_FILTERS: Array<{ label: string; value: ProductStatus }> = [\n { label: 'Pending review', value: 'PENDING_REVIEW' },\n { label: 'Published', value: 'PUBLISHED' },\n { label: 'Rejected', value: 'REJECTED' },\n { label: 'All', value: 'ALL' },\n];\n\nexport const AdminSubmissionsQueuePage: FC = () => {\n const navigate = useNavigate();\n const { authToken, basePath } = useStore();\n const [products, setProducts] = useState<PendingProduct[]>([]);\n const [totalCount, setTotalCount] = useState(0);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [actingId, setActingId] = useState<string | null>(null);\n const [statusFilter, setStatusFilter] = useState<ProductStatus>('PENDING_REVIEW');\n const [rejectingId, setRejectingId] = useState<string | null>(null);\n const [rejectReason, setRejectReason] = useState('');\n const [viewingId, setViewingId] = useState<string | null>(null);\n\n const loadProducts = useCallback(\n async (filter: ProductStatus) => {\n setLoading(true);\n setError(null);\n try {\n if (filter === 'PENDING_REVIEW') {\n const result = await graphqlFetch<{\n pendingReviewProducts: { totalCount: number; products: PendingProduct[] };\n }>({\n gateway: 'global',\n query: PENDING_PRODUCTS_QUERY,\n variables: { limit: 100, offset: 0 },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to load pending products');\n }\n setProducts(result.data?.pendingReviewProducts.products ?? []);\n setTotalCount(result.data?.pendingReviewProducts.totalCount ?? 0);\n } else {\n const result = await graphqlFetch<{ products: PendingProduct[] }>({\n gateway: 'global',\n query: ALL_PRODUCTS_QUERY,\n variables: {\n limit: 100,\n offset: 0,\n ...(filter !== 'ALL' && { status: filter }),\n },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to load products');\n }\n const list = result.data?.products ?? [];\n setProducts(list);\n setTotalCount(list.length);\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to load products');\n } finally {\n setLoading(false);\n }\n },\n [authToken]\n );\n\n useEffect(() => {\n void loadProducts(statusFilter);\n }, [loadProducts, statusFilter]);\n\n const approve = async (id: string) => {\n setActingId(id);\n setError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: APPROVE_PRODUCT_MUTATION,\n variables: { id, notes: null },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Approval failed');\n }\n await loadProducts(statusFilter);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Approval failed');\n } finally {\n setActingId(null);\n }\n };\n\n const reject = async (id: string) => {\n if (!rejectReason.trim()) {\n setError('Rejection reason is required');\n return;\n }\n setActingId(id);\n setError(null);\n try {\n const result = await graphqlFetch({\n gateway: 'global',\n query: REJECT_PRODUCT_MUTATION,\n variables: { id, reason: rejectReason.trim() },\n authToken: authToken || undefined,\n });\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Rejection failed');\n }\n setRejectingId(null);\n setRejectReason('');\n await loadProducts(statusFilter);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Rejection failed');\n } finally {\n setActingId(null);\n }\n };\n\n return (\n <div className=\"min-h-screen bg-surface-default\">\n {/* Header */}\n <header className=\"border-b border-border-default bg-surface-raised\">\n <div className=\"mx-auto max-w-7xl px-6 py-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/dashboard`)}\n className=\"inline-flex items-center gap-2 rounded-md px-3 py-1.5 text-sm text-text-secondary hover:bg-surface-hover hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-4\" />\n Back\n </button>\n <div className=\"flex items-center gap-2\">\n <PackageCheck className=\"size-5 text-text-primary\" />\n <h1 className=\"text-xl font-semibold text-text-primary\">Submission queue</h1>\n <span className=\"rounded-full bg-surface-accent-muted px-2 py-0.5 text-xs font-medium text-text-accent\">\n {totalCount}\n </span>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={() => void loadProducts(statusFilter)}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-default bg-surface-default px-3 py-1.5 text-sm text-text-primary hover:bg-surface-hover\"\n disabled={loading}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n Refresh\n </button>\n </div>\n\n {/* Status filter pills */}\n <div className=\"mt-4 flex gap-2\">\n {STATUS_FILTERS.map((f) => (\n <button\n key={f.value}\n type=\"button\"\n onClick={() => setStatusFilter(f.value)}\n className={`rounded-full px-3 py-1 text-xs font-medium transition-colors ${\n statusFilter === f.value\n ? 'bg-surface-accent text-text-on-accent'\n : 'bg-surface-muted text-text-secondary hover:bg-surface-hover'\n }`}\n >\n {f.label}\n </button>\n ))}\n </div>\n </div>\n </header>\n\n <main className=\"mx-auto max-w-7xl p-6\">\n {error && (\n <div\n role=\"alert\"\n className=\"mb-4 rounded-md border border-border-danger bg-surface-danger-muted px-4 py-3 text-sm text-text-danger\"\n >\n {error}\n </div>\n )}\n\n {loading && products.length === 0 ? (\n <output className=\"block py-16 text-center text-text-secondary\">\n Loading submissions…\n </output>\n ) : products.length === 0 ? (\n <div className=\"py-16 text-center text-text-secondary\">\n No products matching the current filter.\n </div>\n ) : (\n <ul className=\"space-y-3\">\n {products.map((p) => (\n <li\n key={p.id}\n className=\"overflow-hidden rounded-lg border border-border-default bg-surface-raised\"\n >\n <div className=\"flex items-start gap-4 p-4\">\n {p.icon ? (\n <img\n src={p.icon}\n alt=\"\"\n className=\"size-14 flex-shrink-0 rounded-md object-cover\"\n />\n ) : (\n <div className=\"flex size-14 flex-shrink-0 items-center justify-center rounded-md bg-surface-muted text-xs text-text-secondary\">\n {p.type}\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">\n {p.name}\n </h3>\n <span className=\"rounded-full bg-surface-accent-muted px-2 py-0.5 text-xs font-medium text-text-accent\">\n {p.type}\n </span>\n <span className=\"rounded-full bg-surface-muted px-2 py-0.5 text-xs font-medium text-text-secondary\">\n {p.nature}\n </span>\n </div>\n <div className=\"mt-0.5 text-xs text-text-tertiary\">{p.slug}</div>\n <div className=\"mt-1 flex items-center gap-3 text-xs text-text-secondary\">\n <span>Publisher: {p.publisher?.name ?? 'Unknown'}</span>\n <span>Price: {formatPriceForModel(p.price, p.currency, p.pricingModel)}</span>\n {p.submittedAt && (\n <span>Submitted {new Date(p.submittedAt).toLocaleString()}</span>\n )}\n </div>\n {p.description && (\n <p className=\"mt-2 line-clamp-2 text-sm text-text-secondary\">\n {p.description}\n </p>\n )}\n </div>\n <div className=\"flex flex-shrink-0 flex-col gap-2\">\n <button\n type=\"button\"\n onClick={() => setViewingId(viewingId === p.id ? null : p.id)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-default px-3 py-1.5 text-xs text-text-secondary hover:bg-surface-hover\"\n >\n {viewingId === p.id ? (\n <ChevronUp className=\"size-3.5\" />\n ) : (\n <ChevronDown className=\"size-3.5\" />\n )}\n Details\n </button>\n <button\n type=\"button\"\n onClick={() => void approve(p.id)}\n disabled={actingId === p.id}\n className=\"inline-flex items-center gap-1 rounded-md bg-surface-success px-3 py-1.5 text-xs font-medium text-text-on-success hover:bg-surface-success-hover disabled:opacity-60\"\n >\n <Check className=\"size-3.5\" />\n Approve\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setRejectingId(p.id);\n setRejectReason('');\n }}\n disabled={actingId === p.id}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-danger bg-surface-danger-muted px-3 py-1.5 text-xs font-medium text-text-danger hover:bg-surface-danger hover:text-text-on-danger disabled:opacity-60\"\n >\n <X className=\"size-3.5\" />\n Reject\n </button>\n </div>\n </div>\n\n {viewingId === p.id && (\n <div className=\"border-t border-border-default bg-surface-muted p-4\">\n <dl className=\"grid grid-cols-2 gap-x-6 gap-y-3 text-xs sm:grid-cols-3\">\n <div>\n <dt className=\"font-medium text-text-secondary\">Slug</dt>\n <dd className=\"mt-0.5 font-mono text-text-primary\">{p.slug}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-text-secondary\">Type</dt>\n <dd className=\"mt-0.5 text-text-primary\">{p.type}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-text-secondary\">Nature</dt>\n <dd className=\"mt-0.5 text-text-primary\">{p.nature}</dd>\n </div>\n {p.category && (\n <div>\n <dt className=\"font-medium text-text-secondary\">Category</dt>\n <dd className=\"mt-0.5 text-text-primary\">{p.category}</dd>\n </div>\n )}\n <div>\n <dt className=\"font-medium text-text-secondary\">Pricing</dt>\n <dd className=\"mt-0.5 text-text-primary\">\n {formatPriceForModel(p.price, p.currency, p.pricingModel)}{' '}\n <span className=\"text-text-tertiary\">({p.pricingModel})</span>\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-text-secondary\">Publisher</dt>\n <dd className=\"mt-0.5 text-text-primary\">\n {p.publisher?.name ?? '—'}\n {p.publisher?.email && (\n <span className=\"block text-text-tertiary\">{p.publisher.email}</span>\n )}\n </dd>\n </div>\n {p.submittedBy && (\n <div>\n <dt className=\"font-medium text-text-secondary\">Submitted by</dt>\n <dd className=\"mt-0.5 font-mono text-text-primary\">{p.submittedBy}</dd>\n </div>\n )}\n {p.submittedAt && (\n <div>\n <dt className=\"font-medium text-text-secondary\">Submitted at</dt>\n <dd className=\"mt-0.5 text-text-primary\">\n {new Date(p.submittedAt).toLocaleString()}\n </dd>\n </div>\n )}\n {p.description && (\n <div className=\"col-span-2 sm:col-span-3\">\n <dt className=\"font-medium text-text-secondary\">Description</dt>\n <dd className=\"mt-0.5 whitespace-pre-wrap text-text-primary\">\n {p.description}\n </dd>\n </div>\n )}\n </dl>\n </div>\n )}\n\n {rejectingId === p.id && (\n <div className=\"border-t border-border-default bg-surface-muted px-4 py-3\">\n <label\n htmlFor={`reject-reason-${p.id}`}\n className=\"block text-xs font-medium text-text-secondary\"\n >\n Rejection reason (required)\n </label>\n <textarea\n id={`reject-reason-${p.id}`}\n aria-label=\"Rejection reason\"\n value={rejectReason}\n onChange={(e) => setRejectReason(e.target.value)}\n rows={3}\n required\n aria-required=\"true\"\n className=\"mt-1 w-full rounded-md border border-border-default bg-surface-default px-3 py-2 text-sm text-text-primary focus:border-border-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n placeholder=\"Explain why this submission is being rejected…\"\n />\n <div className=\"mt-2 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => {\n setRejectingId(null);\n setRejectReason('');\n }}\n className=\"rounded-md border border-border-default bg-surface-default px-3 py-1 text-xs text-text-primary hover:bg-surface-hover\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => void reject(p.id)}\n disabled={!rejectReason.trim() || actingId === p.id}\n className=\"rounded-md bg-surface-danger px-3 py-1 text-xs font-medium text-text-on-danger hover:bg-surface-danger-hover disabled:opacity-60\"\n >\n Submit rejection\n </button>\n </div>\n </div>\n )}\n </li>\n ))}\n </ul>\n )}\n </main>\n </div>\n );\n};\n"],"mappings":"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"}
|