typescript

This commit is contained in:
2025-11-13 02:37:16 +01:00
parent ada089c05b
commit 6d55e61cee
43 changed files with 923 additions and 406 deletions
+75 -22
View File
@@ -1,4 +1,5 @@
import React, { useCallback, useEffect, useMemo } from 'react';
import type { ReactNode } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAppShell } from '../appShellContext';
import DocumentsLayout from './DocumentsLayout';
@@ -8,7 +9,45 @@ import BreadcrumbTrail from '../ui/BreadcrumbTrail';
import { SidebarProvider, useSidebarContext } from '../sidebar/SidebarContext';
import { PanelManagerProvider, usePanelManager } from './PanelManagerContext';
type Breadcrumb = { id?: string | number; name?: string; label?: string; title?: string };
type Identifier = string | number;
type Breadcrumb = { id?: Identifier; name?: string; label?: string; title?: string };
type EnsureAssetUrl = (
docId: Identifier,
asset: unknown,
options?: Record<string, unknown>,
) => Promise<unknown> | void;
type EnsurePreviewData = (docId: Identifier, options?: Record<string, unknown>) => Promise<unknown>;
type GetDocumentAsset = (document: unknown, assetType: string) => unknown;
type ResolveApiPath = (path: string) => string;
type NotifyApiError = (error: unknown, fallbackMessage?: string) => void;
interface DocumentsTableProps {
breadcrumbs?: Breadcrumb[] | null;
[key: string]: unknown;
}
interface DocumentsRouteAppShell {
sidebarProps?: Record<string, unknown> | null;
documentsTableProps?: DocumentsTableProps | null;
detailPanelProps?: Record<string, unknown> | null;
detailPanelOpen?: boolean;
documentsViewMode?: string;
deskWorkspaceProps?: Record<string, unknown> | null;
openTagsModal?: () => void;
openCorrespondentsModal?: () => void;
previewWorkspaceDocument?: unknown;
previewWorkspaceEntry?: unknown;
previewDocumentId?: Identifier | null;
closeDocumentPreview?: () => void;
ensurePreviewData?: EnsurePreviewData;
resolveApiPath?: ResolveApiPath;
ensureAssetUrl?: EnsureAssetUrl;
getDocumentAsset?: GetDocumentAsset;
notifyApiError?: NotifyApiError;
}
const DocumentsRouteContent: React.FC = () => {
const {
@@ -29,7 +68,7 @@ const DocumentsRouteContent: React.FC = () => {
ensureAssetUrl,
getDocumentAsset,
notifyApiError,
} = useAppShell();
} = useAppShell() as DocumentsRouteAppShell;
const navigate = useNavigate();
const { collapsed: sidebarCollapsed } = useSidebarContext();
const {
@@ -37,18 +76,25 @@ const DocumentsRouteContent: React.FC = () => {
expandSidebar,
} = usePanelManager();
const safeSidebarProps = useMemo<Record<string, unknown>>(
() => (sidebarProps && typeof sidebarProps === 'object' ? sidebarProps : {}),
[sidebarProps],
);
const sidebarPropsWithActions = useMemo(
() => ({
...sidebarProps,
...safeSidebarProps,
onManageTags: openTagsModal,
onManageCorrespondents: openCorrespondentsModal,
}),
[sidebarProps, openTagsModal, openCorrespondentsModal],
[safeSidebarProps, openTagsModal, openCorrespondentsModal],
);
const sidebarHidden = sidebarCollapsed || sidebarSuppressed;
const breadcrumbs = documentsTableProps?.breadcrumbs || null;
const breadcrumbs = Array.isArray(documentsTableProps?.breadcrumbs)
? documentsTableProps?.breadcrumbs
: null;
const parentBreadcrumb = useMemo(() => {
if (!Array.isArray(breadcrumbs) || breadcrumbs.length <= 1) {
return null;
@@ -114,11 +160,14 @@ const DocumentsRouteContent: React.FC = () => {
const variant = surface.variant || 'documents';
const surfaceDetail = (surface as { detail?: ReactNode }).detail || null;
const hasDetail = Boolean(surfaceDetail);
const mainContentClass = `main-content main-content--${variant}${
surface.detail ? ' main-content--has-detail' : ''
hasDetail ? ' main-content--has-detail' : ''
}`;
const bodyClass = `main-content__body main-content__body--${variant}${
surface.detail ? ' main-content__body--has-detail' : ''
hasDetail ? ' main-content__body--has-detail' : ''
}`;
const header = surface.header || null;
@@ -153,26 +202,30 @@ const DocumentsRouteContent: React.FC = () => {
<DocumentsLayout sidebarProps={sidebarPropsWithActions}>
<div className={mainContentClass}>
{header ? (
<div className="main-content__header-wrapper">
<>
<div className="main-content__header-wrapper">
<PanelHeader
className="main-content__header"
leading={header.leading}
title={headerTitle}
titleTag="h2"
actions={header.actions}
/>
</div>
{(header.selectionLabel || header.floatingActions) ? (
<div className="panel-floating" aria-live="polite" aria-atomic="true">
{header.selectionLabel ? (
<span className="panel-floating__label">{header.selectionLabel}</span>
) : null}
{header.floatingActions || null}
<div className="panel-floating-region" aria-live="polite" aria-atomic="true">
<div className="panel-floating">
{header.selectionLabel ? (
<span className="panel-floating__label">{header.selectionLabel}</span>
) : null}
{header.floatingActions || null}
</div>
</div>
) : null}
<PanelHeader
className="main-content__header"
leading={header.leading}
title={headerTitle}
titleTag="h2"
actions={header.actions}
/>
</div>
</>
) : null}
<div className={bodyClass}>{surface.content}</div>
{surface.detail || null}
{surfaceDetail}
</div>
</DocumentsLayout>
);
+17 -3
View File
@@ -221,13 +221,18 @@ const LoginRoute: React.FC = () => {
const publicKey = preparePublicKeyRequestOptions({ publicKey: publicKeyOptions });
setStatusMessage('Confirm the passkey prompt to continue.', 'info');
const assertion = await navigator.credentials.get({ publicKey });
const assertion = await navigator.credentials.get({ publicKey }) as PublicKeyCredential | null;
if (!assertion) {
setStatusMessage('Passkey login cancelled.', 'info');
return;
}
if (!(assertion instanceof PublicKeyCredential)) {
setStatusMessage('Unexpected credential response.', 'error');
return;
}
const serialized = serializeAuthenticationCredential(assertion);
const finishPayload = {
challengeId,
@@ -303,7 +308,11 @@ const LoginRoute: React.FC = () => {
setStatusMessage('Signing you in…', 'info');
try {
const payload = {
const payload: {
magic_token: string;
username?: string;
preferred_tenant_id?: string | number;
} = {
magic_token: magicToken,
};
if (magicUsername) {
@@ -419,13 +428,18 @@ const LoginRoute: React.FC = () => {
const publicKey = preparePublicKeyCreationOptions({ publicKey: publicKeyOptions });
setStatusMessage('Confirm the passkey prompt to finish creating your account.', 'info');
const credential = await navigator.credentials.create({ publicKey });
const credential = await navigator.credentials.create({ publicKey }) as PublicKeyCredential | null;
if (!credential) {
setStatusMessage('Signup cancelled.', 'info');
return;
}
if (!(credential instanceof PublicKeyCredential)) {
setStatusMessage('Unexpected credential response.', 'error');
return;
}
const serialized = serializeRegistrationCredential(credential);
const finishPayload = {
signup_token: signupToken,
+37 -7
View File
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import type { ReactNode } from 'react';
import TagsPanel from '../tags/TagsPanel';
import CorrespondentsPanel from '../correspondents/CorrespondentsPanel';
import CorrespondentsPanel, { CorrespondentsPanelProps } from '../correspondents/CorrespondentsPanel';
import PanelHeader from '../ui/PanelHeader';
const TAGS_MODAL = 'tags';
@@ -135,6 +135,36 @@ export const useManagementModals = ({
tags,
]);
const handleCorrespondentCreateSafe = useCallback<CorrespondentsPanelProps['onCreate']>(
async (payload) => {
if (typeof onCorrespondentCreate !== 'function') {
return undefined;
}
return onCorrespondentCreate(payload) ?? undefined;
},
[onCorrespondentCreate],
);
const handleCorrespondentUpdateSafe = useCallback<CorrespondentsPanelProps['onUpdate']>(
async (id, payload) => {
if (typeof onCorrespondentUpdate !== 'function') {
return;
}
await onCorrespondentUpdate(id, payload);
},
[onCorrespondentUpdate],
);
const handleCorrespondentDeleteSafe = useCallback<CorrespondentsPanelProps['onDelete']>(
async (id) => {
if (typeof onCorrespondentDelete !== 'function') {
return;
}
await onCorrespondentDelete(id);
},
[onCorrespondentDelete],
);
const correspondentsModal = useMemo(() => {
if (activeModal !== CORRESPONDENTS_MODAL) {
return null;
@@ -167,9 +197,9 @@ export const useManagementModals = ({
<CorrespondentsPanel
correspondents={correspondents}
onRefresh={refreshCorrespondents}
onCreate={onCorrespondentCreate}
onUpdate={onCorrespondentUpdate}
onDelete={onCorrespondentDelete}
onCreate={handleCorrespondentCreateSafe}
onUpdate={handleCorrespondentUpdateSafe}
onDelete={handleCorrespondentDeleteSafe}
onNotify={setStatusMessage}
/>
</div>
@@ -180,9 +210,9 @@ export const useManagementModals = ({
activeModal,
closeActiveModal,
correspondents,
onCorrespondentCreate,
onCorrespondentDelete,
onCorrespondentUpdate,
handleCorrespondentCreateSafe,
handleCorrespondentDeleteSafe,
handleCorrespondentUpdateSafe,
refreshCorrespondents,
setStatusMessage,
]);
-2
View File
@@ -140,7 +140,6 @@ export const useWorkspaceSurface = ({
onUpdateTitle,
onUpdateIssued,
resolveFolderPath,
onFolderNavigate,
} = detailExtras;
return createDocumentViewerSurface({
document: previewWorkspaceDocument,
@@ -162,7 +161,6 @@ export const useWorkspaceSurface = ({
onUpdateTitle,
onUpdateIssued,
resolveFolderPath,
onFolderNavigate,
});
}, [
showPreviewWorkspace,