|
|
|
|
@@ -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,106 +14,255 @@ interface LoginViewProps {
|
|
|
|
|
onLoginSuccess: (user: UserRecord) => void
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type ViewMode = "login" | "register" | "reset"
|
|
|
|
|
|
|
|
|
|
const LoginView: Component<LoginViewProps> = (props) => {
|
|
|
|
|
const [users, setUsers] = createSignal<UserRecord[]>([])
|
|
|
|
|
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<ViewMode>("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:", result)
|
|
|
|
|
if (result?.success) {
|
|
|
|
|
toast.success(`Welcome back, ${result.user.name}!`)
|
|
|
|
|
setActiveUserId(result.user.id)
|
|
|
|
|
props.onLoginSuccess(result.user)
|
|
|
|
|
} else {
|
|
|
|
|
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("")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -125,78 +274,250 @@ const LoginView: Component<LoginViewProps> = (props) => {
|
|
|
|
|
<div class="absolute -bottom-[10%] -right-[10%] w-[40%] h-[40%] bg-purple-500/20 blur-[120px] rounded-full animate-pulse delay-700" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="relative w-full max-w-md px-6 py-12 bg-[#141414]/80 backdrop-blur-xl border border-white/10 rounded-3xl shadow-2xl">
|
|
|
|
|
<div class="relative w-full max-w-md px-6 py-10 bg-[#141414]/80 backdrop-blur-xl border border-white/10 rounded-3xl shadow-2xl">
|
|
|
|
|
{/* Logo & Header */}
|
|
|
|
|
<div class="flex flex-col items-center mb-10">
|
|
|
|
|
<div class="w-20 h-20 mb-6 bg-gradient-to-br from-blue-500 via-indigo-600 to-purple-700 p-0.5 rounded-2xl shadow-lg transform rotate-3">
|
|
|
|
|
<div class="flex flex-col items-center mb-8">
|
|
|
|
|
<div class="w-16 h-16 mb-4 bg-gradient-to-br from-blue-500 via-indigo-600 to-purple-700 p-0.5 rounded-2xl shadow-lg transform rotate-3">
|
|
|
|
|
<div class="w-full h-full bg-[#141414] rounded-2xl flex items-center justify-center">
|
|
|
|
|
<Cpu class="w-10 h-10 text-white" />
|
|
|
|
|
<Cpu class="w-8 h-8 text-white" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<h1 class="text-3xl font-bold text-white tracking-tight mb-2">NomadArch</h1>
|
|
|
|
|
<p class="text-gray-400 text-sm">Secure Neural Access Point</p>
|
|
|
|
|
<h1 class="text-2xl font-bold text-white tracking-tight mb-1">NomadArch</h1>
|
|
|
|
|
<p class="text-gray-400 text-sm">
|
|
|
|
|
{mode() === "login" && "Secure Neural Access Point"}
|
|
|
|
|
{mode() === "register" && "Create New Identity"}
|
|
|
|
|
{mode() === "reset" && "Reset Access Key"}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<form onSubmit={handleLogin} class="space-y-6">
|
|
|
|
|
{/* User Selection */}
|
|
|
|
|
<div class="space-y-2">
|
|
|
|
|
<label class="text-xs font-semibold text-gray-500 uppercase tracking-wider ml-1">Identity</label>
|
|
|
|
|
<div class="relative group">
|
|
|
|
|
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
|
|
|
|
<User class="w-5 h-5 text-gray-500 group-focus-within:text-blue-500 transition-colors" />
|
|
|
|
|
</div>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder="Username"
|
|
|
|
|
value={username()}
|
|
|
|
|
onInput={(e) => 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"
|
|
|
|
|
/>
|
|
|
|
|
<datalist id="identity-suggestions">
|
|
|
|
|
<For each={users()}>
|
|
|
|
|
{(user) => <option value={user.name} />}
|
|
|
|
|
</For>
|
|
|
|
|
</datalist>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Password Input */}
|
|
|
|
|
<div class="space-y-2">
|
|
|
|
|
<label class="text-xs font-semibold text-gray-500 uppercase tracking-wider ml-1">Access Key</label>
|
|
|
|
|
<div class="relative group">
|
|
|
|
|
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
|
|
|
|
<Lock class="w-5 h-5 text-gray-500 group-focus-within:text-blue-500 transition-colors" />
|
|
|
|
|
</div>
|
|
|
|
|
<input
|
|
|
|
|
type="password"
|
|
|
|
|
placeholder="Enter password..."
|
|
|
|
|
value={password()}
|
|
|
|
|
onInput={(e) => 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"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Back button for non-login modes */}
|
|
|
|
|
<Show when={mode() !== "login"}>
|
|
|
|
|
<button
|
|
|
|
|
type="submit"
|
|
|
|
|
disabled={isLoggingIn() || !username()}
|
|
|
|
|
class="w-full flex items-center justify-center gap-3 py-4 bg-gradient-to-r from-blue-600 via-indigo-600 to-purple-600 hover:from-blue-500 hover:to-purple-500 text-white font-bold rounded-2xl shadow-xl shadow-blue-900/20 transform active:scale-[0.98] transition-all disabled:opacity-50 disabled:cursor-not-allowed group"
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => switchMode("login")}
|
|
|
|
|
class="flex items-center gap-2 text-gray-400 hover:text-white transition-colors mb-4"
|
|
|
|
|
>
|
|
|
|
|
<Show when={isLoggingIn()} fallback={
|
|
|
|
|
<>
|
|
|
|
|
<ShieldCheck class="w-5 h-5 group-hover:scale-110 transition-transform" />
|
|
|
|
|
<span>Verify Identity</span>
|
|
|
|
|
</>
|
|
|
|
|
}>
|
|
|
|
|
<div class="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
|
|
|
|
<span>Decrypting...</span>
|
|
|
|
|
</Show>
|
|
|
|
|
<ArrowLeft class="w-4 h-4" />
|
|
|
|
|
<span class="text-sm">Back to login</span>
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
|
|
<div class="mt-8 text-center text-xs text-gray-600">
|
|
|
|
|
{/* Login Form */}
|
|
|
|
|
<Show when={mode() === "login"}>
|
|
|
|
|
<form onSubmit={handleLogin} class="space-y-5">
|
|
|
|
|
<div class="space-y-1.5">
|
|
|
|
|
<label class="text-xs font-semibold text-gray-500 uppercase tracking-wider ml-1">Identity</label>
|
|
|
|
|
<div class="relative group">
|
|
|
|
|
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
|
|
|
|
<User class="w-5 h-5 text-gray-500 group-focus-within:text-blue-500 transition-colors" />
|
|
|
|
|
</div>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder="Username"
|
|
|
|
|
value={username()}
|
|
|
|
|
onInput={(e) => 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"
|
|
|
|
|
/>
|
|
|
|
|
<datalist id="identity-suggestions">
|
|
|
|
|
<For each={users()}>{(user) => <option value={user.name} />}</For>
|
|
|
|
|
</datalist>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="space-y-1.5">
|
|
|
|
|
<label class="text-xs font-semibold text-gray-500 uppercase tracking-wider ml-1">Access Key</label>
|
|
|
|
|
<div class="relative group">
|
|
|
|
|
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
|
|
|
|
<Lock class="w-5 h-5 text-gray-500 group-focus-within:text-blue-500 transition-colors" />
|
|
|
|
|
</div>
|
|
|
|
|
<input
|
|
|
|
|
type="password"
|
|
|
|
|
placeholder="Password"
|
|
|
|
|
value={password()}
|
|
|
|
|
onInput={(e) => 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"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
type="submit"
|
|
|
|
|
disabled={isLoading() || !username()}
|
|
|
|
|
class="w-full flex items-center justify-center gap-3 py-3.5 bg-gradient-to-r from-blue-600 via-indigo-600 to-purple-600 hover:from-blue-500 hover:to-purple-500 text-white font-bold rounded-2xl shadow-xl transform active:scale-[0.98] transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
|
|
|
|
>
|
|
|
|
|
<Show when={isLoading()} fallback={<><ShieldCheck class="w-5 h-5" /><span>Verify Identity</span></>}>
|
|
|
|
|
<div class="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
|
|
|
|
<span>Verifying...</span>
|
|
|
|
|
</Show>
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
<div class="mt-6 flex flex-col gap-4">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={handleGuestLogin}
|
|
|
|
|
disabled={isLoading()}
|
|
|
|
|
class="w-full flex items-center justify-center gap-2 py-3 bg-[#1a1a1a] hover:bg-[#252525] border border-white/10 text-gray-300 hover:text-white font-medium rounded-2xl transition-all disabled:opacity-50"
|
|
|
|
|
>
|
|
|
|
|
<Ghost class="w-5 h-5" />
|
|
|
|
|
<span>Continue as Guest</span>
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between text-sm">
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => switchMode("register")}
|
|
|
|
|
class="flex items-center gap-1.5 text-gray-400 hover:text-blue-400 transition-colors"
|
|
|
|
|
>
|
|
|
|
|
<UserPlus class="w-4 h-4" />
|
|
|
|
|
<span>Create Identity</span>
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={() => switchMode("reset")}
|
|
|
|
|
class="flex items-center gap-1.5 text-gray-400 hover:text-purple-400 transition-colors"
|
|
|
|
|
>
|
|
|
|
|
<KeyRound class="w-4 h-4" />
|
|
|
|
|
<span>Reset Password</span>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
|
|
{/* Register Form */}
|
|
|
|
|
<Show when={mode() === "register"}>
|
|
|
|
|
<form onSubmit={handleRegister} class="space-y-5">
|
|
|
|
|
<div class="space-y-1.5">
|
|
|
|
|
<label class="text-xs font-semibold text-gray-500 uppercase tracking-wider ml-1">Choose Username</label>
|
|
|
|
|
<div class="relative group">
|
|
|
|
|
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
|
|
|
|
<User class="w-5 h-5 text-gray-500 group-focus-within:text-green-500 transition-colors" />
|
|
|
|
|
</div>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder="Enter username"
|
|
|
|
|
value={username()}
|
|
|
|
|
onInput={(e) => 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"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="space-y-1.5">
|
|
|
|
|
<label class="text-xs font-semibold text-gray-500 uppercase tracking-wider ml-1">Choose Password</label>
|
|
|
|
|
<div class="relative group">
|
|
|
|
|
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
|
|
|
|
<Lock class="w-5 h-5 text-gray-500 group-focus-within:text-green-500 transition-colors" />
|
|
|
|
|
</div>
|
|
|
|
|
<input
|
|
|
|
|
type="password"
|
|
|
|
|
placeholder="Enter password"
|
|
|
|
|
value={password()}
|
|
|
|
|
onInput={(e) => 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"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="space-y-1.5">
|
|
|
|
|
<label class="text-xs font-semibold text-gray-500 uppercase tracking-wider ml-1">Confirm Password</label>
|
|
|
|
|
<div class="relative group">
|
|
|
|
|
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
|
|
|
|
<Lock class="w-5 h-5 text-gray-500 group-focus-within:text-green-500 transition-colors" />
|
|
|
|
|
</div>
|
|
|
|
|
<input
|
|
|
|
|
type="password"
|
|
|
|
|
placeholder="Confirm password"
|
|
|
|
|
value={confirmPassword()}
|
|
|
|
|
onInput={(e) => 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"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
type="submit"
|
|
|
|
|
disabled={isLoading() || !username() || !password() || !confirmPassword()}
|
|
|
|
|
class="w-full flex items-center justify-center gap-3 py-3.5 bg-gradient-to-r from-green-600 via-emerald-600 to-teal-600 hover:from-green-500 hover:to-teal-500 text-white font-bold rounded-2xl shadow-xl transform active:scale-[0.98] transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
|
|
|
|
>
|
|
|
|
|
<Show when={isLoading()} fallback={<><UserPlus class="w-5 h-5" /><span>Create Identity</span></>}>
|
|
|
|
|
<div class="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
|
|
|
|
<span>Creating...</span>
|
|
|
|
|
</Show>
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
|
|
{/* Reset Password Form */}
|
|
|
|
|
<Show when={mode() === "reset"}>
|
|
|
|
|
<form onSubmit={handleResetPassword} class="space-y-5">
|
|
|
|
|
<div class="space-y-1.5">
|
|
|
|
|
<label class="text-xs font-semibold text-gray-500 uppercase tracking-wider ml-1">Identity</label>
|
|
|
|
|
<div class="relative group">
|
|
|
|
|
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
|
|
|
|
<User class="w-5 h-5 text-gray-500 group-focus-within:text-purple-500 transition-colors" />
|
|
|
|
|
</div>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder="Username"
|
|
|
|
|
value={username()}
|
|
|
|
|
onInput={(e) => 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"
|
|
|
|
|
/>
|
|
|
|
|
<datalist id="identity-suggestions-reset">
|
|
|
|
|
<For each={users()}>{(user) => <option value={user.name} />}</For>
|
|
|
|
|
</datalist>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="space-y-1.5">
|
|
|
|
|
<label class="text-xs font-semibold text-gray-500 uppercase tracking-wider ml-1">Current Password</label>
|
|
|
|
|
<div class="relative group">
|
|
|
|
|
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
|
|
|
|
<Lock class="w-5 h-5 text-gray-500 group-focus-within:text-purple-500 transition-colors" />
|
|
|
|
|
</div>
|
|
|
|
|
<input
|
|
|
|
|
type="password"
|
|
|
|
|
placeholder="Enter current password"
|
|
|
|
|
value={password()}
|
|
|
|
|
onInput={(e) => 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"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="space-y-1.5">
|
|
|
|
|
<label class="text-xs font-semibold text-gray-500 uppercase tracking-wider ml-1">New Password</label>
|
|
|
|
|
<div class="relative group">
|
|
|
|
|
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
|
|
|
|
|
<KeyRound class="w-5 h-5 text-gray-500 group-focus-within:text-purple-500 transition-colors" />
|
|
|
|
|
</div>
|
|
|
|
|
<input
|
|
|
|
|
type="password"
|
|
|
|
|
placeholder="Enter new password"
|
|
|
|
|
value={newPassword()}
|
|
|
|
|
onInput={(e) => 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"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
type="submit"
|
|
|
|
|
disabled={isLoading() || !username() || !password() || !newPassword()}
|
|
|
|
|
class="w-full flex items-center justify-center gap-3 py-3.5 bg-gradient-to-r from-purple-600 via-violet-600 to-fuchsia-600 hover:from-purple-500 hover:to-fuchsia-500 text-white font-bold rounded-2xl shadow-xl transform active:scale-[0.98] transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
|
|
|
|
>
|
|
|
|
|
<Show when={isLoading()} fallback={<><KeyRound class="w-5 h-5" /><span>Reset Password</span></>}>
|
|
|
|
|
<div class="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
|
|
|
|
<span>Resetting...</span>
|
|
|
|
|
</Show>
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
</Show>
|
|
|
|
|
|
|
|
|
|
<div class="mt-6 text-center text-xs text-gray-600">
|
|
|
|
|
Powered by Antigravity OS v4.5 | Encrypted Connection
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|