From 314ce0e656107ab43a8e8681d85525a551f83f21 Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Thu, 15 Jan 2026 18:17:49 +0800 Subject: feat: Implement logout confirmation dialog and enhance account status display in BottomBar for improved user experience --- ui/src/App.svelte | 26 ++++++++++++++++++ ui/src/components/BottomBar.svelte | 23 +++++++++++----- ui/src/stores/auth.svelte.ts | 54 +++++++++++++++++++++++++++++++++++--- ui/src/types/index.ts | 3 +++ 4 files changed, 97 insertions(+), 9 deletions(-) (limited to 'ui/src') diff --git a/ui/src/App.svelte b/ui/src/App.svelte index 0bb31ae..760a15f 100644 --- a/ui/src/App.svelte +++ b/ui/src/App.svelte @@ -141,6 +141,32 @@ + + {#if authState.isLogoutConfirmOpen} +
+
+

Logout

+

+ Are you sure you want to logout {authState.currentAccount?.username}? +

+
+ + +
+
+
+ {/if} + {#if uiState.showConsole}
diff --git a/ui/src/components/BottomBar.svelte b/ui/src/components/BottomBar.svelte index abb0b23..9dcb9ac 100644 --- a/ui/src/components/BottomBar.svelte +++ b/ui/src/components/BottomBar.svelte @@ -67,12 +67,23 @@ {authState.currentAccount ? authState.currentAccount.username : "Login Account"}
- - {authState.currentAccount ? "Online" : "Guest"} + {#if authState.currentAccount} + {#if authState.currentAccount.type === "Microsoft"} + {#if authState.currentAccount.expires_at && authState.currentAccount.expires_at * 1000 < Date.now()} + + Expired + {:else} + + Online + {/if} + {:else} + + Offline + {/if} + {:else} + + Guest + {/if}
diff --git a/ui/src/stores/auth.svelte.ts b/ui/src/stores/auth.svelte.ts index 3d58245..eb9dccd 100644 --- a/ui/src/stores/auth.svelte.ts +++ b/ui/src/stores/auth.svelte.ts @@ -1,11 +1,14 @@ import { invoke } from "@tauri-apps/api/core"; import { open } from "@tauri-apps/plugin-shell"; +import { listen, type UnlistenFn } from "@tauri-apps/api/event"; import type { Account, DeviceCodeResponse } from "../types"; import { uiState } from "./ui.svelte"; +import { logsState } from "./logs.svelte"; export class AuthState { currentAccount = $state(null); isLoginModalOpen = $state(false); + isLogoutConfirmOpen = $state(false); loginMode = $state<"select" | "offline" | "microsoft">("select"); offlineUsername = $state(""); deviceCodeData = $state(null); @@ -14,6 +17,7 @@ export class AuthState { private pollInterval: ReturnType | null = null; private isPollingRequestActive = false; + private authProgressUnlisten: UnlistenFn | null = null; async checkAccount() { try { @@ -26,15 +30,29 @@ export class AuthState { openLoginModal() { if (this.currentAccount) { - if (confirm("Logout " + this.currentAccount.username + "?")) { - invoke("logout").then(() => (this.currentAccount = null)); - } + // Show custom logout confirmation dialog + this.isLogoutConfirmOpen = true; return; } this.resetLoginState(); this.isLoginModalOpen = true; } + cancelLogout() { + this.isLogoutConfirmOpen = false; + } + + async confirmLogout() { + this.isLogoutConfirmOpen = false; + try { + await invoke("logout"); + this.currentAccount = null; + uiState.setStatus("Logged out successfully"); + } catch (e) { + console.error("Logout failed:", e); + } + } + closeLoginModal() { this.stopPolling(); this.isLoginModalOpen = false; @@ -65,6 +83,9 @@ export class AuthState { this.msLoginStatus = "Waiting for authorization..."; this.stopPolling(); + // Setup auth progress listener + this.setupAuthProgressListener(); + try { this.deviceCodeData = (await invoke( "start_microsoft_login" @@ -78,6 +99,7 @@ export class AuthState { } open(this.deviceCodeData.verification_uri); + logsState.addLog("info", "Auth", "Microsoft login started, waiting for browser authorization..."); console.log("Starting polling for token..."); const intervalMs = (this.deviceCodeData.interval || 5) * 1000; @@ -87,6 +109,7 @@ export class AuthState { ); } } catch (e) { + logsState.addLog("error", "Auth", `Failed to start Microsoft login: ${e}`); alert("Failed to start Microsoft login: " + e); this.loginMode = "select"; } finally { @@ -94,6 +117,27 @@ export class AuthState { } } + private async setupAuthProgressListener() { + // Clean up previous listener if exists + if (this.authProgressUnlisten) { + this.authProgressUnlisten(); + this.authProgressUnlisten = null; + } + + this.authProgressUnlisten = await listen("auth-progress", (event) => { + const message = event.payload; + this.msLoginStatus = message; + logsState.addLog("info", "Auth", message); + }); + } + + private cleanupAuthListener() { + if (this.authProgressUnlisten) { + this.authProgressUnlisten(); + this.authProgressUnlisten = null; + } + } + stopPolling() { if (this.pollInterval) { clearInterval(this.pollInterval); @@ -113,7 +157,9 @@ export class AuthState { console.log("Login Successful!", this.currentAccount); this.stopPolling(); + this.cleanupAuthListener(); this.isLoginModalOpen = false; + logsState.addLog("info", "Auth", `Login successful! Welcome, ${this.currentAccount.username}`); uiState.setStatus("Welcome back, " + this.currentAccount.username); } catch (e: any) { const errStr = e.toString(); @@ -122,12 +168,14 @@ export class AuthState { } else { console.error("Polling Error:", errStr); this.msLoginStatus = "Error: " + errStr; + logsState.addLog("error", "Auth", `Login error: ${errStr}`); if ( errStr.includes("expired_token") || errStr.includes("access_denied") ) { this.stopPolling(); + this.cleanupAuthListener(); alert("Login failed: " + errStr); this.loginMode = "select"; } diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index 09a7d5e..0f02d64 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -12,6 +12,9 @@ export interface Account { type: "Offline" | "Microsoft"; username: string; uuid: string; + access_token?: string; + refresh_token?: string; + expires_at?: number; // Unix timestamp for Microsoft accounts } export interface DeviceCodeResponse { -- cgit v1.2.3-70-g09d2