From 92352c593621a194c030efced0a4ed24b88c9f47 Mon Sep 17 00:00:00 2001 From: Gemini AI Date: Mon, 29 Dec 2025 03:31:38 +0400 Subject: [PATCH] feat: add user registration, password reset, and guest login to LoginView --- packages/ui/src/components/auth/LoginView.tsx | 521 ++++++++++++++---- 1 file changed, 420 insertions(+), 101 deletions(-) diff --git a/packages/ui/src/components/auth/LoginView.tsx b/packages/ui/src/components/auth/LoginView.tsx index 62e9316..5b8aba0 100644 --- a/packages/ui/src/components/auth/LoginView.tsx +++ b/packages/ui/src/components/auth/LoginView.tsx @@ -1,5 +1,5 @@ import { Component, createSignal, onMount, For, Show } from "solid-js" -import { Lock, User, LogIn, ShieldCheck, Cpu } from "lucide-solid" +import { Lock, User, ShieldCheck, Cpu, UserPlus, KeyRound, ArrowLeft, Ghost } from "lucide-solid" import toast from "solid-toast" import { isElectronHost } from "../../lib/runtime-env" import { setActiveUserId } from "../../lib/user-context" @@ -14,108 +14,255 @@ interface LoginViewProps { onLoginSuccess: (user: UserRecord) => void } +type ViewMode = "login" | "register" | "reset" + const LoginView: Component = (props) => { const [users, setUsers] = createSignal([]) const [username, setUsername] = createSignal("") const [password, setPassword] = createSignal("") - const [isLoggingIn, setIsLoggingIn] = createSignal(false) - const [isLoadingUsers, setIsLoadingUsers] = createSignal(true) + const [confirmPassword, setConfirmPassword] = createSignal("") + const [newPassword, setNewPassword] = createSignal("") + const [isLoading, setIsLoading] = createSignal(false) + const [mode, setMode] = createSignal("login") const getApi = () => { const api = (window as any).electronAPI - console.log("[LoginView] getApi:", api ? Object.keys(api) : "null") return api } - onMount(async () => { - console.log("[LoginView] onMount, isElectronHost:", isElectronHost()) + const loadUsers = async () => { try { if (isElectronHost()) { const api = getApi() - if (api && api.listUsers) { + if (api?.listUsers) { const userList = await api.listUsers() - console.log("[LoginView] listUsers result:", userList) if (userList && Array.isArray(userList)) { setUsers(userList) - if (userList.length > 0) { + if (userList.length > 0 && !username()) { setUsername(userList[0].name) } } - } else { - console.error("[LoginView] listUsers method not found on API") - toast.error("API bridge incomplete") } - } else { - setUsername("web-explorer") } } catch (error) { console.error("Failed to fetch users:", error) - toast.error("Failed to load identities") - } finally { - setIsLoadingUsers(false) } - }) + } + + onMount(loadUsers) + + const resetForm = () => { + setPassword("") + setConfirmPassword("") + setNewPassword("") + } const handleLogin = async (e: Event) => { e.preventDefault() const name = username().trim() - console.log("[LoginView] handleLogin called, name:", name) if (!name) { toast.error("Identity required") return } - setIsLoggingIn(true) + setIsLoading(true) try { - console.log("[LoginView] isElectronHost:", isElectronHost()) if (isElectronHost()) { const api = getApi() - if (!api || !api.listUsers || !api.loginUser) { + if (!api?.listUsers || !api?.loginUser) { toast.error("API bridge not ready") return } - console.log("[LoginView] Fetching users...") const userList = await api.listUsers() - if (!userList || !Array.isArray(userList)) { - toast.error("Bridge failure: try restarting") - return - } - - console.log("[LoginView] Users found:", userList.map((u: any) => u.name)) - const user = userList.find((u: UserRecord) => u.name.toLowerCase() === name.toLowerCase()) + const user = userList?.find((u: UserRecord) => u.name.toLowerCase() === name.toLowerCase()) if (!user) { toast.error(`Identity "${name}" not found`) return } - console.log("[LoginView] Attempting login for:", user.id) const result = await api.loginUser({ id: user.id, password: password(), }) - console.log("[LoginView] Login result:", JSON.stringify(result)) if (result?.success) { - console.log("[LoginView] SUCCESS! Calling onLoginSuccess with:", result.user) toast.success(`Welcome back, ${result.user.name}!`) setActiveUserId(result.user.id) props.onLoginSuccess(result.user) } else { - console.log("[LoginView] FAILED - result.success is:", result?.success) - toast.error("Invalid key for this identity") + toast.error("Invalid access key") } } else { - console.log("[LoginView] Web mode login") toast.success("Web mode access granted") - props.onLoginSuccess({ id: "web-user", name: "Web Explorer" }) + props.onLoginSuccess({ id: "web-user", name: username() || "Web Explorer" }) } } catch (error) { console.error("Login failed:", error) - toast.error("Decryption failed: check your key") + toast.error("Authentication failed") } finally { - setIsLoggingIn(false) + setIsLoading(false) + } + } + + const handleGuestLogin = async () => { + setIsLoading(true) + try { + const api = getApi() + if (api?.createGuest) { + const guestUser = await api.createGuest() + if (guestUser?.id) { + toast.success(`Welcome, ${guestUser.name}!`) + setActiveUserId(guestUser.id) + props.onLoginSuccess(guestUser) + } else { + toast.error("Failed to create guest session") + } + } else { + // Web mode fallback + const guestId = `guest-${Date.now()}` + toast.success("Guest session started") + props.onLoginSuccess({ id: guestId, name: "Guest", isGuest: true }) + } + } catch (error) { + console.error("Guest login failed:", error) + toast.error("Guest login failed") + } finally { + setIsLoading(false) + } + } + + const handleRegister = async (e: Event) => { + e.preventDefault() + const name = username().trim() + + if (!name) { + toast.error("Username required") + return + } + if (name.length < 3) { + toast.error("Username must be at least 3 characters") + return + } + if (!password()) { + toast.error("Password required") + return + } + if (password().length < 4) { + toast.error("Password must be at least 4 characters") + return + } + if (password() !== confirmPassword()) { + toast.error("Passwords do not match") + return + } + + // Check if user already exists + const existingUser = users().find(u => u.name.toLowerCase() === name.toLowerCase()) + if (existingUser) { + toast.error(`Identity "${name}" already exists`) + return + } + + setIsLoading(true) + try { + const api = getApi() + if (!api?.createUser) { + toast.error("Registration unavailable") + return + } + + const newUser = await api.createUser({ + name: name, + password: password(), + }) + + if (newUser?.id) { + toast.success(`Identity "${name}" created successfully!`) + await loadUsers() + setMode("login") + setUsername(name) + resetForm() + } else { + toast.error("Failed to create identity") + } + } catch (error) { + console.error("Registration failed:", error) + toast.error("Registration failed") + } finally { + setIsLoading(false) + } + } + + const handleResetPassword = async (e: Event) => { + e.preventDefault() + const name = username().trim() + + if (!name) { + toast.error("Select an identity first") + return + } + if (!password()) { + toast.error("Current password required") + return + } + if (!newPassword()) { + toast.error("New password required") + return + } + if (newPassword().length < 4) { + toast.error("New password must be at least 4 characters") + return + } + + const user = users().find(u => u.name.toLowerCase() === name.toLowerCase()) + if (!user) { + toast.error(`Identity "${name}" not found`) + return + } + + setIsLoading(true) + try { + const api = getApi() + + // First verify current password + const verifyResult = await api.loginUser({ + id: user.id, + password: password(), + }) + + if (!verifyResult?.success) { + toast.error("Current password is incorrect") + return + } + + // Update password + const updateResult = await api.updateUser({ + id: user.id, + password: newPassword(), + }) + + if (updateResult?.id) { + toast.success("Password updated successfully!") + setMode("login") + resetForm() + } else { + toast.error("Failed to update password") + } + } catch (error) { + console.error("Password reset failed:", error) + toast.error("Password reset failed") + } finally { + setIsLoading(false) + } + } + + const switchMode = (newMode: ViewMode) => { + setMode(newMode) + resetForm() + if (newMode === "register") { + setUsername("") } } @@ -127,78 +274,250 @@ const LoginView: Component = (props) => {
-
+
{/* Logo & Header */} -
-
+
+
- +
-

NomadArch

-

Secure Neural Access Point

+

NomadArch

+

+ {mode() === "login" && "Secure Neural Access Point"} + {mode() === "register" && "Create New Identity"} + {mode() === "reset" && "Reset Access Key"} +

-
- {/* User Selection */} -
- -
-
- -
- setUsername(e.currentTarget.value)} - class="block w-full pl-12 pr-4 py-4 bg-[#1a1a1a] border border-white/5 rounded-2xl text-white appearance-none focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500/50 transition-all cursor-text" - list="identity-suggestions" - /> - - - {(user) => - -
-
- - {/* Password Input */} -
- -
-
- -
- setPassword(e.currentTarget.value)} - required - class="block w-full pl-12 pr-4 py-4 bg-[#1a1a1a] border border-white/5 rounded-2xl text-white placeholder-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500/50 transition-all font-mono" - /> -
-
- + {/* Back button for non-login modes */} + - +
-
+ {/* Login Form */} + +
+
+ +
+
+ +
+ setUsername(e.currentTarget.value)} + class="block w-full pl-12 pr-4 py-3.5 bg-[#1a1a1a] border border-white/5 rounded-2xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 transition-all" + list="identity-suggestions" + /> + + {(user) => + +
+
+ +
+ +
+
+ +
+ setPassword(e.currentTarget.value)} + class="block w-full pl-12 pr-4 py-3.5 bg-[#1a1a1a] border border-white/5 rounded-2xl text-white placeholder-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500/50 transition-all font-mono" + /> +
+
+ + +
+ +
+ + +
+ + +
+
+
+ + {/* Register Form */} + +
+
+ +
+
+ +
+ setUsername(e.currentTarget.value)} + class="block w-full pl-12 pr-4 py-3.5 bg-[#1a1a1a] border border-white/5 rounded-2xl text-white focus:outline-none focus:ring-2 focus:ring-green-500/50 transition-all" + /> +
+
+ +
+ +
+
+ +
+ setPassword(e.currentTarget.value)} + class="block w-full pl-12 pr-4 py-3.5 bg-[#1a1a1a] border border-white/5 rounded-2xl text-white placeholder-gray-600 focus:outline-none focus:ring-2 focus:ring-green-500/50 transition-all font-mono" + /> +
+
+ +
+ +
+
+ +
+ setConfirmPassword(e.currentTarget.value)} + class="block w-full pl-12 pr-4 py-3.5 bg-[#1a1a1a] border border-white/5 rounded-2xl text-white placeholder-gray-600 focus:outline-none focus:ring-2 focus:ring-green-500/50 transition-all font-mono" + /> +
+
+ + +
+
+ + {/* Reset Password Form */} + +
+
+ +
+
+ +
+ setUsername(e.currentTarget.value)} + class="block w-full pl-12 pr-4 py-3.5 bg-[#1a1a1a] border border-white/5 rounded-2xl text-white focus:outline-none focus:ring-2 focus:ring-purple-500/50 transition-all" + list="identity-suggestions-reset" + /> + + {(user) => + +
+
+ +
+ +
+
+ +
+ setPassword(e.currentTarget.value)} + class="block w-full pl-12 pr-4 py-3.5 bg-[#1a1a1a] border border-white/5 rounded-2xl text-white placeholder-gray-600 focus:outline-none focus:ring-2 focus:ring-purple-500/50 transition-all font-mono" + /> +
+
+ +
+ +
+
+ +
+ setNewPassword(e.currentTarget.value)} + class="block w-full pl-12 pr-4 py-3.5 bg-[#1a1a1a] border border-white/5 rounded-2xl text-white placeholder-gray-600 focus:outline-none focus:ring-2 focus:ring-purple-500/50 transition-all font-mono" + /> +
+
+ + +
+
+ +
Powered by Antigravity OS v4.5 | Encrypted Connection