diff --git a/src/lib/components/views/LoginView.svelte b/src/lib/components/views/LoginView.svelte index 04cc88d55..5d68e605e 100644 --- a/src/lib/components/views/LoginView.svelte +++ b/src/lib/components/views/LoginView.svelte @@ -24,24 +24,49 @@ let initStatus = $state('Connecting to Qobuz...'); let error = $state(null); let initError = $state(null); + let isTimedOut = $state(false); + let timeoutId: ReturnType | null = null; + + const LOGIN_TIMEOUT_MS = 60000; // 60 seconds // Initialize the Qobuz client on mount $effect(() => { initializeClient(); + return () => { + // Cleanup timeout on unmount + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } + }; }); async function initializeClient() { try { isInitializing = true; initError = null; + isTimedOut = false; initStatus = 'Connecting to Qobuz...'; + // Start timeout timer + if (timeoutId) { + clearTimeout(timeoutId); + } + timeoutId = setTimeout(() => { + if (isInitializing) { + console.warn('Login initialization timed out after 60 seconds'); + isTimedOut = true; + isInitializing = false; + } + }, LOGIN_TIMEOUT_MS); + const result = await invoke('init_client'); console.log('Client initialized:', result); // Check if already logged in (in-memory session) const loggedIn = await invoke('is_logged_in'); if (loggedIn) { + clearTimeoutTimer(); const userInfo = await invoke<{ user_name: string; subscription: string } | null>('get_user_info'); if (userInfo) { onLoginSuccess({ userName: userInfo.user_name, subscription: userInfo.subscription }); @@ -65,6 +90,7 @@ }>('auto_login'); if (response.success) { + clearTimeoutTimer(); console.log('Auto-login successful'); onLoginSuccess({ userName: response.user_name || 'Qobuz User', @@ -76,14 +102,32 @@ // Don't show error, just fall through to manual login } } + + // If we reach here, no auto-login - clear timeout and show login form + clearTimeoutTimer(); } catch (err) { console.error('Failed to initialize client:', err); + clearTimeoutTimer(); initError = String(err); } finally { - isInitializing = false; + if (!isTimedOut) { + isInitializing = false; + } } } + function clearTimeoutTimer() { + if (timeoutId) { + clearTimeout(timeoutId); + timeoutId = null; + } + } + + function handleRetryLogin() { + isTimedOut = false; + initializeClient(); + } + async function handleLogin(e: Event) { e.preventDefault(); @@ -154,7 +198,18 @@

Native Qobuz Client for Linux

- {#if isInitializing} + {#if isTimedOut} +
+

Connection is taking too long

+

+ Unable to connect to Qobuz after 60 seconds. This could be a network issue or Qobuz may be temporarily unavailable. +

+
+ + +
+
+ {:else if isInitializing}

{initStatus}

@@ -311,9 +366,37 @@ word-break: break-word; } + .timeout-box { + text-align: center; + padding: 24px; + background-color: rgba(251, 191, 36, 0.1); + border: 1px solid rgba(251, 191, 36, 0.3); + border-radius: 8px; + margin-bottom: 16px; + } + + .timeout-title { + font-size: 16px; + font-weight: 600; + color: #fbbf24; + margin-bottom: 12px; + } + + .timeout-detail { + font-size: 13px; + color: var(--text-muted); + line-height: 1.5; + margin-bottom: 20px; + } + + .timeout-actions { + display: flex; + gap: 12px; + justify-content: center; + } + .retry-btn { - margin-top: 16px; - padding: 8px 24px; + padding: 10px 24px; background-color: var(--accent-primary); color: white; border: none; @@ -328,6 +411,23 @@ background-color: var(--accent-hover); } + .offline-btn { + padding: 10px 24px; + background-color: transparent; + color: var(--text-secondary); + border: 1px solid var(--text-muted); + border-radius: 8px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: all 150ms ease; + } + + .offline-btn:hover { + border-color: var(--text-primary); + color: var(--text-primary); + } + form { display: flex; flex-direction: column; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index a53d4a9ae..53f63b9a7 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1266,6 +1266,9 @@ setLoggedIn(info); showToast(`Welcome, ${info.userName}!`, 'success'); + // Load favorites now that login is confirmed + loadFavorites(); + // Refresh offline status now that we're logged in await refreshOfflineStatus(); @@ -1569,8 +1572,8 @@ initDownloadStates(); startDownloadEventListeners(); - // Load favorites for global state - loadFavorites(); + // Note: loadFavorites() is called in handleLoginSuccess after login is confirmed + // This prevents API calls before authentication is complete // Subscribe to download state changes to trigger reactivity const unsubscribeDownloads = subscribeDownloads(() => {