mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
🐛 fix(desktop): resolve onboarding navigation issues after logout (#11628)
* 🐛 fix(desktop): resolve onboarding navigation issues after logout - Refactor step-based navigation to screen-based navigation system - Add DesktopOnboardingScreen enum for type-safe screen handling - Fix screen persistence and URL synchronization - Improve platform-specific screen resolution (macOS permissions) - Extract navigation logic into reusable utility functions * cleanup Signed-off-by: Innei <tukon479@gmail.com> * 🐛 fix(UserPanel): handle errors during remote server config clearance - Added error handling for the remote server configuration clearance process in the UserPanel component. - Ensured that the onboarding completion and sign-out actions are executed regardless of the error state. Signed-off-by: Innei <tukon479@gmail.com> --------- Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
@@ -21,7 +21,9 @@ async function generateJwks() {
|
||||
console.error('正在生成 RSA 密钥对...');
|
||||
|
||||
// 生成 RS256 密钥对
|
||||
const { privateKey } = await generateKeyPair('RS256');
|
||||
const { privateKey } = await generateKeyPair('RS256', {
|
||||
extractable: true,
|
||||
});
|
||||
|
||||
// 导出为 JWK 格式
|
||||
const jwk = await exportJWK(privateKey);
|
||||
|
||||
@@ -199,10 +199,13 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
});
|
||||
|
||||
const handleCancelAuth = async () => {
|
||||
await remoteServerService.cancelAuthorization();
|
||||
setRemoteError(null);
|
||||
clearRemoteServerSyncError();
|
||||
|
||||
setCloudLoginStatus('idle');
|
||||
setSelfhostLoginStatus('idle');
|
||||
setAuthProgress(null);
|
||||
await remoteServerService.cancelAuthorization();
|
||||
};
|
||||
|
||||
// 渲染 Cloud 登录内容
|
||||
@@ -238,10 +241,9 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
|
||||
if (cloudLoginStatus === 'error') {
|
||||
return (
|
||||
<>
|
||||
<Flexbox style={{ width: '100%' }}>
|
||||
<Alert
|
||||
description={remoteError || t('authResult.failed.desc')}
|
||||
style={{ width: '100%' }}
|
||||
title={t('authResult.failed.title')}
|
||||
type={'secondary'}
|
||||
/>
|
||||
@@ -254,7 +256,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
>
|
||||
{t('screen5.actions.tryAgain')}
|
||||
</Button>
|
||||
</>
|
||||
</Flexbox>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -340,10 +342,9 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
|
||||
if (selfhostLoginStatus === 'error') {
|
||||
return (
|
||||
<Flexbox gap={16}>
|
||||
<Flexbox gap={16} style={{ width: '100%' }}>
|
||||
<Alert
|
||||
description={remoteError || t('authResult.failed.desc')}
|
||||
style={{ width: '100%' }}
|
||||
title={t('authResult.failed.title')}
|
||||
type={'secondary'}
|
||||
/>
|
||||
@@ -354,6 +355,47 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (selfhostLoginStatus === 'loading') {
|
||||
const phaseText = t(authorizationPhaseI18nKeyMap[authProgress?.phase ?? 'browser_opened'], {
|
||||
defaultValue: t('screen5.actions.connecting'),
|
||||
});
|
||||
const remainingSeconds = authProgress
|
||||
? Math.max(0, Math.ceil((authProgress.maxPollTime - authProgress.elapsed) / 1000))
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Flexbox gap={8} style={{ width: '100%' }}>
|
||||
<Button
|
||||
block
|
||||
disabled={true}
|
||||
icon={Server}
|
||||
loading={true}
|
||||
size={'large'}
|
||||
type={'primary'}
|
||||
>
|
||||
{t('screen5.actions.connecting')}
|
||||
</Button>
|
||||
<Text style={{ color: cssVar.colorTextDescription }} type={'secondary'}>
|
||||
{phaseText}
|
||||
</Text>
|
||||
<Flexbox align={'center'} horizontal justify={'space-between'}>
|
||||
{remainingSeconds !== null ? (
|
||||
<Text style={{ color: cssVar.colorTextDescription }} type={'secondary'}>
|
||||
{t('screen5.auth.remaining', {
|
||||
time: remainingSeconds,
|
||||
})}
|
||||
</Text>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<Button onClick={handleCancelAuth} size={'small'} type={'text'}>
|
||||
{t('screen5.actions.cancel')}
|
||||
</Button>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flexbox gap={16} style={{ width: '100%' }}>
|
||||
<Text color={cssVar.colorTextSecondary}>{t(loginMethodMetas.selfhost.descriptionKey)}</Text>
|
||||
@@ -365,6 +407,11 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
const { electronSystemService } = await import('@/services/electron/system');
|
||||
await electronSystemService.showContextMenu('edit');
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSelfhostConnect();
|
||||
}
|
||||
}}
|
||||
placeholder={t('screen5.selfhost.endpointPlaceholder')}
|
||||
prefix={<Icon icon={Server} style={{ marginRight: 4 }} />}
|
||||
size={'large'}
|
||||
@@ -372,16 +419,14 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
value={endpoint}
|
||||
/>
|
||||
<Button
|
||||
disabled={!endpoint.trim() || selfhostLoginStatus === 'loading' || isConnectingServer}
|
||||
loading={selfhostLoginStatus === 'loading'}
|
||||
disabled={!endpoint.trim() || isConnectingServer}
|
||||
loading={false}
|
||||
onClick={handleSelfhostConnect}
|
||||
size={'large'}
|
||||
style={{ width: '100%' }}
|
||||
type={'primary'}
|
||||
>
|
||||
{selfhostLoginStatus === 'loading'
|
||||
? t('screen5.actions.connecting')
|
||||
: t('screen5.actions.connectToServer')}
|
||||
{t('screen5.actions.connectToServer')}
|
||||
</Button>
|
||||
</Flexbox>
|
||||
);
|
||||
|
||||
@@ -14,40 +14,79 @@ import LoginStep from './features/LoginStep';
|
||||
import PermissionsStep from './features/PermissionsStep';
|
||||
import WelcomeStep from './features/WelcomeStep';
|
||||
import {
|
||||
clearDesktopOnboardingStep,
|
||||
getDesktopOnboardingStep,
|
||||
clearDesktopOnboardingScreen,
|
||||
getDesktopOnboardingScreen,
|
||||
setDesktopOnboardingCompleted,
|
||||
setDesktopOnboardingStep,
|
||||
setDesktopOnboardingScreen,
|
||||
} from './storage';
|
||||
import { DesktopOnboardingScreen, isDesktopOnboardingScreen } from './types';
|
||||
|
||||
const DesktopOnboardingPage = memo(() => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [isMac, setIsMac] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
// 从 localStorage 或 URL query 参数获取初始步骤
|
||||
// 优先使用 localStorage 以支持重启后恢复
|
||||
const getInitialStep = useCallback(() => {
|
||||
// First try localStorage (for app restart scenario)
|
||||
const savedStep = getDesktopOnboardingStep();
|
||||
if (savedStep !== null) {
|
||||
return savedStep;
|
||||
}
|
||||
// Then try URL params
|
||||
const stepParam = searchParams.get('step');
|
||||
if (stepParam) {
|
||||
const step = parseInt(stepParam, 10);
|
||||
if (step >= 1 && step <= 4) return step;
|
||||
}
|
||||
return 1;
|
||||
const flow = isMac
|
||||
? [
|
||||
DesktopOnboardingScreen.Welcome,
|
||||
DesktopOnboardingScreen.Permissions,
|
||||
DesktopOnboardingScreen.DataMode,
|
||||
DesktopOnboardingScreen.Login,
|
||||
]
|
||||
: [
|
||||
DesktopOnboardingScreen.Welcome,
|
||||
DesktopOnboardingScreen.DataMode,
|
||||
DesktopOnboardingScreen.Login,
|
||||
];
|
||||
|
||||
const resolveScreenForPlatform = useCallback(
|
||||
(screen: DesktopOnboardingScreen) => {
|
||||
if (!isMac && screen === DesktopOnboardingScreen.Permissions)
|
||||
return DesktopOnboardingScreen.DataMode;
|
||||
return screen;
|
||||
},
|
||||
[isMac],
|
||||
);
|
||||
|
||||
const getRequestedScreenFromUrl = useCallback((): DesktopOnboardingScreen | null => {
|
||||
const screenParam = searchParams.get('screen');
|
||||
if (isDesktopOnboardingScreen(screenParam)) return screenParam;
|
||||
|
||||
return null;
|
||||
}, [searchParams]);
|
||||
|
||||
const [currentStep, setCurrentStep] = useState(getInitialStep);
|
||||
const [currentScreen, setCurrentScreen] = useState<DesktopOnboardingScreen>(
|
||||
DesktopOnboardingScreen.Welcome,
|
||||
);
|
||||
|
||||
// 持久化当前步骤到 localStorage
|
||||
useEffect(() => {
|
||||
setDesktopOnboardingStep(currentStep);
|
||||
}, [currentStep]);
|
||||
if (isLoading) return;
|
||||
|
||||
const saved = getDesktopOnboardingScreen();
|
||||
const requested = getRequestedScreenFromUrl();
|
||||
|
||||
const initial = resolveScreenForPlatform(requested ?? saved ?? DesktopOnboardingScreen.Welcome);
|
||||
|
||||
setCurrentScreen(initial);
|
||||
|
||||
// Canonicalize URL to `?screen=...`
|
||||
const currentUrlScreen = searchParams.get('screen');
|
||||
if (currentUrlScreen !== initial) {
|
||||
setSearchParams({ screen: initial });
|
||||
}
|
||||
}, [
|
||||
getRequestedScreenFromUrl,
|
||||
isLoading,
|
||||
resolveScreenForPlatform,
|
||||
searchParams,
|
||||
setSearchParams,
|
||||
]);
|
||||
|
||||
// Persist current screen to localStorage.
|
||||
useEffect(() => {
|
||||
if (isLoading) return;
|
||||
setDesktopOnboardingScreen(currentScreen);
|
||||
}, [currentScreen, isLoading]);
|
||||
|
||||
// 设置窗口大小和可调整性
|
||||
useEffect(() => {
|
||||
@@ -91,79 +130,48 @@ const DesktopOnboardingPage = memo(() => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// 监听 URL query 参数变化
|
||||
// Listen URL changes: allow deep-linking between screens.
|
||||
useEffect(() => {
|
||||
const stepParam = searchParams.get('step');
|
||||
if (stepParam) {
|
||||
const step = parseInt(stepParam, 10);
|
||||
if (step >= 1 && step <= 4 && step !== currentStep) {
|
||||
setCurrentStep(step);
|
||||
}
|
||||
}
|
||||
}, [searchParams, currentStep]);
|
||||
if (isLoading) return;
|
||||
const requested = getRequestedScreenFromUrl();
|
||||
if (!requested) return;
|
||||
const resolved = resolveScreenForPlatform(requested);
|
||||
if (resolved !== currentScreen) setCurrentScreen(resolved);
|
||||
}, [currentScreen, getRequestedScreenFromUrl, isLoading, resolveScreenForPlatform]);
|
||||
|
||||
const goToNextStep = useCallback(() => {
|
||||
setCurrentStep((prev) => {
|
||||
let nextStep: number;
|
||||
// 如果是第1步(WelcomeStep),下一步根据平台决定
|
||||
switch (prev) {
|
||||
case 1: {
|
||||
nextStep = isMac ? 2 : 3; // macOS 显示权限页,其他平台跳过
|
||||
setCurrentScreen((prev) => {
|
||||
const idx = flow.indexOf(prev);
|
||||
const next = flow[idx + 1];
|
||||
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// 如果是第2步(PermissionsStep,仅 macOS),下一步是第3步
|
||||
nextStep = 3;
|
||||
if (!next) {
|
||||
// Complete onboarding.
|
||||
setDesktopOnboardingCompleted();
|
||||
clearDesktopOnboardingScreen();
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// 如果是第3步(DataModeStep),下一步是第4步
|
||||
nextStep = 4;
|
||||
// Restore window minimum size before hard reload (cleanup won't run due to hard navigation)
|
||||
electronSystemService
|
||||
.setWindowMinimumSize(APP_WINDOW_MIN_SIZE)
|
||||
.catch(console.error)
|
||||
.finally(() => {
|
||||
// Use hard reload instead of SPA navigation to ensure the app boots with the new desktop state.
|
||||
window.location.replace('/');
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case 4: {
|
||||
// 如果是第4步(LoginStep),完成 onboarding
|
||||
setDesktopOnboardingCompleted();
|
||||
clearDesktopOnboardingStep(); // Clear persisted step since onboarding is complete
|
||||
// Restore window minimum size before hard reload (cleanup won't run due to hard navigation)
|
||||
electronSystemService
|
||||
.setWindowMinimumSize(APP_WINDOW_MIN_SIZE)
|
||||
.catch(console.error)
|
||||
.finally(() => {
|
||||
// Use hard reload instead of SPA navigation to ensure the app boots with the new desktop state.
|
||||
window.location.replace('/');
|
||||
});
|
||||
return prev;
|
||||
}
|
||||
default: {
|
||||
nextStep = prev + 1;
|
||||
}
|
||||
return prev;
|
||||
}
|
||||
// 更新 URL query 参数
|
||||
setSearchParams({ step: nextStep.toString() });
|
||||
return nextStep;
|
||||
|
||||
setSearchParams({ screen: next });
|
||||
return next;
|
||||
});
|
||||
}, [isMac, setSearchParams]);
|
||||
|
||||
const goToPreviousStep = useCallback(() => {
|
||||
setCurrentStep((prev) => {
|
||||
if (prev <= 1) return 1;
|
||||
let prevStep: number;
|
||||
// 如果当前是第3步(DataModeStep),上一步根据平台决定
|
||||
if (prev === 3) {
|
||||
prevStep = isMac ? 2 : 1;
|
||||
} else if (prev === 2) {
|
||||
// 如果当前是第2步(PermissionsStep),上一步是第1步
|
||||
prevStep = 1;
|
||||
} else {
|
||||
prevStep = prev - 1;
|
||||
}
|
||||
// 更新 URL query 参数
|
||||
setSearchParams({ step: prevStep.toString() });
|
||||
return prevStep;
|
||||
setCurrentScreen((prev) => {
|
||||
const idx = flow.indexOf(prev);
|
||||
const prevScreen = flow[Math.max(0, idx - 1)] ?? DesktopOnboardingScreen.Welcome;
|
||||
setSearchParams({ screen: prevScreen });
|
||||
return prevScreen;
|
||||
});
|
||||
}, [isMac, setSearchParams]);
|
||||
|
||||
@@ -172,21 +180,22 @@ const DesktopOnboardingPage = memo(() => {
|
||||
}
|
||||
|
||||
const renderStep = () => {
|
||||
switch (currentStep) {
|
||||
case 1: {
|
||||
switch (currentScreen) {
|
||||
case DesktopOnboardingScreen.Welcome: {
|
||||
return <WelcomeStep onNext={goToNextStep} />;
|
||||
}
|
||||
case 2: {
|
||||
// 仅 macOS 显示权限页
|
||||
case DesktopOnboardingScreen.Permissions: {
|
||||
// macOS-only screen; fallback to DataMode if platform doesn't support.
|
||||
if (!isMac) {
|
||||
return <DataModeStep onBack={goToPreviousStep} onNext={goToNextStep} />;
|
||||
setCurrentScreen(DesktopOnboardingScreen.DataMode);
|
||||
return null;
|
||||
}
|
||||
return <PermissionsStep onBack={goToPreviousStep} onNext={goToNextStep} />;
|
||||
}
|
||||
case 3: {
|
||||
case DesktopOnboardingScreen.DataMode: {
|
||||
return <DataModeStep onBack={goToPreviousStep} onNext={goToNextStep} />;
|
||||
}
|
||||
case 4: {
|
||||
case DesktopOnboardingScreen.Login: {
|
||||
return <LoginStep onBack={goToPreviousStep} onNext={goToNextStep} />;
|
||||
}
|
||||
default: {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { DesktopOnboardingScreen } from './types';
|
||||
|
||||
const DESKTOP_ONBOARDING_ROUTE = '/desktop-onboarding';
|
||||
export const getDesktopOnboardingPath = (screen?: DesktopOnboardingScreen) => {
|
||||
if (!screen) return DESKTOP_ONBOARDING_ROUTE;
|
||||
return `${DESKTOP_ONBOARDING_ROUTE}?screen=${encodeURIComponent(screen)}`;
|
||||
};
|
||||
|
||||
export const navigateToDesktopOnboarding = (screen?: DesktopOnboardingScreen) => {
|
||||
location.href = getDesktopOnboardingPath(screen);
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
import { DesktopOnboardingScreen, isDesktopOnboardingScreen } from './types';
|
||||
|
||||
export const DESKTOP_ONBOARDING_STORAGE_KEY = 'lobechat:desktop:onboarding:completed:v1';
|
||||
export const DESKTOP_ONBOARDING_STEP_KEY = 'lobechat:desktop:onboarding:step:v1';
|
||||
export const DESKTOP_ONBOARDING_SCREEN_KEY = 'lobechat:desktop:onboarding:screen:v1';
|
||||
|
||||
export const getDesktopOnboardingCompleted = () => {
|
||||
if (typeof window === 'undefined') return true;
|
||||
@@ -35,33 +37,29 @@ export const clearDesktopOnboardingCompleted = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the persisted onboarding step (for restoring after app restart)
|
||||
* Get the persisted onboarding screen (for restoring after app restart)
|
||||
*/
|
||||
export const getDesktopOnboardingStep = (): number | null => {
|
||||
export const getDesktopOnboardingScreen = () => {
|
||||
if (typeof window === 'undefined') return null;
|
||||
|
||||
try {
|
||||
const step = window.localStorage.getItem(DESKTOP_ONBOARDING_STEP_KEY);
|
||||
if (step) {
|
||||
const parsedStep = Number.parseInt(step, 10);
|
||||
if (parsedStep >= 1 && parsedStep <= 4) {
|
||||
return parsedStep;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
const screen = window.localStorage.getItem(DESKTOP_ONBOARDING_SCREEN_KEY);
|
||||
if (!screen) return null;
|
||||
if (!isDesktopOnboardingScreen(screen)) return null;
|
||||
return screen;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Persist the current onboarding step
|
||||
* Persist the current onboarding screen
|
||||
*/
|
||||
export const setDesktopOnboardingStep = (step: number) => {
|
||||
export const setDesktopOnboardingScreen = (screen: DesktopOnboardingScreen) => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
|
||||
try {
|
||||
window.localStorage.setItem(DESKTOP_ONBOARDING_STEP_KEY, step.toString());
|
||||
window.localStorage.setItem(DESKTOP_ONBOARDING_SCREEN_KEY, screen);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
@@ -69,13 +67,13 @@ export const setDesktopOnboardingStep = (step: number) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the persisted onboarding step (called when onboarding completes)
|
||||
* Clear the persisted onboarding screen (called when onboarding completes)
|
||||
*/
|
||||
export const clearDesktopOnboardingStep = () => {
|
||||
export const clearDesktopOnboardingScreen = () => {
|
||||
if (typeof window === 'undefined') return false;
|
||||
|
||||
try {
|
||||
window.localStorage.removeItem(DESKTOP_ONBOARDING_STEP_KEY);
|
||||
window.localStorage.removeItem(DESKTOP_ONBOARDING_SCREEN_KEY);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
|
||||
11
src/app/[variants]/(desktop)/desktop-onboarding/types.ts
Normal file
11
src/app/[variants]/(desktop)/desktop-onboarding/types.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export enum DesktopOnboardingScreen {
|
||||
DataMode = 'data-mode',
|
||||
Login = 'login',
|
||||
Permissions = 'permissions',
|
||||
Welcome = 'welcome',
|
||||
}
|
||||
|
||||
export const isDesktopOnboardingScreen = (value: unknown): value is DesktopOnboardingScreen => {
|
||||
if (typeof value !== 'string') return false;
|
||||
return (Object.values(DesktopOnboardingScreen) as string[]).includes(value);
|
||||
};
|
||||
@@ -1,15 +1,17 @@
|
||||
import { ENABLE_BUSINESS_FEATURES } from '@lobechat/business-const';
|
||||
import { Flexbox } from '@lobehub/ui';
|
||||
import { useRouter } from '@/libs/next/navigation';
|
||||
import { memo } from 'react';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { navigateToDesktopOnboarding } from '@/app/[variants]/(desktop)/desktop-onboarding/navigation';
|
||||
import { clearDesktopOnboardingCompleted } from '@/app/[variants]/(desktop)/desktop-onboarding/storage';
|
||||
import { DesktopOnboardingScreen } from '@/app/[variants]/(desktop)/desktop-onboarding/types';
|
||||
import BusinessPanelContent from '@/business/client/features/User/BusinessPanelContent';
|
||||
import BrandWatermark from '@/components/BrandWatermark';
|
||||
import Menu from '@/components/Menu';
|
||||
import { isDesktop } from '@/const/version';
|
||||
import { enableBetterAuth, enableNextAuth } from '@/envs/auth';
|
||||
import { useRouter } from '@/libs/next/navigation';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { authSelectors } from '@/store/user/selectors';
|
||||
|
||||
@@ -21,7 +23,7 @@ import { useMenu } from './useMenu';
|
||||
|
||||
const PanelContent = memo<{ closePopover: () => void }>(({ closePopover }) => {
|
||||
const router = useRouter();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const isLoginWithAuth = useUserStore(authSelectors.isLoginWithAuth);
|
||||
const [openSignIn, signOut] = useUserStore((s) => [s.openLogin, s.logout]);
|
||||
const { mainItems, logoutItems } = useMenu();
|
||||
@@ -35,17 +37,16 @@ const PanelContent = memo<{ closePopover: () => void }>(({ closePopover }) => {
|
||||
if (isDesktop) {
|
||||
closePopover();
|
||||
|
||||
// Desktop: clear OIDC tokens (electron main) + re-enter desktop onboarding at Screen5.
|
||||
try {
|
||||
const { remoteServerService } = await import('@/services/electron/remoteServer');
|
||||
await remoteServerService.clearRemoteServerConfig();
|
||||
} catch {
|
||||
// Ignore: even if IPC is unavailable, still proceed to onboarding.
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
clearDesktopOnboardingCompleted();
|
||||
signOut();
|
||||
navigateToDesktopOnboarding(DesktopOnboardingScreen.Login);
|
||||
}
|
||||
|
||||
clearDesktopOnboardingCompleted();
|
||||
signOut();
|
||||
navigate('/desktop-onboarding#5', { replace: true });
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user