import { lazy, Suspense } from 'preact/compat'; import { useEffect } from 'preact/hooks'; import { Link, Route, Switch } from 'wouter'; import { ArrowUpDown, Cloud, FileClock, Globe2, LogOut, Settings as SettingsIcon, Shield, ShieldUser } from 'lucide-preact'; import type { ImportAttachmentFile, ImportResultSummary } from '@/components/ImportPage'; import LoadingState from '@/components/LoadingState'; import type { AdminBackupImportResponse, AdminBackupRunResponse, AdminBackupSettings, RemoteBackupBrowserResponse } from '@/lib/api/backup'; import type { AuditLogFilters } from '@/lib/api/admin'; import type { CiphersImportPayload } from '@/lib/api/vault'; import { t } from '@/lib/i18n'; import type { AccountPasskeyCredential, AdminInvite, AdminUser, AuditLogListResult, AuditLogSettings, AuthorizedDevice, Cipher, CustomEquivalentDomain, DomainRules, Folder as VaultFolder, Profile, Send, SendDraft, SessionState, VaultDraft } from '@/lib/types'; import type { ExportRequest } from '@/lib/export-formats'; const VaultPage = lazy(() => import('@/components/VaultPage')); const SendsPage = lazy(() => import('@/components/SendsPage')); const TotpCodesPage = lazy(() => import('@/components/TotpCodesPage')); const SettingsPage = lazy(() => import('@/components/SettingsPage')); const DomainRulesPage = lazy(() => import('@/components/DomainRulesPage')); const SecurityDevicesPage = lazy(() => import('@/components/SecurityDevicesPage')); const AdminPage = lazy(() => import('@/components/AdminPage')); const LogCenterPage = lazy(() => import('@/components/LogCenterPage')); const BackupCenterPage = lazy(() => import('@/components/BackupCenterPage')); const ImportPage = lazy(() => import('@/components/ImportPage')); function RouteContentFallback() { return ; } function LegacyBackupRedirect(props: { onNavigate: (path: string) => void }) { useEffect(() => { props.onNavigate('/backup'); }, [props]); return null; } export interface AppMainRoutesProps { profile: Profile | null; profileLoading: boolean; session: SessionState | null; mobileLayout: boolean; mobileSidebarToggleKey: number; importRoute: string; settingsHomeRoute: string; settingsAccountRoute: string; decryptedCiphers: Cipher[]; decryptedFolders: VaultFolder[]; decryptedSends: Send[]; vaultError: string; ciphersLoading: boolean; foldersLoading: boolean; sendsLoading: boolean; users: AdminUser[]; invites: AdminInvite[]; adminLoading: boolean; adminError: string; totpEnabled: boolean; lockTimeoutMinutes: 0 | 1 | 5 | 15 | 30; sessionTimeoutAction: 'lock' | 'logout'; authorizedDevices: AuthorizedDevice[]; authorizedDevicesLoading: boolean; authorizedDevicesError: string; domainRules: DomainRules | null; domainRulesLoading: boolean; domainRulesError: string; onNavigate: (path: string) => void; onLogout: () => void; onNotify: (type: 'success' | 'error' | 'warning', text: string) => void; onImport: ( payload: CiphersImportPayload, options: { folderMode: 'original' | 'none' | 'target'; targetFolderId: string | null }, attachments?: ImportAttachmentFile[] ) => Promise; onImportEncryptedRaw: ( payload: CiphersImportPayload, options: { folderMode: 'original' | 'none' | 'target'; targetFolderId: string | null }, attachments?: ImportAttachmentFile[] ) => Promise; onExport: (request: ExportRequest) => Promise; onCreateVaultItem: (draft: VaultDraft, attachments?: File[]) => Promise; onUpdateVaultItem: (cipher: Cipher, draft: VaultDraft, options?: { addFiles?: File[]; removeAttachmentIds?: string[] }) => Promise; onDeleteVaultItem: (cipher: Cipher) => Promise; onArchiveVaultItem: (cipher: Cipher) => Promise; onUnarchiveVaultItem: (cipher: Cipher) => Promise; onRestoreVaultItems: (ids: string[]) => Promise; onBulkDeleteVaultItems: (ids: string[]) => Promise; onBulkPermanentDeleteVaultItems: (ids: string[]) => Promise; onBulkRestoreVaultItems: (ids: string[]) => Promise; onBulkArchiveVaultItems: (ids: string[]) => Promise; onBulkUnarchiveVaultItems: (ids: string[]) => Promise; onBulkMoveVaultItems: (ids: string[], folderId: string | null) => Promise; onVerifyMasterPassword: (email: string, password: string) => Promise; onCreateFolder: (name: string) => Promise; onRenameFolder: (folderId: string, name: string) => Promise; onDeleteFolder: (folderId: string) => Promise; onBulkDeleteFolders: (folderIds: string[]) => Promise; onDownloadVaultAttachment: (cipher: Cipher, attachmentId: string) => Promise; downloadingAttachmentKey: string; attachmentDownloadPercent: number | null; uploadingAttachmentName: string; attachmentUploadPercent: number | null; onRefreshVault: () => Promise; onCreateSend: (draft: SendDraft, autoCopyLink: boolean) => Promise; onUpdateSend: (send: Send, draft: SendDraft, autoCopyLink: boolean) => Promise; onDeleteSend: (send: Send) => Promise; onBulkDeleteSends: (ids: string[]) => Promise; uploadingSendFileName: string; sendUploadPercent: number | null; onChangePassword: (currentPassword: string, nextPassword: string, nextPassword2: string) => Promise; onSavePasswordHint: (masterPasswordHint: string) => Promise; onEnableTotp: (secret: string, token: string) => Promise; onOpenDisableTotp: () => void; onGetRecoveryCode: (masterPassword: string) => Promise; onGetApiKey: (masterPassword: string) => Promise; onRotateApiKey: (masterPassword: string) => Promise; onListAccountPasskeys: () => Promise; onCreateAccountPasskey: (name: string, masterPassword: string, directUnlock: boolean) => Promise; onEnableAccountPasskeyDirectUnlock: (id: string, masterPassword: string) => Promise; onDeleteAccountPasskey: (id: string, masterPassword: string) => Promise; onLockTimeoutChange: (minutes: 0 | 1 | 5 | 15 | 30) => void; onSessionTimeoutActionChange: (action: 'lock' | 'logout') => void; onRefreshAuthorizedDevices: () => Promise; onRefreshDomainRules: () => void; onSaveDomainRules: (customEquivalentDomains: CustomEquivalentDomain[], excludedGlobalEquivalentDomains: number[]) => Promise; onRenameAuthorizedDevice: (device: AuthorizedDevice, name: string) => Promise; onRevokeDeviceTrust: (device: AuthorizedDevice) => void; onTrustDevicePermanently: (device: AuthorizedDevice) => void; onRemoveDevice: (device: AuthorizedDevice) => void; onRevokeAllDeviceTrust: () => void; onRemoveAllDevices: () => void; onCreateInvite: (hours: number) => Promise; onRefreshAdmin: () => void; onDeleteAllInvites: () => Promise; onToggleUserStatus: (userId: string, status: 'active' | 'banned') => Promise; onDeleteUser: (userId: string) => Promise; onRevokeInvite: (code: string) => Promise; onLoadAuditLogs: (filters: AuditLogFilters) => Promise; onLoadAuditLogSettings: () => Promise; onSaveAuditLogSettings: (settings: AuditLogSettings) => Promise; onClearAuditLogs: () => Promise; onExportBackup: (includeAttachments?: boolean) => Promise; onImportBackup: (file: File, replaceExisting?: boolean) => Promise; onImportBackupAllowingChecksumMismatch: (file: File, replaceExisting?: boolean) => Promise; onLoadBackupSettings: () => Promise; onSaveBackupSettings: (settings: AdminBackupSettings) => Promise; onRunRemoteBackup: (destinationId?: string | null) => Promise; onListRemoteBackups: (destinationId: string, path: string) => Promise; onDownloadRemoteBackup: (destinationId: string, path: string, onProgress?: (percent: number | null) => void) => Promise; onInspectRemoteBackup: (destinationId: string, path: string) => Promise<{ object: 'backup-remote-integrity'; destinationId: string; path: string; fileName: string; integrity: { hasChecksumPrefix: boolean; expectedPrefix: string | null; actualPrefix: string; matches: boolean } }>; onDeleteRemoteBackup: (destinationId: string, path: string) => Promise; onRestoreRemoteBackup: (destinationId: string, path: string, replaceExisting?: boolean) => Promise; onRestoreRemoteBackupAllowingChecksumMismatch: (destinationId: string, path: string, replaceExisting?: boolean) => Promise; } export default function AppMainRoutes(props: AppMainRoutesProps) { const importRoutePaths = [props.importRoute, '/tools/import', '/tools/import-export', '/tools/import-data', '/import', '/import-export'] as const; const isAdmin = String(props.profile?.role || '').toLowerCase() === 'admin'; const importPageContent = ( }> ); const renderImportPageRoute = () => (
{props.mobileLayout && (
)} {importPageContent}
); return ( }> }> }> {props.profile ? (
{props.mobileLayout && (
)} }>
) : props.profileLoading ? ( ) : null}
{props.profile ? (
{t('nav_account_settings')} {t('nav_device_management')} {t('nav_domain_rules')} {t('nav_import_export')} {isAdmin && ( {t('nav_admin_panel')} )} {isAdmin && ( {t('nav_log_center')} )} {isAdmin && ( {t('nav_backup_strategy')} )}
) : props.profileLoading ? ( ) : null}
{props.mobileLayout && (
)} }> void props.onRefreshAuthorizedDevices()} onRenameDevice={props.onRenameAuthorizedDevice} onRevokeTrust={props.onRevokeDeviceTrust} onTrustPermanently={props.onTrustDevicePermanently} onRemoveDevice={props.onRemoveDevice} onRevokeAll={props.onRevokeAllDeviceTrust} onRemoveAll={props.onRemoveAllDevices} />
{props.mobileLayout && (
)} }>
{props.mobileLayout && (
)} }>
{isAdmin ? (
}> props.onNavigate(props.settingsHomeRoute)} />
) : null}
{importRoutePaths.map((path) => ( {renderImportPageRoute()} ))} {isAdmin ? (
{props.mobileLayout && (
)} }>
) : null}
); }