@withpotter/pages-admin 0.13.0-next.120 → 0.13.0-next.123
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/admin/_ui/admin.css +7 -0
- package/dist/admin/_ui/stat-card.js +11 -2
- package/dist/admin/_ui/stat-card.js.map +1 -1
- package/dist/admin/admins/access-dialog.js +2 -7
- package/dist/admin/admins/access-dialog.js.map +1 -1
- package/dist/admin/admins/invite-dialog.js +0 -6
- package/dist/admin/admins/invite-dialog.js.map +1 -1
- package/dist/admin/hooks.js +0 -7
- package/dist/admin/hooks.js.map +1 -1
- package/dist/admin/revenue/earnings/earnings-colors.js +25 -0
- package/dist/admin/revenue/earnings/earnings-colors.js.map +1 -0
- package/dist/admin/revenue/earnings/earnings-legend.js +75 -0
- package/dist/admin/revenue/earnings/earnings-legend.js.map +1 -0
- package/dist/admin/revenue/earnings/earnings-summary.js +296 -0
- package/dist/admin/revenue/earnings/earnings-summary.js.map +1 -0
- package/dist/admin/revenue/earnings/earnings-table.js +143 -0
- package/dist/admin/revenue/earnings/earnings-table.js.map +1 -0
- package/dist/admin/revenue/earnings/earnings.module.css +201 -0
- package/dist/admin/revenue/earnings/use-earnings.js +27 -0
- package/dist/admin/revenue/earnings/use-earnings.js.map +1 -0
- package/dist/admin/revenue/health-panel.js +5 -4
- package/dist/admin/revenue/health-panel.js.map +1 -1
- package/dist/admin/revenue/revenue-kpis.js +1 -1
- package/dist/admin/revenue/revenue-kpis.js.map +1 -1
- package/dist/admin/revenue/revenue-overview.js +20 -118
- package/dist/admin/revenue/revenue-overview.js.map +1 -1
- package/dist/admin/revenue/revenue-workspace.js +4 -10
- package/dist/admin/revenue/revenue-workspace.js.map +1 -1
- package/dist/admin/roles/page.js +4 -14
- package/dist/admin/roles/page.js.map +1 -1
- package/dist/admin/roles/role-dialog.js +1 -2
- package/dist/admin/roles/role-dialog.js.map +1 -1
- package/package.json +4 -4
- package/dist/admin/roles/role-requests.js +0 -80
- package/dist/admin/roles/role-requests.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/revenue/revenue-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport { ADMIN_NAME } from '../_shell/admin-brand';\nimport {\n
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/revenue/revenue-workspace.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo } from 'react';\nimport { ADMIN_NAME } from '../_shell/admin-brand';\nimport {\n useMerchantPayments,\n usePlatformCockpit,\n usePlatformRevenue,\n} from '../hooks';\nimport { LoadFailed } from '../audit/load-failed';\nimport {\n AdmLoading,\n CurrencyFilter,\n PageHeader,\n PageTabs,\n PeriodSelector,\n customRange,\n focusCurrency,\n moneyIn,\n periodPhrase,\n rangeFromKey,\n useCurrencyParam,\n usePersistedDateRange,\n useTabParam,\n} from '../_ui';\nimport { MerchantPaymentsView } from './merchant-payments-view';\nimport { usePlanList } from '../pricing/use-plans';\nimport { RevenueOverview } from './revenue-overview';\nimport { RevenueRetention } from './revenue-retention';\nimport { SubscriptionsView } from './subscriptions-view';\nimport { SummaryDownload } from './summary-download';\n\nconst TABS = [\n { id: 'overview', label: 'Money Olive earns' },\n { id: 'merchants', label: 'Merchant payments' },\n { id: 'subscriptions', label: 'Subscriptions' },\n { id: 'retention', label: 'Retention' },\n] as const;\n\nconst KEYS = TABS.map((t) => t.id);\n\n/**\n * Revenue: the whole picture on Overview, what customers paid merchants on\n * Merchant payments, what merchants pay Olive on Subscriptions, and revenue\n * quality and cohorts on Retention.\n */\nexport function RevenueWorkspace() {\n const [tab, setTab] = useTabParam(KEYS);\n const {\n rangeKey,\n setRangeKey,\n customStart,\n customEnd,\n onCustomChange,\n } = usePersistedDateRange();\n // Merchant GMV comes back once per currency; `?currency=` narrows it.\n const [currency, setCurrency] = useCurrencyParam();\n\n const range = useMemo(\n () =>\n rangeKey === 'custom'\n ? customRange(customStart, customEnd)\n : rangeFromKey(rangeKey),\n [rangeKey, customStart, customEnd],\n );\n\n const { data, isLoading, isError, refetch } = usePlatformRevenue({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n // Revenue health (MRR movement, logo churn, concentration) is computed as\n // part of the cockpit, not the revenue endpoint — fetched here too rather\n // than duplicating that aggregation.\n const plans = usePlanList();\n const cockpit = usePlatformCockpit({\n startDate: range.startDate,\n endDate: range.endDate,\n });\n\n // The same totals the Merchant payments tab reads, one row only, for the summary download.\n const ledger = useMerchantPayments({\n startDate: range.startDate,\n endDate: range.endDate,\n limit: 1,\n });\n\n const currencies = useMemo(() => data?.currencies ?? [], [data]);\n const focus = focusCurrency(currencies, currency);\n // Recurring revenue is the platform's own billing, in the currency the API\n // names for it (`currency`), shown as it comes, never converted.\n const recurringCurrency = data?.currency || 'NGN';\n const money = useMemo(() => moneyIn(recurringCurrency), [recurringCurrency]);\n const money2 = useMemo(\n () => moneyIn(recurringCurrency, 2),\n [recurringCurrency],\n );\n\n const span = periodPhrase(range);\n const period = `${span[0].toUpperCase()}${span.slice(1)}`;\n\n let body;\n if (isError) {\n body = (\n <LoadFailed\n message=\"Revenue for this period could not be loaded.\"\n onRetry={() => void refetch()}\n />\n );\n } else if (isLoading || !data) {\n body = <AdmLoading label=\"Loading revenue metrics…\" />;\n } else if (tab === 'merchants') {\n body = (\n <MerchantPaymentsView\n startDate={range.startDate}\n endDate={range.endDate}\n currency={currency}\n />\n );\n } else if (tab === 'subscriptions') {\n body = (\n <SubscriptionsView\n startDate={range.startDate}\n endDate={range.endDate}\n revenue={data}\n />\n );\n } else if (tab === 'retention') {\n body = (\n <RevenueRetention\n quality={data.quality}\n money={money}\n currency={currency}\n caption={currency ? `${period} · ${currency}` : period}\n />\n );\n } else {\n body = (\n <RevenueOverview\n startDate={range.startDate}\n endDate={range.endDate}\n data={data}\n health={cockpit.data?.health}\n money={money}\n money2={money2}\n currency={currency}\n focus={focus}\n span={span}\n recurringCaption={`${period} · ${recurringCurrency}`}\n plans={plans.data}\n />\n );\n }\n\n return (\n <div>\n <PageHeader\n title=\"Revenue\"\n description={`How money flows through ${ADMIN_NAME}: what it earns, where it comes from, what it costs and what is left.`}\n actions={\n <div className=\"adm-toolbar\">\n {tab === 'overview' ? (\n <SummaryDownload\n ledger={ledger.data}\n revenue={data}\n currency={focus ?? 'NGN'}\n startDate={range.startDate}\n endDate={range.endDate}\n period={period}\n />\n ) : null}\n <CurrencyFilter\n value={currency}\n onChange={setCurrency}\n currencies={currencies}\n />\n <PeriodSelector\n value={rangeKey}\n onChange={setRangeKey}\n customStart={customStart}\n customEnd={customEnd}\n onCustomChange={onCustomChange}\n />\n </div>\n }\n />\n <PageTabs tabs={TABS} active={tab} onChange={setTab} />\n {body}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/admin/roles/page.js
CHANGED
|
@@ -16,7 +16,6 @@ import { EmptyState, Panel } from "../_ui";
|
|
|
16
16
|
import { EmptyBlock } from "../audit/empty-block";
|
|
17
17
|
import { SectionHeader } from "../audit/section-header";
|
|
18
18
|
import { RolesKpis } from "./roles-kpis";
|
|
19
|
-
import { RoleRequests } from "./role-requests";
|
|
20
19
|
import { RoleDialog } from "./role-dialog";
|
|
21
20
|
import { RoleRow, ROLE_COLUMNS, adminCount } from "./role-row";
|
|
22
21
|
import section from "../audit/page.module.css";
|
|
@@ -48,14 +47,11 @@ function RolesPage() {
|
|
|
48
47
|
const remove = useMutation({
|
|
49
48
|
mutationFn: (r) => platformAdminApi.deleteRole(r.id),
|
|
50
49
|
onSuccess: (_, r) => {
|
|
51
|
-
toast({
|
|
52
|
-
title: "Sent for approval",
|
|
53
|
-
description: `Another super admin has to approve deleting ${r.name}.`
|
|
54
|
-
});
|
|
50
|
+
toast({ title: `${r.name} deleted` });
|
|
55
51
|
void invalidate();
|
|
56
52
|
},
|
|
57
53
|
onError: (error) => toast({
|
|
58
|
-
title: "The
|
|
54
|
+
title: "The role was not deleted",
|
|
59
55
|
description: error.message,
|
|
60
56
|
variant: "destructive"
|
|
61
57
|
})
|
|
@@ -64,7 +60,7 @@ function RolesPage() {
|
|
|
64
60
|
const ok = await confirm({
|
|
65
61
|
title: `Delete ${r.name}?`,
|
|
66
62
|
description: r.assignedCount > 0 ? `${adminCount(r.assignedCount)} hold this role and will lose the access it grants. This can\u2019t be undone.` : "No admin holds this role. This can\u2019t be undone.",
|
|
67
|
-
confirmLabel: "
|
|
63
|
+
confirmLabel: "Delete role",
|
|
68
64
|
tone: "danger"
|
|
69
65
|
});
|
|
70
66
|
if (ok) remove.mutate(r);
|
|
@@ -146,19 +142,13 @@ function RolesPage() {
|
|
|
146
142
|
children: table
|
|
147
143
|
}
|
|
148
144
|
) }),
|
|
149
|
-
/* @__PURE__ */ jsx(RoleRequests, {}),
|
|
150
145
|
/* @__PURE__ */ jsx(
|
|
151
146
|
RoleDialog,
|
|
152
147
|
{
|
|
153
148
|
mode: dialog,
|
|
154
149
|
catalog: availablePermissions,
|
|
155
150
|
onClose: () => setDialog(null),
|
|
156
|
-
onSaved: () =>
|
|
157
|
-
void invalidate();
|
|
158
|
-
void queryClient.invalidateQueries({
|
|
159
|
-
queryKey: ["platform-admin", "roles", "requests"]
|
|
160
|
-
});
|
|
161
|
-
}
|
|
151
|
+
onSaved: () => void invalidate()
|
|
162
152
|
}
|
|
163
153
|
)
|
|
164
154
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/roles/page.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo, useState } from 'react';\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport {\n Button,\n TableShell,\n TableSkeleton,\n Th,\n useConfirm,\n useToast,\n} from '@withpotter/ui-kit';\nimport { platformAdminApi, type RoleRow as Role } from '@withpotter/lib/admin-api';\nimport { useAdminRoles, useAdminPermissions } from '../hooks';\nimport { EmptyState, Panel } from '../_ui';\nimport { EmptyBlock } from '../audit/empty-block';\nimport { SectionHeader } from '../audit/section-header';\nimport { RolesKpis } from './roles-kpis';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/roles/page.tsx"],"sourcesContent":["'use client';\n\nimport { useMemo, useState } from 'react';\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport {\n Button,\n TableShell,\n TableSkeleton,\n Th,\n useConfirm,\n useToast,\n} from '@withpotter/ui-kit';\nimport { platformAdminApi, type RoleRow as Role } from '@withpotter/lib/admin-api';\nimport { useAdminRoles, useAdminPermissions } from '../hooks';\nimport { EmptyState, Panel } from '../_ui';\nimport { EmptyBlock } from '../audit/empty-block';\nimport { SectionHeader } from '../audit/section-header';\nimport { RolesKpis } from './roles-kpis';\nimport { RoleDialog, type RoleDialogMode } from './role-dialog';\nimport { RoleRow, ROLE_COLUMNS, adminCount } from './role-row';\nimport section from '../audit/page.module.css';\nimport kpi from '../audit/kpi-brand.module.css';\nimport styles from './roles.module.css';\n\nconst CONTROLLED_PERMISSIONS = [\n 'money.cash.propose',\n 'money.cash.approve',\n 'money.payout_tiers.read',\n 'money.payout_tiers.manage',\n 'pricing.read',\n 'pricing.propose',\n 'pricing.approve',\n 'sales.read',\n 'sales.manage',\n] as const;\n\nexport default function RolesPage() {\n const queryClient = useQueryClient();\n const confirm = useConfirm();\n const { toast } = useToast();\n const [dialog, setDialog] = useState<RoleDialogMode | null>(null);\n const { data: roles = [], isLoading, isError, refetch } = useAdminRoles();\n const { data: catalog = [] } = useAdminPermissions();\n const availablePermissions = useMemo(\n () => Array.from(new Set([...catalog, ...CONTROLLED_PERMISSIONS])),\n [catalog],\n );\n\n const invalidate = () =>\n queryClient.invalidateQueries({ queryKey: ['platform-admin', 'roles'] });\n\n const remove = useMutation({\n mutationFn: (r: Role) => platformAdminApi.deleteRole(r.id),\n onSuccess: (_, r) => {\n toast({ title: `${r.name} deleted` });\n void invalidate();\n },\n onError: (error) =>\n toast({\n title: 'The role was not deleted',\n description: error.message,\n variant: 'destructive',\n }),\n });\n\n const confirmDelete = async (r: Role) => {\n const ok = await confirm({\n title: `Delete ${r.name}?`,\n description:\n r.assignedCount > 0\n ? `${adminCount(r.assignedCount)} hold this role and will lose the access it grants. This can’t be undone.`\n : 'No admin holds this role. This can’t be undone.',\n confirmLabel: 'Delete role',\n tone: 'danger',\n });\n if (ok) remove.mutate(r);\n };\n\n const create = (\n <Button onClick={() => setDialog({ kind: 'create' })}>Create role</Button>\n );\n\n let table;\n if (isLoading) {\n table = (\n <div aria-hidden>\n <TableSkeleton bare rows={5} columns={ROLE_COLUMNS} />\n </div>\n );\n } else if (isError) {\n table = (\n <EmptyState\n tone=\"error\"\n title=\"Roles could not be loaded\"\n description=\"Try again. If this keeps happening, contact engineering.\"\n action={\n <Button size=\"sm\" variant=\"secondary\" onClick={() => void refetch()}>\n Try again\n </Button>\n }\n />\n );\n } else if (roles.length === 0) {\n table = (\n <EmptyBlock\n message=\"No roles yet. A role bundles permissions so you can grant them in one step.\"\n action={create}\n />\n );\n } else {\n table = (\n <TableShell\n bare\n fixed\n minWidth={760}\n density=\"compact\"\n head={\n <tr>\n <Th className={styles.colRole}>Role</Th>\n <Th>Permissions</Th>\n <Th className={styles.colType}>Type</Th>\n <Th className={styles.colHeld} align=\"right\">\n Held by\n </Th>\n <Th className={styles.colMenu} align=\"right\">\n <span className=\"sr-only\">Actions</span>\n </Th>\n </tr>\n }\n >\n {roles.map((r) => (\n <RoleRow\n key={r.id}\n r={r}\n onEdit={() => setDialog({ kind: 'edit', role: r })}\n onDuplicate={() => setDialog({ kind: 'duplicate', role: r })}\n onDelete={() => void confirmDelete(r)}\n />\n ))}\n </TableShell>\n );\n }\n\n return (\n <div className={styles.page}>\n <SectionHeader\n title=\"Roles\"\n description=\"Reusable permission bundles. An admin’s access is the union of every role they hold.\"\n actions={create}\n />\n\n <div className={kpi.kpis}>\n <RolesKpis\n roles={roles}\n permissions={availablePermissions.length}\n isLoading={isLoading}\n />\n </div>\n\n <div className={section.section} aria-busy={isLoading || undefined}>\n <Panel\n title=\"All roles\"\n count={isLoading ? undefined : roles.length}\n height=\"auto\"\n flush\n >\n {table}\n </Panel>\n </div>\n\n <RoleDialog\n mode={dialog}\n catalog={availablePermissions}\n onClose={() => setDialog(null)}\n onSaved={() => void invalidate()}\n />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -66,8 +66,7 @@ function RoleForm({
|
|
|
66
66
|
),
|
|
67
67
|
onSuccess: () => {
|
|
68
68
|
toast({
|
|
69
|
-
title: "
|
|
70
|
-
description: "Another super admin has to approve this before the role changes."
|
|
69
|
+
title: mode.kind === "edit" ? `${name} updated` : `${name.trim()} created`
|
|
71
70
|
});
|
|
72
71
|
onSaved();
|
|
73
72
|
onClose();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/roles/role-dialog.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { useMutation } from '@tanstack/react-query';\nimport { Button, FormDialog, Input, useToast } from '@withpotter/ui-kit';\nimport { platformAdminApi, type RoleRow as Role } from '@withpotter/lib/admin-api';\nimport { PermissionPicker, toggleIn } from './permission-picker';\nimport styles from './roles.module.css';\n\nexport type RoleDialogMode =\n | { kind: 'create' }\n | { kind: 'edit'; role: Role }\n | { kind: 'duplicate'; role: Role };\n\nconst COPY = {\n create: { title: 'Create role', action: 'Create role' },\n edit: { title: 'Edit role', action: 'Save changes' },\n duplicate: { title: 'Duplicate role', action: 'Create copy' },\n} as const;\n\nconst FORM_ID = 'role-dialog-form';\n\n/** Create, edit or duplicate a role: name, purpose and grouped permissions. */\nexport function RoleDialog({\n mode,\n catalog,\n onClose,\n onSaved,\n}: {\n mode: RoleDialogMode | null;\n catalog: readonly string[];\n onClose: () => void;\n onSaved: () => void;\n}) {\n // Keep the last mode while the dialog animates out, and count each opening\n // so the form remounts fresh — a reopened Create must not show the last\n // role's name, nor a cancelled edit come back.\n const [shown, setShown] = useState(mode);\n const [opens, setOpens] = useState(0);\n const [wasOpen, setWasOpen] = useState(false);\n if (mode && mode !== shown) setShown(mode);\n if (Boolean(mode) !== wasOpen) {\n setWasOpen(Boolean(mode));\n if (mode) setOpens((n) => n + 1);\n }\n const active = mode ?? shown;\n\n if (!active) return null;\n return (\n <RoleForm\n key={`${opens}-${active.kind}-${'role' in active ? active.role.id : ''}`}\n open={mode !== null}\n mode={active}\n catalog={catalog}\n onClose={onClose}\n onSaved={onSaved}\n />\n );\n}\n\nfunction RoleForm({\n open,\n mode,\n catalog,\n onClose,\n onSaved,\n}: {\n open: boolean;\n mode: RoleDialogMode;\n catalog: readonly string[];\n onClose: () => void;\n onSaved: () => void;\n}) {\n const { toast } = useToast();\n const source = mode.kind === 'create' ? null : mode.role;\n const [name, setName] = useState(\n !source\n ? ''\n : mode.kind === 'duplicate'\n ? `${source.name} (copy)`\n : source.name,\n );\n const [description, setDescription] = useState(source?.description ?? '');\n const [perms, setPerms] = useState<string[]>(source?.permissions ?? []);\n const lockedName = mode.kind === 'edit' && mode.role.isSystem;\n\n const save = useMutation({\n mutationFn: async (): Promise<unknown> =>\n mode.kind === 'edit'\n ? platformAdminApi.updateRole(mode.role.id, {\n ...(lockedName\n ? {}\n : { name: name.trim(), description: description.trim() }),\n permissions: perms,\n })\n : platformAdminApi.createRole(\n name.trim(),\n perms,\n description.trim() || undefined,\n ),\n onSuccess: () => {\n toast({\n title
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/roles/role-dialog.tsx"],"sourcesContent":["'use client';\n\nimport { useState } from 'react';\nimport { useMutation } from '@tanstack/react-query';\nimport { Button, FormDialog, Input, useToast } from '@withpotter/ui-kit';\nimport { platformAdminApi, type RoleRow as Role } from '@withpotter/lib/admin-api';\nimport { PermissionPicker, toggleIn } from './permission-picker';\nimport styles from './roles.module.css';\n\nexport type RoleDialogMode =\n | { kind: 'create' }\n | { kind: 'edit'; role: Role }\n | { kind: 'duplicate'; role: Role };\n\nconst COPY = {\n create: { title: 'Create role', action: 'Create role' },\n edit: { title: 'Edit role', action: 'Save changes' },\n duplicate: { title: 'Duplicate role', action: 'Create copy' },\n} as const;\n\nconst FORM_ID = 'role-dialog-form';\n\n/** Create, edit or duplicate a role: name, purpose and grouped permissions. */\nexport function RoleDialog({\n mode,\n catalog,\n onClose,\n onSaved,\n}: {\n mode: RoleDialogMode | null;\n catalog: readonly string[];\n onClose: () => void;\n onSaved: () => void;\n}) {\n // Keep the last mode while the dialog animates out, and count each opening\n // so the form remounts fresh — a reopened Create must not show the last\n // role's name, nor a cancelled edit come back.\n const [shown, setShown] = useState(mode);\n const [opens, setOpens] = useState(0);\n const [wasOpen, setWasOpen] = useState(false);\n if (mode && mode !== shown) setShown(mode);\n if (Boolean(mode) !== wasOpen) {\n setWasOpen(Boolean(mode));\n if (mode) setOpens((n) => n + 1);\n }\n const active = mode ?? shown;\n\n if (!active) return null;\n return (\n <RoleForm\n key={`${opens}-${active.kind}-${'role' in active ? active.role.id : ''}`}\n open={mode !== null}\n mode={active}\n catalog={catalog}\n onClose={onClose}\n onSaved={onSaved}\n />\n );\n}\n\nfunction RoleForm({\n open,\n mode,\n catalog,\n onClose,\n onSaved,\n}: {\n open: boolean;\n mode: RoleDialogMode;\n catalog: readonly string[];\n onClose: () => void;\n onSaved: () => void;\n}) {\n const { toast } = useToast();\n const source = mode.kind === 'create' ? null : mode.role;\n const [name, setName] = useState(\n !source\n ? ''\n : mode.kind === 'duplicate'\n ? `${source.name} (copy)`\n : source.name,\n );\n const [description, setDescription] = useState(source?.description ?? '');\n const [perms, setPerms] = useState<string[]>(source?.permissions ?? []);\n const lockedName = mode.kind === 'edit' && mode.role.isSystem;\n\n const save = useMutation({\n mutationFn: async (): Promise<unknown> =>\n mode.kind === 'edit'\n ? platformAdminApi.updateRole(mode.role.id, {\n ...(lockedName\n ? {}\n : { name: name.trim(), description: description.trim() }),\n permissions: perms,\n })\n : platformAdminApi.createRole(\n name.trim(),\n perms,\n description.trim() || undefined,\n ),\n onSuccess: () => {\n toast({\n title:\n mode.kind === 'edit' ? `${name} updated` : `${name.trim()} created`,\n });\n onSaved();\n onClose();\n },\n onError: (error) =>\n toast({\n title: 'The role was not saved',\n description: error.message,\n variant: 'destructive',\n }),\n });\n const canSave = name.trim().length > 0 && !save.isPending;\n\n return (\n <FormDialog\n open={open}\n size=\"lg\"\n title={COPY[mode.kind].title}\n description={\n mode.kind === 'edit'\n ? `Changes apply to every admin holding ${mode.role.name}.`\n : 'Bundle permissions you can grant in one step.'\n }\n onClose={onClose}\n footer={\n <>\n <span className={styles.footNote}>\n {perms.length} permission{perms.length === 1 ? '' : 's'} selected\n </span>\n <div className={styles.footActions}>\n <Button type=\"button\" variant=\"secondary\" onClick={onClose}>\n Cancel\n </Button>\n <Button\n type=\"submit\"\n form={FORM_ID}\n disabled={!canSave}\n loading={save.isPending}\n >\n {COPY[mode.kind].action}\n </Button>\n </div>\n </>\n }\n >\n <form\n id={FORM_ID}\n className={styles.dialogBody}\n onSubmit={(e) => {\n e.preventDefault();\n if (canSave) save.mutate();\n }}\n >\n <div className={styles.dialogFields}>\n <Input\n label=\"Role name\"\n placeholder=\"e.g. Support\"\n value={name}\n disabled={lockedName}\n onChange={(e) => setName(e.target.value)}\n autoFocus={!lockedName}\n />\n <Input\n label=\"Description (optional)\"\n placeholder=\"What this role is for\"\n value={description}\n disabled={lockedName}\n onChange={(e) => setDescription(e.target.value)}\n />\n </div>\n {lockedName && (\n <p className={styles.note}>\n Built-in roles keep their name. You can still change what they\n grant.\n </p>\n )}\n <PermissionPicker\n keys={catalog}\n value={perms}\n onToggle={(key) => setPerms(toggleIn(key))}\n onSet={setPerms}\n />\n </form>\n </FormDialog>\n );\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@withpotter/pages-admin",
|
|
3
|
-
"version": "0.13.0-next.
|
|
3
|
+
"version": "0.13.0-next.123",
|
|
4
4
|
"description": "Box console platform-admin routes: overview, revenue, accounts, tenants, admins, roles, audit.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@radix-ui/react-popover": "^1.1.23",
|
|
33
33
|
"@tanstack/react-query": "^5.90.20",
|
|
34
|
-
"@withpotter/lib": "^0.13.0-next.
|
|
35
|
-
"@withpotter/platform-admin": "^0.13.0-next.
|
|
36
|
-
"@withpotter/ui-kit": "^0.13.0-next.
|
|
34
|
+
"@withpotter/lib": "^0.13.0-next.123",
|
|
35
|
+
"@withpotter/platform-admin": "^0.13.0-next.123",
|
|
36
|
+
"@withpotter/ui-kit": "^0.13.0-next.123",
|
|
37
37
|
"lucide-react": "^0.562.0",
|
|
38
38
|
"motion": "^12.42.2"
|
|
39
39
|
},
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
4
|
-
import { Button, useToast } from "@withpotter/ui-kit";
|
|
5
|
-
import { platformAdminApi } from "@withpotter/lib/admin-api";
|
|
6
|
-
import { useAdminSession, useRoleChangeRequests } from "../hooks";
|
|
7
|
-
import { Panel } from "../_ui";
|
|
8
|
-
import section from "../audit/page.module.css";
|
|
9
|
-
function RoleRequests() {
|
|
10
|
-
const queryClient = useQueryClient();
|
|
11
|
-
const { toast } = useToast();
|
|
12
|
-
const { data: me } = useAdminSession();
|
|
13
|
-
const { data: requests = [] } = useRoleChangeRequests();
|
|
14
|
-
const pending = requests.filter((r) => r.status === "pending");
|
|
15
|
-
const decided = requests.filter((r) => r.status !== "pending").slice(0, 5);
|
|
16
|
-
const refresh = () => {
|
|
17
|
-
void queryClient.invalidateQueries({ queryKey: ["platform-admin", "roles"] });
|
|
18
|
-
void queryClient.invalidateQueries({ queryKey: ["platform-admin", "admins"] });
|
|
19
|
-
};
|
|
20
|
-
const decide = useMutation({
|
|
21
|
-
mutationFn: ({ id, approve }) => approve ? platformAdminApi.approveRoleChange(id) : platformAdminApi.rejectRoleChange(id),
|
|
22
|
-
onSuccess: (_, { approve }) => {
|
|
23
|
-
toast({ title: approve ? "Approved and applied" : "Rejected" });
|
|
24
|
-
refresh();
|
|
25
|
-
},
|
|
26
|
-
onError: (error) => toast({ title: "That was not saved", description: error.message, variant: "destructive" })
|
|
27
|
-
});
|
|
28
|
-
if (requests.length === 0) return null;
|
|
29
|
-
return /* @__PURE__ */ jsx("div", { className: section.section, children: /* @__PURE__ */ jsxs(Panel, { title: "Waiting for approval", count: pending.length, height: "auto", flush: true, children: [
|
|
30
|
-
/* @__PURE__ */ jsxs("ul", { children: [
|
|
31
|
-
pending.map((r) => {
|
|
32
|
-
const mine = me?.email === r.requestedByEmail;
|
|
33
|
-
return /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-3 px-4 py-3 text-sm", children: [
|
|
34
|
-
/* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
|
|
35
|
-
r.summary,
|
|
36
|
-
/* @__PURE__ */ jsxs("span", { className: "block text-xs text-[var(--text-tertiary)]", children: [
|
|
37
|
-
"Asked by ",
|
|
38
|
-
mine ? "you" : r.requestedByEmail,
|
|
39
|
-
" \xB7 ",
|
|
40
|
-
new Date(r.createdAt).toLocaleString()
|
|
41
|
-
] })
|
|
42
|
-
] }),
|
|
43
|
-
/* @__PURE__ */ jsxs("span", { className: "flex shrink-0 gap-2", children: [
|
|
44
|
-
!mine && /* @__PURE__ */ jsx(
|
|
45
|
-
Button,
|
|
46
|
-
{
|
|
47
|
-
size: "sm",
|
|
48
|
-
disabled: decide.isPending,
|
|
49
|
-
onClick: () => decide.mutate({ id: r.id, approve: true }),
|
|
50
|
-
children: "Approve"
|
|
51
|
-
}
|
|
52
|
-
),
|
|
53
|
-
/* @__PURE__ */ jsx(
|
|
54
|
-
Button,
|
|
55
|
-
{
|
|
56
|
-
size: "sm",
|
|
57
|
-
variant: "secondary",
|
|
58
|
-
disabled: decide.isPending,
|
|
59
|
-
onClick: () => decide.mutate({ id: r.id, approve: false }),
|
|
60
|
-
children: mine ? "Withdraw" : "Reject"
|
|
61
|
-
}
|
|
62
|
-
)
|
|
63
|
-
] })
|
|
64
|
-
] }, r.id);
|
|
65
|
-
}),
|
|
66
|
-
pending.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-4 py-3 text-sm text-[var(--text-secondary)]", children: "Nothing is waiting." })
|
|
67
|
-
] }),
|
|
68
|
-
decided.length > 0 && /* @__PURE__ */ jsx("ul", { className: "border-t border-[var(--border-subtle)]", children: decided.map((r) => /* @__PURE__ */ jsxs("li", { className: "px-4 py-2 text-xs text-[var(--text-secondary)]", children: [
|
|
69
|
-
r.summary,
|
|
70
|
-
" \xB7 ",
|
|
71
|
-
r.status === "approved" ? "approved" : "rejected",
|
|
72
|
-
" by ",
|
|
73
|
-
r.decidedByEmail
|
|
74
|
-
] }, r.id)) })
|
|
75
|
-
] }) });
|
|
76
|
-
}
|
|
77
|
-
export {
|
|
78
|
-
RoleRequests
|
|
79
|
-
};
|
|
80
|
-
//# sourceMappingURL=role-requests.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/roles/role-requests.tsx"],"sourcesContent":["'use client';\n\nimport { useMutation, useQueryClient } from '@tanstack/react-query';\nimport { Button, useToast } from '@withpotter/ui-kit';\nimport { platformAdminApi } from '@withpotter/lib/admin-api';\nimport { useAdminSession, useRoleChangeRequests } from '../hooks';\nimport { Panel } from '../_ui';\nimport section from '../audit/page.module.css';\n\n/**\n * Role changes wait here for a second super admin. You can approve or reject what others\n * asked for, and withdraw your own, but never approve your own.\n */\nexport function RoleRequests() {\n const queryClient = useQueryClient();\n const { toast } = useToast();\n const { data: me } = useAdminSession();\n const { data: requests = [] } = useRoleChangeRequests();\n const pending = requests.filter((r) => r.status === 'pending');\n const decided = requests.filter((r) => r.status !== 'pending').slice(0, 5);\n\n const refresh = () => {\n void queryClient.invalidateQueries({ queryKey: ['platform-admin', 'roles'] });\n void queryClient.invalidateQueries({ queryKey: ['platform-admin', 'admins'] });\n };\n const decide = useMutation({\n mutationFn: ({ id, approve }: { id: string; approve: boolean }) =>\n approve ? platformAdminApi.approveRoleChange(id) : platformAdminApi.rejectRoleChange(id),\n onSuccess: (_, { approve }) => {\n toast({ title: approve ? 'Approved and applied' : 'Rejected' });\n refresh();\n },\n onError: (error) =>\n toast({ title: 'That was not saved', description: error.message, variant: 'destructive' }),\n });\n\n if (requests.length === 0) return null;\n\n return (\n <div className={section.section}>\n <Panel title=\"Waiting for approval\" count={pending.length} height=\"auto\" flush>\n <ul>\n {pending.map((r) => {\n const mine = me?.email === r.requestedByEmail;\n return (\n <li key={r.id} className=\"flex items-center justify-between gap-3 px-4 py-3 text-sm\">\n <span className=\"min-w-0\">\n {r.summary}\n <span className=\"block text-xs text-[var(--text-tertiary)]\">\n Asked by {mine ? 'you' : r.requestedByEmail} · {new Date(r.createdAt).toLocaleString()}\n </span>\n </span>\n <span className=\"flex shrink-0 gap-2\">\n {!mine && (\n <Button\n size=\"sm\"\n disabled={decide.isPending}\n onClick={() => decide.mutate({ id: r.id, approve: true })}\n >\n Approve\n </Button>\n )}\n <Button\n size=\"sm\"\n variant=\"secondary\"\n disabled={decide.isPending}\n onClick={() => decide.mutate({ id: r.id, approve: false })}\n >\n {mine ? 'Withdraw' : 'Reject'}\n </Button>\n </span>\n </li>\n );\n })}\n {pending.length === 0 && (\n <li className=\"px-4 py-3 text-sm text-[var(--text-secondary)]\">Nothing is waiting.</li>\n )}\n </ul>\n {decided.length > 0 && (\n <ul className=\"border-t border-[var(--border-subtle)]\">\n {decided.map((r) => (\n <li key={r.id} className=\"px-4 py-2 text-xs text-[var(--text-secondary)]\">\n {r.summary} · {r.status === 'approved' ? 'approved' : 'rejected'} by {r.decidedByEmail}\n </li>\n ))}\n </ul>\n )}\n </Panel>\n </div>\n );\n}\n"],"mappings":";AAgDkB,SAME,KANF;AA9ClB,SAAS,aAAa,sBAAsB;AAC5C,SAAS,QAAQ,gBAAgB;AACjC,SAAS,wBAAwB;AACjC,SAAS,iBAAiB,6BAA6B;AACvD,SAAS,aAAa;AACtB,OAAO,aAAa;AAMb,SAAS,eAAe;AAC7B,QAAM,cAAc,eAAe;AACnC,QAAM,EAAE,MAAM,IAAI,SAAS;AAC3B,QAAM,EAAE,MAAM,GAAG,IAAI,gBAAgB;AACrC,QAAM,EAAE,MAAM,WAAW,CAAC,EAAE,IAAI,sBAAsB;AACtD,QAAM,UAAU,SAAS,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS;AAC7D,QAAM,UAAU,SAAS,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,EAAE,MAAM,GAAG,CAAC;AAEzE,QAAM,UAAU,MAAM;AACpB,SAAK,YAAY,kBAAkB,EAAE,UAAU,CAAC,kBAAkB,OAAO,EAAE,CAAC;AAC5E,SAAK,YAAY,kBAAkB,EAAE,UAAU,CAAC,kBAAkB,QAAQ,EAAE,CAAC;AAAA,EAC/E;AACA,QAAM,SAAS,YAAY;AAAA,IACzB,YAAY,CAAC,EAAE,IAAI,QAAQ,MACzB,UAAU,iBAAiB,kBAAkB,EAAE,IAAI,iBAAiB,iBAAiB,EAAE;AAAA,IACzF,WAAW,CAAC,GAAG,EAAE,QAAQ,MAAM;AAC7B,YAAM,EAAE,OAAO,UAAU,yBAAyB,WAAW,CAAC;AAC9D,cAAQ;AAAA,IACV;AAAA,IACA,SAAS,CAAC,UACR,MAAM,EAAE,OAAO,sBAAsB,aAAa,MAAM,SAAS,SAAS,cAAc,CAAC;AAAA,EAC7F,CAAC;AAED,MAAI,SAAS,WAAW,EAAG,QAAO;AAElC,SACE,oBAAC,SAAI,WAAW,QAAQ,SACtB,+BAAC,SAAM,OAAM,wBAAuB,OAAO,QAAQ,QAAQ,QAAO,QAAO,OAAK,MAC5E;AAAA,yBAAC,QACE;AAAA,cAAQ,IAAI,CAAC,MAAM;AAClB,cAAM,OAAO,IAAI,UAAU,EAAE;AAC7B,eACE,qBAAC,QAAc,WAAU,6DACvB;AAAA,+BAAC,UAAK,WAAU,WACb;AAAA,cAAE;AAAA,YACH,qBAAC,UAAK,WAAU,6CAA4C;AAAA;AAAA,cAChD,OAAO,QAAQ,EAAE;AAAA,cAAiB;AAAA,cAAI,IAAI,KAAK,EAAE,SAAS,EAAE,eAAe;AAAA,eACvF;AAAA,aACF;AAAA,UACA,qBAAC,UAAK,WAAU,uBACb;AAAA,aAAC,QACA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,UAAU,OAAO;AAAA,gBACjB,SAAS,MAAM,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,SAAS,KAAK,CAAC;AAAA,gBACzD;AAAA;AAAA,YAED;AAAA,YAEF;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAQ;AAAA,gBACR,UAAU,OAAO;AAAA,gBACjB,SAAS,MAAM,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,SAAS,MAAM,CAAC;AAAA,gBAExD,iBAAO,aAAa;AAAA;AAAA,YACvB;AAAA,aACF;AAAA,aAzBO,EAAE,EA0BX;AAAA,MAEJ,CAAC;AAAA,MACA,QAAQ,WAAW,KAClB,oBAAC,QAAG,WAAU,kDAAiD,iCAAmB;AAAA,OAEtF;AAAA,IACC,QAAQ,SAAS,KAChB,oBAAC,QAAG,WAAU,0CACX,kBAAQ,IAAI,CAAC,MACZ,qBAAC,QAAc,WAAU,kDACtB;AAAA,QAAE;AAAA,MAAQ;AAAA,MAAI,EAAE,WAAW,aAAa,aAAa;AAAA,MAAW;AAAA,MAAK,EAAE;AAAA,SADjE,EAAE,EAEX,CACD,GACH;AAAA,KAEJ,GACF;AAEJ;","names":[]}
|