From 0f67a5b8d761dd86c40a79c2af665c1c62c6be06 Mon Sep 17 00:00:00 2001 From: Innei Date: Thu, 19 Mar 2026 21:18:41 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=84=20style(desktop):=20improve=20Welc?= =?UTF-8?q?omeStep=20layout=20centering=20in=20onboarding=20(#13125)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 💄 style(desktop): improve WelcomeStep layout centering in onboarding Made-with: Cursor * 🐛 fix(desktop): validate remote server URL in isRemoteServerConfigured Made-with: Cursor --- .../main/controllers/RemoteServerConfigCtr.ts | 26 +++++++++++--- .../__tests__/RemoteServerConfigCtr.test.ts | 34 +++++++++++++++++++ .../features/WelcomeStep.tsx | 4 +-- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/main/controllers/RemoteServerConfigCtr.ts b/apps/desktop/src/main/controllers/RemoteServerConfigCtr.ts index b24d7924f7..b54a62ea56 100644 --- a/apps/desktop/src/main/controllers/RemoteServerConfigCtr.ts +++ b/apps/desktop/src/main/controllers/RemoteServerConfigCtr.ts @@ -93,10 +93,26 @@ export default class RemoteServerConfigCtr extends ControllerModule { */ async isRemoteServerConfigured(config?: DataSyncConfig): Promise { const effectiveConfig = config ?? (await this.getRemoteServerConfig()); - return ( - effectiveConfig.active && - (effectiveConfig.storageMode !== 'selfHost' || !!effectiveConfig.remoteServerUrl) - ); + const isActive = Boolean(effectiveConfig.active); + const isSelfHostConfigured = + effectiveConfig.storageMode !== 'selfHost' || + this.isValidSelfHostRemoteUrl(effectiveConfig.remoteServerUrl); + + return isActive && isSelfHostConfigured; + } + + private isValidSelfHostRemoteUrl(remoteServerUrl?: string): boolean { + if (!remoteServerUrl) return false; + const normalizedUrl = remoteServerUrl.trim(); + + if (!normalizedUrl) return false; + + try { + const parsedUrl = new URL(normalizedUrl); + return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:'; + } catch { + return false; + } } /** @@ -537,7 +553,7 @@ export default class RemoteServerConfigCtr extends ControllerModule { } async getRemoteServerUrl(config?: DataSyncConfig) { - const dataConfig = this.normalizeConfig(config ? config : await this.getRemoteServerConfig()); + const dataConfig = this.normalizeConfig(config ?? (await this.getRemoteServerConfig())); return dataConfig.storageMode === 'cloud' ? OFFICIAL_CLOUD_SERVER : dataConfig.remoteServerUrl; } diff --git a/apps/desktop/src/main/controllers/__tests__/RemoteServerConfigCtr.test.ts b/apps/desktop/src/main/controllers/__tests__/RemoteServerConfigCtr.test.ts index 509fe3646f..45eb831246 100644 --- a/apps/desktop/src/main/controllers/__tests__/RemoteServerConfigCtr.test.ts +++ b/apps/desktop/src/main/controllers/__tests__/RemoteServerConfigCtr.test.ts @@ -747,6 +747,16 @@ describe('RemoteServerConfigCtr', () => { }); describe('isRemoteServerConfigured', () => { + it('should return false when active is undefined', async () => { + mockStoreManager.get.mockReturnValue({ + storageMode: 'cloud', + }); + + const result = await controller.isRemoteServerConfigured(); + + expect(result).toBe(false); + }); + it('should return true for active cloud mode (no remoteServerUrl needed)', async () => { mockStoreManager.get.mockReturnValue({ active: true, @@ -794,6 +804,30 @@ describe('RemoteServerConfigCtr', () => { expect(result).toBe(false); }); + it('should return false for selfHost mode with blank remoteServerUrl', async () => { + mockStoreManager.get.mockReturnValue({ + active: true, + remoteServerUrl: ' ', + storageMode: 'selfHost', + }); + + const result = await controller.isRemoteServerConfigured(); + + expect(result).toBe(false); + }); + + it('should return false for selfHost mode with invalid remoteServerUrl', async () => { + mockStoreManager.get.mockReturnValue({ + active: true, + remoteServerUrl: 'foo', + storageMode: 'selfHost', + }); + + const result = await controller.isRemoteServerConfigured(); + + expect(result).toBe(false); + }); + it('should use provided config instead of fetching', async () => { // Store has inactive config mockStoreManager.get.mockReturnValue({ diff --git a/src/routes/(desktop)/desktop-onboarding/features/WelcomeStep.tsx b/src/routes/(desktop)/desktop-onboarding/features/WelcomeStep.tsx index b79f4d834e..0868393e59 100644 --- a/src/routes/(desktop)/desktop-onboarding/features/WelcomeStep.tsx +++ b/src/routes/(desktop)/desktop-onboarding/features/WelcomeStep.tsx @@ -46,9 +46,9 @@ const WelcomeStep = memo(({ onNext }) => { }, []); return ( - + - + }