💄 style(desktop): improve WelcomeStep layout centering in onboarding (#13125)

* 💄 style(desktop): improve WelcomeStep layout centering in onboarding

Made-with: Cursor

* 🐛 fix(desktop): validate remote server URL in isRemoteServerConfigured

Made-with: Cursor
This commit is contained in:
Innei
2026-03-19 21:18:41 +08:00
committed by GitHub
parent 8d387a98a0
commit 0f67a5b8d7
3 changed files with 57 additions and 7 deletions

View File

@@ -93,10 +93,26 @@ export default class RemoteServerConfigCtr extends ControllerModule {
*/ */
async isRemoteServerConfigured(config?: DataSyncConfig): Promise<boolean> { async isRemoteServerConfigured(config?: DataSyncConfig): Promise<boolean> {
const effectiveConfig = config ?? (await this.getRemoteServerConfig()); const effectiveConfig = config ?? (await this.getRemoteServerConfig());
return ( const isActive = Boolean(effectiveConfig.active);
effectiveConfig.active && const isSelfHostConfigured =
(effectiveConfig.storageMode !== 'selfHost' || !!effectiveConfig.remoteServerUrl) 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) { 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; return dataConfig.storageMode === 'cloud' ? OFFICIAL_CLOUD_SERVER : dataConfig.remoteServerUrl;
} }

View File

@@ -747,6 +747,16 @@ describe('RemoteServerConfigCtr', () => {
}); });
describe('isRemoteServerConfigured', () => { 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 () => { it('should return true for active cloud mode (no remoteServerUrl needed)', async () => {
mockStoreManager.get.mockReturnValue({ mockStoreManager.get.mockReturnValue({
active: true, active: true,
@@ -794,6 +804,30 @@ describe('RemoteServerConfigCtr', () => {
expect(result).toBe(false); 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 () => { it('should use provided config instead of fetching', async () => {
// Store has inactive config // Store has inactive config
mockStoreManager.get.mockReturnValue({ mockStoreManager.get.mockReturnValue({

View File

@@ -46,9 +46,9 @@ const WelcomeStep = memo<WelcomeStepProps>(({ onNext }) => {
}, []); }, []);
return ( return (
<Flexbox gap={16}> <Flexbox align="center" gap={16} justify="center">
<ProductLogo size={64} /> <ProductLogo size={64} />
<Flexbox style={{ marginBottom: 16 }}> <Flexbox align="center" gap={16} justify="center" style={{ marginBottom: 16 }}>
<Text as={'h1'} fontSize={28} weight={'bold'}> <Text as={'h1'} fontSize={28} weight={'bold'}>
<TypewriterEffect <TypewriterEffect
cursorCharacter={<LoadingDots size={28} variant={'pulse'} />} cursorCharacter={<LoadingDots size={28} variant={'pulse'} />}