@tekki-forge/runtime-admin 0.2.0 → 0.3.2

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 (136) hide show
  1. package/dist/admin-dashboard.d.ts +16 -0
  2. package/dist/admin-dashboard.js +60 -0
  3. package/dist/admin-dashboard.js.map +1 -0
  4. package/dist/admin-orgs.d.ts +14 -0
  5. package/dist/admin-orgs.js +75 -0
  6. package/dist/admin-orgs.js.map +1 -0
  7. package/dist/admin-shell.d.ts +55 -0
  8. package/dist/admin-shell.js +141 -0
  9. package/dist/admin-shell.js.map +1 -0
  10. package/dist/admin.js +35 -4
  11. package/dist/admin.js.map +1 -1
  12. package/dist/app-pages.d.ts +19 -0
  13. package/dist/app-pages.js +109 -0
  14. package/dist/app-pages.js.map +1 -0
  15. package/dist/auth-pages.js +7 -2
  16. package/dist/auth-pages.js.map +1 -1
  17. package/dist/charts.d.ts +98 -2
  18. package/dist/charts.js +264 -24
  19. package/dist/charts.js.map +1 -1
  20. package/dist/context.js +32 -1
  21. package/dist/context.js.map +1 -1
  22. package/dist/dashboard.d.ts +63 -8
  23. package/dist/dashboard.js +180 -36
  24. package/dist/dashboard.js.map +1 -1
  25. package/dist/i18n/messages/de.js +751 -0
  26. package/dist/i18n/messages/de.js.map +1 -1
  27. package/dist/i18n/messages/en.js +751 -0
  28. package/dist/i18n/messages/en.js.map +1 -1
  29. package/dist/i18n/messages/es.js +751 -0
  30. package/dist/i18n/messages/es.js.map +1 -1
  31. package/dist/i18n/messages/fr.js +751 -0
  32. package/dist/i18n/messages/fr.js.map +1 -1
  33. package/dist/i18n/messages/pt.js +751 -0
  34. package/dist/i18n/messages/pt.js.map +1 -1
  35. package/dist/index.d.ts +9 -0
  36. package/dist/index.js +9 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/shell.js +37 -34
  39. package/dist/shell.js.map +1 -1
  40. package/dist/site/editor/api.d.ts +74 -0
  41. package/dist/site/editor/api.js +93 -0
  42. package/dist/site/editor/api.js.map +1 -0
  43. package/dist/site/editor/content-page.d.ts +4 -0
  44. package/dist/site/editor/content-page.js +65 -0
  45. package/dist/site/editor/content-page.js.map +1 -0
  46. package/dist/site/editor/inbox-tab.d.ts +4 -0
  47. package/dist/site/editor/inbox-tab.js +88 -0
  48. package/dist/site/editor/inbox-tab.js.map +1 -0
  49. package/dist/site/editor/index.d.ts +1 -0
  50. package/dist/site/editor/index.js +3 -0
  51. package/dist/site/editor/index.js.map +1 -0
  52. package/dist/site/editor/model.d.ts +145 -0
  53. package/dist/site/editor/model.js +408 -0
  54. package/dist/site/editor/model.js.map +1 -0
  55. package/dist/site/editor/page-editor.d.ts +10 -0
  56. package/dist/site/editor/page-editor.js +257 -0
  57. package/dist/site/editor/page-editor.js.map +1 -0
  58. package/dist/site/editor/page-settings.d.ts +9 -0
  59. package/dist/site/editor/page-settings.js +105 -0
  60. package/dist/site/editor/page-settings.js.map +1 -0
  61. package/dist/site/editor/pages-tab.d.ts +5 -0
  62. package/dist/site/editor/pages-tab.js +108 -0
  63. package/dist/site/editor/pages-tab.js.map +1 -0
  64. package/dist/site/editor/posts-tab.d.ts +6 -0
  65. package/dist/site/editor/posts-tab.js +139 -0
  66. package/dist/site/editor/posts-tab.js.map +1 -0
  67. package/dist/site/editor/preview.d.ts +32 -0
  68. package/dist/site/editor/preview.js +104 -0
  69. package/dist/site/editor/preview.js.map +1 -0
  70. package/dist/site/editor/prop-fields.d.ts +53 -0
  71. package/dist/site/editor/prop-fields.js +233 -0
  72. package/dist/site/editor/prop-fields.js.map +1 -0
  73. package/dist/site/editor/section-list.d.ts +14 -0
  74. package/dist/site/editor/section-list.js +110 -0
  75. package/dist/site/editor/section-list.js.map +1 -0
  76. package/dist/site/editor/section-picker.d.ts +12 -0
  77. package/dist/site/editor/section-picker.js +44 -0
  78. package/dist/site/editor/section-picker.js.map +1 -0
  79. package/dist/site/editor/widgets.d.ts +84 -0
  80. package/dist/site/editor/widgets.js +130 -0
  81. package/dist/site/editor/widgets.js.map +1 -0
  82. package/dist/site/format.d.ts +31 -0
  83. package/dist/site/format.js +106 -0
  84. package/dist/site/format.js.map +1 -0
  85. package/dist/site/forms.d.ts +43 -0
  86. package/dist/site/forms.js +155 -0
  87. package/dist/site/forms.js.map +1 -0
  88. package/dist/site/header.d.ts +32 -0
  89. package/dist/site/header.js +76 -0
  90. package/dist/site/header.js.map +1 -0
  91. package/dist/site/icons.d.ts +10 -0
  92. package/dist/site/icons.js +28 -0
  93. package/dist/site/icons.js.map +1 -0
  94. package/dist/site/index.d.ts +11 -0
  95. package/dist/site/index.js +13 -0
  96. package/dist/site/index.js.map +1 -0
  97. package/dist/site/markdown.d.ts +14 -0
  98. package/dist/site/markdown.js +457 -0
  99. package/dist/site/markdown.js.map +1 -0
  100. package/dist/site/page.d.ts +16 -0
  101. package/dist/site/page.js +65 -0
  102. package/dist/site/page.js.map +1 -0
  103. package/dist/site/proxy.d.ts +13 -0
  104. package/dist/site/proxy.js +43 -0
  105. package/dist/site/proxy.js.map +1 -0
  106. package/dist/site/sections.d.ts +43 -0
  107. package/dist/site/sections.js +560 -0
  108. package/dist/site/sections.js.map +1 -0
  109. package/dist/site/server.d.ts +73 -0
  110. package/dist/site/server.js +235 -0
  111. package/dist/site/server.js.map +1 -0
  112. package/dist/site/types.d.ts +137 -0
  113. package/dist/site/types.js +2 -0
  114. package/dist/site/types.js.map +1 -0
  115. package/dist/support.d.ts +29 -0
  116. package/dist/support.js +165 -0
  117. package/dist/support.js.map +1 -0
  118. package/dist/system-config.d.ts +24 -0
  119. package/dist/system-config.js +37 -0
  120. package/dist/system-config.js.map +1 -0
  121. package/dist/system-settings.d.ts +9 -0
  122. package/dist/system-settings.js +60 -0
  123. package/dist/system-settings.js.map +1 -0
  124. package/dist/theme-catalog.d.ts +72 -2
  125. package/dist/theme-catalog.js +47 -0
  126. package/dist/theme-catalog.js.map +1 -1
  127. package/dist/theme-switcher.d.ts +8 -0
  128. package/dist/theme-switcher.js +39 -4
  129. package/dist/theme-switcher.js.map +1 -1
  130. package/dist/theme.js +8 -0
  131. package/dist/theme.js.map +1 -1
  132. package/dist/transactions.d.ts +4 -0
  133. package/dist/transactions.js +60 -0
  134. package/dist/transactions.js.map +1 -0
  135. package/package.json +9 -5
  136. package/styles.css +1089 -0
@@ -0,0 +1,16 @@
1
+ import { type LucideIcon } from "lucide-react";
2
+ import { type ReactNode } from "react";
3
+ /** Amounts are stored in the smallest currency unit (cents). */
4
+ export declare function formatMoney(amount: number, currency: string | null | undefined, locale: string): string;
5
+ export declare function StatCard({ icon: Icon, label, value, href, children, tone }: {
6
+ icon: LucideIcon;
7
+ label: string;
8
+ value: ReactNode;
9
+ href?: string;
10
+ children?: ReactNode;
11
+ tone?: "warn";
12
+ }): import("react").JSX.Element;
13
+ /** Administration → Dashboard: the system at a glance (organization admins see their organization). */
14
+ export declare function AdminDashboardPage(): import("react").JSX.Element;
15
+ /** Administration → Analytics: sign-ups, active users and records created over time. */
16
+ export declare function AdminAnalyticsPage(): import("react").JSX.Element;
@@ -0,0 +1,60 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { Activity, Building2, CreditCard, LifeBuoy, Users, Webhook, Layers } from "lucide-react";
5
+ import Link from "next/link";
6
+ import { useState } from "react";
7
+ import { BarChart, LineChart } from "./charts.js";
8
+ import { useForge } from "./context.js";
9
+ import { Sparkline, TrendBadge, localizeSeries } from "./dashboard.js";
10
+ import { CardGridSkeleton } from "./skeleton.js";
11
+ import { Alert, Badge, Card, EmptyState, PageHeader, Select, cn } from "./ui.js";
12
+ /** Amounts are stored in the smallest currency unit (cents). */
13
+ export function formatMoney(amount, currency, locale) {
14
+ try {
15
+ return new Intl.NumberFormat(locale, { style: "currency", currency: (currency ?? "usd").toUpperCase() }).format(amount / 100);
16
+ }
17
+ catch {
18
+ return `${(amount / 100).toFixed(2)} ${currency ?? ""}`.trim();
19
+ }
20
+ }
21
+ function weekTrend(o) {
22
+ const current = o.users.newThisWeek;
23
+ const previous = o.users.newPreviousWeek;
24
+ const change = previous > 0 ? (current - previous) / previous : null;
25
+ const direction = current > previous ? "up" : current < previous ? "down" : "flat";
26
+ return { period: "week", trend: "up-is-good", current, previous, change, direction, good: direction === "flat" ? null : direction === "up", series: o.users.weekly.map((p) => ({ start: p.key, end: p.key, value: p.value })) };
27
+ }
28
+ export function StatCard({ icon: Icon, label, value, href, children, tone }) {
29
+ const body = (_jsxs(Card, { className: cn("forge-metric h-full p-[var(--forge-pad-card)]", href && "forge-card-interactive"), children: [_jsxs("div", { className: "flex items-center gap-2.5 text-sm font-medium text-ink-muted", children: [_jsx("span", { className: "forge-chip flex size-9 shrink-0 items-center justify-center", "aria-hidden": true, children: _jsx(Icon, { className: "size-4" }) }), _jsx("span", { className: "truncate", children: label })] }), _jsx("p", { className: cn("forge-metric-value forge-heading mt-4 text-3xl font-semibold tracking-tight tabular-nums", tone === "warn" ? "text-danger" : "text-ink"), children: value }), children] }));
30
+ return href ? (_jsx(Link, { href: href, className: "forge-focusable block rounded-[var(--forge-radius)]", children: body })) : (body);
31
+ }
32
+ /** Administration → Dashboard: the system at a glance (organization admins see their organization). */
33
+ export function AdminDashboardPage() {
34
+ const { api, locale, t } = useForge();
35
+ const q = useQuery({ queryKey: ["forge", "admin", "overview"], queryFn: () => api("/admin/overview"), refetchInterval: 60_000 });
36
+ const n = (v) => v.toLocaleString(locale);
37
+ const header = _jsx(PageHeader, { title: t("adminArea.dashboard.title"), description: t(q.data?.scope === "organization" ? "adminArea.dashboard.descriptionOrg" : "adminArea.dashboard.description") });
38
+ if (q.isLoading)
39
+ return (_jsxs("div", { children: [header, _jsx(CardGridSkeleton, { count: 4 })] }));
40
+ if (q.error)
41
+ return _jsxs("div", { children: [header, _jsx(Alert, { children: q.error.message })] });
42
+ const o = q.data;
43
+ const trend = weekTrend(o);
44
+ const fmt = (v) => n(v);
45
+ return (_jsxs("div", { className: "space-y-6", children: [header, _jsxs("div", { className: "forge-stagger grid gap-4 sm:grid-cols-2 xl:grid-cols-4", children: [_jsx(StatCard, { icon: Users, label: t("adminArea.dashboard.users"), value: n(o.users.total), href: o.scope === "global" ? "/admin/users" : "/admin/members", children: _jsxs("div", { className: "mt-2 flex items-end justify-between gap-2", children: [_jsxs("div", { children: [_jsx("p", { className: "text-xs text-ink-muted", children: t("adminArea.dashboard.newThisWeek", { count: o.users.newThisWeek }) }), _jsx(TrendBadge, { trend: trend, format: fmt })] }), _jsx(Sparkline, { values: o.users.weekly.map((p) => p.value), className: "text-link" })] }) }), o.organizations && (_jsx(StatCard, { icon: Building2, label: t("adminArea.nav.organizations"), value: n(o.organizations.total), href: "/admin/organizations", children: _jsx("p", { className: "mt-2 text-xs text-ink-muted", children: t("adminArea.dashboard.newThisWeek", { count: o.organizations.newThisWeek }) }) })), _jsx(StatCard, { icon: Activity, label: t("adminArea.dashboard.activeSessions"), value: n(o.sessions.active), href: "/admin/analytics" }), o.billing && (_jsx(StatCard, { icon: CreditCard, label: t("adminArea.dashboard.mrr"), value: o.billing.mrr.length ? formatMoney(o.billing.mrr[0].amount, o.billing.mrr[0].currency, locale) : formatMoney(0, "usd", locale), href: "/admin/transactions", children: _jsxs("p", { className: "mt-2 text-xs text-ink-muted", children: [t("adminArea.dashboard.subscriptions", { active: o.billing.subscriptions.active, trialing: o.billing.subscriptions.trialing }), o.billing.mrr.length > 1 && ` · ${o.billing.mrr.slice(1).map((m) => formatMoney(m.amount, m.currency, locale)).join(" · ")}`] }) })), o.support && (_jsx(StatCard, { icon: LifeBuoy, label: t("adminArea.dashboard.openTickets"), value: n(o.support.open), href: "/admin/support", children: _jsx("p", { className: "mt-2 text-xs text-ink-muted", children: t("adminArea.dashboard.ticketsDetail", { pending: o.support.pending, unassigned: o.support.unassigned }) }) }))] }), (o.jobs || o.webhooks || o.billing) && (_jsxs("section", { "aria-labelledby": "admin-health", children: [_jsx("h2", { id: "admin-health", className: "mb-3 text-sm font-semibold text-ink", children: t("adminArea.dashboard.health") }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [o.jobs && (_jsx(StatCard, { icon: Layers, label: t("adminArea.dashboard.failedJobs"), value: n(o.jobs.failed), tone: o.jobs.failed ? "warn" : undefined, href: "/admin/jobs", children: _jsx("p", { className: "mt-2 text-xs text-ink-muted", children: t("adminArea.dashboard.jobsDetail", { active: o.jobs.active, waiting: o.jobs.waiting }) }) })), o.webhooks && (_jsx(StatCard, { icon: Webhook, label: t("adminArea.dashboard.webhookFailures"), value: n(o.webhooks.failed), tone: o.webhooks.failed ? "warn" : undefined, href: "/admin/webhooks", children: _jsx("p", { className: "mt-2 text-xs text-ink-muted", children: t("adminArea.dashboard.webhooksDetail", { succeeded: o.webhooks.succeeded, pending: o.webhooks.pending }) }) })), o.billing && (_jsx(StatCard, { icon: CreditCard, label: t("adminArea.dashboard.failedPayments"), value: n(o.billing.failedPayments30d), tone: o.billing.failedPayments30d ? "warn" : undefined, href: "/admin/transactions?tab=invoices&status=failed", children: _jsx("p", { className: "mt-2 text-xs text-ink-muted", children: t("adminArea.dashboard.paymentsDetail", { pastDue: o.billing.subscriptions.pastDue, refunds: o.billing.refunds30d }) }) }))] })] })), _jsxs("div", { className: "grid gap-4 lg:grid-cols-3", children: [_jsxs(Card, { className: "p-[var(--forge-pad-card)] lg:col-span-2", children: [_jsxs("div", { className: "mb-3 flex items-center justify-between gap-2", children: [_jsx("h2", { className: "text-sm font-medium text-ink-muted", children: t("adminArea.dashboard.recordsCreated") }), _jsx(Link, { href: "/admin/analytics", className: "text-xs text-ink-subtle hover:text-link", children: t("adminArea.nav.analytics") })] }), o.records.length ? (_jsx(BarChart, { title: t("adminArea.dashboard.recordsCreated"), data: o.records.map((r) => ({ label: r.label, value: r.last30, href: `/${r.path}` })), format: fmt })) : (_jsx(EmptyState, { title: t("dashboard.nothingYet") }))] }), _jsxs(Card, { className: "p-[var(--forge-pad-card)]", children: [_jsx("h2", { className: "mb-2 text-sm font-medium text-ink-muted", children: t(o.scope === "organization" ? "adminArea.dashboard.recentMembers" : "adminArea.dashboard.recentSignups") }), o.recentSignups.length === 0 ? (_jsx("p", { className: "text-sm text-ink-subtle", children: t("dashboard.nothingYet") })) : (_jsx("ul", { className: "divide-y divide-line", children: o.recentSignups.map((u) => (_jsxs("li", { className: "flex items-center justify-between gap-3 py-2 text-sm", children: [_jsxs("span", { className: "min-w-0", children: [_jsx("span", { className: "block truncate font-medium text-ink", children: u.name }), _jsx("span", { className: "block truncate text-xs text-ink-muted", children: u.email })] }), _jsx("span", { className: "shrink-0 text-xs text-ink-subtle tabular-nums", children: new Date(u.createdAt).toLocaleDateString(locale) })] }, u.id))) }))] })] }), _jsxs(Card, { className: "p-[var(--forge-pad-card)]", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [_jsx("h2", { className: "text-sm font-medium text-ink-muted", children: t("adminArea.dashboard.recentAudit") }), _jsx(Link, { href: "/admin/audit-logs", className: "text-xs text-ink-subtle hover:text-link", children: t("dashboard.viewAll") })] }), o.recentAudit.length === 0 ? (_jsx("p", { className: "text-sm text-ink-subtle", children: t("admin.audit.empty") })) : (_jsx("ul", { className: "divide-y divide-line", children: o.recentAudit.map((l) => (_jsxs("li", { className: "flex flex-wrap items-center gap-x-3 gap-y-1 py-2 text-sm", children: [_jsx(Badge, { tone: l.action === "create" ? 1 : l.action.includes("delete") ? 4 : 0, children: l.action }), _jsxs("span", { className: "min-w-0 flex-1 truncate text-ink", children: [l.entity, " ", _jsx("span", { className: "font-mono text-xs text-ink-subtle", children: l.recordId.slice(0, 8) })] }), _jsx("span", { className: "text-xs text-ink-muted", children: l.user?.name ?? t("admin.audit.system") }), _jsx("span", { className: "text-xs text-ink-subtle tabular-nums", children: new Date(l.createdAt).toLocaleString(locale) })] }, l.id))) }))] })] }));
46
+ }
47
+ const PERIODS = [7, 30, 90, 365];
48
+ /** Administration → Analytics: sign-ups, active users and records created over time. */
49
+ export function AdminAnalyticsPage() {
50
+ const { api, locale, resources, t } = useForge();
51
+ const [days, setDays] = useState(30);
52
+ const [entity, setEntity] = useState("");
53
+ const q = useQuery({ queryKey: ["forge", "admin", "analytics", days], queryFn: () => api("/admin/analytics", { query: { days } }) });
54
+ const fmt = (v) => v.toLocaleString(locale);
55
+ const series = (points, by) => localizeSeries(points.map((p) => ({ key: p.key, label: p.key, value: p.value })), undefined, by, locale, t);
56
+ const a = q.data;
57
+ const chosen = a?.records.find((r) => r.entity === entity) ?? a?.records[0];
58
+ return (_jsxs("div", { className: "space-y-6", children: [_jsx(PageHeader, { title: t("adminArea.nav.analytics"), description: t(a?.scope === "organization" ? "adminArea.analytics.descriptionOrg" : "adminArea.analytics.description"), actions: _jsx("div", { className: "forge-admin-segmented inline-flex gap-0.5 p-0.5", role: "group", "aria-label": t("adminArea.analytics.period"), children: PERIODS.map((d) => (_jsx("button", { type: "button", "aria-pressed": days === d, onClick: () => setDays(d), className: cn("forge-btn h-7 px-2.5 text-xs font-medium", days === d ? "forge-btn-secondary" : "forge-btn-ghost"), children: t(`adminArea.analytics.days.${d}`) }, d))) }) }), q.isLoading ? (_jsx(CardGridSkeleton, { count: 4 })) : q.error ? (_jsx(Alert, { children: q.error.message })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "grid gap-4 lg:grid-cols-2", children: [_jsxs(Card, { className: "p-[var(--forge-pad-card)]", children: [_jsx("h2", { className: "text-sm font-medium text-ink-muted", children: t(a.scope === "organization" ? "adminArea.analytics.joined" : "adminArea.analytics.signups") }), _jsx("p", { className: "forge-heading mt-1 text-2xl font-semibold text-ink tabular-nums", children: fmt(a.signups.total) }), _jsx("div", { className: "mt-3", children: _jsx(LineChart, { area: true, title: t("adminArea.analytics.signups"), data: series(a.signups.series, a.by), format: fmt }) })] }), _jsxs(Card, { className: "p-[var(--forge-pad-card)]", children: [_jsx("h2", { className: "text-sm font-medium text-ink-muted", children: t("adminArea.analytics.activeUsers") }), _jsx("p", { className: "forge-heading mt-1 text-2xl font-semibold text-ink tabular-nums", children: fmt(a.activeUsers.total) }), _jsx("div", { className: "mt-3", children: _jsx(LineChart, { area: true, title: t("adminArea.analytics.activeUsers"), data: series(a.activeUsers.series, a.by), format: fmt }) }), _jsx("p", { className: "mt-2 text-xs text-ink-subtle", children: t("adminArea.analytics.activeHint") })] })] }), _jsxs("div", { className: "grid gap-4 lg:grid-cols-3", children: [_jsxs(Card, { className: "p-[var(--forge-pad-card)] lg:col-span-2", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [_jsx("h2", { className: "text-sm font-medium text-ink-muted", children: t("adminArea.analytics.recordsOverTime") }), a.records.length > 1 && (_jsx("div", { className: "w-48 max-w-full", children: _jsx(Select, { "aria-label": t("admin.audit.entity"), value: chosen?.entity ?? "", onChange: (e) => setEntity(e.target.value), className: "h-8 py-0 text-xs", children: a.records.map((r) => (_jsx("option", { value: r.entity, children: resources[r.entity]?.pluralLabel ?? r.label }, r.entity))) }) }))] }), chosen ? (_jsx("div", { className: "mt-3", children: _jsx(LineChart, { area: true, title: resources[chosen.entity]?.pluralLabel ?? chosen.label, data: series(chosen.series, a.by), format: fmt }) })) : (_jsx(EmptyState, { title: t("dashboard.nothingYet") }))] }), _jsxs(Card, { className: "p-[var(--forge-pad-card)]", children: [_jsx("h2", { className: "mb-3 text-sm font-medium text-ink-muted", children: t("adminArea.analytics.topEntities") }), a.topEntities.length ? (_jsx(BarChart, { orientation: "horizontal", title: t("adminArea.analytics.topEntities"), data: a.topEntities.map((e) => ({ label: resources[e.entity]?.pluralLabel ?? e.label, value: e.total, href: `/${e.path}` })), format: fmt })) : (_jsx(EmptyState, { title: t("dashboard.nothingYet") }))] })] })] }))] }));
59
+ }
60
+ //# sourceMappingURL=admin-dashboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-dashboard.js","sourceRoot":"","sources":["../src/admin-dashboard.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ interface Plan {
2
+ id: string;
3
+ name: string;
4
+ status: string;
5
+ }
6
+ export declare function useDebouncedValue<T>(value: T, ms?: number): T;
7
+ /** Sets one search param of the current URL (keeps the others). */
8
+ export declare function useQueryParam(name: string): [string | null, (value: string | null) => void];
9
+ export declare function PlanBadge({ plan }: {
10
+ plan: Plan | null;
11
+ }): import("react").JSX.Element;
12
+ /** Administration → Organizations (global admins, multi-tenant apps): list, then one organization with `?id=`. */
13
+ export declare function AdminOrganizationsPage(): import("react").JSX.Element;
14
+ export {};
@@ -0,0 +1,75 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useQuery } from "@tanstack/react-query";
4
+ import { ArrowLeft, Search } from "lucide-react";
5
+ import Link from "next/link";
6
+ import { usePathname, useRouter, useSearchParams } from "next/navigation";
7
+ import { useEffect, useState } from "react";
8
+ import { useForge } from "./context.js";
9
+ import { TableSkeleton } from "./skeleton.js";
10
+ import { Alert, Badge, Button, Card, EmptyState, Input, PageHeader, Pagination, Spinner } from "./ui.js";
11
+ export function useDebouncedValue(value, ms = 300) {
12
+ const [v, setV] = useState(value);
13
+ useEffect(() => {
14
+ const id = setTimeout(() => setV(value), ms);
15
+ return () => clearTimeout(id);
16
+ }, [value, ms]);
17
+ return v;
18
+ }
19
+ /** Sets one search param of the current URL (keeps the others). */
20
+ export function useQueryParam(name) {
21
+ const params = useSearchParams();
22
+ const router = useRouter();
23
+ const pathname = usePathname();
24
+ const set = (value) => {
25
+ const next = new URLSearchParams(params.toString());
26
+ if (value === null || value === "")
27
+ next.delete(name);
28
+ else
29
+ next.set(name, value);
30
+ const qs = next.toString();
31
+ router.push(qs ? `${pathname}?${qs}` : pathname);
32
+ };
33
+ return [params.get(name), set];
34
+ }
35
+ export function PlanBadge({ plan }) {
36
+ const { t } = useForge();
37
+ if (!plan)
38
+ return _jsx("span", { className: "text-ink-subtle", children: "\u2014" });
39
+ const tone = plan.status === "active" ? 1 : plan.status === "trialing" ? 3 : plan.status === "past_due" || plan.status === "unpaid" ? 4 : 6;
40
+ return (_jsxs("span", { className: "inline-flex flex-wrap items-center gap-1", children: [_jsx(Badge, { tone: 0, children: plan.name }), plan.status !== "none" && _jsx(Badge, { tone: tone, children: t(`adminArea.subscriptionStatus.${plan.status}`) })] }));
41
+ }
42
+ /** Administration → Organizations (global admins, multi-tenant apps): list, then one organization with `?id=`. */
43
+ export function AdminOrganizationsPage() {
44
+ const [id] = useQueryParam("id");
45
+ return id ? _jsx(OrganizationDetail, { id: id }) : _jsx(OrganizationList, {});
46
+ }
47
+ function OrganizationList() {
48
+ const { api, locale, t } = useForge();
49
+ const [search, setSearch] = useState("");
50
+ const [page, setPage] = useState(1);
51
+ const q = useDebouncedValue(search);
52
+ const orgs = useQuery({
53
+ queryKey: ["forge", "admin", "orgs", q, page],
54
+ queryFn: () => api("/admin/organizations", { query: { search: q, page } }),
55
+ });
56
+ return (_jsxs("div", { children: [_jsx(PageHeader, { title: t("adminArea.nav.organizations"), description: t("adminArea.orgs.description") }), _jsxs(Card, { children: [_jsx("div", { className: "border-b border-line p-3", children: _jsxs("div", { className: "relative max-w-sm", children: [_jsx(Search, { className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-ink-subtle", "aria-hidden": true }), _jsx(Input, { "aria-label": t("adminArea.orgs.search"), placeholder: t("adminArea.orgs.search"), value: search, onChange: (e) => { setSearch(e.target.value); setPage(1); }, className: "h-8 pl-8" })] }) }), orgs.isLoading ? (_jsx(TableSkeleton, { columns: 4 })) : orgs.error ? (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: orgs.error.message }) })) : !orgs.data.data.length ? (_jsx(EmptyState, { title: t("adminArea.orgs.empty") })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "forge-table w-full text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "text-left text-xs font-medium tracking-wide uppercase", children: [_jsx("th", { className: "px-4 py-2.5", children: t("adminArea.orgs.name") }), _jsx("th", { className: "px-4 py-2.5", children: t("admin.members.title") }), orgs.data.billing && _jsx("th", { className: "px-4 py-2.5", children: t("adminArea.orgs.plan") }), _jsx("th", { className: "px-4 py-2.5", children: t("adminArea.orgs.created") })] }) }), _jsx("tbody", { children: orgs.data.data.map((o) => (_jsxs("tr", { children: [_jsxs("td", { className: "px-4 py-2.5", children: [_jsx(Link, { href: `/admin/organizations?id=${o.id}`, className: "font-medium text-ink hover:text-link hover:underline", children: o.name }), _jsx("p", { className: "font-mono text-xs text-ink-subtle", children: o.slug })] }), _jsx("td", { className: "px-4 py-2.5 tabular-nums", children: o.members.toLocaleString(locale) }), orgs.data.billing && (_jsx("td", { className: "px-4 py-2.5", children: _jsx(PlanBadge, { plan: o.plan }) })), _jsx("td", { className: "px-4 py-2.5 text-ink-muted tabular-nums", children: new Date(o.createdAt).toLocaleDateString(locale) })] }, o.id))) })] }) })), orgs.data && orgs.data.meta.total > 0 && _jsx(Pagination, { page: orgs.data.meta.page, pages: orgs.data.meta.pages, total: orgs.data.meta.total, onPage: setPage })] })] }));
57
+ }
58
+ function OrganizationDetail({ id }) {
59
+ const { api, auth, locale, t } = useForge();
60
+ const [error, setError] = useState(null);
61
+ const org = useQuery({ queryKey: ["forge", "admin", "org", id], queryFn: () => api(`/admin/organizations/${id}`) });
62
+ if (org.isLoading)
63
+ return _jsx(Spinner, {});
64
+ if (org.error)
65
+ return _jsx(Alert, { children: org.error.message });
66
+ const o = org.data;
67
+ return (_jsxs("div", { className: "space-y-6", children: [_jsxs(Link, { href: "/admin/organizations", className: "inline-flex items-center gap-1.5 text-sm text-ink-muted hover:text-ink", children: [_jsx(ArrowLeft, { className: "size-4", "aria-hidden": true }), " ", t("adminArea.nav.organizations")] }), _jsx(PageHeader, { title: o.name, description: t("adminArea.orgs.detail", { slug: o.slug, date: new Date(o.createdAt).toLocaleDateString(locale) }), actions: _jsx(Link, { href: `/admin/audit-logs?organizationId=${o.id}`, className: "forge-btn forge-btn-secondary inline-flex h-[var(--forge-control-h)] items-center px-3.5 text-sm font-medium", children: t("adminArea.nav.auditLogs") }) }), error && _jsx(Alert, { children: error }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [_jsxs(Card, { className: "p-[var(--forge-pad-card)]", children: [_jsx("p", { className: "text-sm text-ink-muted", children: t("admin.members.title") }), _jsx("p", { className: "forge-heading mt-1 text-2xl font-semibold text-ink tabular-nums", children: o.members.length }), o.pendingInvitations > 0 && _jsx("p", { className: "mt-1 text-xs text-ink-subtle", children: t("adminArea.orgs.pendingInvitations", { count: o.pendingInvitations }) })] }), _jsxs(Card, { className: "p-[var(--forge-pad-card)]", children: [_jsx("p", { className: "text-sm text-ink-muted", children: t("adminArea.orgs.plan") }), _jsx("div", { className: "mt-2", children: _jsx(PlanBadge, { plan: o.plan }) })] }), _jsxs(Card, { className: "p-[var(--forge-pad-card)]", children: [_jsx("p", { className: "text-sm text-ink-muted", children: t("adminArea.orgs.records") }), _jsx("ul", { className: "mt-1 space-y-0.5 text-sm", children: o.records.map((r) => (_jsxs("li", { className: "flex justify-between gap-2", children: [_jsx("span", { className: "text-ink", children: r.label }), _jsx("span", { className: "text-ink-muted tabular-nums", children: r.total.toLocaleString(locale) })] }, r.entity))) })] })] }), _jsx(Card, { children: _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "forge-table w-full text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "text-left text-xs font-medium tracking-wide uppercase", children: [_jsx("th", { className: "px-4 py-2.5", children: t("admin.users.user") }), _jsx("th", { className: "px-4 py-2.5", children: t("admin.role") }), _jsx("th", { className: "px-4 py-2.5", children: t("admin.users.joined") }), _jsx("th", { className: "px-4 py-2.5 text-right", children: t("common.actions") })] }) }), _jsx("tbody", { children: o.members.map((m) => (_jsxs("tr", { children: [_jsxs("td", { className: "px-4 py-2.5", children: [_jsx("p", { className: "font-medium text-ink", children: m.name }), _jsx("p", { className: "text-xs text-ink-muted", children: m.email })] }), _jsxs("td", { className: "px-4 py-2.5", children: [_jsx(Badge, { tone: m.role === "owner" ? 5 : m.role === "admin" ? 0 : 6, children: m.role }), m.banned && _jsx("span", { className: "ml-1", children: _jsx(Badge, { tone: 4, children: t("admin.users.banned") }) })] }), _jsx("td", { className: "px-4 py-2.5 text-ink-muted tabular-nums", children: new Date(m.joinedAt).toLocaleDateString(locale) }), _jsx("td", { className: "px-4 py-2.5 text-right", children: _jsx(Button, { size: "sm", variant: "ghost", onClick: async () => {
68
+ const r = await auth.admin.impersonateUser({ userId: m.userId });
69
+ if (r.error)
70
+ setError(r.error.message);
71
+ else
72
+ window.location.href = "/";
73
+ }, children: t("admin.users.impersonate") }) })] }, m.id))) })] }) }) })] }));
74
+ }
75
+ //# sourceMappingURL=admin-orgs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-orgs.js","sourceRoot":"","sources":["../src/admin-orgs.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import type { AppIR } from "@tekki-forge/compiler";
2
+ import { type ReactNode } from "react";
3
+ import { type PluginPages } from "./context.js";
4
+ /**
5
+ * Who may use the administration area:
6
+ * - `global`: the `admin` role on the account (Users page, every organization, system settings);
7
+ * - `organization`: with organizations, an admin or owner of the active organization (their organization only).
8
+ */
9
+ export interface AdminAccess {
10
+ loading: boolean;
11
+ global: boolean;
12
+ organization: boolean;
13
+ /** Can open /admin at all. */
14
+ any: boolean;
15
+ }
16
+ export declare function useAdminAccess(): AdminAccess;
17
+ export type AdminGroup = "overview" | "people" | "business" | "developers" | "system";
18
+ export interface AdminSection {
19
+ /** Registry path ("admin/users"); the URL is `/${path}`. */
20
+ path: string;
21
+ label: string;
22
+ icon: ReactNode;
23
+ group: AdminGroup;
24
+ exact?: boolean;
25
+ }
26
+ /**
27
+ * The administration navigation, filtered for the viewer. Documented in the README ("Administration area"):
28
+ * Dashboard, Analytics, Audit logs: every admin (organization admins see their organization);
29
+ * Users, Organizations, Content, Transactions, Support, System settings, Jobs: global admins;
30
+ * Members, Teams, API keys, Webhooks: admins of the active organization (and global admins).
31
+ */
32
+ export declare function adminSections(app: AppIR, access: Pick<AdminAccess, "global" | "organization">, pages: PluginPages, t: (key: string) => string, dev?: boolean): AdminSection[];
33
+ /** Section of the current URL (/admin/users?x → admin/users). */
34
+ export declare function adminPathOf(pathname: string): string;
35
+ /** Clean 403 page: the person is signed in but may not see this. */
36
+ export declare function AdminForbidden({ standalone }: {
37
+ standalone?: boolean;
38
+ }): import("react").JSX.Element;
39
+ /** Renders an administration page from the registry (`/admin/[page]`): built-in sections and plugin pages. */
40
+ export declare function AdminPage({ path }: {
41
+ path: string;
42
+ }): import("react").JSX.Element;
43
+ /**
44
+ * The administration area (/admin): its own navigation, separate from the app's. Global admins and, with
45
+ * organizations, organization admins get in; everyone else sees a 403 page. Sections the viewer may not
46
+ * open are hidden from the navigation and answered with 403 when visited directly.
47
+ */
48
+ export declare function AdminShell({ children }: {
49
+ children: ReactNode;
50
+ }): import("react").JSX.Element;
51
+ /** The "Admin" entry shown in the app's navigation to people who can open the administration area. */
52
+ export declare function AdminEntryLink({ variant, tooltip }: {
53
+ variant?: "nav" | "button" | "icon";
54
+ tooltip?: string;
55
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,141 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { ArrowLeft, Blocks, Building2, ChartLine, CreditCard, FileText, KeyRound, LayoutDashboard, LifeBuoy, LogOut, Menu, ScrollText, Settings2, ShieldAlert, ShieldCheck, Users, UsersRound, Webhook, X, } from "lucide-react";
4
+ import Link from "next/link";
5
+ import { usePathname, useRouter } from "next/navigation";
6
+ import { useEffect, useState } from "react";
7
+ import { BrandMark } from "./brand.js";
8
+ import { useForge, useMe } from "./context.js";
9
+ import { LocaleSwitcher } from "./locale-switcher.js";
10
+ import { NotificationBell } from "./notifications.js";
11
+ import { MaintenanceBanner, useAppName } from "./system-config.js";
12
+ import { PageHeader, ResourceIcon, Spinner, cn } from "./ui.js";
13
+ const isGlobal = (role) => (role ?? "").split(",").map((r) => r.trim()).includes("admin");
14
+ export function useAdminAccess() {
15
+ const { app } = useForge();
16
+ const me = useMe();
17
+ const global = isGlobal(me.data?.user?.role);
18
+ const organization = app.tenancy.enabled && !!me.data?.organizationId && !!me.data?.roles.includes("admin");
19
+ return { loading: me.isLoading, global, organization, any: app.admin.enabled && (global || organization || (!app.tenancy.enabled && !!me.data?.roles.includes("admin"))) };
20
+ }
21
+ const icon = (I) => _jsx(I, { className: "size-4 shrink-0", "aria-hidden": true });
22
+ const has = (app, plugin) => app.plugins.some((p) => p.name === plugin);
23
+ /**
24
+ * The administration navigation, filtered for the viewer. Documented in the README ("Administration area"):
25
+ * Dashboard, Analytics, Audit logs: every admin (organization admins see their organization);
26
+ * Users, Organizations, Content, Transactions, Support, System settings, Jobs: global admins;
27
+ * Members, Teams, API keys, Webhooks: admins of the active organization (and global admins).
28
+ */
29
+ export function adminSections(app, access, pages, t, dev = process.env.NODE_ENV !== "production") {
30
+ const g = access.global;
31
+ const admin = access.global || access.organization;
32
+ const orgAdmin = app.tenancy.enabled ? access.organization : access.global;
33
+ const site = !!app.site;
34
+ const out = [
35
+ { path: "admin", label: t("adminArea.nav.dashboard"), icon: icon(LayoutDashboard), group: "overview", exact: true },
36
+ { path: "admin/analytics", label: t("adminArea.nav.analytics"), icon: icon(ChartLine), group: "overview" },
37
+ g && { path: "admin/users", label: t("admin.users.title"), icon: icon(Users), group: "people" },
38
+ g && app.tenancy.enabled && { path: "admin/organizations", label: t("adminArea.nav.organizations"), icon: icon(Building2), group: "people" },
39
+ app.tenancy.enabled && access.organization && { path: "admin/members", label: t("admin.members.title"), icon: icon(UsersRound), group: "people" },
40
+ !!app.teams?.enabled && orgAdmin && { path: "admin/teams", label: t("admin.teams.title"), icon: icon(UsersRound), group: "people" },
41
+ g && site && !!pages["admin/content"] && { path: "admin/content", label: t("adminArea.nav.content"), icon: icon(FileText), group: "business" },
42
+ g && has(app, "payments") && { path: "admin/transactions", label: t("adminArea.nav.transactions"), icon: icon(CreditCard), group: "business" },
43
+ g && !!app.admin.support && { path: "admin/support", label: t("adminArea.nav.support"), icon: icon(LifeBuoy), group: "business" },
44
+ orgAdmin && { path: "admin/api-keys", label: t("admin.apiKeys.title"), icon: icon(KeyRound), group: "developers" },
45
+ orgAdmin && has(app, "webhooks") && { path: "admin/webhooks", label: t("adminArea.nav.webhooks"), icon: icon(Webhook), group: "developers" },
46
+ ...app.plugins
47
+ .filter((p) => p.name !== "webhooks")
48
+ .flatMap((p) => p.nav.filter((n) => n.admin))
49
+ .map((n) => (n.path === "admin/jobs" ? g : admin) && { path: n.path.startsWith("admin/") ? n.path : `admin/${n.path}`, label: n.label, icon: _jsx(ResourceIcon, { name: n.icon, className: "size-4 shrink-0" }), group: "developers" }),
50
+ admin && dev && { path: "admin/schema", label: t("schema.nav"), icon: icon(Blocks), group: "developers" },
51
+ admin && { path: "admin/audit-logs", label: t("adminArea.nav.auditLogs"), icon: icon(ScrollText), group: "system" },
52
+ g && { path: "admin/settings", label: t("adminArea.nav.settings"), icon: icon(Settings2), group: "system" },
53
+ ];
54
+ return out.filter((s) => !!s);
55
+ }
56
+ /** Every section that exists in this app for some admin (used to tell "forbidden" from "not found"). */
57
+ function knownSection(app, pages, path, t) {
58
+ return adminSections(app, { global: true, organization: true }, pages, t).some((s) => s.path === path) || (path === "admin/members" && app.tenancy.enabled);
59
+ }
60
+ /** Section of the current URL (/admin/users?x → admin/users). */
61
+ export function adminPathOf(pathname) {
62
+ const parts = pathname.replace(/^\/+|\/+$/g, "").split("/");
63
+ return parts.slice(0, 2).join("/") || "admin";
64
+ }
65
+ /** Clean 403 page: the person is signed in but may not see this. */
66
+ export function AdminForbidden({ standalone }) {
67
+ const { t } = useForge();
68
+ return (_jsxs("div", { className: cn("forge-admin-forbidden flex flex-col items-center justify-center px-6 text-center", standalone ? "min-h-dvh" : "py-20"), children: [_jsx("span", { className: "forge-chip mb-4 flex size-12 items-center justify-center", "aria-hidden": true, children: _jsx(ShieldAlert, { className: "size-6" }) }), _jsx("p", { className: "text-xs font-semibold tracking-widest text-ink-subtle uppercase", children: "403" }), _jsx("h1", { className: "forge-heading mt-1 text-xl font-semibold text-ink", children: t("adminArea.forbidden.title") }), _jsx("p", { className: "mt-2 max-w-md text-sm text-ink-muted", children: t("adminArea.forbidden.description") }), _jsxs(Link, { href: "/", className: "forge-btn forge-btn-primary mt-6 inline-flex h-[var(--forge-control-h)] items-center gap-1.5 px-3.5 text-sm font-medium", children: [_jsx(ArrowLeft, { className: "size-4", "aria-hidden": true }), " ", t("adminArea.backToApp")] })] }));
69
+ }
70
+ /** Renders an administration page from the registry (`/admin/[page]`): built-in sections and plugin pages. */
71
+ export function AdminPage({ path }) {
72
+ const { pages, t } = useForge();
73
+ const Page = pages[path];
74
+ if (!Page)
75
+ return _jsx(PageHeader, { title: t("common.notFound"), description: t("nav.noPage", { path: `/${path}` }) });
76
+ return _jsx(Page, { path: path });
77
+ }
78
+ function AdminNavLink({ section }) {
79
+ const pathname = usePathname();
80
+ const href = `/${section.path}`;
81
+ const active = section.exact ? pathname === href : pathname === href || pathname.startsWith(`${href}/`);
82
+ return (_jsxs(Link, { href: href, "aria-current": active ? "page" : undefined, className: "forge-nav-item flex items-center gap-2.5 px-2.5 py-1.5 text-sm", children: [section.icon, _jsx("span", { className: "min-w-0 truncate", children: section.label })] }));
83
+ }
84
+ const GROUPS = ["overview", "people", "business", "developers", "system"];
85
+ /**
86
+ * The administration area (/admin): its own navigation, separate from the app's. Global admins and, with
87
+ * organizations, organization admins get in; everyone else sees a 403 page. Sections the viewer may not
88
+ * open are hidden from the navigation and answered with 403 when visited directly.
89
+ */
90
+ export function AdminShell({ children }) {
91
+ const { app, auth, pages, t } = useForge();
92
+ const session = auth.useSession();
93
+ const me = useMe();
94
+ const access = useAdminAccess();
95
+ const router = useRouter();
96
+ const pathname = usePathname();
97
+ const appName = useAppName();
98
+ const [mobileOpen, setMobileOpen] = useState(false);
99
+ useEffect(() => {
100
+ if (!session.isPending && !session.data)
101
+ router.replace(`/sign-in?next=${encodeURIComponent(pathname)}`);
102
+ }, [session.isPending, session.data, router, pathname]);
103
+ useEffect(() => setMobileOpen(false), [pathname]);
104
+ if (session.isPending || !session.data || me.isLoading)
105
+ return _jsx(Spinner, {});
106
+ if (!access.any)
107
+ return _jsx("div", { className: "forge-app-bg", children: _jsx(AdminForbidden, { standalone: true }) });
108
+ const user = session.data.user;
109
+ const sections = adminSections(app, access, pages, t);
110
+ const current = adminPathOf(pathname);
111
+ const section = sections.find((s) => s.path === current);
112
+ const allowed = !!section || !knownSection(app, pages, current, t);
113
+ const signOut = async () => {
114
+ await auth.signOut();
115
+ window.location.href = "/sign-in";
116
+ };
117
+ const scope = access.global ? t("adminArea.scope.global") : t("adminArea.scope.organization");
118
+ const nav = (_jsxs("div", { className: "relative z-[1] flex h-full flex-col", children: [_jsxs("div", { className: "flex h-14 items-center gap-2.5 px-4", children: [_jsx(BrandMark, {}), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate text-sm font-semibold text-ink", children: appName }), _jsxs("p", { className: "forge-admin-badge mt-0.5 inline-flex items-center gap-1 px-1.5 py-px text-[10px] font-semibold tracking-wider uppercase", children: [_jsx(ShieldCheck, { className: "size-3", "aria-hidden": true }), " ", t("adminArea.title")] })] })] }), _jsx("div", { className: "px-3 pt-1", children: _jsxs(Link, { href: "/", className: "forge-admin-back forge-nav-item flex items-center gap-2.5 px-2.5 py-1.5 text-sm font-medium", children: [_jsx(ArrowLeft, { className: "size-4 shrink-0", "aria-hidden": true }), " ", t("adminArea.backToApp")] }) }), _jsx("nav", { className: "flex-1 space-y-5 overflow-y-auto px-3 py-4", "aria-label": t("adminArea.navLabel"), children: GROUPS.map((group) => {
119
+ const items = sections.filter((s) => s.group === group);
120
+ if (!items.length)
121
+ return null;
122
+ return (_jsxs("div", { className: "space-y-0.5", children: [_jsx("p", { className: "px-2.5 pb-1 text-xs font-medium tracking-wide text-ink-subtle uppercase", children: t(`adminArea.group.${group}`) }), items.map((s) => (_jsx(AdminNavLink, { section: s }, s.path)))] }, group));
123
+ }) }), _jsxs("div", { className: "border-t border-line p-3", children: [_jsx(LocaleSwitcher, {}), _jsxs("div", { className: "px-2.5 py-1.5", children: [_jsx("p", { className: "truncate text-sm text-ink", children: user.name }), _jsx("p", { className: "truncate text-xs text-ink-subtle", children: user.email })] }), _jsxs("button", { type: "button", onClick: signOut, className: "forge-nav-item flex w-full items-center gap-2.5 px-2.5 py-1.5 text-sm", children: [_jsx(LogOut, { className: "size-4 shrink-0", "aria-hidden": true }), " ", t("nav.signOut")] })] })] }));
124
+ return (_jsxs("div", { className: "forge-app-bg forge-admin", "data-forge-shell": "admin", children: [_jsx("a", { href: "#forge-main", className: "forge-skip-link", children: t("nav.skipToContent") }), _jsx(MaintenanceBanner, {}), me.data?.impersonating && (_jsxs("div", { className: "forge-banner flex items-center justify-center gap-3 px-4 py-1.5 text-sm", children: [t("nav.impersonating", { name: user.name }), _jsx("button", { type: "button", className: "font-medium underline", onClick: async () => {
125
+ await auth.admin.stopImpersonating();
126
+ window.location.href = "/admin/users";
127
+ }, children: t("nav.stopImpersonating") })] })), _jsx("aside", { className: "forge-sidebar forge-admin-sidebar fixed inset-y-0 left-0 hidden w-64 lg:block", children: nav }), mobileOpen && (_jsxs("div", { className: "fixed inset-0 z-40 lg:hidden", children: [_jsx("div", { className: "forge-overlay absolute inset-0", onClick: () => setMobileOpen(false) }), _jsxs("aside", { className: "forge-sidebar forge-admin-sidebar forge-drawer absolute inset-y-0 left-0 w-72 max-w-[85vw]", "aria-label": t("adminArea.navLabel"), children: [_jsx("button", { type: "button", "aria-label": t("nav.closeMenu"), onClick: () => setMobileOpen(false), className: "forge-btn forge-btn-ghost absolute top-4 right-3 z-[2] p-1", children: _jsx(X, { className: "size-4" }) }), nav] })] })), _jsxs("div", { className: "lg:pl-64", children: [_jsxs("header", { className: "forge-topbar forge-admin-topbar sticky top-0 z-30 flex h-14 items-center gap-2 px-4 sm:px-6", children: [_jsx("button", { type: "button", "aria-label": t("nav.openMenu"), onClick: () => setMobileOpen(true), className: "forge-btn forge-btn-ghost p-1.5 lg:hidden", children: _jsx(Menu, { className: "size-5" }) }), _jsxs("nav", { "aria-label": t("adminArea.breadcrumb"), className: "flex min-w-0 flex-1 items-center gap-1.5 text-sm", children: [_jsx(ShieldCheck, { className: "size-4 shrink-0 text-ink-muted", "aria-hidden": true }), _jsx(Link, { href: "/admin", className: "shrink-0 font-medium text-ink-muted hover:text-ink", children: t("adminArea.title") }), section && section.path !== "admin" && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-ink-subtle", "aria-hidden": true, children: "/" }), _jsx("span", { className: "truncate font-medium text-ink", "aria-current": "page", children: section.label })] }))] }), _jsx("span", { className: "forge-admin-scope hidden px-2 py-0.5 text-xs font-medium sm:inline-flex", title: scope, children: scope }), _jsx(NotificationBell, { align: "end" }), _jsxs(Link, { href: "/", className: "forge-btn forge-btn-secondary hidden h-8 items-center gap-1.5 px-3 text-xs font-medium sm:inline-flex", children: [_jsx(ArrowLeft, { className: "size-3.5", "aria-hidden": true }), " ", t("adminArea.backToApp")] })] }), _jsx("main", { id: "forge-main", tabIndex: -1, className: "mx-auto max-w-7xl px-4 py-[var(--forge-main-y)] outline-none sm:px-8", children: allowed ? children : _jsx(AdminForbidden, {}) })] })] }));
128
+ }
129
+ /** The "Admin" entry shown in the app's navigation to people who can open the administration area. */
130
+ export function AdminEntryLink({ variant = "nav", tooltip }) {
131
+ const { t } = useForge();
132
+ const access = useAdminAccess();
133
+ if (!access.any)
134
+ return null;
135
+ if (variant === "button")
136
+ return (_jsxs(Link, { href: "/admin", className: "forge-admin-entry forge-btn forge-btn-secondary inline-flex h-8 shrink-0 items-center gap-1.5 px-3 text-xs font-medium", children: [_jsx(ShieldCheck, { className: "size-4", "aria-hidden": true }), " ", t("adminArea.entry")] }));
137
+ if (variant === "icon")
138
+ return (_jsx(Link, { href: "/admin", "aria-label": t("adminArea.entry"), className: "forge-admin-entry forge-btn forge-btn-ghost ml-auto p-1.5", children: _jsx(ShieldCheck, { className: "size-5", "aria-hidden": true }) }));
139
+ return (_jsxs(Link, { href: "/admin", "data-tooltip": tooltip ?? t("adminArea.entry"), className: "forge-admin-entry forge-nav-item flex items-center gap-2.5 overflow-hidden px-2.5 py-1.5 text-sm font-medium whitespace-nowrap", children: [_jsx(ShieldCheck, { className: "size-4 shrink-0", "aria-hidden": true }), _jsx("span", { className: "forge-nav-label min-w-0 truncate", children: t("adminArea.entry") })] }));
140
+ }
141
+ //# sourceMappingURL=admin-shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-shell.js","sourceRoot":"","sources":["../src/admin-shell.tsx"],"names":[],"mappings":"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"}
package/dist/admin.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useQuery } from "@tanstack/react-query";
4
4
  import { Search, UserPlus } from "lucide-react";
5
+ import { useSearchParams } from "next/navigation";
5
6
  import { useEffect, useState } from "react";
6
7
  import { useForge, useMe } from "./context.js";
7
8
  import { Alert, Badge, Button, Card, ConfirmDialog, Dialog, EmptyState, FieldShell, Input, PageHeader, Pagination, Select, Spinner } from "./ui.js";
@@ -23,6 +24,7 @@ export function AdminUsersPage() {
23
24
  const [confirm, setConfirm] = useState(null);
24
25
  const [notice, setNotice] = useState(null);
25
26
  const [busy, setBusy] = useState(false);
27
+ const [sessionsOf, setSessionsOf] = useState(null);
26
28
  const q = useDebounced(search);
27
29
  // Without organizations a user's role is global, so every app role can be given here; with organizations
28
30
  // roles are per organization (Members page) and only "admin" is global.
@@ -58,7 +60,30 @@ export function AdminUsersPage() {
58
60
  return (_jsxs("div", { children: [_jsx(PageHeader, { title: t("admin.users.title"), description: t("admin.users.description"), actions: _jsxs(Button, { variant: "primary", onClick: () => setCreating(true), children: [_jsx(UserPlus, { className: "size-4" }), " ", t("admin.users.create")] }) }), notice && (_jsx("div", { className: "mb-4", children: _jsx(Alert, { tone: notice.tone, children: notice.text }) })), _jsxs(Card, { children: [_jsx("div", { className: "border-b border-line p-3", children: _jsxs("div", { className: "relative max-w-sm", children: [_jsx(Search, { className: "pointer-events-none absolute top-1/2 left-2.5 size-4 -translate-y-1/2 text-ink-subtle" }), _jsx(Input, { "aria-label": t("admin.users.search"), placeholder: t("admin.users.searchPlaceholder"), value: search, onChange: (e) => { setSearch(e.target.value); setPage(1); }, className: "h-8 pl-8" })] }) }), users.isLoading ? (_jsx(Spinner, {})) : users.error ? (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: users.error.message }) })) : total === 0 ? (_jsx(EmptyState, { title: t("admin.users.empty") })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "forge-table w-full text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "text-left text-xs font-medium tracking-wide uppercase", children: [_jsx("th", { className: "px-4 py-2.5", children: t("admin.users.user") }), _jsx("th", { className: "px-4 py-2.5", children: t("admin.role") }), _jsx("th", { className: "px-4 py-2.5", children: t("admin.users.status") }), _jsx("th", { className: "px-4 py-2.5", children: t("admin.users.joined") }), _jsx("th", { className: "px-4 py-2.5 text-right", children: t("common.actions") })] }) }), _jsx("tbody", { children: users.data.users.map((u) => (_jsxs("tr", { children: [_jsxs("td", { className: "px-4 py-2.5", children: [_jsx("p", { className: "font-medium text-ink", children: u.name }), _jsx("p", { className: "text-xs text-ink-muted", children: u.email })] }), _jsx("td", { className: "px-4 py-2.5", children: _jsx(Select, { "aria-label": t("admin.roleFor", { name: u.name }), value: u.role ?? globalRoles[0], disabled: busy, onChange: (e) => act(() => auth.admin.setRole({ userId: u.id, role: e.target.value }), t("admin.users.roleChanged", { name: u.name, role: e.target.value })), className: "h-8 w-28 py-0 text-xs", children: globalRoles.map((r) => (_jsx("option", { value: r, children: r }, r))) }) }), _jsxs("td", { className: "px-4 py-2.5", children: [u.banned ? _jsx(Badge, { tone: 4, children: t("admin.users.banned") }) : u.emailVerified ? _jsx(Badge, { tone: 1, children: t("admin.users.active") }) : _jsx(Badge, { tone: 2, children: t("admin.users.unverified") }), u.twoFactorEnabled && _jsx("span", { className: "ml-1", children: _jsx(Badge, { tone: 3, children: "2FA" }) })] }), _jsx("td", { className: "px-4 py-2.5 text-ink-muted tabular-nums", children: new Date(u.createdAt).toLocaleDateString(locale) }), _jsx("td", { className: "px-4 py-2.5", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsx(Button, { size: "sm", variant: "ghost", onClick: async () => { const r = await auth.admin.impersonateUser({ userId: u.id }); if (!r.error)
59
61
  window.location.href = "/";
60
62
  else
61
- setNotice({ tone: "error", text: r.error.message }); }, children: t("admin.users.impersonate") }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => act(() => auth.admin.revokeUserSessions({ userId: u.id }), t("admin.users.signedOut", { name: u.name })), children: t("admin.users.signOut") }), u.banned ? (_jsx(Button, { size: "sm", variant: "ghost", onClick: () => act(() => auth.admin.unbanUser({ userId: u.id }), t("admin.users.unbanned", { name: u.name })), children: t("admin.users.unban") })) : (_jsx(Button, { size: "sm", variant: "ghost", onClick: () => setConfirm({ title: t("admin.users.banTitle", { name: u.name }), message: t("admin.users.banMessage"), run: () => auth.admin.banUser({ userId: u.id }), danger: true }), children: t("admin.users.ban") })), _jsx(Button, { size: "sm", variant: "danger-ghost", onClick: () => setConfirm({ title: t("admin.users.deleteTitle", { name: u.name }), message: t("admin.users.deleteMessage"), run: () => auth.admin.removeUser({ userId: u.id }), danger: true }), children: t("common.delete") })] }) })] }, u.id))) })] }) })), total > 0 && _jsx(Pagination, { page: page, pages: Math.max(1, Math.ceil(total / PAGE_SIZE)), total: total, onPage: setPage })] }), _jsx(CreateUserDialog, { open: creating, roles: globalRoles, onClose: () => setCreating(false), onCreated: (name) => { setCreating(false); setNotice({ tone: "success", text: t("admin.users.created", { name }) }); users.refetch(); } }), _jsx(ConfirmDialog, { open: !!confirm, title: confirm?.title ?? "", message: confirm?.message, danger: confirm?.danger, loading: busy, confirmLabel: t("common.confirm"), onConfirm: () => confirm && act(confirm.run, t("common.doneNotice")), onClose: () => setConfirm(null) })] }));
63
+ setNotice({ tone: "error", text: r.error.message }); }, children: t("admin.users.impersonate") }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => setSessionsOf({ id: u.id, name: u.name }), children: t("admin.users.sessions") }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => act(() => auth.admin.revokeUserSessions({ userId: u.id }), t("admin.users.signedOut", { name: u.name })), children: t("admin.users.signOut") }), u.banned ? (_jsx(Button, { size: "sm", variant: "ghost", onClick: () => act(() => auth.admin.unbanUser({ userId: u.id }), t("admin.users.unbanned", { name: u.name })), children: t("admin.users.unban") })) : (_jsx(Button, { size: "sm", variant: "ghost", onClick: () => setConfirm({ title: t("admin.users.banTitle", { name: u.name }), message: t("admin.users.banMessage"), run: () => auth.admin.banUser({ userId: u.id }), danger: true }), children: t("admin.users.ban") })), _jsx(Button, { size: "sm", variant: "danger-ghost", onClick: () => setConfirm({ title: t("admin.users.deleteTitle", { name: u.name }), message: t("admin.users.deleteMessage"), run: () => auth.admin.removeUser({ userId: u.id }), danger: true }), children: t("common.delete") })] }) })] }, u.id))) })] }) })), total > 0 && _jsx(Pagination, { page: page, pages: Math.max(1, Math.ceil(total / PAGE_SIZE)), total: total, onPage: setPage })] }), _jsx(UserSessionsDialog, { user: sessionsOf, onClose: () => setSessionsOf(null) }), _jsx(CreateUserDialog, { open: creating, roles: globalRoles, onClose: () => setCreating(false), onCreated: (name) => { setCreating(false); setNotice({ tone: "success", text: t("admin.users.created", { name }) }); users.refetch(); } }), _jsx(ConfirmDialog, { open: !!confirm, title: confirm?.title ?? "", message: confirm?.message, danger: confirm?.danger, loading: busy, confirmLabel: t("common.confirm"), onConfirm: () => confirm && act(confirm.run, t("common.doneNotice")), onClose: () => setConfirm(null) })] }));
64
+ }
65
+ /** A user's sessions (device, IP, dates) with per-session sign-out. */
66
+ function UserSessionsDialog({ user, onClose }) {
67
+ const { auth, locale, t } = useForge();
68
+ const [error, setError] = useState(null);
69
+ const sessions = useQuery({
70
+ queryKey: ["admin", "sessions", user?.id],
71
+ enabled: !!user,
72
+ queryFn: async () => {
73
+ const res = await auth.admin.listUserSessions({ userId: user.id });
74
+ if (res.error)
75
+ throw new Error(res.error.message);
76
+ return (res.data.sessions ?? []).filter((s) => new Date(s.expiresAt).getTime() > Date.now());
77
+ },
78
+ });
79
+ return (_jsxs(Dialog, { open: !!user, onClose: onClose, size: "lg", title: t("admin.users.sessionsOf", { name: user?.name ?? "" }), children: [error && _jsx("div", { className: "mb-3", children: _jsx(Alert, { children: error }) }), sessions.isLoading ? (_jsx(Spinner, {})) : sessions.error ? (_jsx(Alert, { children: sessions.error.message })) : !sessions.data?.length ? (_jsx(EmptyState, { title: t("admin.users.noSessions") })) : (_jsx("ul", { className: "divide-y divide-line", children: sessions.data.map((s) => (_jsxs("li", { className: "flex flex-wrap items-center justify-between gap-3 py-2.5", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-sm text-ink", children: s.userAgent ?? t("admin.users.unknownDevice") }), _jsxs("p", { className: "text-xs text-ink-muted", children: [s.ipAddress ?? "—", " \u00B7 ", t("admin.users.sessionDates", { created: new Date(s.createdAt).toLocaleString(locale), expires: new Date(s.expiresAt).toLocaleDateString(locale) }), s.impersonatedBy && ` · ${t("admin.users.impersonated")}`] })] }), _jsx(Button, { size: "sm", variant: "danger-ghost", onClick: async () => {
80
+ setError(null);
81
+ const r = await auth.admin.revokeUserSession({ sessionToken: s.token });
82
+ if (r?.error)
83
+ setError(r.error.message);
84
+ else
85
+ void sessions.refetch();
86
+ }, children: t("admin.users.revokeSession") })] }, s.id))) }))] }));
62
87
  }
63
88
  function CreateUserDialog({ open, roles, onClose, onCreated }) {
64
89
  const { auth, t } = useForge();
@@ -111,14 +136,20 @@ export function AdminMembersPage() {
111
136
  }
112
137
  export function AuditLogPage() {
113
138
  const { api, app, locale, t } = useForge();
139
+ const me = useMe();
140
+ const params = useSearchParams();
114
141
  const [page, setPage] = useState(1);
115
142
  const [entity, setEntity] = useState("");
143
+ // Global admins read every organization's history (or one, from Administration → Organizations).
144
+ const everyOrg = app.tenancy.enabled && (me.data?.user?.role ?? "").split(",").map((r) => r.trim()).includes("admin");
145
+ const organizationId = everyOrg ? (params.get("organizationId") ?? "") : "";
116
146
  const logs = useQuery({
117
- queryKey: ["forge", "audit", page, entity],
118
- queryFn: () => api("/audit-logs", { query: { page, entity } }),
147
+ queryKey: ["forge", "audit", page, entity, everyOrg, organizationId],
148
+ enabled: !me.isLoading,
149
+ queryFn: () => api("/audit-logs", { query: { page, entity, ...(everyOrg ? { scope: "all", organizationId } : {}) } }),
119
150
  });
120
151
  const audited = app.entities.filter((e) => e.features.audit);
121
- return (_jsxs("div", { children: [_jsx(PageHeader, { title: t("admin.audit.title"), description: t("admin.audit.description") }), _jsxs(Card, { children: [_jsx("div", { className: "border-b border-line p-3", children: _jsxs(Select, { "aria-label": t("admin.audit.entity"), value: entity, onChange: (e) => { setEntity(e.target.value); setPage(1); }, className: "h-8 w-auto py-0 text-xs", children: [_jsx("option", { value: "", children: t("admin.audit.allResources") }), audited.map((e) => (_jsx("option", { value: e.name, children: e.pluralLabel }, e.name)))] }) }), logs.isLoading ? (_jsx(Spinner, {})) : logs.error ? (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: logs.error.message }) })) : logs.data.data.length === 0 ? (_jsx(EmptyState, { title: t("admin.audit.empty"), description: audited.length ? undefined : t("admin.audit.emptyHint") })) : (_jsxs("table", { className: "forge-table w-full text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "text-left text-xs font-medium tracking-wide uppercase", children: [_jsx("th", { className: "px-4 py-2.5", children: t("admin.audit.when") }), _jsx("th", { className: "px-4 py-2.5", children: t("admin.audit.who") }), _jsx("th", { className: "px-4 py-2.5", children: t("admin.audit.action") }), _jsx("th", { className: "px-4 py-2.5", children: t("admin.audit.record") }), _jsx("th", { className: "px-4 py-2.5", children: t("admin.audit.changes") })] }) }), _jsx("tbody", { children: logs.data.data.map((l) => {
152
+ return (_jsxs("div", { children: [_jsx(PageHeader, { title: t("admin.audit.title"), description: everyOrg ? t(organizationId ? "admin.audit.descriptionOrganization" : "admin.audit.descriptionAll") : t("admin.audit.description") }), _jsxs(Card, { children: [_jsx("div", { className: "border-b border-line p-3", children: _jsxs(Select, { "aria-label": t("admin.audit.entity"), value: entity, onChange: (e) => { setEntity(e.target.value); setPage(1); }, className: "h-8 w-auto py-0 text-xs", children: [_jsx("option", { value: "", children: t("admin.audit.allResources") }), audited.map((e) => (_jsx("option", { value: e.name, children: e.pluralLabel }, e.name)))] }) }), logs.isLoading ? (_jsx(Spinner, {})) : logs.error ? (_jsx("div", { className: "p-3", children: _jsx(Alert, { children: logs.error.message }) })) : logs.data.data.length === 0 ? (_jsx(EmptyState, { title: t("admin.audit.empty"), description: audited.length ? undefined : t("admin.audit.emptyHint") })) : (_jsxs("table", { className: "forge-table w-full text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "text-left text-xs font-medium tracking-wide uppercase", children: [_jsx("th", { className: "px-4 py-2.5", children: t("admin.audit.when") }), _jsx("th", { className: "px-4 py-2.5", children: t("admin.audit.who") }), _jsx("th", { className: "px-4 py-2.5", children: t("admin.audit.action") }), _jsx("th", { className: "px-4 py-2.5", children: t("admin.audit.record") }), _jsx("th", { className: "px-4 py-2.5", children: t("admin.audit.changes") })] }) }), _jsx("tbody", { children: logs.data.data.map((l) => {
122
153
  const e = app.entities.find((x) => x.name === l.entity);
123
154
  return (_jsxs("tr", { className: "align-top", children: [_jsx("td", { className: "px-4 py-2.5 whitespace-nowrap text-ink-muted tabular-nums", children: new Date(l.createdAt).toLocaleString(locale) }), _jsx("td", { className: "px-4 py-2.5", children: l.user?.name ?? (l.apiKey ? t("admin.audit.apiKey", { name: l.apiKey.name }) : t("admin.audit.system")) }), _jsx("td", { className: "px-4 py-2.5", children: _jsx(Badge, { tone: l.action === "create" ? 1 : l.action.includes("delete") ? 4 : 0, children: l.action }) }), _jsx("td", { className: "px-4 py-2.5", children: e ? _jsxs("a", { className: "text-link hover:underline", href: `/${e.path}/${l.recordId}`, children: [e.label, " ", l.recordId.slice(0, 8)] }) : l.recordId }), _jsx("td", { className: "max-w-sm px-4 py-2.5 text-xs text-ink-muted", children: l.action === "update" && l.changes
124
155
  ? Object.entries(l.changes)