@protobase/ui 0.46.3 → 0.46.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/account/account-page.js +50 -0
- package/dist/account/account-pages.js +24 -9
- package/dist/account/account-tree.js +18 -0
- package/dist/account/blocks/account-loading.js +19 -0
- package/dist/account/blocks/connected-accounts-block.js +37 -0
- package/dist/account/blocks/connected-apps-block.js +21 -0
- package/dist/account/blocks/devices-block.js +23 -0
- package/dist/account/blocks/passkeys-block.js +25 -0
- package/dist/account/blocks/password-block.js +27 -0
- package/dist/account/blocks/profile-block.js +28 -0
- package/dist/account/blocks/two-factor-block.js +29 -0
- package/dist/account/blocks/use-account-data.js +24 -0
- package/dist/account/change-email-dialog.js +98 -0
- package/dist/account/connected-apps-section.js +4 -4
- package/dist/account/device-name.js +26 -0
- package/dist/account/devices-section.js +119 -0
- package/dist/account/messages.js +94 -2
- package/dist/account/passkeys-section.js +4 -4
- package/dist/account/password-change-dialog.js +119 -0
- package/dist/account/password-dialog.js +2 -2
- package/dist/account/password-section.js +51 -0
- package/dist/account/profile-section.js +107 -0
- package/dist/account/sign-in-policy-form.js +1 -1
- package/dist/account/sign-in-policy-page.js +1 -1
- package/dist/account/sign-in-providers-section.js +2 -2
- package/dist/account/staff-sign-in-log.js +1 -1
- package/dist/account/two-factor-section.js +1 -1
- package/dist/account/two-factor-setup.js +2 -2
- package/dist/activity-chart/activity-chart.js +2 -2
- package/dist/app/app.js +39 -23
- package/dist/app/charts/chart-view.js +2 -2
- package/dist/app/comments/comment-composer.js +1 -1
- package/dist/app/comments/comments-section.js +3 -3
- package/dist/app/create/create-page.js +4 -4
- package/dist/app/current-address.d.ts +1 -0
- package/dist/app/custom-section.js +4 -4
- package/dist/app/delete/use-delete-flow.js +1 -1
- package/dist/app/displays/board-column.js +1 -1
- package/dist/app/displays/board-display.js +2 -2
- package/dist/app/displays/calendar-display.js +2 -2
- package/dist/app/displays/custom-display.js +3 -3
- package/dist/app/displays/display-controls.js +1 -1
- package/dist/app/displays/map-display.js +2 -2
- package/dist/app/displays/period-controls.js +1 -1
- package/dist/app/displays/record-card.js +2 -2
- package/dist/app/displays/timeline-display.js +2 -2
- package/dist/app/displays/use-calendar-move.js +1 -1
- package/dist/app/document-title.js +2 -2
- package/dist/app/embedded-list/embedded-display.js +1 -1
- package/dist/app/embedded-list/embedded-list.js +1 -1
- package/dist/app/export/export-dialog.js +2 -2
- package/dist/app/export/use-export-flow.js +1 -1
- package/dist/app/field-editor.js +2 -2
- package/dist/app/file-field.js +3 -3
- package/dist/app/gallery/gallery-section.js +2 -2
- package/dist/app/gallery/photo-details.js +1 -1
- package/dist/app/gallery/use-gallery-photos.js +1 -1
- package/dist/app/hand-order/hand-sorted-list.js +3 -3
- package/dist/app/history/history-section.js +2 -2
- package/dist/app/import/import-button.js +1 -1
- package/dist/app/import/import-check-step.js +1 -1
- package/dist/app/incoming-card.js +2 -2
- package/dist/app/list-group-chart.js +1 -1
- package/dist/app/list-page.js +6 -6
- package/dist/app/messages.js +0 -2
- package/dist/app/meta-gate.js +1 -1
- package/dist/app/nav-from-meta.js +1 -1
- package/dist/app/new-version/new-version-banner.js +1 -1
- package/dist/app/new-version/new-version-button.js +1 -1
- package/dist/app/new-version/new-version-toast.js +1 -1
- package/dist/app/pages/action-inputs/action-inputs-dialog.js +2 -2
- package/dist/app/pages/action-inputs/input-answers.js +1 -1
- package/dist/app/pages/blocks/card-row.js +2 -2
- package/dist/app/pages/blocks/field.js +1 -1
- package/dist/app/pages/blocks/form-draft.js +19 -0
- package/dist/app/pages/blocks/modal-form.js +10 -24
- package/dist/app/pages/blocks/page-block.js +1 -1
- package/dist/app/pages/blocks/record-card.js +2 -2
- package/dist/app/pages/blocks/settings.js +179 -0
- package/dist/app/pages/blocks/stat.js +2 -2
- package/dist/app/pages/blocks/tabs.js +59 -0
- package/dist/app/pages/composed-page.js +16 -3
- package/dist/app/pages/custom-boundary.js +1 -1
- package/dist/app/pages/field-value.js +1 -1
- package/dist/app/pages/messages.js +6 -0
- package/dist/app/pages/page-tabs.js +27 -0
- package/dist/app/pages/record-actions.js +2 -2
- package/dist/app/pages/render-node.js +22 -3
- package/dist/app/pages/use-run-action.js +1 -1
- package/dist/app/project-logo.js +2 -2
- package/dist/app/record-chart/record-chart-section.js +4 -4
- package/dist/app/record-page.js +12 -12
- package/dist/app/record-row/record-row-body.js +1 -1
- package/dist/app/record-row/saved-record-row.js +1 -1
- package/dist/app/related-cards/related-card.js +1 -1
- package/dist/app/related-dots/related-dots-columns.js +1 -1
- package/dist/app/related-section.js +2 -2
- package/dist/app/relation-picker.js +1 -1
- package/dist/app/router.js +2 -2
- package/dist/app/saved/saved-list.js +1 -1
- package/dist/app/search/use-global-search.js +1 -1
- package/dist/app/searches/save-search-button.js +1 -1
- package/dist/app/searches/saved-search-rows.js +1 -1
- package/dist/app/searches/saved-search-section.js +2 -2
- package/dist/app/searches/saved-searches.js +3 -3
- package/dist/app/searches/search-controls.js +2 -2
- package/dist/app/searches/search-name-dialog.js +1 -1
- package/dist/app/searches/update-search-dialog.js +2 -2
- package/dist/app/searches/use-save-search.js +1 -1
- package/dist/app/sensitive-field.js +2 -2
- package/dist/app/source-notice.js +1 -1
- package/dist/app/staff-banner.js +2 -2
- package/dist/app/use-app-address.js +2 -2
- package/dist/app/use-incoming-counts.js +1 -1
- package/dist/app-shell/app-shell.js +2 -1
- package/dist/app-shell/breadcrumb.js +1 -1
- package/dist/app-shell/drawer.js +1 -1
- package/dist/app-shell/global-search.js +1 -1
- package/dist/app-shell/profile-menu.js +27 -12
- package/dist/app-shell/sidebar.js +2 -1
- package/dist/assistant-dock/widget-view.js +1 -1
- package/dist/auth/auth-gate.js +2 -2
- package/dist/auth/auth-messages.js +2 -2
- package/dist/auth/auth-provider.js +4 -0
- package/dist/auth/code-form.js +1 -1
- package/dist/auth/email-code-form.js +1 -1
- package/dist/auth/email-code-page.js +1 -1
- package/dist/auth/forgot-password-form.js +4 -4
- package/dist/auth/provider-sign-in-page.js +2 -2
- package/dist/auth/reset-password-form.js +1 -1
- package/dist/auth/setup-required-form.js +1 -1
- package/dist/auth/setup-required-page.js +2 -2
- package/dist/auth/sign-in-form.js +2 -2
- package/dist/auth/sign-in-page.js +1 -1
- package/dist/auth/sign-in-prompt.js +2 -2
- package/dist/auth/sign-up-code-form.js +1 -1
- package/dist/auth/sign-up-details-form.js +2 -2
- package/dist/auth/sign-up-email-form.js +1 -1
- package/dist/auth/sign-up-page.js +1 -1
- package/dist/auth/staff-sign-in-form.js +2 -2
- package/dist/auth/staff-sign-in-page.js +1 -1
- package/dist/auth/two-factor-form.js +3 -3
- package/dist/auth/two-factor-page.js +1 -1
- package/dist/chat/composer.js +1 -1
- package/dist/connect/connect-page.js +4 -4
- package/dist/data/meta-data.d.ts +3 -1
- package/dist/data/use-meta.js +1 -0
- package/dist/data-grid/cell-text.js +1 -1
- package/dist/data-grid/cells.js +3 -3
- package/dist/data-grid/grid-header.js +1 -1
- package/dist/data-grid/grid-row.js +2 -2
- package/dist/data-grid/row-menu.js +1 -1
- package/dist/filter-panel/facet-group.js +1 -1
- package/dist/forms/public-form-page.js +5 -5
- package/dist/index.js +20 -20
- package/dist/inline-child-table/line-row.js +1 -1
- package/dist/inline-child-table/totals-footer.js +1 -1
- package/dist/invitations/invite-form.js +1 -1
- package/dist/lists/paged-search-list.js +1 -1
- package/dist/live/columns-from-model.js +1 -1
- package/dist/live/use-filter-config.js +1 -1
- package/dist/live/use-reference-search.js +1 -1
- package/dist/notifications/bell/notification-bell.js +1 -1
- package/dist/notifications/bell/notification-item.js +2 -2
- package/dist/organizations/all-organizations.js +1 -1
- package/dist/organizations/invitation-page.js +6 -6
- package/dist/organizations/invitations-page.js +3 -3
- package/dist/organizations/members-page.js +5 -5
- package/dist/organizations/organization-page.js +3 -3
- package/dist/organizations/organization-row.js +1 -1
- package/dist/organizations/organizations-page.js +3 -3
- package/dist/organizations/use-organizations.js +1 -1
- package/dist/package.js +1 -1
- package/dist/plugins/before-sign-in.js +1 -1
- package/dist/plugins/plugin-gate.js +1 -1
- package/dist/primitives/sheet.js +1 -1
- package/dist/record-view/field.js +2 -2
- package/dist/record-view/record-header.js +1 -1
- package/dist/record-view/record-tabs.js +1 -1
- package/dist/roles/use-active-role.js +1 -1
- package/dist/shell/assets/app-DYCHMbTU.js +9 -0
- package/dist/shell/assets/index-CT4BrHx-.js +2 -0
- package/dist/shell/assets/index-zVvlyz9B.css +2 -0
- package/dist/shell/assets/{maplibre-BlwO7zTh.js → maplibre-CjNYwEqW.js} +3 -3
- package/dist/shell/assets/{preload-helper-C24C2MCK.js → preload-helper-SYSsmQ2O.js} +2 -2
- package/dist/shell/assets/{record-map-QajKJid1.js → record-map-DjqQGqZ2.js} +2 -2
- package/dist/shell/assets/{records-map-DybHhZlM.js → records-map-CZB4A_qN.js} +2 -2
- package/dist/shell/assets/shared-protobase-ui-DVOkKNxu.js +2 -0
- package/dist/shell/assets/vendor-BzlIWwX2.js +63 -0
- package/dist/shell/index.html +6 -6
- package/dist/shell/protobase.shell.json +1 -1
- package/dist/shell/protobase.sw.js +2 -2
- package/dist/shell-source-maps/assets/app-DYCHMbTU.js.map +1 -0
- package/dist/shell-source-maps/assets/{index-CY22munM.js.map → index-CT4BrHx-.js.map} +1 -1
- package/dist/shell-source-maps/assets/{maplibre-BlwO7zTh.js.map → maplibre-CjNYwEqW.js.map} +1 -1
- package/dist/shell-source-maps/assets/{preload-helper-C24C2MCK.js.map → preload-helper-SYSsmQ2O.js.map} +1 -1
- package/dist/shell-source-maps/assets/{record-map-QajKJid1.js.map → record-map-DjqQGqZ2.js.map} +1 -1
- package/dist/shell-source-maps/assets/{records-map-DybHhZlM.js.map → records-map-CZB4A_qN.js.map} +1 -1
- package/dist/shell-source-maps/assets/{shared-protobase-ui-TOG-RVkz.js.map → shared-protobase-ui-DVOkKNxu.js.map} +1 -1
- package/dist/shell-source-maps/assets/vendor-BzlIWwX2.js.map +1 -0
- package/dist/side-panel/side-panel.js +1 -1
- package/dist/toasts/toast.js +1 -1
- package/dist/users/create-user-form.js +1 -1
- package/dist/users/use-user-roles.js +1 -1
- package/dist/users/user-row.js +1 -1
- package/package.json +4 -4
- package/dist/account/account-security-page.js +0 -97
- package/dist/shell/assets/app-BDvKMNNB.js +0 -9
- package/dist/shell/assets/index-CY22munM.js +0 -2
- package/dist/shell/assets/index-DHaeB8cs.css +0 -2
- package/dist/shell/assets/shared-protobase-ui-TOG-RVkz.js +0 -2
- package/dist/shell/assets/vendor-C4-Hop1T.js +0 -63
- package/dist/shell-source-maps/assets/app-BDvKMNNB.js.map +0 -1
- package/dist/shell-source-maps/assets/vendor-C4-Hop1T.js.map +0 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { text } from "./messages.js";
|
|
2
|
+
import { accountTabPath } from "./account-pages.js";
|
|
3
|
+
import { useAuth } from "../auth/auth-provider.js";
|
|
4
|
+
import { Notice, useAdminMeta } from "../app/meta-gate.js";
|
|
5
|
+
import { PageTabsProvider } from "../app/pages/page-tabs.js";
|
|
6
|
+
import { linkableProviders } from "./blocks/connected-accounts-block.js";
|
|
7
|
+
import { RenderNode } from "../app/pages/render-node.js";
|
|
8
|
+
import { shownAccountTree } from "./account-tree.js";
|
|
9
|
+
import { useMemo } from "react";
|
|
10
|
+
import { jsx } from "react/jsx-runtime";
|
|
11
|
+
//#region src/account/account-page.tsx
|
|
12
|
+
/**
|
|
13
|
+
* The signed-in person's account page as `/meta` gave it to them, the app's own or the standard one, without the
|
|
14
|
+
* account blocks that have nothing to show here and the tabs they leave empty; `undefined` when `/meta` has none.
|
|
15
|
+
*/
|
|
16
|
+
const useAccountPage = () => {
|
|
17
|
+
const { account } = useAdminMeta();
|
|
18
|
+
const auth = useAuth();
|
|
19
|
+
const { mcp } = auth;
|
|
20
|
+
const linkable = linkableProviders(auth).length > 0;
|
|
21
|
+
return useMemo(() => {
|
|
22
|
+
if (!account) return void 0;
|
|
23
|
+
const hidden = /* @__PURE__ */ new Set([...mcp ? [] : ["ConnectedApps"], ...linkable ? [] : ["ConnectedAccounts"]]);
|
|
24
|
+
return {
|
|
25
|
+
...account,
|
|
26
|
+
tree: shownAccountTree(account.tree, hidden)
|
|
27
|
+
};
|
|
28
|
+
}, [
|
|
29
|
+
account,
|
|
30
|
+
mcp,
|
|
31
|
+
linkable
|
|
32
|
+
]);
|
|
33
|
+
};
|
|
34
|
+
/** `/-/account`, and `/-/account/<tab>` for one of its tabs: the signed-in person's account page. */
|
|
35
|
+
const AccountPage = ({ tab }) => {
|
|
36
|
+
const page = useAccountPage();
|
|
37
|
+
if (!page) return /* @__PURE__ */ jsx(Notice, {
|
|
38
|
+
title: text("noAccount"),
|
|
39
|
+
children: text("noAccountDescription")
|
|
40
|
+
});
|
|
41
|
+
return /* @__PURE__ */ jsx(PageTabsProvider, {
|
|
42
|
+
route: {
|
|
43
|
+
...tab && { tab },
|
|
44
|
+
pathOf: accountTabPath
|
|
45
|
+
},
|
|
46
|
+
children: /* @__PURE__ */ jsx(RenderNode, { node: page.tree })
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
//#endregion
|
|
50
|
+
export { AccountPage, useAccountPage };
|
|
@@ -1,15 +1,30 @@
|
|
|
1
1
|
import { text } from "./messages.js";
|
|
2
|
-
import {
|
|
2
|
+
import { ShieldCheck, UserRound } from "lucide-react";
|
|
3
3
|
//#region src/account/account-pages.ts
|
|
4
|
-
const
|
|
4
|
+
const accountPath = "/-/account";
|
|
5
5
|
const signInPolicyPath = "/-/sign-in-policy";
|
|
6
|
-
/** The
|
|
6
|
+
/** The tab of the account page `path` shows, `{}` for its first, or `undefined` for a path outside it. */
|
|
7
|
+
const readAccountPath = (path) => {
|
|
8
|
+
if (path === "/-/account") return {};
|
|
9
|
+
if (!path.startsWith(`/-/account/`)) return void 0;
|
|
10
|
+
const tab = path.slice(11);
|
|
11
|
+
if (tab === "" || tab.includes("/")) return void 0;
|
|
12
|
+
try {
|
|
13
|
+
return { tab: decodeURIComponent(tab) };
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (error instanceof URIError) return void 0;
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
/** The path of the account page's tab `tab`, or of the page itself (its first tab). */
|
|
20
|
+
const accountTabPath = (tab) => tab ? `${accountPath}/${encodeURIComponent(tab)}` : accountPath;
|
|
21
|
+
/** The app's own pages for the signed-in person, the profile menu's last group: their account, and for admins the policy. */
|
|
7
22
|
const accountMenuItems = (basePath, path, admin) => [{
|
|
8
|
-
id: "account
|
|
9
|
-
label: text("
|
|
10
|
-
icon:
|
|
11
|
-
href: `${basePath}${
|
|
12
|
-
active: path
|
|
23
|
+
id: "account",
|
|
24
|
+
label: text("account"),
|
|
25
|
+
icon: UserRound,
|
|
26
|
+
href: `${basePath}${accountPath}`,
|
|
27
|
+
active: readAccountPath(path) !== void 0
|
|
13
28
|
}, ...admin ? [{
|
|
14
29
|
id: "sign-in-policy",
|
|
15
30
|
label: text("signInPolicy"),
|
|
@@ -18,4 +33,4 @@ const accountMenuItems = (basePath, path, admin) => [{
|
|
|
18
33
|
active: path === signInPolicyPath
|
|
19
34
|
}] : []];
|
|
20
35
|
//#endregion
|
|
21
|
-
export { accountMenuItems,
|
|
36
|
+
export { accountMenuItems, accountPath, accountTabPath, readAccountPath, signInPolicyPath };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { mapTree } from "@protobase/layout";
|
|
2
|
+
//#region src/account/account-tree.ts
|
|
3
|
+
/**
|
|
4
|
+
* The account page's tree without the account blocks named in `hidden`, those with nothing to show for this app, and
|
|
5
|
+
* without the tabs that leaves empty, so a tab such as "Connected apps" is not offered on an app without MCP.
|
|
6
|
+
*/
|
|
7
|
+
const shownAccountTree = (tree, hidden) => {
|
|
8
|
+
const shown = mapTree(tree, (node) => {
|
|
9
|
+
if (node.custom) return node;
|
|
10
|
+
if (hidden.has(node.type)) return null;
|
|
11
|
+
if ((node.type === "Tab" || node.type === "Tabs") && node.children.length === 0) return null;
|
|
12
|
+
return node;
|
|
13
|
+
});
|
|
14
|
+
if (!shown) throw new Error("The account page keeps its Page, which no hidden block replaces");
|
|
15
|
+
return shown;
|
|
16
|
+
};
|
|
17
|
+
//#endregion
|
|
18
|
+
export { shownAccountTree };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { text } from "../messages.js";
|
|
2
|
+
import { Spinner } from "../../primitives/spinner.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
//#region src/account/blocks/account-loading.tsx
|
|
5
|
+
/** An account block whose data is not in yet: why it could not be read, or that it is loading. */
|
|
6
|
+
const AccountLoading = ({ error }) => error ? /* @__PURE__ */ jsx("p", {
|
|
7
|
+
role: "alert",
|
|
8
|
+
className: "text-danger-text",
|
|
9
|
+
children: error
|
|
10
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
11
|
+
className: "flex items-center gap-2 text-muted-foreground",
|
|
12
|
+
children: [
|
|
13
|
+
/* @__PURE__ */ jsx(Spinner, {}),
|
|
14
|
+
" ",
|
|
15
|
+
text("loading")
|
|
16
|
+
]
|
|
17
|
+
});
|
|
18
|
+
//#endregion
|
|
19
|
+
export { AccountLoading };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { text } from "../messages.js";
|
|
2
|
+
import { readProviderError, withoutSignInLink } from "../../auth/provider-link.js";
|
|
3
|
+
import { useAuth } from "../../auth/auth-provider.js";
|
|
4
|
+
import { providerMessage } from "../../auth/auth-messages.js";
|
|
5
|
+
import { SignInProvidersSection } from "../sign-in-providers-section.js";
|
|
6
|
+
import { AccountLoading } from "./account-loading.js";
|
|
7
|
+
import { useAccountData } from "./use-account-data.js";
|
|
8
|
+
import { useCallback, useState } from "react";
|
|
9
|
+
import { jsx } from "react/jsx-runtime";
|
|
10
|
+
//#region src/account/blocks/connected-accounts-block.tsx
|
|
11
|
+
const takeLinkError = () => {
|
|
12
|
+
const code = readProviderError(window.location.href);
|
|
13
|
+
if (code) window.history.replaceState(window.history.state, "", withoutSignInLink(window.location.href));
|
|
14
|
+
return code;
|
|
15
|
+
};
|
|
16
|
+
const Linked = ({ providers }) => {
|
|
17
|
+
const { session, linkProvider } = useAuth();
|
|
18
|
+
const { account } = session;
|
|
19
|
+
const [linkError] = useState(takeLinkError);
|
|
20
|
+
const { data: linked, error } = useAccountData(useCallback(() => account.linkedProviders(), [account]), text("couldNotReadMethods"));
|
|
21
|
+
if (!linked) return /* @__PURE__ */ jsx(AccountLoading, { error });
|
|
22
|
+
return /* @__PURE__ */ jsx(SignInProvidersSection, {
|
|
23
|
+
providers,
|
|
24
|
+
linked,
|
|
25
|
+
onConnect: linkProvider,
|
|
26
|
+
error: linkError && providerMessage(linkError, providers.length === 1 ? providers[0].name : text("theProvider"))
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
/** The sign-in providers an account links to from here: the OAuth ones, as a SAML identity provider links it by signing in. */
|
|
30
|
+
const linkableProviders = ({ signInProviders, socialProviders }) => signInProviders.filter((provider) => socialProviders.includes(provider.id));
|
|
31
|
+
/** The `ConnectedAccounts` block: the OAuth providers the signed-in person can link, hidden when the app has none. */
|
|
32
|
+
const ConnectedAccountsBlock = () => {
|
|
33
|
+
const linkable = linkableProviders(useAuth());
|
|
34
|
+
return linkable.length > 0 ? /* @__PURE__ */ jsx(Linked, { providers: linkable }) : null;
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
export { ConnectedAccountsBlock, linkableProviders };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { text } from "../messages.js";
|
|
2
|
+
import { useAuth } from "../../auth/auth-provider.js";
|
|
3
|
+
import { AccountLoading } from "./account-loading.js";
|
|
4
|
+
import { useAccountData } from "./use-account-data.js";
|
|
5
|
+
import { ConnectedAppsSection } from "../connected-apps-section.js";
|
|
6
|
+
import { useCallback } from "react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
//#region src/account/blocks/connected-apps-block.tsx
|
|
9
|
+
const Apps = () => {
|
|
10
|
+
const { account } = useAuth().session;
|
|
11
|
+
const { data: apps, error, reload } = useAccountData(useCallback(() => account.connectedApps(), [account]), text("couldNotReadMethods"));
|
|
12
|
+
if (!apps) return /* @__PURE__ */ jsx(AccountLoading, { error });
|
|
13
|
+
return /* @__PURE__ */ jsx(ConnectedAppsSection, {
|
|
14
|
+
apps,
|
|
15
|
+
onDisconnect: (clientId) => account.disconnectApp(clientId).then(reload)
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
/** The `ConnectedApps` block: the apps the signed-in person allowed to act as them, hidden when the app has no MCP. */
|
|
19
|
+
const ConnectedAppsBlock = () => useAuth().mcp ? /* @__PURE__ */ jsx(Apps, {}) : null;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { ConnectedAppsBlock };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { text } from "../messages.js";
|
|
2
|
+
import { useAuth } from "../../auth/auth-provider.js";
|
|
3
|
+
import { AccountLoading } from "./account-loading.js";
|
|
4
|
+
import { useAccountData } from "./use-account-data.js";
|
|
5
|
+
import { DevicesSection } from "../devices-section.js";
|
|
6
|
+
import { useCallback } from "react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
//#region src/account/blocks/devices-block.tsx
|
|
9
|
+
/** The `Devices` block: the browsers and apps signed in as the person, this one marked, the others to sign out. */
|
|
10
|
+
const DevicesBlock = () => {
|
|
11
|
+
const { session, staffSignIn } = useAuth();
|
|
12
|
+
const { account } = session;
|
|
13
|
+
const { data: sessions, error, reload } = useAccountData(useCallback(() => account.sessions(), [account]), text("couldNotReadDevices"));
|
|
14
|
+
if (!sessions) return /* @__PURE__ */ jsx(AccountLoading, { error });
|
|
15
|
+
return /* @__PURE__ */ jsx(DevicesSection, {
|
|
16
|
+
sessions,
|
|
17
|
+
operator: staffSignIn,
|
|
18
|
+
onSignOut: (token) => account.revokeSession(token).then(reload),
|
|
19
|
+
onSignOutOthers: () => account.revokeOtherSessions().then(reload)
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
//#endregion
|
|
23
|
+
export { DevicesBlock };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { text } from "../messages.js";
|
|
2
|
+
import { useAuth } from "../../auth/auth-provider.js";
|
|
3
|
+
import { AccountLoading } from "./account-loading.js";
|
|
4
|
+
import { useAccountData } from "./use-account-data.js";
|
|
5
|
+
import { PasskeysSection } from "../passkeys-section.js";
|
|
6
|
+
import { useCallback } from "react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
//#region src/account/blocks/passkeys-block.tsx
|
|
9
|
+
/** The `Passkeys` block: the signed-in person's passkeys, within what the sign-in policy allows. */
|
|
10
|
+
const PasskeysBlock = () => {
|
|
11
|
+
const { account } = useAuth().session;
|
|
12
|
+
const load = useCallback(() => Promise.all([account.signInMethods(), account.passkeys()]), [account]);
|
|
13
|
+
const { data, error, reload } = useAccountData(load, text("couldNotReadMethods"));
|
|
14
|
+
if (!data) return /* @__PURE__ */ jsx(AccountLoading, { error });
|
|
15
|
+
const [methods, passkeys] = data;
|
|
16
|
+
return /* @__PURE__ */ jsx(PasskeysSection, {
|
|
17
|
+
rule: methods.policy.passkey,
|
|
18
|
+
passkeys,
|
|
19
|
+
onAdd: () => account.addPasskey().then(reload),
|
|
20
|
+
onRename: (id, name) => account.renamePasskey(id, name).then(reload),
|
|
21
|
+
onRemove: (id) => account.removePasskey(id).then(reload)
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
//#endregion
|
|
25
|
+
export { PasskeysBlock };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { text } from "../messages.js";
|
|
2
|
+
import { useAuth } from "../../auth/auth-provider.js";
|
|
3
|
+
import { AccountLoading } from "./account-loading.js";
|
|
4
|
+
import { useAccountData } from "./use-account-data.js";
|
|
5
|
+
import { PasswordSection } from "../password-section.js";
|
|
6
|
+
import { useCallback } from "react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
//#region src/account/blocks/password-block.tsx
|
|
9
|
+
/**
|
|
10
|
+
* The `Password` block: the signed-in person changes their password, or sets a first one without signing anyone out,
|
|
11
|
+
* within what the sign-in policy allows; staff signed in as the person cannot.
|
|
12
|
+
*/
|
|
13
|
+
const PasswordBlock = () => {
|
|
14
|
+
const { session, state } = useAuth();
|
|
15
|
+
const { account } = session;
|
|
16
|
+
const { data: methods, error, reload } = useAccountData(useCallback(() => account.signInMethods(), [account]), text("couldNotReadMethods"));
|
|
17
|
+
if (!methods) return /* @__PURE__ */ jsx(AccountLoading, { error });
|
|
18
|
+
return /* @__PURE__ */ jsx(PasswordSection, {
|
|
19
|
+
rule: methods.policy.password,
|
|
20
|
+
hasPassword: methods.account.password,
|
|
21
|
+
staff: state.kind === "signed-in" && Boolean(state.staff),
|
|
22
|
+
onChange: (current, next, signOutOthers) => account.changePassword(current, next, signOutOthers).then(reload),
|
|
23
|
+
onSet: (next) => account.setPassword(next).then(reload)
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
export { PasswordBlock };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { text } from "../messages.js";
|
|
2
|
+
import { useAuth } from "../../auth/auth-provider.js";
|
|
3
|
+
import { AccountLoading } from "./account-loading.js";
|
|
4
|
+
import { useAccountData } from "./use-account-data.js";
|
|
5
|
+
import { ProfileSection } from "../profile-section.js";
|
|
6
|
+
import { useCallback } from "react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
//#region src/account/blocks/profile-block.tsx
|
|
9
|
+
/** The `Profile` block: the signed-in person's name and address, which they can change; the address only with mail. */
|
|
10
|
+
const ProfileBlock = () => {
|
|
11
|
+
const { session, changeEmail } = useAuth();
|
|
12
|
+
const { data: user, loaded, error, reload } = useAccountData(useCallback(() => session.session(), [session]), text("couldNotReadProfile"));
|
|
13
|
+
if (!loaded) return /* @__PURE__ */ jsx(AccountLoading, { error });
|
|
14
|
+
if (!user) return null;
|
|
15
|
+
const emailChange = changeEmail ? {
|
|
16
|
+
request: session.account.requestEmailChange,
|
|
17
|
+
confirm: session.account.changeEmail
|
|
18
|
+
} : void 0;
|
|
19
|
+
return /* @__PURE__ */ jsx(ProfileSection, {
|
|
20
|
+
name: user.name,
|
|
21
|
+
email: user.email,
|
|
22
|
+
onRename: (name) => session.account.updateName(name).then(reload),
|
|
23
|
+
emailChange,
|
|
24
|
+
onEmailChanged: reload
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
28
|
+
export { ProfileBlock };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { text } from "../messages.js";
|
|
2
|
+
import { useAuth } from "../../auth/auth-provider.js";
|
|
3
|
+
import { AccountLoading } from "./account-loading.js";
|
|
4
|
+
import { useAccountData } from "./use-account-data.js";
|
|
5
|
+
import { TwoFactorSection } from "../two-factor-section.js";
|
|
6
|
+
import { useCallback } from "react";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
//#region src/account/blocks/two-factor-block.tsx
|
|
9
|
+
/** The `TwoFactor` block: the signed-in person's two-factor authentication, within what the sign-in policy allows. */
|
|
10
|
+
const TwoFactorBlock = () => {
|
|
11
|
+
const { account } = useAuth().session;
|
|
12
|
+
const { data: methods, error, reload } = useAccountData(useCallback(() => account.signInMethods(), [account]), text("couldNotReadMethods"));
|
|
13
|
+
if (!methods) return /* @__PURE__ */ jsx(AccountLoading, { error });
|
|
14
|
+
return /* @__PURE__ */ jsx(TwoFactorSection, {
|
|
15
|
+
rule: methods.policy.twoFactor,
|
|
16
|
+
enabled: methods.account.twoFactor,
|
|
17
|
+
authenticatorApp: methods.account.authenticatorApp,
|
|
18
|
+
mail: methods.mail,
|
|
19
|
+
needsPassword: methods.account.password,
|
|
20
|
+
onStartApp: (password) => account.startAuthenticatorApp(password),
|
|
21
|
+
onConfirmApp: (code) => account.confirmAuthenticatorApp(code),
|
|
22
|
+
onEmailedCodes: (password) => account.turnOnEmailedCodes(password),
|
|
23
|
+
onTurnOff: (password) => account.turnOffTwoFactor(password),
|
|
24
|
+
onNewBackupCodes: (password) => account.newBackupCodes(password),
|
|
25
|
+
onChanged: reload
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { TwoFactorBlock };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { authMessage } from "../../auth/auth-messages.js";
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
|
+
import { AuthError } from "@protobase/client";
|
|
4
|
+
//#region src/account/blocks/use-account-data.ts
|
|
5
|
+
/**
|
|
6
|
+
* What an account block reads, `undefined` until it is in, with `reload` to read it again after a change so the block
|
|
7
|
+
* shows what the server now has. A failure shows the server's reason, or `fallback`.
|
|
8
|
+
*/
|
|
9
|
+
const useAccountData = (load, fallback) => {
|
|
10
|
+
const [data, setData] = useState();
|
|
11
|
+
const [error, setError] = useState();
|
|
12
|
+
const reload = useCallback(() => {
|
|
13
|
+
load().then((value) => setData({ value }), (failure) => setError(failure instanceof AuthError ? authMessage(failure) : fallback));
|
|
14
|
+
}, [load, fallback]);
|
|
15
|
+
useEffect(reload, [reload]);
|
|
16
|
+
return {
|
|
17
|
+
data: data?.value,
|
|
18
|
+
loaded: data !== void 0,
|
|
19
|
+
error,
|
|
20
|
+
reload
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
export { useAccountData };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { text } from "./messages.js";
|
|
2
|
+
import { Button } from "../primitives/button.js";
|
|
3
|
+
import { Dialog } from "../primitives/dialog.js";
|
|
4
|
+
import { Input } from "../primitives/input.js";
|
|
5
|
+
import { SectionError } from "./account-section.js";
|
|
6
|
+
import { authMessage } from "../auth/auth-messages.js";
|
|
7
|
+
import { OtpInput } from "../primitives/otp-input.js";
|
|
8
|
+
import { useState } from "react";
|
|
9
|
+
import { AuthError } from "@protobase/client";
|
|
10
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
//#region src/account/change-email-dialog.tsx
|
|
12
|
+
/** Asks for the new address, mails it a code, and moves the account to it once the code is right. */
|
|
13
|
+
const ChangeEmailDialog = ({ email, change, onDone, onClose }) => {
|
|
14
|
+
const [draft, setDraft] = useState("");
|
|
15
|
+
const [sentTo, setSentTo] = useState();
|
|
16
|
+
const [code, setCode] = useState("");
|
|
17
|
+
const [busy, setBusy] = useState(false);
|
|
18
|
+
const [error, setError] = useState();
|
|
19
|
+
const run = async (step) => {
|
|
20
|
+
setBusy(true);
|
|
21
|
+
setError(void 0);
|
|
22
|
+
try {
|
|
23
|
+
await step();
|
|
24
|
+
} catch (failure) {
|
|
25
|
+
if (!(failure instanceof AuthError)) throw failure;
|
|
26
|
+
setError(authMessage(failure));
|
|
27
|
+
}
|
|
28
|
+
setBusy(false);
|
|
29
|
+
};
|
|
30
|
+
const newEmail = draft.trim();
|
|
31
|
+
const send = () => run(async () => {
|
|
32
|
+
await change.request(newEmail);
|
|
33
|
+
setCode("");
|
|
34
|
+
setSentTo(newEmail);
|
|
35
|
+
});
|
|
36
|
+
const confirm = (complete) => run(async () => {
|
|
37
|
+
await change.confirm(sentTo, complete);
|
|
38
|
+
onDone();
|
|
39
|
+
});
|
|
40
|
+
if (!sentTo) return /* @__PURE__ */ jsxs(Dialog, {
|
|
41
|
+
title: text("changeEmail"),
|
|
42
|
+
description: text("changeEmailDescription"),
|
|
43
|
+
onClose,
|
|
44
|
+
actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
|
|
45
|
+
onClick: onClose,
|
|
46
|
+
children: text("cancel")
|
|
47
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
48
|
+
variant: "primary",
|
|
49
|
+
loading: busy,
|
|
50
|
+
disabled: !newEmail || newEmail.toLowerCase() === email.toLowerCase(),
|
|
51
|
+
onClick: () => void send(),
|
|
52
|
+
children: text("sendCode")
|
|
53
|
+
})] }),
|
|
54
|
+
children: [/* @__PURE__ */ jsxs("label", {
|
|
55
|
+
className: "flex flex-col gap-1.5 font-medium",
|
|
56
|
+
children: [text("newEmail"), /* @__PURE__ */ jsx(Input, {
|
|
57
|
+
type: "email",
|
|
58
|
+
autoComplete: "email",
|
|
59
|
+
autoFocus: true,
|
|
60
|
+
value: draft,
|
|
61
|
+
onChange: (event) => setDraft(event.target.value)
|
|
62
|
+
})]
|
|
63
|
+
}), /* @__PURE__ */ jsx(SectionError, { message: error })]
|
|
64
|
+
});
|
|
65
|
+
return /* @__PURE__ */ jsxs(Dialog, {
|
|
66
|
+
title: text("changeEmail"),
|
|
67
|
+
description: text("codeSentTo", sentTo),
|
|
68
|
+
onClose,
|
|
69
|
+
actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
|
|
70
|
+
variant: "ghost",
|
|
71
|
+
onClick: () => {
|
|
72
|
+
setSentTo(void 0);
|
|
73
|
+
setError(void 0);
|
|
74
|
+
},
|
|
75
|
+
children: text("otherAddress")
|
|
76
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
77
|
+
variant: "primary",
|
|
78
|
+
loading: busy,
|
|
79
|
+
disabled: code.length < 6,
|
|
80
|
+
onClick: () => void confirm(code),
|
|
81
|
+
children: text("changeEmail")
|
|
82
|
+
})] }),
|
|
83
|
+
children: [/* @__PURE__ */ jsxs("label", {
|
|
84
|
+
className: "flex flex-col gap-1.5 font-medium",
|
|
85
|
+
children: [text("code"), /* @__PURE__ */ jsx(OtpInput, {
|
|
86
|
+
name: "code",
|
|
87
|
+
required: true,
|
|
88
|
+
autoFocus: true,
|
|
89
|
+
value: code,
|
|
90
|
+
invalid: Boolean(error),
|
|
91
|
+
onChange: setCode,
|
|
92
|
+
onComplete: (complete) => !busy && void confirm(complete)
|
|
93
|
+
})]
|
|
94
|
+
}), /* @__PURE__ */ jsx(SectionError, { message: error })]
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
//#endregion
|
|
98
|
+
export { ChangeEmailDialog };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { authMessage } from "../auth/auth-messages.js";
|
|
2
|
-
import { clientName } from "../connect/client-name.js";
|
|
3
|
-
import { clientPublisher } from "../connect/client-publisher.js";
|
|
4
|
-
import { formatDate } from "../format/date.js";
|
|
5
1
|
import { Button } from "../primitives/button.js";
|
|
6
2
|
import { Dialog } from "../primitives/dialog.js";
|
|
3
|
+
import { formatDate } from "../format/date.js";
|
|
7
4
|
import { AccountSection, SectionError } from "./account-section.js";
|
|
5
|
+
import { authMessage } from "../auth/auth-messages.js";
|
|
6
|
+
import { clientName } from "../connect/client-name.js";
|
|
7
|
+
import { clientPublisher } from "../connect/client-publisher.js";
|
|
8
8
|
import { useState } from "react";
|
|
9
9
|
import { Bot } from "lucide-react";
|
|
10
10
|
import { AuthError } from "@protobase/client";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/account/device-name.ts
|
|
2
|
+
const browsers = [
|
|
3
|
+
[/Edg(e|A|iOS)?\//, "Edge"],
|
|
4
|
+
[/OPR\/|Opera/, "Opera"],
|
|
5
|
+
[/SamsungBrowser\//, "Samsung Internet"],
|
|
6
|
+
[/Firefox\/|FxiOS\//, "Firefox"],
|
|
7
|
+
[/Chrome\/|CriOS\//, "Chrome"],
|
|
8
|
+
[/Safari\//, "Safari"]
|
|
9
|
+
];
|
|
10
|
+
const systems = [
|
|
11
|
+
[/iPhone/, "iPhone"],
|
|
12
|
+
[/iPad/, "iPad"],
|
|
13
|
+
[/Android/, "Android"],
|
|
14
|
+
[/CrOS/, "ChromeOS"],
|
|
15
|
+
[/Windows/, "Windows"],
|
|
16
|
+
[/Mac OS X|Macintosh/, "macOS"],
|
|
17
|
+
[/Linux/, "Linux"]
|
|
18
|
+
];
|
|
19
|
+
const find = (list, userAgent) => list.find(([pattern]) => pattern.test(userAgent))?.[1];
|
|
20
|
+
/** The browser and system a user agent names, such as Chrome and macOS, each `undefined` when it names none we know. */
|
|
21
|
+
const deviceOf = (userAgent) => userAgent ? {
|
|
22
|
+
browser: find(browsers, userAgent),
|
|
23
|
+
system: find(systems, userAgent)
|
|
24
|
+
} : {};
|
|
25
|
+
//#endregion
|
|
26
|
+
export { deviceOf };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { text } from "./messages.js";
|
|
2
|
+
import { Button } from "../primitives/button.js";
|
|
3
|
+
import { Dialog } from "../primitives/dialog.js";
|
|
4
|
+
import { formatDateTime } from "../format/date.js";
|
|
5
|
+
import { Badge } from "../primitives/badge.js";
|
|
6
|
+
import { AccountSection, SectionError } from "./account-section.js";
|
|
7
|
+
import { authMessage } from "../auth/auth-messages.js";
|
|
8
|
+
import { deviceOf } from "./device-name.js";
|
|
9
|
+
import { useState } from "react";
|
|
10
|
+
import { Laptop, ShieldUser, Smartphone, Tablet } from "lucide-react";
|
|
11
|
+
import { AuthError } from "@protobase/client";
|
|
12
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
13
|
+
//#region src/account/devices-section.tsx
|
|
14
|
+
const nameOf = (session, operator) => {
|
|
15
|
+
if (session.staff) return operator ? text("staffOf", operator) : text("staff");
|
|
16
|
+
const { browser, system } = deviceOf(session.userAgent);
|
|
17
|
+
if (browser && system) return text("browserOn", browser, system);
|
|
18
|
+
if (system) return text("aBrowserOn", system);
|
|
19
|
+
return browser ?? text("unknownDevice");
|
|
20
|
+
};
|
|
21
|
+
const iconOf = (session) => {
|
|
22
|
+
if (session.staff) return ShieldUser;
|
|
23
|
+
const { system } = deviceOf(session.userAgent);
|
|
24
|
+
if (system === "iPhone" || system === "Android") return Smartphone;
|
|
25
|
+
if (system === "iPad") return Tablet;
|
|
26
|
+
return Laptop;
|
|
27
|
+
};
|
|
28
|
+
const ordered = (sessions) => [...sessions].sort((a, b) => Number(b.current) - Number(a.current) || b.createdAt.localeCompare(a.createdAt));
|
|
29
|
+
/** The devices signed in to the person's account, this one marked, each other one of which they can sign out. */
|
|
30
|
+
const DevicesSection = ({ sessions, operator, onSignOut, onSignOutOthers }) => {
|
|
31
|
+
const [busy, setBusy] = useState(false);
|
|
32
|
+
const [error, setError] = useState();
|
|
33
|
+
const [confirming, setConfirming] = useState();
|
|
34
|
+
const others = sessions.filter((session) => !session.current);
|
|
35
|
+
const signOut = async (what) => {
|
|
36
|
+
setBusy(true);
|
|
37
|
+
setError(void 0);
|
|
38
|
+
try {
|
|
39
|
+
await (what.kind === "one" ? onSignOut(what.session.token) : onSignOutOthers());
|
|
40
|
+
setConfirming(void 0);
|
|
41
|
+
} catch (failure) {
|
|
42
|
+
if (!(failure instanceof AuthError)) throw failure;
|
|
43
|
+
setError(authMessage(failure));
|
|
44
|
+
}
|
|
45
|
+
setBusy(false);
|
|
46
|
+
};
|
|
47
|
+
const open = (what) => {
|
|
48
|
+
setError(void 0);
|
|
49
|
+
setConfirming(what);
|
|
50
|
+
};
|
|
51
|
+
return /* @__PURE__ */ jsxs(AccountSection, {
|
|
52
|
+
title: text("devices"),
|
|
53
|
+
description: text("devicesDescription"),
|
|
54
|
+
children: [
|
|
55
|
+
sessions.length === 0 ? /* @__PURE__ */ jsx("p", {
|
|
56
|
+
className: "text-muted-foreground",
|
|
57
|
+
children: text("noDevices")
|
|
58
|
+
}) : /* @__PURE__ */ jsx("ul", {
|
|
59
|
+
className: "divide-y rounded-md border",
|
|
60
|
+
children: ordered(sessions).map((session) => {
|
|
61
|
+
const Icon = iconOf(session);
|
|
62
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
63
|
+
className: "flex flex-wrap items-center gap-3 px-3 py-2.5",
|
|
64
|
+
children: [
|
|
65
|
+
/* @__PURE__ */ jsx(Icon, { className: "size-4 shrink-0 text-muted-foreground" }),
|
|
66
|
+
/* @__PURE__ */ jsxs("span", {
|
|
67
|
+
className: "min-w-0 flex-1",
|
|
68
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
69
|
+
className: "flex flex-wrap items-center gap-2",
|
|
70
|
+
children: [/* @__PURE__ */ jsx("bdi", {
|
|
71
|
+
className: "truncate font-medium",
|
|
72
|
+
children: nameOf(session, operator)
|
|
73
|
+
}), session.current && /* @__PURE__ */ jsx(Badge, {
|
|
74
|
+
tone: "green",
|
|
75
|
+
children: text("thisDevice")
|
|
76
|
+
})]
|
|
77
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
78
|
+
className: "block text-xs text-muted-foreground",
|
|
79
|
+
children: [text("signedInFromUntil", formatDateTime(Date.parse(session.createdAt)), formatDateTime(Date.parse(session.expiresAt))), !session.staff && session.ipAddress && ` · ${session.ipAddress}`]
|
|
80
|
+
})]
|
|
81
|
+
}),
|
|
82
|
+
!session.current && /* @__PURE__ */ jsx(Button, {
|
|
83
|
+
size: "sm",
|
|
84
|
+
variant: "ghost",
|
|
85
|
+
onClick: () => open({
|
|
86
|
+
kind: "one",
|
|
87
|
+
session
|
|
88
|
+
}),
|
|
89
|
+
children: text("signOut")
|
|
90
|
+
})
|
|
91
|
+
]
|
|
92
|
+
}, session.token);
|
|
93
|
+
})
|
|
94
|
+
}),
|
|
95
|
+
others.length > 0 && /* @__PURE__ */ jsx(Button, {
|
|
96
|
+
className: "mt-3",
|
|
97
|
+
onClick: () => open({ kind: "others" }),
|
|
98
|
+
children: text("signOutOthers")
|
|
99
|
+
}),
|
|
100
|
+
confirming && /* @__PURE__ */ jsx(Dialog, {
|
|
101
|
+
title: confirming.kind === "one" ? text("signOutDevice", nameOf(confirming.session, operator)) : text("signOutOthersTitle"),
|
|
102
|
+
description: confirming.kind === "one" ? text("signOutDeviceDescription") : text("signOutOthersDescription"),
|
|
103
|
+
onClose: () => setConfirming(void 0),
|
|
104
|
+
actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
|
|
105
|
+
onClick: () => setConfirming(void 0),
|
|
106
|
+
children: text("cancel")
|
|
107
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
108
|
+
variant: "danger",
|
|
109
|
+
loading: busy,
|
|
110
|
+
onClick: () => void signOut(confirming),
|
|
111
|
+
children: text("signOut")
|
|
112
|
+
})] }),
|
|
113
|
+
children: /* @__PURE__ */ jsx(SectionError, { message: error })
|
|
114
|
+
})
|
|
115
|
+
]
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
//#endregion
|
|
119
|
+
export { DevicesSection };
|