aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorHsiangNianian <i@jyunko.cn>2026-01-15 18:17:49 +0800
committerHsiangNianian <i@jyunko.cn>2026-01-15 18:17:49 +0800
commit314ce0e656107ab43a8e8681d85525a551f83f21 (patch)
tree3beffaf90afc62221eca10dbfd72695800290843
parent20cd97d8b3af67050fbe7b5f8d6d5fb1c1f3237b (diff)
downloadDropOut-314ce0e656107ab43a8e8681d85525a551f83f21.tar.gz
DropOut-314ce0e656107ab43a8e8681d85525a551f83f21.zip
feat: Implement logout confirmation dialog and enhance account status display in BottomBar for improved user experience
-rw-r--r--ui/src/App.svelte26
-rw-r--r--ui/src/components/BottomBar.svelte23
-rw-r--r--ui/src/stores/auth.svelte.ts54
-rw-r--r--ui/src/types/index.ts3
4 files changed, 97 insertions, 9 deletions
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 @@
<LoginModal />
<StatusToast />
+ <!-- Logout Confirmation Dialog -->
+ {#if authState.isLogoutConfirmOpen}
+ <div class="fixed inset-0 z-[200] bg-black/70 backdrop-blur-sm flex items-center justify-center p-4">
+ <div class="bg-zinc-900 border border-zinc-700 rounded-xl shadow-2xl p-6 max-w-sm w-full animate-in fade-in zoom-in-95 duration-200">
+ <h3 class="text-lg font-bold text-white mb-2">Logout</h3>
+ <p class="text-zinc-400 text-sm mb-6">
+ Are you sure you want to logout <span class="text-white font-medium">{authState.currentAccount?.username}</span>?
+ </p>
+ <div class="flex gap-3 justify-end">
+ <button
+ onclick={() => authState.cancelLogout()}
+ class="px-4 py-2 text-sm font-medium text-zinc-300 hover:text-white bg-zinc-800 hover:bg-zinc-700 rounded-lg transition-colors"
+ >
+ Cancel
+ </button>
+ <button
+ onclick={() => authState.confirmLogout()}
+ class="px-4 py-2 text-sm font-medium text-white bg-red-600 hover:bg-red-500 rounded-lg transition-colors"
+ >
+ Logout
+ </button>
+ </div>
+ </div>
+ </div>
+ {/if}
+
{#if uiState.showConsole}
<div class="fixed inset-0 z-[100] bg-black/80 backdrop-blur-sm flex items-center justify-center p-8">
<div class="w-full h-full max-w-6xl max-h-[85vh] bg-[#1e1e1e] rounded-lg overflow-hidden border border-zinc-700 shadow-2xl relative flex flex-col">
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"}
</div>
<div class="text-[10px] uppercase tracking-wider dark:text-zinc-500 text-gray-500 flex items-center gap-2">
- <span
- class="w-1.5 h-1.5 rounded-full {authState.currentAccount
- ? 'bg-emerald-500'
- : 'bg-zinc-400'}"
- ></span>
- {authState.currentAccount ? "Online" : "Guest"}
+ {#if authState.currentAccount}
+ {#if authState.currentAccount.type === "Microsoft"}
+ {#if authState.currentAccount.expires_at && authState.currentAccount.expires_at * 1000 < Date.now()}
+ <span class="w-1.5 h-1.5 rounded-full bg-red-500"></span>
+ <span class="text-red-400">Expired</span>
+ {:else}
+ <span class="w-1.5 h-1.5 rounded-full bg-emerald-500"></span>
+ Online
+ {/if}
+ {:else}
+ <span class="w-1.5 h-1.5 rounded-full bg-amber-500"></span>
+ Offline
+ {/if}
+ {:else}
+ <span class="w-1.5 h-1.5 rounded-full bg-zinc-400"></span>
+ Guest
+ {/if}
</div>
</div>
</div>
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<Account | null>(null);
isLoginModalOpen = $state(false);
+ isLogoutConfirmOpen = $state(false);
loginMode = $state<"select" | "offline" | "microsoft">("select");
offlineUsername = $state("");
deviceCodeData = $state<DeviceCodeResponse | null>(null);
@@ -14,6 +17,7 @@ export class AuthState {
private pollInterval: ReturnType<typeof setInterval> | 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<string>("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 {