refactor: centralize frontend status message handling with useStatusToast

This commit is contained in:
2025-12-07 23:46:10 +01:00
parent b29e856225
commit 595c170c00
13 changed files with 149 additions and 175 deletions
+6 -6
View File
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { useStatusToast } from '../lib/context/StatusToastContext';
import TagsPanel from '../tags/TagsPanel'; import TagsPanel from '../tags/TagsPanel';
import CorrespondentsPanel, { CorrespondentsPanelProps } from '../correspondents/CorrespondentsPanel'; import CorrespondentsPanel, { CorrespondentsPanelProps } from '../correspondents/CorrespondentsPanel';
import PanelHeader from '../components/PanelHeader'; import PanelHeader from '../components/PanelHeader';
@@ -30,7 +31,6 @@ interface UseManagementModalsArgs {
onCorrespondentCreate?: (...args: any[]) => void | Promise<void>; onCorrespondentCreate?: (...args: any[]) => void | Promise<void>;
onCorrespondentUpdate?: (...args: any[]) => void | Promise<void>; onCorrespondentUpdate?: (...args: any[]) => void | Promise<void>;
onCorrespondentDelete?: (...args: any[]) => void | Promise<void>; onCorrespondentDelete?: (...args: any[]) => void | Promise<void>;
setStatusMessage?: (message: string, variant?: string) => void;
} }
interface UseManagementModalsResult { interface UseManagementModalsResult {
@@ -53,8 +53,8 @@ export const useManagementModals = ({
onCorrespondentCreate, onCorrespondentCreate,
onCorrespondentUpdate, onCorrespondentUpdate,
onCorrespondentDelete, onCorrespondentDelete,
setStatusMessage,
}: UseManagementModalsArgs): UseManagementModalsResult => { }: UseManagementModalsArgs): UseManagementModalsResult => {
const { showToast } = useStatusToast();
const [activeModal, setActiveModal] = useState<string | null>(null); const [activeModal, setActiveModal] = useState<string | null>(null);
const openTagsModal = useCallback(() => setActiveModal(TAGS_MODAL), []); const openTagsModal = useCallback(() => setActiveModal(TAGS_MODAL), []);
@@ -117,7 +117,7 @@ export const useManagementModals = ({
onCreateTag={onTagCreate} onCreateTag={onTagCreate}
onUpdateTag={onTagUpdate} onUpdateTag={onTagUpdate}
onDeleteTag={onTagDelete} onDeleteTag={onTagDelete}
onNotify={setStatusMessage} onNotify={showToast}
/> />
</div> </div>
</div> </div>
@@ -130,7 +130,7 @@ export const useManagementModals = ({
onTagDelete, onTagDelete,
onTagUpdate, onTagUpdate,
refreshTags, refreshTags,
setStatusMessage, showToast,
tags, tags,
]); ]);
@@ -199,7 +199,7 @@ export const useManagementModals = ({
onCreate={handleCorrespondentCreateSafe} onCreate={handleCorrespondentCreateSafe}
onUpdate={handleCorrespondentUpdateSafe} onUpdate={handleCorrespondentUpdateSafe}
onDelete={handleCorrespondentDeleteSafe} onDelete={handleCorrespondentDeleteSafe}
onNotify={setStatusMessage} onNotify={showToast}
/> />
</div> </div>
</div> </div>
@@ -213,7 +213,7 @@ export const useManagementModals = ({
handleCorrespondentDeleteSafe, handleCorrespondentDeleteSafe,
handleCorrespondentUpdateSafe, handleCorrespondentUpdateSafe,
refreshCorrespondents, refreshCorrespondents,
setStatusMessage, showToast,
]); ]);
const managementModals = ( const managementModals = (
@@ -1,18 +1,16 @@
import { useCallback, useEffect, useRef } from 'react'; import { useCallback, useEffect, useRef } from 'react';
import type { MutableRefObject } from 'react'; import type { MutableRefObject } from 'react';
import { clearAuthToken, logoutSession, refreshSession, setAuthToken } from '../../lib/api/apiClient'; import { clearAuthToken, logoutSession, refreshSession, setAuthToken } from '../../lib/api/apiClient';
import { useStatusToast } from '../../lib/context/StatusToastContext';
type AppStatus = string; type AppStatus = string;
type AppDispatch = (action: { type: string;[key: string]: unknown }) => void; type AppDispatch = (action: { type: string;[key: string]: unknown }) => void;
type SetStatusMessage = (message: string, variant?: string) => void;
interface UseAuthManagerArgs { interface UseAuthManagerArgs {
token?: string | null; token?: string | null;
appStatus: AppStatus; appStatus: AppStatus;
appDispatch: AppDispatch; appDispatch: AppDispatch;
setStatusMessage: SetStatusMessage;
} }
interface UseAuthManagerResult { interface UseAuthManagerResult {
@@ -25,10 +23,10 @@ const useAuthManager = ({
token, token,
appStatus, appStatus,
appDispatch, appDispatch,
setStatusMessage,
}: UseAuthManagerArgs): UseAuthManagerResult => { }: UseAuthManagerArgs): UseAuthManagerResult => {
const tokenRef = useRef<string | null>(token); const tokenRef = useRef<string | null>(token);
const initialRefreshAttemptedRef = useRef(Boolean(token)); const initialRefreshAttemptedRef = useRef(Boolean(token));
const { showToast } = useStatusToast();
const refreshAccessToken = useCallback(async (): Promise<string> => { const refreshAccessToken = useCallback(async (): Promise<string> => {
console.log('[Auth] Attempting to refresh access token…'); console.log('[Auth] Attempting to refresh access token…');
@@ -73,9 +71,9 @@ const useAuthManager = ({
} finally { } finally {
clearAuthToken(); clearAuthToken();
appDispatch({ type: 'LOGOUT' }); appDispatch({ type: 'LOGOUT' });
setStatusMessage('Logged out.', 'info'); showToast('Logged out.', 'info');
} }
}, [appDispatch, setStatusMessage]); }, [appDispatch, showToast]);
return { tokenRef, refreshAccessToken, handleLogout }; return { tokenRef, refreshAccessToken, handleLogout };
}; };
@@ -1,4 +1,5 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useStatusToast } from '../../lib/context/StatusToastContext';
import { assignCorrespondentsBulk } from '../../lib/api/apiClient'; import { assignCorrespondentsBulk } from '../../lib/api/apiClient';
import type { Identifier } from '../../types/identifiers'; import type { Identifier } from '../../types/identifiers';
import type { MessageOptions } from '../../types/documents'; import type { MessageOptions } from '../../types/documents';
@@ -16,7 +17,6 @@ interface UseBulkDocumentActionsArgs {
resolveTargetDocumentIds: (ids?: Identifier[] | null) => Identifier[]; resolveTargetDocumentIds: (ids?: Identifier[] | null) => Identifier[];
correspondentLookupByName: Map<string, { id?: Identifier }>; correspondentLookupByName: Map<string, { id?: Identifier }>;
handleCorrespondentCreate: (payload: { name: string }) => Promise<{ id?: Identifier } | null>; handleCorrespondentCreate: (payload: { name: string }) => Promise<{ id?: Identifier } | null>;
setStatusMessage: (message: string, variant?: string) => void;
selectedDocumentIds?: Identifier[]; selectedDocumentIds?: Identifier[];
selectedFolderIds?: Identifier[]; selectedFolderIds?: Identifier[];
handleDocumentsDelete: (ids: Identifier[], options?: MessageOptions) => Promise<boolean>; handleDocumentsDelete: (ids: Identifier[], options?: MessageOptions) => Promise<boolean>;
@@ -29,7 +29,6 @@ const useBulkDocumentActions = ({
resolveTargetDocumentIds, resolveTargetDocumentIds,
correspondentLookupByName, correspondentLookupByName,
handleCorrespondentCreate, handleCorrespondentCreate,
setStatusMessage,
selectedDocumentIds, selectedDocumentIds,
selectedFolderIds, selectedFolderIds,
handleDocumentsDelete, handleDocumentsDelete,
@@ -37,16 +36,18 @@ const useBulkDocumentActions = ({
clearDocumentSelection, clearDocumentSelection,
updateDocumentCaches, updateDocumentCaches,
}: UseBulkDocumentActionsArgs) => { }: UseBulkDocumentActionsArgs) => {
const { showToast } = useStatusToast();
const handleBulkCorrespondentAdd = useCallback( const handleBulkCorrespondentAdd = useCallback(
async ({ name, input, documentIds }: { name?: string; input?: HTMLInputElement | null; documentIds?: Identifier[] }) => { async ({ name, input, documentIds }: { name?: string; input?: HTMLInputElement | null; documentIds?: Identifier[] }) => {
const trimmed = name?.trim?.() || ''; const trimmed = name?.trim?.() || '';
if (!trimmed) { if (!trimmed) {
setStatusMessage('Correspondent name is required.', 'error'); showToast('Correspondent name is required.', 'error');
return; return;
} }
const targets = resolveTargetDocumentIds(documentIds); const targets = resolveTargetDocumentIds(documentIds);
if (!targets.length) { if (!targets.length) {
setStatusMessage('Select documents before assigning correspondents.', 'error'); showToast('Select documents before assigning correspondents.', 'error');
return; return;
} }
@@ -60,7 +61,7 @@ const useBulkDocumentActions = ({
} }
if (!target?.id) { if (!target?.id) {
setStatusMessage('Unable to resolve correspondent.', 'error'); showToast('Unable to resolve correspondent.', 'error');
return; return;
} }
@@ -94,12 +95,12 @@ const useBulkDocumentActions = ({
const assignedSuffix = assigned === 1 ? '' : 's'; const assignedSuffix = assigned === 1 ? '' : 's';
if (removed > 0) { if (removed > 0) {
const removedSuffix = removed === 1 ? '' : 's'; const removedSuffix = removed === 1 ? '' : 's';
setStatusMessage( showToast(
`Correspondent assigned (${assigned}) and replaced ${removed} link${removedSuffix}.`, `Correspondent assigned (${assigned}) and replaced ${removed} link${removedSuffix}.`,
'success', 'success',
); );
} else { } else {
setStatusMessage( showToast(
`Correspondent assigned to ${assigned} document${assignedSuffix}.`, `Correspondent assigned to ${assigned} document${assignedSuffix}.`,
'success', 'success',
); );
@@ -113,7 +114,7 @@ const useBulkDocumentActions = ({
correspondentLookupByName, correspondentLookupByName,
handleCorrespondentCreate, handleCorrespondentCreate,
resolveTargetDocumentIds, resolveTargetDocumentIds,
setStatusMessage, showToast,
updateDocumentCaches, updateDocumentCaches,
], ],
); );
@@ -121,14 +122,14 @@ const useBulkDocumentActions = ({
const handleBulkCorrespondentRemove = useCallback( const handleBulkCorrespondentRemove = useCallback(
async ({ assignments = [], documentIds }: { assignments?: CorrespondentAssignment[]; documentIds?: Identifier[] }) => { async ({ assignments = [], documentIds }: { assignments?: CorrespondentAssignment[]; documentIds?: Identifier[] }) => {
if (!assignments.length) { if (!assignments.length) {
setStatusMessage('Select a correspondent to remove.', 'error'); showToast('Select a correspondent to remove.', 'error');
return; return;
} }
const targets = resolveTargetDocumentIds(documentIds); const targets = resolveTargetDocumentIds(documentIds);
if (!targets.length) { if (!targets.length) {
setStatusMessage('Select documents before removing correspondents.', 'error'); showToast('Select documents before removing correspondents.', 'error');
return; return;
} }
@@ -162,18 +163,18 @@ const useBulkDocumentActions = ({
if (removed > 0) { if (removed > 0) {
const removedSuffix = removed === 1 ? '' : 's'; const removedSuffix = removed === 1 ? '' : 's';
setStatusMessage( showToast(
`Correspondent removed from ${removed} link${removedSuffix}.`, `Correspondent removed from ${removed} link${removedSuffix}.`,
'success', 'success',
); );
} else if (assigned > 0) { } else if (assigned > 0) {
const assignedSuffix = assigned === 1 ? '' : 's'; const assignedSuffix = assigned === 1 ? '' : 's';
setStatusMessage(`Correspondent updated ${assigned} link${assignedSuffix}.`, 'info'); showToast(`Correspondent updated ${assigned} link${assignedSuffix}.`, 'info');
} else { } else {
setStatusMessage('No correspondents changed.', 'info'); showToast('No correspondents changed.', 'info');
} }
}, },
[resolveTargetDocumentIds, setStatusMessage, updateDocumentCaches], [resolveTargetDocumentIds, showToast, updateDocumentCaches],
); );
const handleDeleteSelection = useCallback(async () => { const handleDeleteSelection = useCallback(async () => {
@@ -217,7 +218,7 @@ const useBulkDocumentActions = ({
} }
if (!docsOk || !foldersOk) { if (!docsOk || !foldersOk) {
setStatusMessage('Some items could not be deleted. Ensure folders are empty before deletion.', 'error'); showToast('Some items could not be deleted. Ensure folders are empty before deletion.', 'error');
return; return;
} }
@@ -231,14 +232,14 @@ const useBulkDocumentActions = ({
successParts.push(folderIds.length === 1 ? 'Folder deleted.' : 'Folders deleted.'); successParts.push(folderIds.length === 1 ? 'Folder deleted.' : 'Folders deleted.');
} }
setStatusMessage(successParts.join(' '), 'success'); showToast(successParts.join(' '), 'success');
}, [ }, [
clearDocumentSelection, clearDocumentSelection,
handleDocumentsDelete, handleDocumentsDelete,
handleFolderDelete, handleFolderDelete,
selectedDocumentIds, selectedDocumentIds,
selectedFolderIds, selectedFolderIds,
setStatusMessage, showToast,
]); ]);
return { return {
@@ -1,22 +1,22 @@
import { MutableRefObject, useCallback, useState } from 'react'; import { MutableRefObject, useCallback, useState } from 'react';
import { useStatusToast } from '../../lib/context/StatusToastContext';
import type { Correspondent } from '../../types/documents'; import type { Correspondent } from '../../types/documents';
import { listCorrespondents, createCorrespondent, updateCorrespondent, deleteCorrespondent } from '../../lib/api/apiClient'; import { listCorrespondents, createCorrespondent, updateCorrespondent, deleteCorrespondent } from '../../lib/api/apiClient';
interface UseCorrespondentsOptions { interface UseCorrespondentsOptions {
notifyApiError: (error: unknown, fallback: string) => void; notifyApiError: (error: unknown, fallback: string) => void;
setStatusMessage: (message: string, variant?: string) => void;
tenantIdRef: MutableRefObject<string | null>; tenantIdRef: MutableRefObject<string | null>;
mapDocumentCaches?: (mapper: (doc: any) => any) => void; mapDocumentCaches?: (mapper: (doc: any) => any) => void;
} }
const useCorrespondents = ({ const useCorrespondents = ({
notifyApiError, notifyApiError,
setStatusMessage,
tenantIdRef, tenantIdRef,
mapDocumentCaches, mapDocumentCaches,
}: UseCorrespondentsOptions) => { }: UseCorrespondentsOptions) => {
const [correspondents, setCorrespondents] = useState<Correspondent[]>([]); const [correspondents, setCorrespondents] = useState<Correspondent[]>([]);
const { showToast } = useStatusToast();
const refreshCorrespondents = useCallback(async () => { const refreshCorrespondents = useCallback(async () => {
const requestTenantId = tenantIdRef.current; const requestTenantId = tenantIdRef.current;
@@ -56,7 +56,7 @@ const useCorrespondents = ({
try { try {
await updateCorrespondent(correspondentId, payload); await updateCorrespondent(correspondentId, payload);
await refreshCorrespondents(); await refreshCorrespondents();
setStatusMessage('Correspondent updated.', 'success'); showToast('Correspondent updated.', 'success');
return true; return true;
} catch (error) { } catch (error) {
const message = error.response?.data?.error || 'Failed to update correspondent.'; const message = error.response?.data?.error || 'Failed to update correspondent.';
@@ -64,7 +64,7 @@ const useCorrespondents = ({
throw new Error(message); throw new Error(message);
} }
}, },
[notifyApiError, refreshCorrespondents, setStatusMessage], [notifyApiError, refreshCorrespondents, showToast],
); );
const handleCorrespondentCreate = useCallback( const handleCorrespondentCreate = useCallback(
@@ -76,7 +76,7 @@ const useCorrespondents = ({
try { try {
const data = await createCorrespondent({ name: trimmed }); const data = await createCorrespondent({ name: trimmed });
await refreshCorrespondents(); await refreshCorrespondents();
setStatusMessage('Correspondent created.', 'success'); showToast('Correspondent created.', 'success');
return data; return data;
} catch (error) { } catch (error) {
const message = error.response?.data?.error || 'Failed to create correspondent.'; const message = error.response?.data?.error || 'Failed to create correspondent.';
@@ -84,7 +84,7 @@ const useCorrespondents = ({
throw new Error(message); throw new Error(message);
} }
}, },
[notifyApiError, refreshCorrespondents, setStatusMessage], [notifyApiError, refreshCorrespondents, showToast],
); );
const handleCorrespondentDelete = useCallback( const handleCorrespondentDelete = useCallback(
@@ -110,7 +110,7 @@ const useCorrespondents = ({
mapDocumentCaches?.(stripFromDoc); mapDocumentCaches?.(stripFromDoc);
setStatusMessage('Correspondent deleted.', 'success'); showToast('Correspondent deleted.', 'success');
return true; return true;
} catch (error) { } catch (error) {
const message = error.response?.data?.error || 'Failed to delete correspondent.'; const message = error.response?.data?.error || 'Failed to delete correspondent.';
@@ -118,7 +118,7 @@ const useCorrespondents = ({
throw new Error(message); throw new Error(message);
} }
}, },
[mapDocumentCaches, notifyApiError, refreshCorrespondents, setStatusMessage], [mapDocumentCaches, notifyApiError, refreshCorrespondents, showToast],
); );
return { return {
@@ -1,5 +1,6 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import type { Dispatch, MutableRefObject, SetStateAction } from 'react'; import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
import { useStatusToast } from '../../lib/context/StatusToastContext';
import { DEFAULT_FOLDER_NAME } from '../../app/workspaceUtils'; import { DEFAULT_FOLDER_NAME } from '../../app/workspaceUtils';
import { getEntryId, isDocumentEntry } from '../../app/entryKey'; import { getEntryId, isDocumentEntry } from '../../app/entryKey';
import { import {
@@ -19,8 +20,6 @@ import type { Document, MessageOptions } from '../../types/documents';
type FolderId = FolderIdentifier | 'root'; type FolderId = FolderIdentifier | 'root';
type NullableFolderId = FolderId | null; type NullableFolderId = FolderId | null;
type StatusLevel = 'success' | 'error' | 'info' | string;
type DocumentCacheMapper = ( type DocumentCacheMapper = (
doc: Document | null, doc: Document | null,
) => Document | null; ) => Document | null;
@@ -43,8 +42,6 @@ type CloseDocumentPreview = () => void;
type NotifyApiError = (error: unknown, fallbackMessage?: string) => void; type NotifyApiError = (error: unknown, fallbackMessage?: string) => void;
type SetStatusMessage = (message: string, level?: StatusLevel) => void;
interface Tag { interface Tag {
id: DocumentId; id: DocumentId;
label: string; label: string;
@@ -94,7 +91,6 @@ interface UseDocumentMutationsArgs {
setFocusedEntryKey: Dispatch<SetStateAction<string | null>>; setFocusedEntryKey: Dispatch<SetStateAction<string | null>>;
focusedEntryKey: string | null; focusedEntryKey: string | null;
notifyApiError: NotifyApiError; notifyApiError: NotifyApiError;
setStatusMessage: SetStatusMessage;
mapDocumentCaches: MapDocumentCaches; mapDocumentCaches: MapDocumentCaches;
folderNodes: Map<FolderId, FolderNode>; folderNodes: Map<FolderId, FolderNode>;
setFolderNodes: Dispatch<SetStateAction<Map<FolderId, FolderNode>>>; setFolderNodes: Dispatch<SetStateAction<Map<FolderId, FolderNode>>>;
@@ -165,7 +161,6 @@ const useDocumentMutations = ({
setFocusedEntryKey, setFocusedEntryKey,
focusedEntryKey, focusedEntryKey,
notifyApiError, notifyApiError,
setStatusMessage,
mapDocumentCaches, mapDocumentCaches,
folderNodes, folderNodes,
setFolderNodes, setFolderNodes,
@@ -181,6 +176,8 @@ const useDocumentMutations = ({
extractDocumentFromResponse, extractDocumentFromResponse,
ingestDocuments, ingestDocuments,
}: UseDocumentMutationsArgs): UseDocumentMutationsResult => { }: UseDocumentMutationsArgs): UseDocumentMutationsResult => {
const { showToast } = useStatusToast();
const moveDocumentsToFolder = useCallback( const moveDocumentsToFolder = useCallback(
async (documentIds: Array<DocumentId | Document>, targetFolderId?: NullableFolderId) => { async (documentIds: Array<DocumentId | Document>, targetFolderId?: NullableFolderId) => {
const uniqueIds = Array.from( const uniqueIds = Array.from(
@@ -254,7 +251,7 @@ const useDocumentMutations = ({
const count = uniqueIds.length; const count = uniqueIds.length;
const suffix = count === 1 ? '' : 's'; const suffix = count === 1 ? '' : 's';
setStatusMessage(`Moved ${count} document${suffix} to ${targetLabel}.`, 'success'); showToast(`Moved ${count} document${suffix} to ${targetLabel}.`, 'success');
if (updatedDocsMap.size) { if (updatedDocsMap.size) {
mapDocumentCaches((doc) => { mapDocumentCaches((doc) => {
@@ -335,7 +332,7 @@ const useDocumentMutations = ({
setFocusedEntryKey, setFocusedEntryKey,
focusedEntryKey, focusedEntryKey,
notifyApiError, notifyApiError,
setStatusMessage, showToast,
mapDocumentCaches, mapDocumentCaches,
], ],
); );
@@ -343,19 +340,19 @@ const useDocumentMutations = ({
const handleThumbnailRegeneration = useCallback( const handleThumbnailRegeneration = useCallback(
async (documentId: DocumentId) => { async (documentId: DocumentId) => {
if (!token) { if (!token) {
setStatusMessage('Log in to manage assets.', 'error'); showToast('Log in to manage assets.', 'error');
return; return;
} }
try { try {
await queueDocumentReanalysis(documentId, { force: true }); await queueDocumentReanalysis(documentId, { force: true });
setStatusMessage('Document re-analysis queued.', 'info'); showToast('Document re-analysis queued.', 'info');
await refreshCurrentFolder(); await refreshCurrentFolder();
} catch (error) { } catch (error) {
const message = (error as Record<string, any>)?.response?.data?.error || 'Failed to request thumbnail generation.'; const message = (error as Record<string, any>)?.response?.data?.error || 'Failed to request thumbnail generation.';
notifyApiError(error, message); notifyApiError(error, message);
} }
}, },
[token, refreshCurrentFolder, notifyApiError, setStatusMessage], [token, refreshCurrentFolder, notifyApiError, showToast],
); );
const handleDocumentsDelete = useCallback( const handleDocumentsDelete = useCallback(
@@ -365,7 +362,7 @@ const useDocumentMutations = ({
} }
if (!token) { if (!token) {
setStatusMessage('Log in to manage documents.', 'error'); showToast('Log in to manage documents.', 'error');
return false; return false;
} }
@@ -380,7 +377,7 @@ const useDocumentMutations = ({
if (showMessage) { if (showMessage) {
const message = documentIds.length === 1 ? 'Document deleted.' : 'Documents deleted.'; const message = documentIds.length === 1 ? 'Document deleted.' : 'Documents deleted.';
setStatusMessage(message, 'success'); showToast(message, 'success');
} }
return true; return true;
} catch (error) { } catch (error) {
@@ -396,7 +393,7 @@ const useDocumentMutations = ({
previewDocumentId, previewDocumentId,
closeDocumentPreview, closeDocumentPreview,
notifyApiError, notifyApiError,
setStatusMessage, showToast,
], ],
); );
@@ -404,7 +401,7 @@ const useDocumentMutations = ({
async (documentId: DocumentId, nextTitle: string) => { async (documentId: DocumentId, nextTitle: string) => {
const trimmed = nextTitle?.trim?.() || ''; const trimmed = nextTitle?.trim?.() || '';
if (!trimmed) { if (!trimmed) {
setStatusMessage('Document title cannot be empty.', 'error'); showToast('Document title cannot be empty.', 'error');
return false; return false;
} }
try { try {
@@ -422,7 +419,7 @@ const useDocumentMutations = ({
}); });
} }
setStatusMessage('Document title updated.', 'success'); showToast('Document title updated.', 'success');
return true; return true;
} catch (error) { } catch (error) {
const message = (error as Record<string, any>)?.response?.data?.error || 'Failed to update document title.'; const message = (error as Record<string, any>)?.response?.data?.error || 'Failed to update document title.';
@@ -434,7 +431,7 @@ const useDocumentMutations = ({
extractDocumentFromResponse, extractDocumentFromResponse,
ingestDocuments, ingestDocuments,
notifyApiError, notifyApiError,
setStatusMessage, showToast,
updateDocumentCaches, updateDocumentCaches,
], ],
); );
@@ -458,7 +455,7 @@ const useDocumentMutations = ({
} }
const message = payload.issued_at ? 'Issued date updated.' : 'Issued date cleared.'; const message = payload.issued_at ? 'Issued date updated.' : 'Issued date cleared.';
setStatusMessage(message, 'success'); showToast(message, 'success');
return true; return true;
} catch (error) { } catch (error) {
const message = (error as Record<string, any>)?.response?.data?.error || 'Failed to update issued date.'; const message = (error as Record<string, any>)?.response?.data?.error || 'Failed to update issued date.';
@@ -470,7 +467,7 @@ const useDocumentMutations = ({
extractDocumentFromResponse, extractDocumentFromResponse,
ingestDocuments, ingestDocuments,
notifyApiError, notifyApiError,
setStatusMessage, showToast,
updateDocumentCaches, updateDocumentCaches,
], ],
); );
@@ -505,7 +502,7 @@ const useDocumentMutations = ({
} }
return { ...doc, tags: [...currentTags, cachedTag] }; return { ...doc, tags: [...currentTags, cachedTag] };
}); });
setStatusMessage('Tag assigned.', 'success'); showToast('Tag assigned.', 'success');
return true; return true;
} catch (error) { } catch (error) {
const message = (error as Record<string, any>)?.response?.data?.error || 'Failed to assign tag.'; const message = (error as Record<string, any>)?.response?.data?.error || 'Failed to assign tag.';
@@ -513,7 +510,7 @@ const useDocumentMutations = ({
return false; return false;
} }
}, },
[notifyApiError, setStatusMessage, updateDocumentCaches], [notifyApiError, showToast, updateDocumentCaches],
); );
const handleDocumentTagAdd = useCallback( const handleDocumentTagAdd = useCallback(
@@ -616,7 +613,7 @@ const useDocumentMutations = ({
try { try {
await deleteDocumentTag(documentId, tagId); await deleteDocumentTag(documentId, tagId);
applyTagRemovalToCaches(documentId, tagId); applyTagRemovalToCaches(documentId, tagId);
setStatusMessage('Tag removed.', 'success'); showToast('Tag removed.', 'success');
return true; return true;
} catch (error) { } catch (error) {
const message = (error as Record<string, any>)?.response?.data?.error || 'Failed to remove tag.'; const message = (error as Record<string, any>)?.response?.data?.error || 'Failed to remove tag.';
@@ -624,20 +621,20 @@ const useDocumentMutations = ({
return false; return false;
} }
}, },
[applyTagRemovalToCaches, notifyApiError, setStatusMessage], [applyTagRemovalToCaches, notifyApiError, showToast],
); );
const handleFolderDelete = useCallback( const handleFolderDelete = useCallback(
async (folderId?: FolderId, { showMessage = true }: MessageOptions = {}) => { async (folderId?: FolderId, { showMessage = true }: MessageOptions = {}) => {
if (!token) { if (!token) {
if (showMessage) { if (showMessage) {
setStatusMessage('Log in to manage folders.', 'error'); showToast('Log in to manage folders.', 'error');
} }
return false; return false;
} }
if (!folderId || folderId === 'root') { if (!folderId || folderId === 'root') {
if (showMessage) { if (showMessage) {
setStatusMessage('The root folder cannot be removed.', 'error'); showToast('The root folder cannot be removed.', 'error');
} }
return false; return false;
} }
@@ -648,7 +645,7 @@ const useDocumentMutations = ({
const hasDocs = (contents.documents || []).length > 0; const hasDocs = (contents.documents || []).length > 0;
if (hasChildren || hasDocs) { if (hasChildren || hasDocs) {
if (showMessage) { if (showMessage) {
setStatusMessage('Folder must be empty before it can be deleted.', 'error'); showToast('Folder must be empty before it can be deleted.', 'error');
} }
return false; return false;
} }
@@ -686,14 +683,14 @@ const useDocumentMutations = ({
} }
if (showMessage) { if (showMessage) {
setStatusMessage('Folder deleted.', 'success'); showToast('Folder deleted.', 'success');
} }
return true; return true;
} catch (error) { } catch (error) {
const message = (error as Record<string, any>)?.response?.data?.error || 'Failed to delete folder.'; const message = (error as Record<string, any>)?.response?.data?.error || 'Failed to delete folder.';
notifyApiError(error, message); notifyApiError(error, message);
if (showMessage) { if (showMessage) {
setStatusMessage(message, 'error'); showToast(message, 'error');
} }
return false; return false;
} }
@@ -706,7 +703,7 @@ const useDocumentMutations = ({
setSelectedFolder, setSelectedFolder,
setFolderNodes, setFolderNodes,
notifyApiError, notifyApiError,
setStatusMessage, showToast,
], ],
); );
@@ -38,7 +38,7 @@ import {
isDocumentEntry isDocumentEntry
} from '../../app/entryKey'; } from '../../app/entryKey';
import useDocumentsSearch from '../../app/useDocumentsSearch'; import useDocumentsSearch from '../../app/useDocumentsSearch';
import { useStatusToast, type ToastVariant } from '../../lib/context/StatusToastContext'; import { useStatusToast } from '../../lib/context/StatusToastContext';
import useAuthManager from './useAuthManager'; import useAuthManager from './useAuthManager';
import useTenantManager from './useTenantManager'; import useTenantManager from './useTenantManager';
import useDocuments from './useDocuments'; import useDocuments from './useDocuments';
@@ -144,33 +144,26 @@ const useDocumentsWorkspace = ({
? (tenantOptionsRaw as TenantOption[]) ? (tenantOptionsRaw as TenantOption[])
: []; : [];
const { showToast } = useStatusToast(); const { showToast } = useStatusToast();
const setStatusMessage = useCallback(
(message?: string | null, variant: ToastVariant = 'info') => {
if (message) {
showToast(message, variant);
}
},
[showToast],
);
const handleApiReport = useCallback(
({ message, variant }) => setStatusMessage(message, variant),
[setStatusMessage],
);
const reportApiError = useApiError({ const reportApiError = useApiError({
onReport: handleApiReport, onReport: useCallback(
({ message, variant }) => showToast(message, variant),
[showToast],
),
}); });
const notifyApiError = useCallback( const notifyApiError = useCallback(
(error, fallbackMessage, variant = 'error') => (error, fallbackMessage, variant = 'error') =>
reportApiError(error, { message: fallbackMessage, variant }), reportApiError(error, { message: fallbackMessage, variant }),
[reportApiError], [reportApiError],
); );
const [creatingFolder, setCreatingFolder] = useState(false); const [creatingFolder, setCreatingFolder] = useState(false);
const { tokenRef, handleLogout } = useAuthManager({ const { tokenRef, handleLogout } = useAuthManager({
token, token,
appStatus, appStatus,
appDispatch, appDispatch,
setStatusMessage,
}); });
@@ -514,9 +507,8 @@ const useDocumentsWorkspace = ({
setTags, setTags,
} = useTags({ } = useTags({
notifyApiError, notifyApiError,
setStatusMessage,
tagManager,
tenantIdRef, tenantIdRef,
tagManager,
setActiveTagFilters, setActiveTagFilters,
mapDocumentCaches, mapDocumentCaches,
}); });
@@ -544,7 +536,6 @@ const useDocumentsWorkspace = ({
setCorrespondents, setCorrespondents,
} = useCorrespondents({ } = useCorrespondents({
notifyApiError, notifyApiError,
setStatusMessage,
tenantIdRef, tenantIdRef,
mapDocumentCaches, mapDocumentCaches,
}); });
@@ -558,7 +549,6 @@ const useDocumentsWorkspace = ({
correspondents, correspondents,
handleCorrespondentCreate, handleCorrespondentCreate,
notifyApiError, notifyApiError,
setStatusMessage,
updateDocumentCaches, updateDocumentCaches,
}); });
@@ -573,7 +563,6 @@ const useDocumentsWorkspace = ({
revokePasskey, revokePasskey,
} = usePasskeys({ } = usePasskeys({
notifyApiError, notifyApiError,
setStatusMessage,
token, token,
}); });
@@ -606,7 +595,6 @@ const useDocumentsWorkspace = ({
refreshTags, refreshTags,
resolveTargetDocumentIds, resolveTargetDocumentIds,
notifyApiError, notifyApiError,
setStatusMessage,
updateDocumentCaches, updateDocumentCaches,
}); });
@@ -625,7 +613,6 @@ const useDocumentsWorkspace = ({
refreshCurrentFolder, refreshCurrentFolder,
shellRef, shellRef,
notifyApiError, notifyApiError,
setStatusMessage,
}); });
const { const {
@@ -761,7 +748,6 @@ const useDocumentsWorkspace = ({
setFocusedEntryKey, setFocusedEntryKey,
focusedEntryKey, focusedEntryKey,
notifyApiError, notifyApiError,
setStatusMessage,
mapDocumentCaches, mapDocumentCaches,
folderNodes, folderNodes,
setFolderNodes, setFolderNodes,
@@ -792,7 +778,6 @@ const useDocumentsWorkspace = ({
selectedFolder, selectedFolder,
setSelectedFolder, setSelectedFolder,
notifyApiError, notifyApiError,
setStatusMessage,
navigate, navigate,
handleFileDrop, handleFileDrop,
moveDocumentsToFolder, moveDocumentsToFolder,
@@ -905,7 +890,6 @@ const useDocumentsWorkspace = ({
resolveTargetDocumentIds, resolveTargetDocumentIds,
correspondentLookupByName, correspondentLookupByName,
handleCorrespondentCreate, handleCorrespondentCreate,
setStatusMessage,
selectedDocumentIds, selectedDocumentIds,
selectedFolderIds, selectedFolderIds,
handleDocumentsDelete, handleDocumentsDelete,
@@ -914,8 +898,6 @@ const useDocumentsWorkspace = ({
updateDocumentCaches, updateDocumentCaches,
}); });
const ensureAssetUrl = useCallback( const ensureAssetUrl = useCallback(
async (documentId, asset, { force = false } = {}) => { async (documentId, asset, { force = false } = {}) => {
if (!documentId || !asset?.id) { if (!documentId || !asset?.id) {
@@ -942,8 +924,6 @@ const useDocumentsWorkspace = ({
[assetManager, updateDocumentCaches, notifyApiError], [assetManager, updateDocumentCaches, notifyApiError],
); );
const handlePromptCreateFolder = useCallback(async (parentId?: Identifier | null) => { const handlePromptCreateFolder = useCallback(async (parentId?: Identifier | null) => {
if (creatingFolder) { if (creatingFolder) {
return; return;
@@ -954,19 +934,19 @@ const useDocumentsWorkspace = ({
} }
const trimmed = input.trim(); const trimmed = input.trim();
if (!trimmed) { if (!trimmed) {
setStatusMessage('Folder name cannot be empty.', 'error'); showToast('Folder name cannot be empty.', 'error');
return; return;
} }
setCreatingFolder(true); setCreatingFolder(true);
try { try {
const success = await handleFolderCreate(trimmed, parentId); const success = await handleFolderCreate(trimmed, parentId);
if (!success) { if (!success) {
setStatusMessage('Unable to create folder. Check the status message for details.', 'error'); showToast('Unable to create folder. Check the status message for details.', 'error');
} }
} finally { } finally {
setCreatingFolder(false); setCreatingFolder(false);
} }
}, [creatingFolder, handleFolderCreate, setStatusMessage]); }, [creatingFolder, handleFolderCreate, showToast]);
const { managementModals, openTagsModal, openCorrespondentsModal } = useManagementModals({ const { managementModals, openTagsModal, openCorrespondentsModal } = useManagementModals({
locationPathname: location.pathname, locationPathname: location.pathname,
@@ -980,7 +960,6 @@ const useDocumentsWorkspace = ({
onCorrespondentCreate: handleCorrespondentCreate, onCorrespondentCreate: handleCorrespondentCreate,
onCorrespondentUpdate: handleCorrespondentUpdate, onCorrespondentUpdate: handleCorrespondentUpdate,
onCorrespondentDelete: handleCorrespondentDelete, onCorrespondentDelete: handleCorrespondentDelete,
setStatusMessage,
}); });
const [settingsOpen, setSettingsOpen] = useState(false); const [settingsOpen, setSettingsOpen] = useState(false);
@@ -1126,7 +1105,6 @@ const useDocumentsWorkspace = ({
appDispatch, appDispatch,
currentTenantId, currentTenantId,
resetWorkspaceState, resetWorkspaceState,
setStatusMessage,
notifyApiError, notifyApiError,
refreshTags, refreshTags,
refreshCorrespondents, refreshCorrespondents,
@@ -1148,7 +1126,6 @@ const useDocumentsWorkspace = ({
}; };
const uiContext = { const uiContext = {
setStatusMessage,
notifyApiError, notifyApiError,
settingsOpen, settingsOpen,
openSettings, openSettings,
+8 -8
View File
@@ -1,4 +1,5 @@
import { MutableRefObject, useCallback, useState } from 'react'; import { MutableRefObject, useCallback, useState } from 'react';
import { useStatusToast } from '../../lib/context/StatusToastContext';
import type { TagId, TenantId } from '../../types/identifiers'; import type { TagId, TenantId } from '../../types/identifiers';
import type { Tag } from '../../types/documents'; import type { Tag } from '../../types/documents';
@@ -11,7 +12,6 @@ interface TagManagerInterface {
interface UseTagsOptions { interface UseTagsOptions {
// apiClient removed // apiClient removed
notifyApiError: (error: unknown, fallback: string) => void; notifyApiError: (error: unknown, fallback: string) => void;
setStatusMessage: (message: string, variant?: string) => void;
tagManager: TagManagerInterface; tagManager: TagManagerInterface;
tenantIdRef: MutableRefObject<TenantId | null>; tenantIdRef: MutableRefObject<TenantId | null>;
setActiveTagFilters: (updater: (prev: Array<TagId>) => Array<TagId>) => void; setActiveTagFilters: (updater: (prev: Array<TagId>) => Array<TagId>) => void;
@@ -21,13 +21,13 @@ interface UseTagsOptions {
const useTags = ({ const useTags = ({
// apiClient removed // apiClient removed
notifyApiError, notifyApiError,
setStatusMessage,
tagManager, tagManager,
tenantIdRef, tenantIdRef,
setActiveTagFilters, setActiveTagFilters,
mapDocumentCaches, mapDocumentCaches,
}: UseTagsOptions) => { }: UseTagsOptions) => {
const [tags, setTags] = useState<Tag[]>([]); const [tags, setTags] = useState<Tag[]>([]);
const { showToast } = useStatusToast();
const refreshTags = useCallback(async () => { const refreshTags = useCallback(async () => {
const requestTenantId = tenantIdRef.current; const requestTenantId = tenantIdRef.current;
@@ -66,7 +66,7 @@ const useTags = ({
try { try {
await updateTag(tagId, payload); await updateTag(tagId, payload);
await refreshTags(); await refreshTags();
setStatusMessage('Tag updated.', 'success'); showToast('Tag updated.', 'success');
return true; return true;
} catch (error) { } catch (error) {
const message = error.response?.data?.error || 'Failed to update tag.'; const message = error.response?.data?.error || 'Failed to update tag.';
@@ -74,7 +74,7 @@ const useTags = ({
throw new Error(message); throw new Error(message);
} }
}, },
[notifyApiError, refreshTags, setStatusMessage], [notifyApiError, refreshTags, showToast],
); );
const handleTagCreate = useCallback( const handleTagCreate = useCallback(
@@ -83,14 +83,14 @@ const useTags = ({
try { try {
await createTag(payload); await createTag(payload);
await refreshTags(); await refreshTags();
setStatusMessage('Tag created.', 'success'); showToast('Tag created.', 'success');
} catch (error) { } catch (error) {
const message = error.response?.data?.error || 'Failed to create tag.'; const message = error.response?.data?.error || 'Failed to create tag.';
notifyApiError(error, message); notifyApiError(error, message);
throw new Error(message); throw new Error(message);
} }
}, },
[notifyApiError, refreshTags, setStatusMessage, tagManager], [notifyApiError, refreshTags, showToast, tagManager],
); );
const handleTagDelete = useCallback( const handleTagDelete = useCallback(
@@ -117,7 +117,7 @@ const useTags = ({
mapDocumentCaches?.(stripTagFromDoc); mapDocumentCaches?.(stripTagFromDoc);
await refreshTags(); await refreshTags();
setStatusMessage('Tag deleted.', 'success'); showToast('Tag deleted.', 'success');
return true; return true;
} catch (error) { } catch (error) {
const message = error.response?.data?.error || 'Failed to delete tag.'; const message = error.response?.data?.error || 'Failed to delete tag.';
@@ -125,7 +125,7 @@ const useTags = ({
throw new Error(message); throw new Error(message);
} }
}, },
[mapDocumentCaches, notifyApiError, refreshTags, setActiveTagFilters, setStatusMessage], [mapDocumentCaches, notifyApiError, refreshTags, setActiveTagFilters, showToast],
); );
return { return {
@@ -1,6 +1,7 @@
import { MutableRefObject, useCallback } from 'react'; import { MutableRefObject, useCallback } from 'react';
import type { NavigateFunction } from 'react-router-dom'; import type { NavigateFunction } from 'react-router-dom';
import type { FolderId, TenantId } from '../../types/identifiers'; import type { FolderId, TenantId } from '../../types/identifiers';
import { useStatusToast } from '../../lib/context/StatusToastContext';
import { api, listTenants, switchTenant } from '../../lib/api/apiClient'; import { api, listTenants, switchTenant } from '../../lib/api/apiClient';
@@ -13,7 +14,6 @@ interface UseTenantManagerOptions {
appDispatch: (action: any) => void; appDispatch: (action: any) => void;
currentTenantId: TenantId | null; currentTenantId: TenantId | null;
resetWorkspaceState: () => void; resetWorkspaceState: () => void;
setStatusMessage: (message: string, variant?: string) => void;
notifyApiError: (error: unknown, message: string) => void; notifyApiError: (error: unknown, message: string) => void;
refreshTags: () => Promise<void>; refreshTags: () => Promise<void>;
refreshCorrespondents: () => Promise<void>; refreshCorrespondents: () => Promise<void>;
@@ -28,7 +28,6 @@ const useTenantManager = ({
appDispatch, appDispatch,
currentTenantId, currentTenantId,
resetWorkspaceState, resetWorkspaceState,
setStatusMessage,
notifyApiError, notifyApiError,
refreshTags, refreshTags,
refreshCorrespondents, refreshCorrespondents,
@@ -38,6 +37,7 @@ const useTenantManager = ({
tokenRef, tokenRef,
tenantIdRef, tenantIdRef,
}: UseTenantManagerOptions) => { }: UseTenantManagerOptions) => {
const { showToast } = useStatusToast();
const handleTenantSelect = useCallback( const handleTenantSelect = useCallback(
async (tenantOption: TenantOption | null, { refreshOnly = false }: { refreshOnly?: boolean } = {}) => { async (tenantOption: TenantOption | null, { refreshOnly = false }: { refreshOnly?: boolean } = {}) => {
const requestedTenantId = tenantOption?.id ?? null; const requestedTenantId = tenantOption?.id ?? null;
@@ -88,7 +88,7 @@ const useTenantManager = ({
await loadFolder('root', { preserveSearch: false }); await loadFolder('root', { preserveSearch: false });
const tenantLabel = data?.tenant?.name || data?.tenant?.id || 'tenant'; const tenantLabel = data?.tenant?.name || data?.tenant?.id || 'tenant';
setStatusMessage(`Switched to ${tenantLabel}.`, 'info'); showToast(`Switched to ${tenantLabel}.`, 'info');
} catch (error) { } catch (error) {
notifyApiError(error, 'Failed to switch tenant.'); notifyApiError(error, 'Failed to switch tenant.');
} }
@@ -103,7 +103,7 @@ const useTenantManager = ({
refreshCorrespondents, refreshCorrespondents,
refreshTags, refreshTags,
resetWorkspaceState, resetWorkspaceState,
setStatusMessage, showToast,
tenantIdRef, tenantIdRef,
tokenRef, tokenRef,
], ],
@@ -1,4 +1,5 @@
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useStatusToast } from '../../../lib/context/StatusToastContext';
import type { Identifier } from '../../../types/identifiers'; import type { Identifier } from '../../../types/identifiers';
@@ -14,7 +15,6 @@ interface UseDocumentCorrespondentActionsArgs {
correspondents: CorrespondentOption[]; correspondents: CorrespondentOption[];
handleCorrespondentCreate: (payload: { name: string }) => Promise<CorrespondentOption | null>; handleCorrespondentCreate: (payload: { name: string }) => Promise<CorrespondentOption | null>;
notifyApiError: (error: unknown, fallback: string) => void; notifyApiError: (error: unknown, fallback: string) => void;
setStatusMessage: (message: string, variant?: string) => void;
updateDocumentCaches?: ( updateDocumentCaches?: (
id: Identifier, id: Identifier,
updater: (doc: { correspondents?: CorrespondentOption[] } | null) => { correspondents?: CorrespondentOption[] } | null, updater: (doc: { correspondents?: CorrespondentOption[] } | null) => { correspondents?: CorrespondentOption[] } | null,
@@ -25,9 +25,10 @@ const useDocumentCorrespondentActions = ({
correspondents, correspondents,
handleCorrespondentCreate, handleCorrespondentCreate,
notifyApiError, notifyApiError,
setStatusMessage,
updateDocumentCaches, updateDocumentCaches,
}: UseDocumentCorrespondentActionsArgs) => { }: UseDocumentCorrespondentActionsArgs) => {
const { showToast } = useStatusToast();
const correspondentLookupByName = useMemo(() => { const correspondentLookupByName = useMemo(() => {
const map = new Map<string, CorrespondentOption>(); const map = new Map<string, CorrespondentOption>();
correspondents.forEach((correspondent) => { correspondents.forEach((correspondent) => {
@@ -71,7 +72,7 @@ const useDocumentCorrespondentActions = ({
}); });
} }
if (notify) { if (notify) {
setStatusMessage('Correspondent assigned.', 'success'); showToast('Correspondent assigned.', 'success');
} }
return true; return true;
} catch (error) { } catch (error) {
@@ -80,7 +81,7 @@ const useDocumentCorrespondentActions = ({
throw new Error(message); throw new Error(message);
} }
}, },
[correspondents, notifyApiError, setStatusMessage, updateDocumentCaches], [correspondents, notifyApiError, showToast, updateDocumentCaches],
); );
const handleCorrespondentRemove = useCallback( const handleCorrespondentRemove = useCallback(
@@ -103,7 +104,7 @@ const useDocumentCorrespondentActions = ({
}); });
} }
if (notify) { if (notify) {
setStatusMessage('Correspondent removed.', 'success'); showToast('Correspondent removed.', 'success');
} }
return true; return true;
} catch (error) { } catch (error) {
@@ -112,7 +113,7 @@ const useDocumentCorrespondentActions = ({
throw new Error(message); throw new Error(message);
} }
}, },
[notifyApiError, setStatusMessage, updateDocumentCaches], [notifyApiError, showToast, updateDocumentCaches],
); );
const normalizeOption = ( const normalizeOption = (
@@ -138,7 +139,7 @@ const useDocumentCorrespondentActions = ({
} }
const trimmed = name?.trim?.() || ''; const trimmed = name?.trim?.() || '';
if (!trimmed) { if (!trimmed) {
setStatusMessage('Correspondent name is required.', 'error'); showToast('Correspondent name is required.', 'error');
return; return;
} }
@@ -152,7 +153,7 @@ const useDocumentCorrespondentActions = ({
} }
if (!target?.id) { if (!target?.id) {
setStatusMessage('Unable to resolve correspondent.', 'error'); showToast('Unable to resolve correspondent.', 'error');
return; return;
} }
@@ -166,7 +167,7 @@ const useDocumentCorrespondentActions = ({
input.value = ''; input.value = '';
} }
} catch (error) { } catch (error) {
setStatusMessage('Failed to assign correspondent.', 'error'); showToast('Failed to assign correspondent.', 'error');
console.error('[documents] assign correspondent failed', error); console.error('[documents] assign correspondent failed', error);
} }
}, },
@@ -174,7 +175,7 @@ const useDocumentCorrespondentActions = ({
correspondentLookupByName, correspondentLookupByName,
handleCorrespondentCreate, handleCorrespondentCreate,
handleDocumentCorrespondentAttach, handleDocumentCorrespondentAttach,
setStatusMessage, showToast,
], ],
); );
@@ -1,5 +1,6 @@
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import type { DragEvent } from 'react'; import type { DragEvent } from 'react';
import { useStatusToast } from '../../../lib/context/StatusToastContext';
import { DEFAULT_FOLDER_NAME, hasFiles } from '../../../app/workspaceUtils'; import { DEFAULT_FOLDER_NAME, hasFiles } from '../../../app/workspaceUtils';
import { import {
createFolder, createFolder,
@@ -35,7 +36,6 @@ interface UseFolderTreeActionsOptions {
selectedFolder: FolderKey; selectedFolder: FolderKey;
setSelectedFolder: (folderId: FolderKey) => void; setSelectedFolder: (folderId: FolderKey) => void;
notifyApiError: (error: unknown, message?: string) => void; notifyApiError: (error: unknown, message?: string) => void;
setStatusMessage: (message: string, level?: string) => void;
navigate?: (path: string, options?: { replace?: boolean }) => void; navigate?: (path: string, options?: { replace?: boolean }) => void;
handleFileDrop: (dataTransfer: DataTransfer, folderId: FolderKey) => Promise<void> | void; handleFileDrop: (dataTransfer: DataTransfer, folderId: FolderKey) => Promise<void> | void;
moveDocumentsToFolder: (docIds: FolderId[], folderId: FolderKey) => Promise<void>; moveDocumentsToFolder: (docIds: FolderId[], folderId: FolderKey) => Promise<void>;
@@ -54,7 +54,6 @@ const useFolderTreeActions = ({
selectedFolder, selectedFolder,
setSelectedFolder, setSelectedFolder,
notifyApiError, notifyApiError,
setStatusMessage,
navigate, navigate,
handleFileDrop, handleFileDrop,
moveDocumentsToFolder, moveDocumentsToFolder,
@@ -65,11 +64,13 @@ const useFolderTreeActions = ({
isInvalidFolderDrop, isInvalidFolderDrop,
setCreatingFolder, setCreatingFolder,
}: UseFolderTreeActionsOptions) => { }: UseFolderTreeActionsOptions) => {
const { showToast } = useStatusToast();
const moveFolder = useCallback( const moveFolder = useCallback(
async (folderId: FolderKey, targetFolderId: FolderKey | null) => { async (folderId: FolderKey, targetFolderId: FolderKey | null) => {
const node = folderNodes.get(folderId); const node = folderNodes.get(folderId);
if (!node) { if (!node) {
setStatusMessage('Folder metadata unavailable. Try refreshing.', 'error'); showToast('Folder metadata unavailable. Try refreshing.', 'error');
return; return;
} }
@@ -134,7 +135,7 @@ const useFolderTreeActions = ({
setSelectedFolder(folderId); setSelectedFolder(folderId);
} }
setStatusMessage('Folder moved.', 'success'); showToast('Folder moved.', 'success');
} catch (error) { } catch (error) {
const message = error.response?.data?.error || 'Failed to move folder.'; const message = error.response?.data?.error || 'Failed to move folder.';
notifyApiError(error, message); notifyApiError(error, message);
@@ -146,7 +147,7 @@ const useFolderTreeActions = ({
selectedFolder, selectedFolder,
setFolderNodes, setFolderNodes,
setSelectedFolder, setSelectedFolder,
setStatusMessage, showToast,
], ],
); );
@@ -180,12 +181,12 @@ const useFolderTreeActions = ({
const handleFolderRename = useCallback( const handleFolderRename = useCallback(
async (folderId: FolderKey, nextName: string) => { async (folderId: FolderKey, nextName: string) => {
if (!token) { if (!token) {
setStatusMessage('Log in to rename folders.', 'error'); showToast('Log in to rename folders.', 'error');
return false; return false;
} }
const trimmed = nextName?.trim?.() || ''; const trimmed = nextName?.trim?.() || '';
if (!trimmed) { if (!trimmed) {
setStatusMessage('Folder name cannot be empty.', 'error'); showToast('Folder name cannot be empty.', 'error');
return false; return false;
} }
try { try {
@@ -200,7 +201,7 @@ const useFolderTreeActions = ({
return next; return next;
}); });
setStatusMessage('Folder renamed.', 'success'); showToast('Folder renamed.', 'success');
return true; return true;
} catch (error) { } catch (error) {
const message = error.response?.data?.error || 'Failed to rename folder.'; const message = error.response?.data?.error || 'Failed to rename folder.';
@@ -211,7 +212,7 @@ const useFolderTreeActions = ({
[ [
notifyApiError, notifyApiError,
setFolderNodes, setFolderNodes,
setStatusMessage, showToast,
token, token,
], ],
); );
@@ -219,11 +220,11 @@ const useFolderTreeActions = ({
const handleFolderCreate = useCallback( const handleFolderCreate = useCallback(
async (name: string, parentId?: FolderKey | null) => { async (name: string, parentId?: FolderKey | null) => {
if (!token) { if (!token) {
setStatusMessage('Log in to create folders.', 'error'); showToast('Log in to create folders.', 'error');
return false; return false;
} }
if (!name.trim()) { if (!name.trim()) {
setStatusMessage('Folder name cannot be empty.', 'error'); showToast('Folder name cannot be empty.', 'error');
return false; return false;
} }
@@ -243,7 +244,7 @@ const useFolderTreeActions = ({
if (!folderData?.id) { if (!folderData?.id) {
throw new Error('Folder creation failed.'); throw new Error('Folder creation failed.');
} }
setStatusMessage('Folder created.', 'success'); showToast('Folder created.', 'success');
setFolderNodes((prev) => { setFolderNodes((prev) => {
const next = new Map(prev); const next = new Map(prev);
const parentId = folderData.parent_id ?? payload.parent_id ?? 'root'; const parentId = folderData.parent_id ?? payload.parent_id ?? 'root';
@@ -278,7 +279,7 @@ const useFolderTreeActions = ({
} finally { } finally {
setCreatingFolder(false); setCreatingFolder(false);
if (!succeeded) { if (!succeeded) {
setStatusMessage('Folder creation failed.', 'error'); showToast('Folder creation failed.', 'error');
} }
} }
}, },
@@ -288,7 +289,7 @@ const useFolderTreeActions = ({
selectedFolder, selectedFolder,
setCreatingFolder, setCreatingFolder,
setFolderNodes, setFolderNodes,
setStatusMessage, showToast,
token, token,
], ],
); );
@@ -297,13 +298,13 @@ const useFolderTreeActions = ({
async (folderId: FolderKey, { showMessage = true }: MessageOptions = {}) => { async (folderId: FolderKey, { showMessage = true }: MessageOptions = {}) => {
if (!token) { if (!token) {
if (showMessage) { if (showMessage) {
setStatusMessage('Log in to manage folders.', 'error'); showToast('Log in to manage folders.', 'error');
} }
return false; return false;
} }
if (!folderId || folderId === 'root') { if (!folderId || folderId === 'root') {
if (showMessage) { if (showMessage) {
setStatusMessage('The root folder cannot be removed.', 'error'); showToast('The root folder cannot be removed.', 'error');
} }
return false; return false;
} }
@@ -337,14 +338,14 @@ const useFolderTreeActions = ({
} }
if (showMessage) { if (showMessage) {
setStatusMessage('Folder deleted.', 'success'); showToast('Folder deleted.', 'success');
} }
return true; return true;
} catch (error) { } catch (error) {
const message = error.response?.data?.error || 'Failed to delete folder.'; const message = error.response?.data?.error || 'Failed to delete folder.';
notifyApiError(error, message); notifyApiError(error, message);
if (showMessage) { if (showMessage) {
setStatusMessage(message, 'error'); showToast(message, 'error');
} }
return false; return false;
} }
@@ -356,7 +357,7 @@ const useFolderTreeActions = ({
selectedFolder, selectedFolder,
setFolderNodes, setFolderNodes,
setSelectedFolder, setSelectedFolder,
setStatusMessage, showToast,
], ],
); );
@@ -404,7 +405,7 @@ const useFolderTreeActions = ({
setDraggedFolderId(null); setDraggedFolderId(null);
const invalidMove = folderIds.some((sourceId) => isInvalidFolderDrop(sourceId, folderId)); const invalidMove = folderIds.some((sourceId) => isInvalidFolderDrop(sourceId, folderId));
if (invalidMove) { if (invalidMove) {
setStatusMessage( showToast(
'Cannot move a folder into itself or one of its descendants.', 'Cannot move a folder into itself or one of its descendants.',
'error', 'error',
); );
@@ -492,7 +493,7 @@ const useFolderTreeActions = ({
selectedFolder, selectedFolder,
setDraggedDocumentIds, setDraggedDocumentIds,
setDraggedFolderId, setDraggedFolderId,
setStatusMessage, showToast,
], ],
); );
@@ -1,4 +1,5 @@
import { useCallback } from 'react'; import { useCallback } from 'react';
import { useStatusToast } from '../../../lib/context/StatusToastContext';
import type { Identifier } from '../../../types/identifiers'; import type { Identifier } from '../../../types/identifiers';
@@ -20,7 +21,6 @@ interface UseDocumentTaggingArgs {
refreshTags: () => Promise<void> | void; refreshTags: () => Promise<void> | void;
resolveTargetDocumentIds: (ids?: Identifier[] | null) => Identifier[]; resolveTargetDocumentIds: (ids?: Identifier[] | null) => Identifier[];
notifyApiError: (error: unknown, message: string) => void; notifyApiError: (error: unknown, message: string) => void;
setStatusMessage: (message: string, variant?: string) => void;
updateDocumentCaches?: (id: Identifier, updater: (doc: TagRecord | null) => TagRecord | null) => void; updateDocumentCaches?: (id: Identifier, updater: (doc: TagRecord | null) => TagRecord | null) => void;
} }
@@ -44,9 +44,10 @@ const useDocumentTagActions = ({
refreshTags, refreshTags,
resolveTargetDocumentIds, resolveTargetDocumentIds,
notifyApiError, notifyApiError,
setStatusMessage,
updateDocumentCaches, updateDocumentCaches,
}: UseDocumentTaggingArgs) => { }: UseDocumentTaggingArgs) => {
const { showToast } = useStatusToast();
const bulkTagOperation = useCallback( const bulkTagOperation = useCallback(
async ({ labels, action, documentIds }: BulkTagOperationArgs): Promise<BulkTagOperationResult> => { async ({ labels, action, documentIds }: BulkTagOperationArgs): Promise<BulkTagOperationResult> => {
const normalized = labels.map((label) => label.trim()).filter((label) => label.length > 0); const normalized = labels.map((label) => label.trim()).filter((label) => label.length > 0);
@@ -184,12 +185,12 @@ const useDocumentTagActions = ({
async ({ label, input, documentIds }: { label?: string; input?: HTMLInputElement | null; documentIds?: Identifier[] }) => { async ({ label, input, documentIds }: { label?: string; input?: HTMLInputElement | null; documentIds?: Identifier[] }) => {
const trimmed = label?.trim?.() || ''; const trimmed = label?.trim?.() || '';
if (!trimmed) { if (!trimmed) {
setStatusMessage('Enter a tag label.', 'error'); showToast('Enter a tag label.', 'error');
return; return;
} }
const targetIds = resolveTargetDocumentIds(documentIds); const targetIds = resolveTargetDocumentIds(documentIds);
if (!targetIds.length) { if (!targetIds.length) {
setStatusMessage('Select documents before assigning tags.', 'error'); showToast('Select documents before assigning tags.', 'error');
return; return;
} }
const result = await bulkTagOperation({ const result = await bulkTagOperation({
@@ -199,7 +200,7 @@ const useDocumentTagActions = ({
}); });
if (result?.ok) { if (result?.ok) {
const { tagCount, docsCount } = result; const { tagCount, docsCount } = result;
setStatusMessage( showToast(
`Assigned ${tagCount} tag${tagCount === 1 ? '' : 's'} to ${docsCount} document${docsCount === 1 ? '' : 's' `Assigned ${tagCount} tag${tagCount === 1 ? '' : 's'} to ${docsCount} document${docsCount === 1 ? '' : 's'
}.`, }.`,
'success', 'success',
@@ -209,19 +210,19 @@ const useDocumentTagActions = ({
} }
} }
}, },
[bulkTagOperation, resolveTargetDocumentIds, setStatusMessage], [bulkTagOperation, resolveTargetDocumentIds, showToast],
); );
const handleBulkTagRemoveFromDetail = useCallback( const handleBulkTagRemoveFromDetail = useCallback(
async ({ label, input, documentIds }: { label?: string; input?: HTMLInputElement | null; documentIds?: Identifier[] }) => { async ({ label, input, documentIds }: { label?: string; input?: HTMLInputElement | null; documentIds?: Identifier[] }) => {
const trimmed = label?.trim?.() || ''; const trimmed = label?.trim?.() || '';
if (!trimmed) { if (!trimmed) {
setStatusMessage('Enter a tag label to remove.', 'error'); showToast('Enter a tag label to remove.', 'error');
return; return;
} }
const targetIds = resolveTargetDocumentIds(documentIds); const targetIds = resolveTargetDocumentIds(documentIds);
if (!targetIds.length) { if (!targetIds.length) {
setStatusMessage('Select documents before removing tags.', 'error'); showToast('Select documents before removing tags.', 'error');
return; return;
} }
const result = await bulkTagOperation({ const result = await bulkTagOperation({
@@ -231,7 +232,7 @@ const useDocumentTagActions = ({
}); });
if (result?.ok) { if (result?.ok) {
const { docsCount } = result; const { docsCount } = result;
setStatusMessage( showToast(
`Removed tags from ${docsCount} document${docsCount === 1 ? '' : 's'}.`, `Removed tags from ${docsCount} document${docsCount === 1 ? '' : 's'}.`,
'success', 'success',
); );
@@ -239,17 +240,17 @@ const useDocumentTagActions = ({
input.value = ''; input.value = '';
} }
} else if (result?.reason === 'tag-missing') { } else if (result?.reason === 'tag-missing') {
setStatusMessage(`Tag “${result.label}” not found.`, 'error'); showToast(`Tag “${result.label}” not found.`, 'error');
} }
}, },
[bulkTagOperation, resolveTargetDocumentIds, setStatusMessage], [bulkTagOperation, resolveTargetDocumentIds, showToast],
); );
const handleBulkSelectionReanalyze = useCallback( const handleBulkSelectionReanalyze = useCallback(
async (documentIdsOverride: Identifier[] | null = null) => { async (documentIdsOverride: Identifier[] | null = null) => {
const targetIds = resolveTargetDocumentIds(documentIdsOverride); const targetIds = resolveTargetDocumentIds(documentIdsOverride);
if (!targetIds.length) { if (!targetIds.length) {
setStatusMessage('Select documents before requesting re-analysis.', 'error'); showToast('Select documents before requesting re-analysis.', 'error');
return; return;
} }
@@ -262,7 +263,7 @@ const useDocumentTagActions = ({
const queued = payload?.queued != null const queued = payload?.queued != null
? Number(payload.queued) ? Number(payload.queued)
: targetIds.length; : targetIds.length;
setStatusMessage( showToast(
`Queued re-analysis for ${queued} document${queued === 1 ? '' : 's'}.`, `Queued re-analysis for ${queued} document${queued === 1 ? '' : 's'}.`,
'success', 'success',
); );
@@ -272,7 +273,7 @@ const useDocumentTagActions = ({
notifyApiError(error, message); notifyApiError(error, message);
} }
}, },
[resolveTargetDocumentIds, notifyApiError, setStatusMessage], [resolveTargetDocumentIds, notifyApiError, showToast],
); );
return { return {
@@ -1,6 +1,7 @@
import { useCallback, useRef, useState } from 'react'; import { useCallback, useRef, useState } from 'react';
import type { Dispatch, MutableRefObject, SetStateAction } from 'react'; import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
import useFileDrop from './useFileDrop'; import useFileDrop from './useFileDrop';
import { useStatusToast } from '../../../lib/context/StatusToastContext';
import { DEFAULT_FOLDER_NAME, hasFiles } from '../../../app/workspaceUtils'; import { DEFAULT_FOLDER_NAME, hasFiles } from '../../../app/workspaceUtils';
import { fetchDocument, uploadDocument, resolveFolderPath } from '../../../lib/api/apiClient'; import { fetchDocument, uploadDocument, resolveFolderPath } from '../../../lib/api/apiClient';
import type { Identifier } from '../../../types/identifiers'; import type { Identifier } from '../../../types/identifiers';
@@ -26,9 +27,7 @@ type UploadQueueItem = {
conflictDocumentId: Identifier | null; conflictDocumentId: Identifier | null;
}; };
type StatusLevel = 'success' | 'info' | 'warning' | 'error' | string;
type NotifyApiError = (error: unknown, fallbackMessage?: string) => void; type NotifyApiError = (error: unknown, fallbackMessage?: string) => void;
type SetStatusMessage = (message: string, level?: StatusLevel) => void;
type DropOverlayState = { type DropOverlayState = {
active: boolean; active: boolean;
@@ -99,7 +98,6 @@ interface UseDocumentUploadsArgs {
refreshCurrentFolder: () => Promise<void>; refreshCurrentFolder: () => Promise<void>;
shellRef: MutableRefObject<HTMLElement | null>; shellRef: MutableRefObject<HTMLElement | null>;
notifyApiError?: NotifyApiError; notifyApiError?: NotifyApiError;
setStatusMessage?: SetStatusMessage;
} }
interface UseDocumentUploadsResult { interface UseDocumentUploadsResult {
@@ -128,7 +126,6 @@ const useDocumentUploads = ({
refreshCurrentFolder, refreshCurrentFolder,
shellRef, shellRef,
notifyApiError, notifyApiError,
setStatusMessage,
}: UseDocumentUploadsArgs): UseDocumentUploadsResult => { }: UseDocumentUploadsArgs): UseDocumentUploadsResult => {
const [dropOverlayState, setDropOverlayState] = useState<DropOverlayState>({ const [dropOverlayState, setDropOverlayState] = useState<DropOverlayState>({
active: false, active: false,
@@ -138,6 +135,7 @@ const useDocumentUploads = ({
const folderPathCacheRef = useRef<Map<string, FolderId>>(new Map()); const folderPathCacheRef = useRef<Map<string, FolderId>>(new Map());
const queueIdRef = useRef(0); const queueIdRef = useRef(0);
const [uploadQueue, setUploadQueue] = useState<UploadQueueItem[]>([]); const [uploadQueue, setUploadQueue] = useState<UploadQueueItem[]>([]);
const { showToast } = useStatusToast();
const uploadFile = useCallback( const uploadFile = useCallback(
async (file: File, targetFolderId: FolderId) => { async (file: File, targetFolderId: FolderId) => {
@@ -180,12 +178,12 @@ const useDocumentUploads = ({
} }
const message = error.response?.data?.error || `Failed to upload ${file.name}.`; const message = error.response?.data?.error || `Failed to upload ${file.name}.`;
notifyApiError?.(error, message); notifyApiError?.(error, message);
setStatusMessage?.(message, 'error'); showToast(message, 'error');
const wrapped = Object.assign(new Error(message), { response: error.response }); const wrapped = Object.assign(new Error(message), { response: error.response });
throw wrapped; throw wrapped;
} }
}, },
[notifyApiError, setStatusMessage], [notifyApiError, showToast],
); );
const appendQueueItems = useCallback((entries: FileEntry[], targetFolderId?: FolderId) => { const appendQueueItems = useCallback((entries: FileEntry[], targetFolderId?: FolderId) => {
+9 -9
View File
@@ -1,4 +1,5 @@
import { useState, useCallback } from 'react'; import { useState, useCallback } from 'react';
import { useStatusToast } from '../lib/context/StatusToastContext';
/* global PublicKeyCredentialCreationOptions, CredentialCreationOptions */ /* global PublicKeyCredentialCreationOptions, CredentialCreationOptions */
import { import {
@@ -14,7 +15,6 @@ import {
} from '../lib/api/apiClient'; } from '../lib/api/apiClient';
import type { PasskeyId } from '../types/identifiers'; import type { PasskeyId } from '../types/identifiers';
type StatusMessageFn = (message: string, variant?: string) => void;
type NotifyApiErrorFn = (error: unknown, message: string) => void; type NotifyApiErrorFn = (error: unknown, message: string) => void;
type ApiError = { type ApiError = {
@@ -71,7 +71,6 @@ type RevokePasskeyResult =
interface UsePasskeysArgs { interface UsePasskeysArgs {
notifyApiError: NotifyApiErrorFn; notifyApiError: NotifyApiErrorFn;
setStatusMessage: StatusMessageFn;
token?: string | null; token?: string | null;
} }
@@ -89,12 +88,13 @@ interface UsePasskeysResult {
) => Promise<RevokePasskeyResult>; ) => Promise<RevokePasskeyResult>;
} }
const usePasskeys = ({ notifyApiError, setStatusMessage, token }: UsePasskeysArgs): UsePasskeysResult => { const usePasskeys = ({ notifyApiError, token }: UsePasskeysArgs): UsePasskeysResult => {
const [passkeys, setPasskeys] = useState<PasskeyRecord[]>([]); const [passkeys, setPasskeys] = useState<PasskeyRecord[]>([]);
const [passkeysSupported, setPasskeysSupported] = useState<boolean | null>(null); const [passkeysSupported, setPasskeysSupported] = useState<boolean | null>(null);
const [passkeysLoading, setPasskeysLoading] = useState(false); const [passkeysLoading, setPasskeysLoading] = useState(false);
const [registeringPasskey, setRegisteringPasskey] = useState(false); const [registeringPasskey, setRegisteringPasskey] = useState(false);
const [revokingPasskeyId, setRevokingPasskeyId] = useState<PasskeyId | null>(null); const [revokingPasskeyId, setRevokingPasskeyId] = useState<PasskeyId | null>(null);
const { showToast } = useStatusToast();
const refreshPasskeys = useCallback(async (): Promise<void> => { const refreshPasskeys = useCallback(async (): Promise<void> => {
if (!token) { if (!token) {
@@ -122,7 +122,7 @@ const usePasskeys = ({ notifyApiError, setStatusMessage, token }: UsePasskeysArg
async ({ nickname }: { nickname?: string } = {}): Promise<RegisterPasskeyResult> => { async ({ nickname }: { nickname?: string } = {}): Promise<RegisterPasskeyResult> => {
if (!isWebAuthnAvailable()) { if (!isWebAuthnAvailable()) {
setPasskeysSupported(false); setPasskeysSupported(false);
setStatusMessage('Passkeys are not supported in this browser.', 'error'); showToast('Passkeys are not supported in this browser.', 'error');
return { ok: false, reason: 'unsupported' }; return { ok: false, reason: 'unsupported' };
} }
if (registeringPasskey) { if (registeringPasskey) {
@@ -166,12 +166,12 @@ const usePasskeys = ({ notifyApiError, setStatusMessage, token }: UsePasskeysArg
await finishPasskeyRegistration(payload); await finishPasskeyRegistration(payload);
await refreshPasskeys(); await refreshPasskeys();
setPasskeysSupported(true); setPasskeysSupported(true);
setStatusMessage('Passkey registered.', 'success'); showToast('Passkey registered.', 'success');
return { ok: true }; return { ok: true };
} catch (error) { } catch (error) {
const typedError = error as ApiError; const typedError = error as ApiError;
if (typedError?.name === 'NotAllowedError') { if (typedError?.name === 'NotAllowedError') {
setStatusMessage('Passkey registration cancelled.', 'info'); showToast('Passkey registration cancelled.', 'info');
return { ok: false, reason: 'cancelled' }; return { ok: false, reason: 'cancelled' };
} }
@@ -187,7 +187,7 @@ const usePasskeys = ({ notifyApiError, setStatusMessage, token }: UsePasskeysArg
setRegisteringPasskey(false); setRegisteringPasskey(false);
} }
}, },
[notifyApiError, refreshPasskeys, registeringPasskey, setStatusMessage], [notifyApiError, refreshPasskeys, registeringPasskey, showToast],
); );
const revokePasskey = useCallback( const revokePasskey = useCallback(
@@ -202,7 +202,7 @@ const usePasskeys = ({ notifyApiError, setStatusMessage, token }: UsePasskeysArg
try { try {
await deletePasskey(passkeyId, { reason }); await deletePasskey(passkeyId, { reason });
await refreshPasskeys(); await refreshPasskeys();
setStatusMessage('Passkey revoked.', 'success'); showToast('Passkey revoked.', 'success');
return { ok: true }; return { ok: true };
} catch (error) { } catch (error) {
const message = (error as ApiError)?.response?.data?.error || 'Failed to revoke passkey.'; const message = (error as ApiError)?.response?.data?.error || 'Failed to revoke passkey.';
@@ -212,7 +212,7 @@ const usePasskeys = ({ notifyApiError, setStatusMessage, token }: UsePasskeysArg
setRevokingPasskeyId(null); setRevokingPasskeyId(null);
} }
}, },
[notifyApiError, refreshPasskeys, setStatusMessage], [notifyApiError, refreshPasskeys, showToast],
); );
return { return {