aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/packages/ui/src/stores/auth-store.ts
diff options
context:
space:
mode:
author苏向夜 <46275354+fu050409@users.noreply.github.com>2026-03-29 21:35:34 +0800
committerGitHub <noreply@github.com>2026-03-29 21:35:34 +0800
commit70348cefb7de8c1e044800296a99177309c5a81e (patch)
treeeb0fdfbcc880574e9b386a3f2fc9b3a89489e5b5 /packages/ui/src/stores/auth-store.ts
parentf2f5383a1b615a7493316d558dc55271198e772a (diff)
parent1c115141cc7b676e6a07786594155c3ac293fe34 (diff)
downloadDropOut-70348cefb7de8c1e044800296a99177309c5a81e.tar.gz
DropOut-70348cefb7de8c1e044800296a99177309c5a81e.zip
refactor(ui): full rewrite instance and code struct (#129)
## Summary by Sourcery Refactor the UI to modernize effect handling, routing, and legacy APIs while adding a reusable alert dialog component and cleaning up obsolete stores. New Features: - Introduce a shared SaturnEffect context via ParticleBackground so pages can access the effect without relying on global window APIs. - Add a Base UI–powered alert dialog component for consistent confirmation and warning flows across the app. - Define a central router configuration module with instance routes to standardize page wiring. Bug Fixes: - Ensure SaturnEffect nullish checks are handled safely when forwarding pointer and touch events from the home view. Enhancements: - Rewrite ParticleBackground to manage its own SaturnEffect lifecycle via React state and context instead of global accessors. - Update the home view to use the SaturnEffect hook, simplify pointer/touch handlers, and remove legacy game and release store usage. - Refine layout and accessibility attributes for various form field and label components, including field grouping and error rendering keys. - Simplify sidebar navigation and adjust the user dropdown trigger to work with the updated dropdown menu API. - Wrap the root outlet for the home route with ParticleBackground only on the index path to limit the effect to the intended view. - Clean up imports and code style in radio group and other UI primitives for consistency. Chores: - Remove deprecated UI stores and utility modules that are no longer used with the new architecture. - Add changeset entries documenting the Saturn effect refactor, ParticleBackground rewrite, and removal of legacy store code.
Diffstat (limited to 'packages/ui/src/stores/auth-store.ts')
-rw-r--r--packages/ui/src/stores/auth-store.ts296
1 files changed, 0 insertions, 296 deletions
diff --git a/packages/ui/src/stores/auth-store.ts b/packages/ui/src/stores/auth-store.ts
deleted file mode 100644
index bf7e3c5..0000000
--- a/packages/ui/src/stores/auth-store.ts
+++ /dev/null
@@ -1,296 +0,0 @@
-import { invoke } from "@tauri-apps/api/core";
-import { listen, type UnlistenFn } from "@tauri-apps/api/event";
-import { open } from "@tauri-apps/plugin-shell";
-import { toast } from "sonner";
-import { create } from "zustand";
-import type { Account, DeviceCodeResponse } from "../types/bindings/auth";
-
-interface AuthState {
- // State
- currentAccount: Account | null;
- isLoginModalOpen: boolean;
- isLogoutConfirmOpen: boolean;
- loginMode: "select" | "offline" | "microsoft";
- offlineUsername: string;
- deviceCodeData: DeviceCodeResponse | null;
- msLoginLoading: boolean;
- msLoginStatus: string;
-
- // Private state
- pollInterval: ReturnType<typeof setInterval> | null;
- isPollingRequestActive: boolean;
- authProgressUnlisten: UnlistenFn | null;
-
- // Actions
- checkAccount: () => Promise<void>;
- openLoginModal: () => void;
- openLogoutConfirm: () => void;
- cancelLogout: () => void;
- confirmLogout: () => Promise<void>;
- closeLoginModal: () => void;
- resetLoginState: () => void;
- performOfflineLogin: () => Promise<void>;
- startMicrosoftLogin: () => Promise<void>;
- checkLoginStatus: (deviceCode: string) => Promise<void>;
- stopPolling: () => void;
- cancelMicrosoftLogin: () => void;
- setLoginMode: (mode: "select" | "offline" | "microsoft") => void;
- setOfflineUsername: (username: string) => void;
-}
-
-export const useAuthStore = create<AuthState>((set, get) => ({
- // Initial state
- currentAccount: null,
- isLoginModalOpen: false,
- isLogoutConfirmOpen: false,
- loginMode: "select",
- offlineUsername: "",
- deviceCodeData: null,
- msLoginLoading: false,
- msLoginStatus: "Waiting for authorization...",
-
- // Private state
- pollInterval: null,
- isPollingRequestActive: false,
- authProgressUnlisten: null,
-
- // Actions
- checkAccount: async () => {
- try {
- const acc = await invoke<Account | null>("get_active_account");
- set({ currentAccount: acc });
- } catch (error) {
- console.error("Failed to check account:", error);
- }
- },
-
- openLoginModal: () => {
- const { currentAccount } = get();
- if (currentAccount) {
- // Show custom logout confirmation dialog
- set({ isLogoutConfirmOpen: true });
- return;
- }
- get().resetLoginState();
- set({ isLoginModalOpen: true });
- },
-
- openLogoutConfirm: () => {
- set({ isLogoutConfirmOpen: true });
- },
-
- cancelLogout: () => {
- set({ isLogoutConfirmOpen: false });
- },
-
- confirmLogout: async () => {
- set({ isLogoutConfirmOpen: false });
- try {
- await invoke("logout");
- set({ currentAccount: null });
- } catch (error) {
- console.error("Logout failed:", error);
- }
- },
-
- closeLoginModal: () => {
- get().stopPolling();
- set({ isLoginModalOpen: false });
- },
-
- resetLoginState: () => {
- set({
- loginMode: "select",
- offlineUsername: "",
- deviceCodeData: null,
- msLoginLoading: false,
- msLoginStatus: "Waiting for authorization...",
- });
- },
-
- performOfflineLogin: async () => {
- const { offlineUsername } = get();
- if (!offlineUsername.trim()) return;
-
- try {
- const account = await invoke<Account>("login_offline", {
- username: offlineUsername,
- });
- set({
- currentAccount: account,
- isLoginModalOpen: false,
- offlineUsername: "",
- });
- } catch (error) {
- // Keep UI-friendly behavior consistent with prior code
- alert("Login failed: " + String(error));
- }
- },
-
- startMicrosoftLogin: async () => {
- // Prepare UI state
- set({
- msLoginLoading: true,
- msLoginStatus: "Waiting for authorization...",
- loginMode: "microsoft",
- deviceCodeData: null,
- });
-
- // Listen to general launcher logs so we can display progress to the user.
- // The backend emits logs via "launcher-log"; using that keeps this store decoupled
- // from a dedicated auth event channel (backend may reuse launcher-log).
- try {
- const unlisten = await listen("launcher-log", (event) => {
- const payload = event.payload;
- // Normalize payload to string if possible
- const message =
- typeof payload === "string"
- ? payload
- : (payload?.toString?.() ?? JSON.stringify(payload));
- set({ msLoginStatus: message });
- });
- set({ authProgressUnlisten: unlisten });
- } catch (err) {
- console.warn("Failed to attach launcher-log listener:", err);
- }
-
- try {
- const deviceCodeData = await invoke<DeviceCodeResponse>(
- "start_microsoft_login",
- );
- set({ deviceCodeData });
-
- if (deviceCodeData) {
- // Try to copy user code to clipboard for convenience (best-effort)
- try {
- await navigator.clipboard?.writeText(deviceCodeData.userCode ?? "");
- } catch (err) {
- // ignore clipboard errors
- console.debug("Clipboard copy failed:", err);
- }
-
- // Open verification URI in default browser
- try {
- if (deviceCodeData.verificationUri) {
- await open(deviceCodeData.verificationUri);
- }
- } catch (err) {
- console.debug("Failed to open verification URI:", err);
- }
-
- // Start polling for completion
- // `interval` from the bindings is a bigint (seconds). Convert safely to number.
- const intervalSeconds =
- deviceCodeData.interval !== undefined &&
- deviceCodeData.interval !== null
- ? Number(deviceCodeData.interval)
- : 5;
- const intervalMs = intervalSeconds * 1000;
- const pollInterval = setInterval(
- () => get().checkLoginStatus(deviceCodeData.deviceCode),
- intervalMs,
- );
- set({ pollInterval });
- }
- } catch (error) {
- toast.error(`Failed to start Microsoft login: ${error}`);
- set({ loginMode: "select" });
- // cleanup listener if present
- const { authProgressUnlisten } = get();
- if (authProgressUnlisten) {
- authProgressUnlisten();
- set({ authProgressUnlisten: null });
- }
- } finally {
- set({ msLoginLoading: false });
- }
- },
-
- checkLoginStatus: async (deviceCode: string) => {
- const { isPollingRequestActive } = get();
- if (isPollingRequestActive) return;
-
- set({ isPollingRequestActive: true });
-
- try {
- const account = await invoke<Account>("complete_microsoft_login", {
- deviceCode,
- });
-
- // On success, stop polling and cleanup listener
- get().stopPolling();
- const { authProgressUnlisten } = get();
- if (authProgressUnlisten) {
- authProgressUnlisten();
- set({ authProgressUnlisten: null });
- }
-
- set({
- currentAccount: account,
- isLoginModalOpen: false,
- });
- } catch (error: unknown) {
- const errStr = String(error);
- if (errStr.includes("authorization_pending")) {
- // Still waiting — keep polling
- } else {
- set({ msLoginStatus: "Error: " + errStr });
-
- if (
- errStr.includes("expired_token") ||
- errStr.includes("access_denied")
- ) {
- // Terminal errors — stop polling and reset state
- get().stopPolling();
- const { authProgressUnlisten } = get();
- if (authProgressUnlisten) {
- authProgressUnlisten();
- set({ authProgressUnlisten: null });
- }
- alert("Login failed: " + errStr);
- set({ loginMode: "select" });
- }
- }
- } finally {
- set({ isPollingRequestActive: false });
- }
- },
-
- stopPolling: () => {
- const { pollInterval, authProgressUnlisten } = get();
- if (pollInterval) {
- try {
- clearInterval(pollInterval);
- } catch (err) {
- console.debug("Failed to clear poll interval:", err);
- }
- set({ pollInterval: null });
- }
- if (authProgressUnlisten) {
- try {
- authProgressUnlisten();
- } catch (err) {
- console.debug("Failed to unlisten auth progress:", err);
- }
- set({ authProgressUnlisten: null });
- }
- },
-
- cancelMicrosoftLogin: () => {
- get().stopPolling();
- set({
- deviceCodeData: null,
- msLoginLoading: false,
- msLoginStatus: "",
- loginMode: "select",
- });
- },
-
- setLoginMode: (mode: "select" | "offline" | "microsoft") => {
- set({ loginMode: mode });
- },
-
- setOfflineUsername: (username: string) => {
- set({ offlineUsername: username });
- },
-}));