@eifi1/ui-kit 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -4
- package/dist/account/account-parts.d.ts +21 -0
- package/dist/account/account-parts.js +40 -0
- package/dist/account/account-parts.js.map +1 -0
- package/dist/account/data-export-setting.d.ts +73 -0
- package/dist/account/data-export-setting.js +113 -0
- package/dist/account/data-export-setting.js.map +1 -0
- package/dist/account/delete-account-setting.d.ts +91 -0
- package/dist/account/delete-account-setting.js +199 -0
- package/dist/account/delete-account-setting.js.map +1 -0
- package/dist/account/email-change-setting.d.ts +136 -0
- package/dist/account/email-change-setting.js +277 -0
- package/dist/account/email-change-setting.js.map +1 -0
- package/dist/account/sessions-setting.d.ts +110 -0
- package/dist/account/sessions-setting.js +146 -0
- package/dist/account/sessions-setting.js.map +1 -0
- package/dist/admin/admin-action-confirm.d.ts +91 -0
- package/dist/admin/admin-action-confirm.js +276 -0
- package/dist/admin/admin-action-confirm.js.map +1 -0
- package/dist/admin/admin-action-log.d.ts +105 -0
- package/dist/admin/admin-action-log.js +153 -0
- package/dist/admin/admin-action-log.js.map +1 -0
- package/dist/admin/admin-parts.d.ts +40 -0
- package/dist/admin/admin-parts.js +45 -0
- package/dist/admin/admin-parts.js.map +1 -0
- package/dist/admin/invitations-panel.d.ts +91 -0
- package/dist/admin/invitations-panel.js +289 -0
- package/dist/admin/invitations-panel.js.map +1 -0
- package/dist/admin/reviewer-scope-editor.d.ts +91 -0
- package/dist/admin/reviewer-scope-editor.js +134 -0
- package/dist/admin/reviewer-scope-editor.js.map +1 -0
- package/dist/admin/role-select.d.ts +91 -0
- package/dist/admin/role-select.js +128 -0
- package/dist/admin/role-select.js.map +1 -0
- package/dist/admin/transfer-ownership-dialog.d.ts +91 -0
- package/dist/admin/transfer-ownership-dialog.js +83 -0
- package/dist/admin/transfer-ownership-dialog.js.map +1 -0
- package/dist/admin/user-roster.d.ts +91 -0
- package/dist/admin/user-roster.js +285 -0
- package/dist/admin/user-roster.js.map +1 -0
- package/dist/auth/auth-errors.d.ts +85 -6
- package/dist/auth/auth-errors.js +62 -2
- package/dist/auth/auth-errors.js.map +1 -1
- package/dist/auth/forgot-password-form.d.ts +10 -3
- package/dist/auth/forgot-password-form.js +6 -3
- package/dist/auth/forgot-password-form.js.map +1 -1
- package/dist/auth/register-form.d.ts +17 -12
- package/dist/auth/register-form.js +6 -4
- package/dist/auth/register-form.js.map +1 -1
- package/dist/auth/reset-password-form.d.ts +10 -3
- package/dist/auth/reset-password-form.js +6 -3
- package/dist/auth/reset-password-form.js.map +1 -1
- package/dist/auth/sign-in-form.d.ts +61 -9
- package/dist/auth/sign-in-form.js +94 -13
- package/dist/auth/sign-in-form.js.map +1 -1
- package/dist/auth/verify-email.d.ts +7 -2
- package/dist/auth/verify-email.js +44 -8
- package/dist/auth/verify-email.js.map +1 -1
- package/dist/chart.d.ts +16 -11
- package/dist/components/account-chips.d.ts +47 -7
- package/dist/components/account-chips.js +19 -3
- package/dist/components/account-chips.js.map +1 -1
- package/dist/components/account-settings-labels.d.ts +12 -1
- package/dist/components/account-settings-labels.js +4 -1
- package/dist/components/account-settings-labels.js.map +1 -1
- package/dist/components/account-settings.d.ts +61 -6
- package/dist/components/account-settings.js +115 -15
- package/dist/components/account-settings.js.map +1 -1
- package/dist/components/amount-input.d.ts +17 -12
- package/dist/components/button-group.d.ts +17 -12
- package/dist/components/calculator.d.ts +17 -12
- package/dist/components/confirm-dialog.d.ts +17 -12
- package/dist/components/copy-button.d.ts +14 -9
- package/dist/components/danger-confirm.d.ts +17 -12
- package/dist/components/facing-pair.d.ts +17 -12
- package/dist/components/file-button.d.ts +17 -12
- package/dist/components/file-dropzone.d.ts +17 -12
- package/dist/components/form-actions.d.ts +17 -12
- package/dist/components/iban-input.d.ts +17 -12
- package/dist/components/language-select.d.ts +17 -12
- package/dist/components/legal-page.d.ts +17 -12
- package/dist/components/legal.d.ts +17 -12
- package/dist/components/money-field.d.ts +17 -12
- package/dist/components/number-field.d.ts +17 -12
- package/dist/components/number-input.d.ts +17 -12
- package/dist/components/numpad-sheet.d.ts +17 -12
- package/dist/components/phone-input.d.ts +17 -12
- package/dist/components/series-chart.d.ts +17 -12
- package/dist/components/settings-fields.d.ts +16 -11
- package/dist/components/share-card.d.ts +16 -11
- package/dist/components/text-link.d.ts +17 -12
- package/dist/components/time-input.d.ts +17 -12
- package/dist/components/ui.d.ts +13 -8
- package/dist/{register-form-V1OeNlMr.d.ts → delete-account-setting-CGVMckMw.d.ts} +6616 -5645
- package/dist/feedback/feedback-attachment.d.ts +17 -12
- package/dist/feedback/feedback-context.d.ts +17 -12
- package/dist/feedback/feedback-crash.d.ts +17 -12
- package/dist/feedback/feedback-dialog.d.ts +17 -12
- package/dist/feedback/feedback-inbox.d.ts +17 -12
- package/dist/feedback/feedback-labels.d.ts +17 -12
- package/dist/feedback/feedback-menu.d.ts +17 -12
- package/dist/feedback/feedback-record.d.ts +17 -12
- package/dist/feedback/feedback-row-detail.d.ts +17 -12
- package/dist/feedback/feedback-status-undo.d.ts +17 -12
- package/dist/feedback/feedback-submit.d.ts +17 -12
- package/dist/feedback/feedback-swipe.d.ts +17 -12
- package/dist/feedback/feedback-table.d.ts +14 -9
- package/dist/feedback/feedback-thread.d.ts +17 -12
- package/dist/feedback.d.ts +17 -12
- package/dist/hooks/use-anchored-panel.d.ts +6 -2
- package/dist/hooks/use-anchored-panel.js +2 -1
- package/dist/hooks/use-anchored-panel.js.map +1 -1
- package/dist/hooks/use-file-drop.d.ts +17 -12
- package/dist/i18n/defaults.d.ts +17 -12
- package/dist/i18n/defaults.js +22 -0
- package/dist/i18n/defaults.js.map +1 -1
- package/dist/i18n/german.d.ts +17 -12
- package/dist/i18n/german.js +215 -11
- package/dist/i18n/german.js.map +1 -1
- package/dist/i18n/kit-labels.d.ts +10 -5
- package/dist/i18n/kit-labels.js.map +1 -1
- package/dist/i18n/languages.d.ts +11 -6
- package/dist/i18n/locales/de-CH.d.ts +17 -12
- package/dist/i18n/locales/en.d.ts +17 -12
- package/dist/i18n/locales/es.d.ts +17 -12
- package/dist/i18n/locales/es.js +207 -9
- package/dist/i18n/locales/es.js.map +1 -1
- package/dist/i18n/locales/fr.d.ts +17 -12
- package/dist/i18n/locales/fr.js +209 -6
- package/dist/i18n/locales/fr.js.map +1 -1
- package/dist/i18n/locales/hu.d.ts +17 -12
- package/dist/i18n/locales/hu.js +207 -11
- package/dist/i18n/locales/hu.js.map +1 -1
- package/dist/i18n/locales/it.d.ts +17 -12
- package/dist/i18n/locales/it.js +209 -6
- package/dist/i18n/locales/it.js.map +1 -1
- package/dist/i18n/locales/zh.d.ts +17 -12
- package/dist/i18n/locales/zh.js +200 -6
- package/dist/i18n/locales/zh.js.map +1 -1
- package/dist/i18n/review.d.ts +17 -12
- package/dist/i18n/review.js +37 -1
- package/dist/i18n/review.js.map +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -1
- package/dist/rhf/fields.d.ts +17 -12
- package/dist/rhf/form.d.ts +17 -12
- package/dist/rhf.d.ts +17 -12
- package/dist/shell/app-shell.d.ts +17 -12
- package/dist/shell/top-bar-brand.d.ts +17 -12
- package/dist/shell.d.ts +11 -6
- package/dist/wizard/stepper-nav.d.ts +17 -12
- package/dist/wizard.d.ts +17 -12
- package/package.json +1 -1
- package/src/account/account-parts.ts +67 -0
- package/src/account/data-export-setting.tsx +218 -0
- package/src/account/delete-account-setting.tsx +362 -0
- package/src/account/email-change-setting.tsx +457 -0
- package/src/account/sessions-setting.tsx +283 -0
- package/src/admin/admin-action-confirm.tsx +495 -0
- package/src/admin/admin-action-log.tsx +297 -0
- package/src/admin/admin-parts.ts +82 -0
- package/src/admin/invitations-panel.tsx +516 -0
- package/src/admin/reviewer-scope-editor.tsx +234 -0
- package/src/admin/role-select.tsx +286 -0
- package/src/admin/transfer-ownership-dialog.tsx +172 -0
- package/src/admin/user-roster.tsx +573 -0
- package/src/auth/auth-errors.ts +165 -8
- package/src/auth/forgot-password-form.tsx +21 -5
- package/src/auth/register-form.tsx +24 -6
- package/src/auth/reset-password-form.tsx +19 -5
- package/src/auth/sign-in-form.tsx +207 -35
- package/src/auth/verify-email.tsx +59 -8
- package/src/components/account-chips.tsx +64 -7
- package/src/components/account-settings-labels.ts +15 -1
- package/src/components/account-settings.tsx +199 -23
- package/src/hooks/use-anchored-panel.ts +14 -3
- package/src/i18n/defaults.ts +22 -0
- package/src/i18n/german.ts +241 -5
- package/src/i18n/kit-labels.tsx +33 -0
- package/src/i18n/locales/es.ts +230 -3
- package/src/i18n/locales/fr.ts +232 -0
- package/src/i18n/locales/hu.ts +224 -5
- package/src/i18n/locales/it.ts +230 -0
- package/src/i18n/locales/zh.ts +205 -0
- package/src/i18n/review.ts +36 -0
- package/src/index.ts +93 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { DateInput } from '../lib/format.js';
|
|
4
|
+
import { AdminPerson } from './admin-parts.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The admin actions, as the audit table records them (docs/user-admin-harmonization.md
|
|
8
|
+
* §2.3, §4.3): `GET /admin/actions?limit=&offset=&target=`, newest first, on the admin
|
|
9
|
+
* page and filtered to one account from its row.
|
|
10
|
+
*
|
|
11
|
+
* Every app writes the same small `admin_actions` row in the transaction of the action
|
|
12
|
+
* (keksdose and Kurvenschmiede had log lines; kastlan has an HTTP log with no named
|
|
13
|
+
* action), so the vocabulary is shared — {@link AdminActionKind} — and an app's own
|
|
14
|
+
* actions join it through `labels.actions`.
|
|
15
|
+
*
|
|
16
|
+
* What a row says, and what it never does: who, what, to whom, when, and a summary of the
|
|
17
|
+
* `detail` the APP writes (ids, roles, counts — never content, §4.3). Three kinds of
|
|
18
|
+
* actor: an admin; **the user themselves** (`deletion_request`: the actor is the target);
|
|
19
|
+
* and **nobody** (the scheduled erasure, the hand-over at erasure, §2.7), said as
|
|
20
|
+
* "automatically". After an erasure the target's name and address are scrubbed and the
|
|
21
|
+
* row stays: it reads "an erased account".
|
|
22
|
+
*
|
|
23
|
+
* It never fetches: the page of rows and the paging are the app's.
|
|
24
|
+
*/
|
|
25
|
+
/** The shared vocabulary of `admin_actions.action` (§4.3). */
|
|
26
|
+
type AdminActionKind = "deactivate" | "reactivate" | "role" | "membership_remove" | "password_change_require" | "password_change_withdraw" | "mail_verification" | "mail_reset" | "reviewer" | "invite" | "invite_resend" | "invite_revoke" | "transfer" | "deletion_request" | "deletion_cancel" | "erase";
|
|
27
|
+
/** Every {@link AdminActionKind}, in the contract's order. */
|
|
28
|
+
declare const ADMIN_ACTION_KINDS: readonly AdminActionKind[];
|
|
29
|
+
/** One logged action — the app's `admin_actions` row with the people joined in. */
|
|
30
|
+
interface AdminActionLogEntry {
|
|
31
|
+
id: string | number;
|
|
32
|
+
/** When, as the row's `at`. */
|
|
33
|
+
at: DateInput;
|
|
34
|
+
/**
|
|
35
|
+
* Who did it: a person; `"self"` — the user themselves (a deletion request); or `null`
|
|
36
|
+
* — nobody, the scheduled job. A person whose `id` (or address) is the target's is
|
|
37
|
+
* read as `"self"` too.
|
|
38
|
+
*/
|
|
39
|
+
actor: AdminPerson | "self" | null;
|
|
40
|
+
/** A {@link AdminActionKind}, or one of the app's own (named in `labels.actions`). */
|
|
41
|
+
action: AdminActionKind | (string & {});
|
|
42
|
+
/** Whom it was done to. `null`, or a person with nothing left in it: an erased
|
|
43
|
+
* account. An invitation's target is its address (`target_email`). */
|
|
44
|
+
target?: AdminPerson | null;
|
|
45
|
+
/** The `detail` in the app's words — "Member → Admin", "12 items to Ben Example". */
|
|
46
|
+
detail?: ReactNode;
|
|
47
|
+
}
|
|
48
|
+
/** The `adminActionLog` namespace. */
|
|
49
|
+
interface AdminActionLogLabels {
|
|
50
|
+
/** The list's accessible name. */
|
|
51
|
+
title: string;
|
|
52
|
+
empty: string;
|
|
53
|
+
loading: string;
|
|
54
|
+
/** "by Ada Example". */
|
|
55
|
+
by: (actor: string) => string;
|
|
56
|
+
/** The user acted on their own account. */
|
|
57
|
+
bySelf: string;
|
|
58
|
+
/** No actor: a scheduled job. */
|
|
59
|
+
automatic: string;
|
|
60
|
+
/** A target scrubbed by an erasure. */
|
|
61
|
+
erased: string;
|
|
62
|
+
/** The filter chip: only one account's actions are shown. */
|
|
63
|
+
filteredTo: (target: string) => string;
|
|
64
|
+
/** The chip's ×. */
|
|
65
|
+
showAll: string;
|
|
66
|
+
/** A target name as a filter button — "Show only the actions on Ada Example". */
|
|
67
|
+
filterBy: (target: string) => string;
|
|
68
|
+
/** What each action is called, past tense, keyed by the vocabulary. An app adds its
|
|
69
|
+
* own here (`labels={{ actions: { plan: "Plan changed" } }}`). */
|
|
70
|
+
actions: {
|
|
71
|
+
[K in AdminActionKind]?: string;
|
|
72
|
+
} & {
|
|
73
|
+
[action: string]: string | undefined;
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
declare const DEFAULT_ADMIN_ACTION_LOG_LABELS: AdminActionLogLabels;
|
|
77
|
+
/** The paging of `GET /admin/actions` — DataTable's `ServerPagination`, which the
|
|
78
|
+
* footer is. */
|
|
79
|
+
interface AdminActionLogPaging {
|
|
80
|
+
/** 0-based. */
|
|
81
|
+
page: number;
|
|
82
|
+
pageSize: number;
|
|
83
|
+
total: number;
|
|
84
|
+
onPageChange: (page: number) => void;
|
|
85
|
+
onPageSizeChange?: (pageSize: number) => void;
|
|
86
|
+
}
|
|
87
|
+
interface AdminActionLogProps {
|
|
88
|
+
/** One page, newest first, as the server sends it. */
|
|
89
|
+
entries: readonly AdminActionLogEntry[];
|
|
90
|
+
/** Left out: no footer (a short list on a row). */
|
|
91
|
+
paging?: AdminActionLogPaging;
|
|
92
|
+
/** The page is being fetched. */
|
|
93
|
+
loading?: boolean;
|
|
94
|
+
/** The filter in force: only this account's actions are shown (`?target=`). With
|
|
95
|
+
* `onTargetFilter`, a chip says so and its × clears it. */
|
|
96
|
+
target?: AdminPerson | null;
|
|
97
|
+
/** Filter by an account — or clear it with `null`. Given, every target's name is a
|
|
98
|
+
* button that narrows the list to it. Left out, the list is not filterable here. */
|
|
99
|
+
onTargetFilter?: (target: AdminPerson | null) => void;
|
|
100
|
+
className?: string;
|
|
101
|
+
labels?: Partial<AdminActionLogLabels>;
|
|
102
|
+
}
|
|
103
|
+
declare function AdminActionLog({ entries, paging, loading, target, onTargetFilter, className, labels: labelsProp, }: AdminActionLogProps): react.JSX.Element;
|
|
104
|
+
|
|
105
|
+
export { ADMIN_ACTION_KINDS, type AdminActionKind, AdminActionLog, type AdminActionLogEntry, type AdminActionLogLabels, type AdminActionLogPaging, type AdminActionLogProps, DEFAULT_ADMIN_ACTION_LOG_LABELS };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
import { useKitLabels } from "../i18n/kit-labels.js";
|
|
5
|
+
import { DateMark } from "../components/account-chips.js";
|
|
6
|
+
import { Chip } from "../components/chip.js";
|
|
7
|
+
import { Pagination } from "../components/data-table-pagination.js";
|
|
8
|
+
import { Caption } from "../components/text.js";
|
|
9
|
+
import { Button, Spinner } from "../components/ui.js";
|
|
10
|
+
import { usePersonLabel } from "./admin-parts.js";
|
|
11
|
+
const ADMIN_ACTION_KINDS = [
|
|
12
|
+
"deactivate",
|
|
13
|
+
"reactivate",
|
|
14
|
+
"role",
|
|
15
|
+
"membership_remove",
|
|
16
|
+
"password_change_require",
|
|
17
|
+
"password_change_withdraw",
|
|
18
|
+
"mail_verification",
|
|
19
|
+
"mail_reset",
|
|
20
|
+
"reviewer",
|
|
21
|
+
"invite",
|
|
22
|
+
"invite_resend",
|
|
23
|
+
"invite_revoke",
|
|
24
|
+
"transfer",
|
|
25
|
+
"deletion_request",
|
|
26
|
+
"deletion_cancel",
|
|
27
|
+
"erase"
|
|
28
|
+
];
|
|
29
|
+
const DEFAULT_ADMIN_ACTION_LOG_LABELS = {
|
|
30
|
+
title: "Admin actions",
|
|
31
|
+
empty: "No admin actions yet.",
|
|
32
|
+
loading: "Loading\u2026",
|
|
33
|
+
by: (actor) => `by ${actor}`,
|
|
34
|
+
bySelf: "by the user themselves",
|
|
35
|
+
automatic: "automatically",
|
|
36
|
+
erased: "an erased account",
|
|
37
|
+
filteredTo: (target) => `Only ${target}`,
|
|
38
|
+
showAll: "Show all actions",
|
|
39
|
+
filterBy: (target) => `Show only the actions on ${target}`,
|
|
40
|
+
actions: {
|
|
41
|
+
deactivate: "Deactivated",
|
|
42
|
+
reactivate: "Reactivated",
|
|
43
|
+
role: "Role changed",
|
|
44
|
+
membership_remove: "Removed from the company",
|
|
45
|
+
password_change_require: "New password required",
|
|
46
|
+
password_change_withdraw: "Password requirement withdrawn",
|
|
47
|
+
mail_verification: "Verification mail sent",
|
|
48
|
+
mail_reset: "Password reset mail sent",
|
|
49
|
+
reviewer: "Reviewer scope changed",
|
|
50
|
+
invite: "Invited",
|
|
51
|
+
invite_resend: "Invitation resent",
|
|
52
|
+
invite_revoke: "Invitation revoked",
|
|
53
|
+
transfer: "Work transferred",
|
|
54
|
+
deletion_request: "Deletion requested",
|
|
55
|
+
deletion_cancel: "Deletion cancelled",
|
|
56
|
+
erase: "Account erased"
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
function scrubbed(person) {
|
|
60
|
+
return !person || ![person.first, person.last, person.name, person.email].some((part) => (part ?? "").trim());
|
|
61
|
+
}
|
|
62
|
+
function samePerson(a, b) {
|
|
63
|
+
if (!b) return false;
|
|
64
|
+
if (a.id !== void 0 && a.id !== null && b.id !== void 0 && b.id !== null) return a.id === b.id;
|
|
65
|
+
const ea = (a.email ?? "").trim().toLowerCase();
|
|
66
|
+
return ea !== "" && ea === (b.email ?? "").trim().toLowerCase();
|
|
67
|
+
}
|
|
68
|
+
function AdminActionLog({
|
|
69
|
+
entries,
|
|
70
|
+
paging,
|
|
71
|
+
loading = false,
|
|
72
|
+
target,
|
|
73
|
+
onTargetFilter,
|
|
74
|
+
className,
|
|
75
|
+
labels: labelsProp
|
|
76
|
+
}) {
|
|
77
|
+
const labels = useKitLabels("adminActionLog", DEFAULT_ADMIN_ACTION_LOG_LABELS, labelsProp);
|
|
78
|
+
const nameOf = usePersonLabel();
|
|
79
|
+
const filtered = target !== void 0 && target !== null && !scrubbed(target);
|
|
80
|
+
const actorText = (entry) => {
|
|
81
|
+
const { actor } = entry;
|
|
82
|
+
if (actor === null) return labels.automatic;
|
|
83
|
+
if (actor === "self" || samePerson(actor, entry.target)) return labels.bySelf;
|
|
84
|
+
return labels.by(nameOf(actor) || labels.erased);
|
|
85
|
+
};
|
|
86
|
+
const targetNode = (entry) => {
|
|
87
|
+
if (scrubbed(entry.target)) return /* @__PURE__ */ jsx("span", { className: "italic text-[var(--text-muted)]", children: labels.erased });
|
|
88
|
+
const person = entry.target;
|
|
89
|
+
const name = nameOf(person);
|
|
90
|
+
const address = (person.email ?? "").trim();
|
|
91
|
+
const text = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
92
|
+
/* @__PURE__ */ jsx("span", { "data-private": true, className: "font-medium text-[var(--text-primary)]", children: name }),
|
|
93
|
+
address && address !== name && /* @__PURE__ */ jsx("span", { "data-private": true, className: "ms-1 text-xs text-[var(--text-muted)]", children: address })
|
|
94
|
+
] });
|
|
95
|
+
if (!onTargetFilter || filtered && samePerson(person, target)) return text;
|
|
96
|
+
return /* @__PURE__ */ jsx(
|
|
97
|
+
Button,
|
|
98
|
+
{
|
|
99
|
+
type: "button",
|
|
100
|
+
variant: "link",
|
|
101
|
+
size: "sm",
|
|
102
|
+
className: "h-auto min-h-0 p-0 text-start",
|
|
103
|
+
"aria-label": labels.filterBy(name),
|
|
104
|
+
onClick: () => onTargetFilter(person),
|
|
105
|
+
children: text
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
};
|
|
109
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), children: [
|
|
110
|
+
filtered && onTargetFilter && /* @__PURE__ */ jsx(Chip, { size: "sm", tone: "brand", onRemove: () => onTargetFilter(null), removeLabel: labels.showAll, children: /* @__PURE__ */ jsx("span", { "data-private": true, children: labels.filteredTo(nameOf(target)) }) }),
|
|
111
|
+
loading && entries.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex justify-center py-4", children: /* @__PURE__ */ jsx(Spinner, { label: labels.loading }) }) : entries.length === 0 ? /* @__PURE__ */ jsx(Caption, { children: labels.empty }) : /* @__PURE__ */ jsx(
|
|
112
|
+
"ol",
|
|
113
|
+
{
|
|
114
|
+
"aria-label": labels.title,
|
|
115
|
+
"aria-busy": loading || void 0,
|
|
116
|
+
className: cn("divide-y divide-[var(--border)]", loading && "opacity-60"),
|
|
117
|
+
children: entries.map((entry) => /* @__PURE__ */ jsxs("li", { "data-action": entry.action, className: "space-y-0.5 py-2 first:pt-0 last:pb-0", children: [
|
|
118
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-x-3 gap-y-0.5", children: [
|
|
119
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5 text-sm", children: [
|
|
120
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-[var(--text-primary)]", children: labels.actions[entry.action] ?? entry.action }),
|
|
121
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 break-words", children: targetNode(entry) })
|
|
122
|
+
] }),
|
|
123
|
+
/* @__PURE__ */ jsx(DateMark, { value: entry.at, display: "relative", className: "text-xs text-[var(--text-muted)]" })
|
|
124
|
+
] }),
|
|
125
|
+
/* @__PURE__ */ jsxs("div", { className: "text-xs text-[var(--text-muted)]", children: [
|
|
126
|
+
/* @__PURE__ */ jsx("span", { "data-private": entry.actor !== null && entry.actor !== "self" ? true : void 0, children: actorText(entry) }),
|
|
127
|
+
entry.detail !== void 0 && entry.detail !== null && entry.detail !== "" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
128
|
+
" \xB7 ",
|
|
129
|
+
/* @__PURE__ */ jsx("span", { className: "text-[var(--text-secondary)]", children: entry.detail })
|
|
130
|
+
] })
|
|
131
|
+
] })
|
|
132
|
+
] }, entry.id))
|
|
133
|
+
}
|
|
134
|
+
),
|
|
135
|
+
paging && paging.total > 0 && /* @__PURE__ */ jsx(
|
|
136
|
+
Pagination,
|
|
137
|
+
{
|
|
138
|
+
page: paging.page,
|
|
139
|
+
pageSize: paging.pageSize,
|
|
140
|
+
total: paging.total,
|
|
141
|
+
totalPages: Math.max(1, Math.ceil(paging.total / Math.max(1, paging.pageSize))),
|
|
142
|
+
onPage: paging.onPageChange,
|
|
143
|
+
onPageSize: paging.onPageSizeChange
|
|
144
|
+
}
|
|
145
|
+
)
|
|
146
|
+
] });
|
|
147
|
+
}
|
|
148
|
+
export {
|
|
149
|
+
ADMIN_ACTION_KINDS,
|
|
150
|
+
AdminActionLog,
|
|
151
|
+
DEFAULT_ADMIN_ACTION_LOG_LABELS
|
|
152
|
+
};
|
|
153
|
+
//# sourceMappingURL=admin-action-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/admin/admin-action-log.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport type { DateInput } from \"../lib/format\";\nimport { useKitLabels } from \"../i18n/kit-labels\";\nimport { DateMark } from \"../components/account-chips\";\nimport { Chip } from \"../components/chip\";\nimport { Pagination } from \"../components/data-table-pagination\";\nimport { Caption } from \"../components/text\";\nimport { Button, Spinner } from \"../components/ui\";\nimport { usePersonLabel } from \"./admin-parts\";\nimport type { AdminPerson } from \"./admin-parts\";\n\n/**\n * The admin actions, as the audit table records them (docs/user-admin-harmonization.md\n * §2.3, §4.3): `GET /admin/actions?limit=&offset=&target=`, newest first, on the admin\n * page and filtered to one account from its row.\n *\n * Every app writes the same small `admin_actions` row in the transaction of the action\n * (keksdose and Kurvenschmiede had log lines; kastlan has an HTTP log with no named\n * action), so the vocabulary is shared — {@link AdminActionKind} — and an app's own\n * actions join it through `labels.actions`.\n *\n * What a row says, and what it never does: who, what, to whom, when, and a summary of the\n * `detail` the APP writes (ids, roles, counts — never content, §4.3). Three kinds of\n * actor: an admin; **the user themselves** (`deletion_request`: the actor is the target);\n * and **nobody** (the scheduled erasure, the hand-over at erasure, §2.7), said as\n * \"automatically\". After an erasure the target's name and address are scrubbed and the\n * row stays: it reads \"an erased account\".\n *\n * It never fetches: the page of rows and the paging are the app's.\n */\n\n/** The shared vocabulary of `admin_actions.action` (§4.3). */\nexport type AdminActionKind =\n | \"deactivate\"\n | \"reactivate\"\n | \"role\"\n | \"membership_remove\"\n | \"password_change_require\"\n | \"password_change_withdraw\"\n | \"mail_verification\"\n | \"mail_reset\"\n | \"reviewer\"\n | \"invite\"\n | \"invite_resend\"\n | \"invite_revoke\"\n | \"transfer\"\n | \"deletion_request\"\n | \"deletion_cancel\"\n | \"erase\";\n\n/** Every {@link AdminActionKind}, in the contract's order. */\nexport const ADMIN_ACTION_KINDS: readonly AdminActionKind[] = [\n \"deactivate\",\n \"reactivate\",\n \"role\",\n \"membership_remove\",\n \"password_change_require\",\n \"password_change_withdraw\",\n \"mail_verification\",\n \"mail_reset\",\n \"reviewer\",\n \"invite\",\n \"invite_resend\",\n \"invite_revoke\",\n \"transfer\",\n \"deletion_request\",\n \"deletion_cancel\",\n \"erase\",\n];\n\n/** One logged action — the app's `admin_actions` row with the people joined in. */\nexport interface AdminActionLogEntry {\n id: string | number;\n /** When, as the row's `at`. */\n at: DateInput;\n /**\n * Who did it: a person; `\"self\"` — the user themselves (a deletion request); or `null`\n * — nobody, the scheduled job. A person whose `id` (or address) is the target's is\n * read as `\"self\"` too.\n */\n actor: AdminPerson | \"self\" | null;\n /** A {@link AdminActionKind}, or one of the app's own (named in `labels.actions`). */\n action: AdminActionKind | (string & {});\n /** Whom it was done to. `null`, or a person with nothing left in it: an erased\n * account. An invitation's target is its address (`target_email`). */\n target?: AdminPerson | null;\n /** The `detail` in the app's words — \"Member → Admin\", \"12 items to Ben Example\". */\n detail?: ReactNode;\n}\n\n/** The `adminActionLog` namespace. */\nexport interface AdminActionLogLabels {\n /** The list's accessible name. */\n title: string;\n empty: string;\n loading: string;\n /** \"by Ada Example\". */\n by: (actor: string) => string;\n /** The user acted on their own account. */\n bySelf: string;\n /** No actor: a scheduled job. */\n automatic: string;\n /** A target scrubbed by an erasure. */\n erased: string;\n /** The filter chip: only one account's actions are shown. */\n filteredTo: (target: string) => string;\n /** The chip's ×. */\n showAll: string;\n /** A target name as a filter button — \"Show only the actions on Ada Example\". */\n filterBy: (target: string) => string;\n /** What each action is called, past tense, keyed by the vocabulary. An app adds its\n * own here (`labels={{ actions: { plan: \"Plan changed\" } }}`). */\n actions: { [K in AdminActionKind]?: string } & { [action: string]: string | undefined };\n}\n\nexport const DEFAULT_ADMIN_ACTION_LOG_LABELS: AdminActionLogLabels = {\n title: \"Admin actions\",\n empty: \"No admin actions yet.\",\n loading: \"Loading…\",\n by: (actor) => `by ${actor}`,\n bySelf: \"by the user themselves\",\n automatic: \"automatically\",\n erased: \"an erased account\",\n filteredTo: (target) => `Only ${target}`,\n showAll: \"Show all actions\",\n filterBy: (target) => `Show only the actions on ${target}`,\n actions: {\n deactivate: \"Deactivated\",\n reactivate: \"Reactivated\",\n role: \"Role changed\",\n membership_remove: \"Removed from the company\",\n password_change_require: \"New password required\",\n password_change_withdraw: \"Password requirement withdrawn\",\n mail_verification: \"Verification mail sent\",\n mail_reset: \"Password reset mail sent\",\n reviewer: \"Reviewer scope changed\",\n invite: \"Invited\",\n invite_resend: \"Invitation resent\",\n invite_revoke: \"Invitation revoked\",\n transfer: \"Work transferred\",\n deletion_request: \"Deletion requested\",\n deletion_cancel: \"Deletion cancelled\",\n erase: \"Account erased\",\n },\n};\n\n/** The paging of `GET /admin/actions` — DataTable's `ServerPagination`, which the\n * footer is. */\nexport interface AdminActionLogPaging {\n /** 0-based. */\n page: number;\n pageSize: number;\n total: number;\n onPageChange: (page: number) => void;\n onPageSizeChange?: (pageSize: number) => void;\n}\n\nexport interface AdminActionLogProps {\n /** One page, newest first, as the server sends it. */\n entries: readonly AdminActionLogEntry[];\n /** Left out: no footer (a short list on a row). */\n paging?: AdminActionLogPaging;\n /** The page is being fetched. */\n loading?: boolean;\n /** The filter in force: only this account's actions are shown (`?target=`). With\n * `onTargetFilter`, a chip says so and its × clears it. */\n target?: AdminPerson | null;\n /** Filter by an account — or clear it with `null`. Given, every target's name is a\n * button that narrows the list to it. Left out, the list is not filterable here. */\n onTargetFilter?: (target: AdminPerson | null) => void;\n className?: string;\n labels?: Partial<AdminActionLogLabels>;\n}\n\n/** Whether a person has nothing left that names them — an erased target. */\nfunction scrubbed(person: AdminPerson | null | undefined): boolean {\n return !person || ![person.first, person.last, person.name, person.email].some((part) => (part ?? \"\").trim());\n}\n\nfunction samePerson(a: AdminPerson, b: AdminPerson | null | undefined): boolean {\n if (!b) return false;\n if (a.id !== undefined && a.id !== null && b.id !== undefined && b.id !== null) return a.id === b.id;\n const ea = (a.email ?? \"\").trim().toLowerCase();\n return ea !== \"\" && ea === (b.email ?? \"\").trim().toLowerCase();\n}\n\nexport function AdminActionLog({\n entries,\n paging,\n loading = false,\n target,\n onTargetFilter,\n className,\n labels: labelsProp,\n}: AdminActionLogProps) {\n const labels = useKitLabels(\"adminActionLog\", DEFAULT_ADMIN_ACTION_LOG_LABELS, labelsProp);\n const nameOf = usePersonLabel();\n const filtered = target !== undefined && target !== null && !scrubbed(target);\n\n const actorText = (entry: AdminActionLogEntry): string => {\n const { actor } = entry;\n if (actor === null) return labels.automatic;\n if (actor === \"self\" || samePerson(actor, entry.target)) return labels.bySelf;\n return labels.by(nameOf(actor) || labels.erased);\n };\n\n const targetNode = (entry: AdminActionLogEntry): ReactNode => {\n if (scrubbed(entry.target)) return <span className=\"italic text-[var(--text-muted)]\">{labels.erased}</span>;\n const person = entry.target!;\n const name = nameOf(person);\n const address = (person.email ?? \"\").trim();\n const text = (\n <>\n <span data-private className=\"font-medium text-[var(--text-primary)]\">\n {name}\n </span>\n {address && address !== name && (\n <span data-private className=\"ms-1 text-xs text-[var(--text-muted)]\">\n {address}\n </span>\n )}\n </>\n );\n if (!onTargetFilter || (filtered && samePerson(person, target))) return text;\n return (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n className=\"h-auto min-h-0 p-0 text-start\"\n aria-label={labels.filterBy(name)}\n onClick={() => onTargetFilter(person)}\n >\n {text}\n </Button>\n );\n };\n\n return (\n <div className={cn(\"space-y-3\", className)}>\n {filtered && onTargetFilter && (\n <Chip size=\"sm\" tone=\"brand\" onRemove={() => onTargetFilter(null)} removeLabel={labels.showAll}>\n <span data-private>{labels.filteredTo(nameOf(target))}</span>\n </Chip>\n )}\n {loading && entries.length === 0 ? (\n <div className=\"flex justify-center py-4\">\n <Spinner label={labels.loading} />\n </div>\n ) : entries.length === 0 ? (\n <Caption>{labels.empty}</Caption>\n ) : (\n <ol\n aria-label={labels.title}\n aria-busy={loading || undefined}\n className={cn(\"divide-y divide-[var(--border)]\", loading && \"opacity-60\")}\n >\n {entries.map((entry) => (\n <li key={entry.id} data-action={entry.action} className=\"space-y-0.5 py-2 first:pt-0 last:pb-0\">\n <div className=\"flex flex-wrap items-baseline justify-between gap-x-3 gap-y-0.5\">\n <div className=\"flex min-w-0 flex-wrap items-baseline gap-x-2 gap-y-0.5 text-sm\">\n <span className=\"font-medium text-[var(--text-primary)]\">\n {labels.actions[entry.action] ?? entry.action}\n </span>\n <span className=\"min-w-0 break-words\">{targetNode(entry)}</span>\n </div>\n <DateMark value={entry.at} display=\"relative\" className=\"text-xs text-[var(--text-muted)]\" />\n </div>\n <div className=\"text-xs text-[var(--text-muted)]\">\n <span data-private={entry.actor !== null && entry.actor !== \"self\" ? true : undefined}>\n {actorText(entry)}\n </span>\n {entry.detail !== undefined && entry.detail !== null && entry.detail !== \"\" && (\n <>\n {\" · \"}\n <span className=\"text-[var(--text-secondary)]\">{entry.detail}</span>\n </>\n )}\n </div>\n </li>\n ))}\n </ol>\n )}\n {paging && paging.total > 0 && (\n <Pagination\n page={paging.page}\n pageSize={paging.pageSize}\n total={paging.total}\n totalPages={Math.max(1, Math.ceil(paging.total / Math.max(1, paging.pageSize)))}\n onPage={paging.onPageChange}\n onPageSize={paging.onPageSizeChange}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { DateInput } from '../lib/format.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* What the admin parts share and do not export (0.30.0). Internal to `src/admin`.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
/** A callback that may answer a promise: resolve = done, reject = stay and say why. */
|
|
10
|
+
type MaybePromise<T = unknown> = void | Promise<T>;
|
|
11
|
+
/** A person as the admin parts show one: the two name parts of the auth contract
|
|
12
|
+
* (§3.2), an older whole `name` as the fallback, and the address. */
|
|
13
|
+
interface AdminPerson {
|
|
14
|
+
id?: string | number | null;
|
|
15
|
+
first?: string | null;
|
|
16
|
+
last?: string | null;
|
|
17
|
+
/** A whole name, for an API that still sends `display_name` only. */
|
|
18
|
+
name?: string | null;
|
|
19
|
+
email?: string | null;
|
|
20
|
+
}
|
|
21
|
+
/** A label that is really there — `null`, `false` and `""` are what `cond && text`
|
|
22
|
+
* evaluates to on the path where there is none. */
|
|
23
|
+
declare function hasMessage(node: ReactNode): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The name to say for a person, in the READER's order (auth §3.2,
|
|
26
|
+
* {@link formatPersonName}), else the whole `name`, else the address, else `""`.
|
|
27
|
+
*/
|
|
28
|
+
declare function personLabel(person: AdminPerson | null | undefined, locale: string | undefined): string;
|
|
29
|
+
/** {@link personLabel} in the provider's locale. */
|
|
30
|
+
declare function usePersonLabel(localeProp?: string): (person: AdminPerson | null | undefined) => string;
|
|
31
|
+
/**
|
|
32
|
+
* A day as words in a sentence ("6 Nov 2026"): the provider's `formatDate` when the app
|
|
33
|
+
* set one (keksdose K12), asked for no weekday — a date in a sentence carries none — else
|
|
34
|
+
* `Intl`'s medium date. `""` for no date.
|
|
35
|
+
*/
|
|
36
|
+
declare function useDayText(localeProp?: string): (value: DateInput) => string;
|
|
37
|
+
/** Call a callback that may throw or answer a promise, as a promise. */
|
|
38
|
+
declare function settle<T>(call: () => MaybePromise<T>): Promise<T | void>;
|
|
39
|
+
|
|
40
|
+
export { type AdminPerson, type MaybePromise, hasMessage, personLabel, settle, useDayText, usePersonLabel };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
import { useKitDateFormatter, useKitLocale } from "../i18n/kit-labels.js";
|
|
4
|
+
import { formatDate, toDate } from "../lib/format.js";
|
|
5
|
+
import { toLocalIso } from "../lib/dates.js";
|
|
6
|
+
import { formatPersonName } from "../lib/person-name.js";
|
|
7
|
+
function hasMessage(node) {
|
|
8
|
+
return node !== void 0 && node !== null && node !== false && node !== "";
|
|
9
|
+
}
|
|
10
|
+
function personLabel(person, locale) {
|
|
11
|
+
if (!person) return "";
|
|
12
|
+
return formatPersonName({ first: person.first, last: person.last }, locale) || (person.name ?? "").trim() || (person.email ?? "").trim();
|
|
13
|
+
}
|
|
14
|
+
function usePersonLabel(localeProp) {
|
|
15
|
+
const locale = useKitLocale(localeProp);
|
|
16
|
+
return useCallback((person) => personLabel(person, locale), [locale]);
|
|
17
|
+
}
|
|
18
|
+
function useDayText(localeProp) {
|
|
19
|
+
const locale = useKitLocale(localeProp);
|
|
20
|
+
const fromProvider = useKitDateFormatter();
|
|
21
|
+
return useCallback(
|
|
22
|
+
(value) => {
|
|
23
|
+
const date = toDate(value);
|
|
24
|
+
if (!date) return "";
|
|
25
|
+
const own = fromProvider ? fromProvider(toLocalIso(date), { unit: "day", source: "dateMark", locale, weekday: false }) : "";
|
|
26
|
+
return own || formatDate(date, "medium", { locale });
|
|
27
|
+
},
|
|
28
|
+
[fromProvider, locale]
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
function settle(call) {
|
|
32
|
+
try {
|
|
33
|
+
return Promise.resolve(call());
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return Promise.reject(error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
hasMessage,
|
|
40
|
+
personLabel,
|
|
41
|
+
settle,
|
|
42
|
+
useDayText,
|
|
43
|
+
usePersonLabel
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=admin-parts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/admin/admin-parts.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { useKitDateFormatter, useKitLocale } from \"../i18n/kit-labels\";\nimport { formatDate, toDate } from \"../lib/format\";\nimport type { DateInput } from \"../lib/format\";\nimport { toLocalIso } from \"../lib/dates\";\nimport { formatPersonName } from \"../lib/person-name\";\n\n/**\n * What the admin parts share and do not export (0.30.0). Internal to `src/admin`.\n *\n * @internal\n */\n\n/** A callback that may answer a promise: resolve = done, reject = stay and say why. */\nexport type MaybePromise<T = unknown> = void | Promise<T>;\n\n/** A person as the admin parts show one: the two name parts of the auth contract\n * (§3.2), an older whole `name` as the fallback, and the address. */\nexport interface AdminPerson {\n id?: string | number | null;\n first?: string | null;\n last?: string | null;\n /** A whole name, for an API that still sends `display_name` only. */\n name?: string | null;\n email?: string | null;\n}\n\n/** A label that is really there — `null`, `false` and `\"\"` are what `cond && text`\n * evaluates to on the path where there is none. */\nexport function hasMessage(node: ReactNode): boolean {\n return node !== undefined && node !== null && node !== false && node !== \"\";\n}\n\n/**\n * The name to say for a person, in the READER's order (auth §3.2,\n * {@link formatPersonName}), else the whole `name`, else the address, else `\"\"`.\n */\nexport function personLabel(person: AdminPerson | null | undefined, locale: string | undefined): string {\n if (!person) return \"\";\n return (\n formatPersonName({ first: person.first, last: person.last }, locale) ||\n (person.name ?? \"\").trim() ||\n (person.email ?? \"\").trim()\n );\n}\n\n/** {@link personLabel} in the provider's locale. */\nexport function usePersonLabel(localeProp?: string): (person: AdminPerson | null | undefined) => string {\n const locale = useKitLocale(localeProp);\n return useCallback((person) => personLabel(person, locale), [locale]);\n}\n\n/**\n * A day as words in a sentence (\"6 Nov 2026\"): the provider's `formatDate` when the app\n * set one (keksdose K12), asked for no weekday — a date in a sentence carries none — else\n * `Intl`'s medium date. `\"\"` for no date.\n */\nexport function useDayText(localeProp?: string): (value: DateInput) => string {\n const locale = useKitLocale(localeProp);\n const fromProvider = useKitDateFormatter();\n return useCallback(\n (value) => {\n const date = toDate(value);\n if (!date) return \"\";\n const own = fromProvider\n ? fromProvider(toLocalIso(date), { unit: \"day\", source: \"dateMark\", locale, weekday: false })\n : \"\";\n return own || formatDate(date, \"medium\", { locale });\n },\n [fromProvider, locale],\n );\n}\n\n/** Call a callback that may throw or answer a promise, as a promise. */\nexport function settle<T>(call: () => MaybePromise<T>): Promise<T | void> {\n try {\n return Promise.resolve(call());\n } catch (error) {\n return Promise.reject(error);\n }\n}\n"],"mappings":";AAAA,SAAS,mBAAmB;AAE5B,SAAS,qBAAqB,oBAAoB;AAClD,SAAS,YAAY,cAAc;AAEnC,SAAS,kBAAkB;AAC3B,SAAS,wBAAwB;AAwB1B,SAAS,WAAW,MAA0B;AACnD,SAAO,SAAS,UAAa,SAAS,QAAQ,SAAS,SAAS,SAAS;AAC3E;AAMO,SAAS,YAAY,QAAwC,QAAoC;AACtG,MAAI,CAAC,OAAQ,QAAO;AACpB,SACE,iBAAiB,EAAE,OAAO,OAAO,OAAO,MAAM,OAAO,KAAK,GAAG,MAAM,MAClE,OAAO,QAAQ,IAAI,KAAK,MACxB,OAAO,SAAS,IAAI,KAAK;AAE9B;AAGO,SAAS,eAAe,YAAyE;AACtG,QAAM,SAAS,aAAa,UAAU;AACtC,SAAO,YAAY,CAAC,WAAW,YAAY,QAAQ,MAAM,GAAG,CAAC,MAAM,CAAC;AACtE;AAOO,SAAS,WAAW,YAAmD;AAC5E,QAAM,SAAS,aAAa,UAAU;AACtC,QAAM,eAAe,oBAAoB;AACzC,SAAO;AAAA,IACL,CAAC,UAAU;AACT,YAAM,OAAO,OAAO,KAAK;AACzB,UAAI,CAAC,KAAM,QAAO;AAClB,YAAM,MAAM,eACR,aAAa,WAAW,IAAI,GAAG,EAAE,MAAM,OAAO,QAAQ,YAAY,QAAQ,SAAS,MAAM,CAAC,IAC1F;AACJ,aAAO,OAAO,WAAW,MAAM,UAAU,EAAE,OAAO,CAAC;AAAA,IACrD;AAAA,IACA,CAAC,cAAc,MAAM;AAAA,EACvB;AACF;AAGO,SAAS,OAAU,MAAgD;AACxE,MAAI;AACF,WAAO,QAAQ,QAAQ,KAAK,CAAC;AAAA,EAC/B,SAAS,OAAO;AACd,WAAO,QAAQ,OAAO,KAAK;AAAA,EAC7B;AACF;","names":[]}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import 'react';
|
|
2
|
+
import '../lib/format.js';
|
|
3
|
+
export { cv as DEFAULT_INVITATIONS_LABELS, dE as InvitationDraft, dF as InvitationRow, dG as InvitationScope, dH as InvitationSentAnswer, dI as InvitationStatus, dJ as InvitationsLabels, dK as InvitationsPanel, dL as InvitationsPanelProps } from '../delete-account-setting-CGVMckMw.js';
|
|
4
|
+
import '../components/account-chips.js';
|
|
5
|
+
import './admin-parts.js';
|
|
6
|
+
import '../components/tooltip.js';
|
|
7
|
+
import '../hooks/use-anchored-rect.js';
|
|
8
|
+
import '../lib/clipping.js';
|
|
9
|
+
import '../data-table-labels-DUELeXpa.js';
|
|
10
|
+
import '../components/data-table-sort.js';
|
|
11
|
+
import '../components/swipeable-row.js';
|
|
12
|
+
import '../components/mini-calendar.js';
|
|
13
|
+
import '../components/calendar-heatmap.js';
|
|
14
|
+
import '../components/popover.js';
|
|
15
|
+
import '../components/chip.js';
|
|
16
|
+
import 'lucide-react';
|
|
17
|
+
import '../components/field-sync.js';
|
|
18
|
+
import '../wizard/types.js';
|
|
19
|
+
import '../tour/tour.js';
|
|
20
|
+
import '../search/command-palette.js';
|
|
21
|
+
import '../search/global-search.js';
|
|
22
|
+
import '../search/search-index.js';
|
|
23
|
+
import '../components/month-picker.js';
|
|
24
|
+
import '../components/page-contents.js';
|
|
25
|
+
import '../components/series-chart-labels.js';
|
|
26
|
+
import '../components/pie-chart-labels.js';
|
|
27
|
+
import '../components/sparkline.js';
|
|
28
|
+
import '../components/stat-tile.js';
|
|
29
|
+
import '../components/signature-pad.js';
|
|
30
|
+
import '../components/password-strength.js';
|
|
31
|
+
import '../components/swatch-picker.js';
|
|
32
|
+
import '../components/tile-radio.js';
|
|
33
|
+
import '../components/icon-picker.js';
|
|
34
|
+
import '../components/dialog-frame.js';
|
|
35
|
+
import '../components/modal.js';
|
|
36
|
+
import '../hooks/use-search-param-state.js';
|
|
37
|
+
import '../components/measured-grid.js';
|
|
38
|
+
import '../components/user-avatar.js';
|
|
39
|
+
import '../lib/person-name.js';
|
|
40
|
+
import '../components/status-dot.js';
|
|
41
|
+
import '../components/progress-bar.js';
|
|
42
|
+
import '../components/signed-amount.js';
|
|
43
|
+
import '../shell/topbar-action-menu.js';
|
|
44
|
+
import '../components/menu-item.js';
|
|
45
|
+
import '../components/floating-panel.js';
|
|
46
|
+
import '../hooks/use-authed-src.js';
|
|
47
|
+
import '../components/account-settings-labels.js';
|
|
48
|
+
import '../components/bulk-action-bar.js';
|
|
49
|
+
import '../components/list.js';
|
|
50
|
+
import '../components/breadcrumbs.js';
|
|
51
|
+
import '../components/toast.js';
|
|
52
|
+
import '../components/description-list.js';
|
|
53
|
+
import '../components/line-items.js';
|
|
54
|
+
import '../components/error-boundary.js';
|
|
55
|
+
import '../components/authed-image.js';
|
|
56
|
+
import '../components/image-grid.js';
|
|
57
|
+
import '../components/lightbox.js';
|
|
58
|
+
import '../components/write-lock.js';
|
|
59
|
+
import '../auth/sign-in-form.js';
|
|
60
|
+
import '../shell/topbar-controls.js';
|
|
61
|
+
import '../theme/palette-presets.js';
|
|
62
|
+
import '../theme/chart-palette.js';
|
|
63
|
+
import '../theme/theme-store.js';
|
|
64
|
+
import 'zustand/middleware';
|
|
65
|
+
import 'zustand';
|
|
66
|
+
import '../auth/complete-name-dialog.js';
|
|
67
|
+
import '../auth/forgot-password-form.js';
|
|
68
|
+
import '../auth/status-parts.js';
|
|
69
|
+
import '../auth/reset-password-form.js';
|
|
70
|
+
import '../auth/verify-email.js';
|
|
71
|
+
import '../auth/not-found-page.js';
|
|
72
|
+
import '../auth/accept-invitation.js';
|
|
73
|
+
import '../shell/company-switcher.js';
|
|
74
|
+
import '../account/email-change-setting.js';
|
|
75
|
+
import '../account/sessions-setting.js';
|
|
76
|
+
import '../account/data-export-setting.js';
|
|
77
|
+
import './admin-action-log.js';
|
|
78
|
+
import '../components/reauth-dialog.js';
|
|
79
|
+
import '../components/server-wake.js';
|
|
80
|
+
import '../lib/server-wake.js';
|
|
81
|
+
import '../components/translation-review-labels.js';
|
|
82
|
+
import '../lib/translation-review.js';
|
|
83
|
+
import '../components/country-select.js';
|
|
84
|
+
import '../components/inline-edit-field.js';
|
|
85
|
+
import '../lib/iban.js';
|
|
86
|
+
import '../lib/phone.js';
|
|
87
|
+
import '../components/sign-chip.js';
|
|
88
|
+
import '../components/column-mapper.js';
|
|
89
|
+
import '../lib/column-mapping.js';
|
|
90
|
+
import '../lib/table-text.js';
|
|
91
|
+
import '../components/field-parts.js';
|