From b275a3668b140d9ce4663de646519d2dbd4297e7 Mon Sep 17 00:00:00 2001
From: 苏向夜 <fu050409@163.com>
Date: Tue, 24 Feb 2026 22:41:36 +0800
Subject: refactor: rewrite login and settings pages

---
 packages/ui-new/src/components/login-modal.tsx | 292 ++++++++++++++-----------
 1 file changed, 162 insertions(+), 130 deletions(-)

(limited to 'packages/ui-new/src/components/login-modal.tsx')

diff --git a/packages/ui-new/src/components/login-modal.tsx b/packages/ui-new/src/components/login-modal.tsx
index 9152494..49596da 100644
--- a/packages/ui-new/src/components/login-modal.tsx
+++ b/packages/ui-new/src/components/login-modal.tsx
@@ -1,156 +1,188 @@
 import { Mail, User } from "lucide-react";
-import { useAuthStore } from "@/stores/auth-store";
+import { useCallback, useState } from "react";
+import { toast } from "sonner";
+import { useAuthStore } from "@/models/auth";
+import { Button } from "./ui/button";
+import {
+  Dialog,
+  DialogContent,
+  DialogDescription,
+  DialogFooter,
+  DialogHeader,
+  DialogTitle,
+} from "./ui/dialog";
+import {
+  Field,
+  FieldDescription,
+  FieldError,
+  FieldGroup,
+  FieldLabel,
+} from "./ui/field";
+import { Input } from "./ui/input";
 
-export function LoginModal() {
+export interface LoginModalProps
+  extends Omit<React.ComponentPropsWithoutRef<typeof Dialog>, "onOpenChange"> {
+  open: boolean;
+  onOpenChange: (open: boolean) => void;
+}
+
+export function LoginModal({ onOpenChange, ...props }: LoginModalProps) {
   const authStore = useAuthStore();
 
-  const handleOfflineLogin = () => {
-    if (authStore.offlineUsername.trim()) {
-      authStore.performOfflineLogin();
-    }
-  };
+  const [offlineUsername, setOfflineUsername] = useState<string>("");
+  const [errorMessage, setErrorMessage] = useState<string>("");
+  const [isLoggingIn, setIsLoggingIn] = useState<boolean>(false);
 
-  const handleKeyPress = (e: React.KeyboardEvent) => {
-    if (e.key === "Enter") {
-      handleOfflineLogin();
+  const handleMicrosoftLogin = useCallback(async () => {
+    setIsLoggingIn(true);
+    authStore.setLoginMode("microsoft");
+    try {
+      await authStore.loginOnline(() => onOpenChange?.(false));
+    } catch (error) {
+      const err = error as Error;
+      console.error("Failed to login with Microsoft:", err);
+      setErrorMessage(err.message);
+    } finally {
+      setIsLoggingIn(false);
     }
-  };
+  }, [authStore.loginOnline, authStore.setLoginMode, onOpenChange]);
 
-  if (!authStore.isLoginModalOpen) return null;
+  const handleOfflineLogin = useCallback(async () => {
+    setIsLoggingIn(true);
+    try {
+      await authStore.loginOffline(offlineUsername);
+      toast.success("Logged in offline successfully");
+      onOpenChange?.(false);
+    } catch (error) {
+      const err = error as Error;
+      console.error("Failed to login offline:", err);
+      setErrorMessage(err.message);
+    } finally {
+      setIsLoggingIn(false);
+    }
+  }, [authStore, offlineUsername, onOpenChange]);
 
   return (
-    <div className="fixed inset-0 z-200 bg-black/70 backdrop-blur-sm flex items-center justify-center p-4">
-      <div className="bg-zinc-900 border border-zinc-700 rounded-xl shadow-2xl max-w-md w-full animate-in fade-in zoom-in-95 duration-200">
-        <div className="p-6">
-          {/* Header */}
-          <div className="flex items-center justify-between mb-6">
-            <h3 className="text-xl font-bold text-white">Login</h3>
-            <button
-              type="button"
-              onClick={() => {
-                authStore.setLoginMode("select");
-                authStore.setOfflineUsername("");
-                authStore.cancelMicrosoftLogin();
-              }}
-              className="text-zinc-400 hover:text-white transition-colors p-1"
-            >
-              ×
-            </button>
-          </div>
-
-          {/* Content based on mode */}
-          {authStore.loginMode === "select" && (
-            <div className="space-y-4">
-              <p className="text-zinc-400 text-sm">
-                Choose your preferred login method
-              </p>
-              <button
-                type="button"
-                onClick={() => authStore.startMicrosoftLogin()}
-                className="w-full flex items-center justify-center gap-3 px-4 py-3 bg-blue-600 hover:bg-blue-500 text-white rounded-lg transition-colors"
+    <Dialog onOpenChange={onOpenChange} {...props}>
+      <DialogContent className="md:max-w-md">
+        <DialogHeader>
+          <DialogTitle>Login</DialogTitle>
+          <DialogDescription>
+            Login to your Minecraft account or play offline
+          </DialogDescription>
+        </DialogHeader>
+        <div className="p-4 w-full overflow-hidden">
+          {!authStore.loginMode && (
+            <div className="flex flex-col space-y-4">
+              <Button size="lg" onClick={handleMicrosoftLogin}>
+                <Mail />
+                Login with Microsoft
+              </Button>
+              <Button
+                variant="secondary"
+                onClick={() => authStore.setLoginMode("offline")}
+                size="lg"
               >
-                <Mail size={18} />
-                <span className="font-medium">Microsoft Account</span>
-              </button>
+                <User />
+                Login Offline
+              </Button>
+            </div>
+          )}
+          {authStore.loginMode === "microsoft" && (
+            <div className="flex flex-col space-y-4">
               <button
                 type="button"
+                className="text-4xl font-bold text-center bg-accent p-4 cursor-pointer"
                 onClick={() => {
-                  authStore.loginMode = "offline";
+                  if (authStore.deviceCode?.userCode) {
+                    navigator.clipboard?.writeText(
+                      authStore.deviceCode?.userCode,
+                    );
+                    toast.success("Copied to clipboard");
+                  }
                 }}
-                className="w-full flex items-center justify-center gap-3 px-4 py-3 bg-zinc-800 hover:bg-zinc-700 text-white rounded-lg transition-colors"
               >
-                <User size={18} />
-                <span className="font-medium">Offline Mode</span>
+                {authStore.deviceCode?.userCode}
               </button>
-            </div>
-          )}
-
-          {authStore.loginMode === "offline" && (
-            <div className="space-y-4">
-              <div>
-                <label
-                  htmlFor="username"
-                  className="block text-sm font-medium text-zinc-300 mb-2"
-                >
-                  Username
-                </label>
-                <input
-                  name="username"
-                  type="text"
-                  value={authStore.offlineUsername}
-                  onChange={(e) => authStore.setOfflineUsername(e.target.value)}
-                  onKeyDown={handleKeyPress}
-                  className="w-full px-4 py-2.5 bg-zinc-800 border border-zinc-700 rounded-lg text-white placeholder:text-zinc-500 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition-colors"
-                  placeholder="Enter your Minecraft username"
-                />
-              </div>
-              <div className="flex gap-3">
-                <button
-                  type="button"
+              <span className="text-muted-foreground w-full overflow-hidden text-ellipsis">
+                To sign in, use a web browser to open the page{" "}
+                <a href={authStore.deviceCode?.verificationUri}>
+                  {authStore.deviceCode?.verificationUri}
+                </a>{" "}
+                and enter the code{" "}
+                <code
+                  className="font-semibold cursor-pointer"
                   onClick={() => {
-                    authStore.loginMode = "select";
-                    authStore.setOfflineUsername("");
+                    if (authStore.deviceCode?.userCode) {
+                      navigator.clipboard?.writeText(
+                        authStore.deviceCode?.userCode,
+                      );
+                    }
+                  }}
+                  onKeyDown={() => {
+                    if (authStore.deviceCode?.userCode) {
+                      navigator.clipboard?.writeText(
+                        authStore.deviceCode?.userCode,
+                      );
+                    }
                   }}
-                  className="flex-1 px-4 py-2.5 text-sm font-medium text-zinc-300 hover:text-white bg-zinc-800 hover:bg-zinc-700 rounded-lg transition-colors"
-                >
-                  Back
-                </button>
-                <button
-                  type="button"
-                  onClick={handleOfflineLogin}
-                  disabled={!authStore.offlineUsername.trim()}
-                  className="flex-1 px-4 py-2.5 text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-500 disabled:bg-indigo-600/50 disabled:cursor-not-allowed rounded-lg transition-colors"
                 >
-                  Login
-                </button>
-              </div>
+                  {authStore.deviceCode?.userCode}
+                </code>{" "}
+                to authenticate, this code will be expired in{" "}
+                {authStore.deviceCode?.expiresIn} seconds.
+              </span>
+              <FieldError>{errorMessage}</FieldError>
             </div>
           )}
-
-          {authStore.loginMode === "microsoft" && (
-            <div className="space-y-4">
-              {authStore.deviceCodeData && (
-                <div className="bg-zinc-800/50 border border-zinc-700 rounded-lg p-4">
-                  <div className="text-center mb-4">
-                    <div className="text-xs font-mono bg-zinc-900 px-3 py-2 rounded border border-zinc-700 mb-3">
-                      {authStore.deviceCodeData.userCode}
-                    </div>
-                    <p className="text-zinc-300 text-sm font-medium">
-                      Your verification code
-                    </p>
-                  </div>
-                  <p className="text-zinc-400 text-sm text-center">
-                    Visit{" "}
-                    <a
-                      href={authStore.deviceCodeData.verificationUri}
-                      target="_blank"
-                      className="text-indigo-400 hover:text-indigo-300 font-medium"
-                    >
-                      {authStore.deviceCodeData.verificationUri}
-                    </a>{" "}
-                    and enter the code above
-                  </p>
-                </div>
-              )}
-              <div className="text-center">
-                <p className="text-zinc-300 text-sm mb-2">
-                  {authStore.msLoginStatus}
-                </p>
-                <button
-                  type="button"
-                  onClick={() => {
-                    authStore.cancelMicrosoftLogin();
-                    authStore.setLoginMode("select");
+          {authStore.loginMode === "offline" && (
+            <FieldGroup>
+              <Field>
+                <FieldLabel>Username</FieldLabel>
+                <FieldDescription>
+                  Enter a username to play offline
+                </FieldDescription>
+                <Input
+                  value={offlineUsername}
+                  onChange={(e) => {
+                    setOfflineUsername(e.target.value);
+                    setErrorMessage("");
                   }}
-                  className="text-sm text-zinc-400 hover:text-white transition-colors"
-                >
-                  Cancel
-                </button>
-              </div>
-            </div>
+                  aria-invalid={!!errorMessage}
+                />
+                <FieldError>{errorMessage}</FieldError>
+              </Field>
+            </FieldGroup>
           )}
         </div>
-      </div>
-    </div>
+        <DialogFooter>
+          <div className="flex flex-col justify-center items-center">
+            <span className="text-xs text-muted-foreground ">
+              {authStore.statusMessage}
+            </span>
+          </div>
+          <Button
+            variant="outline"
+            onClick={() => {
+              if (authStore.loginMode) {
+                if (authStore.loginMode === "microsoft") {
+                  authStore.cancelLoginOnline();
+                }
+                authStore.setLoginMode(null);
+              } else {
+                onOpenChange?.(false);
+              }
+            }}
+          >
+            Cancel
+          </Button>
+          {authStore.loginMode === "offline" && (
+            <Button onClick={handleOfflineLogin} disabled={isLoggingIn}>
+              Login
+            </Button>
+          )}
+        </DialogFooter>
+      </DialogContent>
+    </Dialog>
   );
 }
-- 
cgit v1.2.3-70-g09d2

