@withpotter/pages-admin 0.13.0-next.120 → 0.13.0-next.122

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.
Files changed (35) hide show
  1. package/dist/admin/_ui/admin.css +7 -0
  2. package/dist/admin/_ui/stat-card.js +11 -2
  3. package/dist/admin/_ui/stat-card.js.map +1 -1
  4. package/dist/admin/admins/access-dialog.js +2 -7
  5. package/dist/admin/admins/access-dialog.js.map +1 -1
  6. package/dist/admin/admins/invite-dialog.js +0 -6
  7. package/dist/admin/admins/invite-dialog.js.map +1 -1
  8. package/dist/admin/hooks.js +0 -7
  9. package/dist/admin/hooks.js.map +1 -1
  10. package/dist/admin/revenue/earnings/earnings-colors.js +23 -0
  11. package/dist/admin/revenue/earnings/earnings-colors.js.map +1 -0
  12. package/dist/admin/revenue/earnings/earnings-legend.js +38 -0
  13. package/dist/admin/revenue/earnings/earnings-legend.js.map +1 -0
  14. package/dist/admin/revenue/earnings/earnings-summary.js +194 -0
  15. package/dist/admin/revenue/earnings/earnings-summary.js.map +1 -0
  16. package/dist/admin/revenue/earnings/earnings-table.js +141 -0
  17. package/dist/admin/revenue/earnings/earnings-table.js.map +1 -0
  18. package/dist/admin/revenue/earnings/earnings.module.css +118 -0
  19. package/dist/admin/revenue/earnings/use-earnings.js +27 -0
  20. package/dist/admin/revenue/earnings/use-earnings.js.map +1 -0
  21. package/dist/admin/revenue/health-panel.js +5 -4
  22. package/dist/admin/revenue/health-panel.js.map +1 -1
  23. package/dist/admin/revenue/revenue-kpis.js +1 -1
  24. package/dist/admin/revenue/revenue-kpis.js.map +1 -1
  25. package/dist/admin/revenue/revenue-overview.js +18 -1
  26. package/dist/admin/revenue/revenue-overview.js.map +1 -1
  27. package/dist/admin/revenue/revenue-workspace.js +2 -0
  28. package/dist/admin/revenue/revenue-workspace.js.map +1 -1
  29. package/dist/admin/roles/page.js +4 -14
  30. package/dist/admin/roles/page.js.map +1 -1
  31. package/dist/admin/roles/role-dialog.js +1 -2
  32. package/dist/admin/roles/role-dialog.js.map +1 -1
  33. package/package.json +4 -4
  34. package/dist/admin/roles/role-requests.js +0 -80
  35. package/dist/admin/roles/role-requests.js.map +0 -1
@@ -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 deletion was not sent",
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: "Ask to delete",
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 { RoleRequests } from './role-requests';\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({\n title: 'Sent for approval',\n description: `Another super admin has to approve deleting ${r.name}.`,\n });\n void invalidate();\n },\n onError: (error) =>\n toast({\n title: 'The deletion was not sent',\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: 'Ask to delete',\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 <RoleRequests />\n\n <RoleDialog\n mode={dialog}\n catalog={availablePermissions}\n onClose={() => setDialog(null)}\n onSaved={() => {\n void invalidate();\n void queryClient.invalidateQueries({\n queryKey: ['platform-admin', 'roles', 'requests'],\n });\n }}\n />\n </div>\n );\n}\n"],"mappings":"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","names":[]}
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: "Sent for approval",
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: 'Sent for approval',\n description:\n 'Another super admin has to approve this before the role changes.',\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":[]}
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.120",
3
+ "version": "0.13.0-next.122",
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.120",
35
- "@withpotter/platform-admin": "^0.13.0-next.120",
36
- "@withpotter/ui-kit": "^0.13.0-next.120",
34
+ "@withpotter/lib": "^0.13.0-next.122",
35
+ "@withpotter/platform-admin": "^0.13.0-next.122",
36
+ "@withpotter/ui-kit": "^0.13.0-next.122",
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":[]}