Improve app startup and route fallbacks

This commit is contained in:
shuaiplus
2026-05-04 04:19:02 +08:00
parent 45f0387526
commit 75a6a593dc
14 changed files with 858 additions and 87 deletions
+19 -5
View File
@@ -3,6 +3,7 @@ import { useEffect } from 'preact/hooks';
import { Link, Route, Switch } from 'wouter';
import { ArrowUpDown, Cloud, 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 { CiphersImportPayload } from '@/lib/api/vault';
import { t } from '@/lib/i18n';
@@ -19,7 +20,7 @@ const BackupCenterPage = lazy(() => import('@/components/BackupCenterPage'));
const ImportPage = lazy(() => import('@/components/ImportPage'));
function RouteContentFallback() {
return <div className="loading-screen">{t('txt_loading_nodewarden')}</div>;
return <LoadingState card lines={5} />;
}
function LegacyBackupRedirect(props: { onNavigate: (path: string) => void }) {
@@ -31,6 +32,7 @@ function LegacyBackupRedirect(props: { onNavigate: (path: string) => void }) {
export interface AppMainRoutesProps {
profile: Profile | null;
profileLoading: boolean;
session: SessionState | null;
mobileLayout: boolean;
mobileSidebarToggleKey: number;
@@ -40,16 +42,20 @@ export interface AppMainRoutesProps {
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;
onNavigate: (path: string) => void;
onLogout: () => void;
onNotify: (type: 'success' | 'error' | 'warning', text: string) => void;
@@ -187,6 +193,7 @@ export default function AppMainRoutes(props: AppMainRoutesProps) {
ciphers={props.decryptedCiphers}
folders={props.decryptedFolders}
loading={props.ciphersLoading || props.foldersLoading}
error={props.vaultError}
emailForReprompt={props.profile?.email || props.session?.email || ''}
onRefresh={props.onRefreshVault}
onCreate={props.onCreateVaultItem}
@@ -216,7 +223,7 @@ export default function AppMainRoutes(props: AppMainRoutesProps) {
</Suspense>
</Route>
<Route path={props.settingsAccountRoute}>
{props.profile && (
{props.profile ? (
<div className="stack">
{props.mobileLayout && (
<div className="mobile-settings-subhead">
@@ -245,10 +252,12 @@ export default function AppMainRoutes(props: AppMainRoutesProps) {
/>
</Suspense>
</div>
)}
) : props.profileLoading ? (
<LoadingState card lines={5} />
) : null}
</Route>
<Route path="/settings">
{props.profile && (
{props.profile ? (
<section className="card mobile-settings-card">
<div className="mobile-settings-links">
<Link href={props.settingsAccountRoute} className="mobile-settings-link">
@@ -281,7 +290,9 @@ export default function AppMainRoutes(props: AppMainRoutesProps) {
{t('txt_sign_out')}
</button>
</section>
)}
) : props.profileLoading ? (
<LoadingState card lines={4} />
) : null}
</Route>
<Route path="/security/devices">
<div className="stack">
@@ -297,6 +308,7 @@ export default function AppMainRoutes(props: AppMainRoutesProps) {
<SecurityDevicesPage
devices={props.authorizedDevices}
loading={props.authorizedDevicesLoading}
error={props.authorizedDevicesError}
onRefresh={() => void props.onRefreshAuthorizedDevices()}
onRenameDevice={props.onRenameAuthorizedDevice}
onRevokeTrust={props.onRevokeDeviceTrust}
@@ -322,6 +334,8 @@ export default function AppMainRoutes(props: AppMainRoutesProps) {
currentUserId={props.profile?.id || ''}
users={props.users}
invites={props.invites}
loading={props.adminLoading}
error={props.adminError}
onRefresh={props.onRefreshAdmin}
onCreateInvite={props.onCreateInvite}
onDeleteAllInvites={props.onDeleteAllInvites}
+58
View File
@@ -0,0 +1,58 @@
import { Home } from 'lucide-preact';
import { t } from '@/lib/i18n';
interface NotFoundPageProps {
title?: string;
message?: string;
homeHref?: string;
}
export default function NotFoundPage(props: NotFoundPageProps) {
const starBoxes = [1, 2, 3, 4];
const stars = [1, 2, 3, 4, 5, 6, 7];
return (
<main className="not-found-page">
<div className="not-found-space" aria-hidden="true">
{starBoxes.map((box) => (
<div key={box} className={`not-found-star-box not-found-star-box-${box}`}>
{stars.map((star) => (
<span key={star} className={`not-found-star not-found-star-position-${star}`} />
))}
</div>
))}
</div>
<section className="not-found-shell" aria-labelledby="not-found-title">
<div className="not-found-brand">
<img src="/nodewarden-logo.svg" alt="NodeWarden logo" className="not-found-logo" />
<span className="not-found-wordmark" aria-label="NodeWarden" role="img" />
</div>
<div className="not-found-astro-stage" aria-hidden="true">
<div className="not-found-astronaut">
<div className="not-found-astro-head" />
<div className="not-found-astro-arm not-found-astro-arm-left" />
<div className="not-found-astro-arm not-found-astro-arm-right" />
<div className="not-found-astro-body">
<div className="not-found-astro-panel" />
</div>
<div className="not-found-astro-leg not-found-astro-leg-left" />
<div className="not-found-astro-leg not-found-astro-leg-right" />
<div className="not-found-astro-pack" />
</div>
</div>
<div className="not-found-copy">
<div className="not-found-code">404</div>
<h1 id="not-found-title">{props.title || t('txt_page_not_found')}</h1>
<p>{props.message || t('txt_page_not_found_hint')}</p>
<a className="btn btn-primary not-found-action" href={props.homeHref || '/'}>
<Home size={14} className="btn-icon" />
{t('txt_back_to_home')}
</a>
</div>
</section>
</main>
);
}