typescript
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
]);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user