feat: implement mandatory login on startup and set roman password
Some checks failed
Release Binaries / release (push) Has been cancelled
Some checks failed
Release Binaries / release (push) Has been cancelled
This commit is contained in:
161
packages/ui/src/components/auth/LoginView.tsx
Normal file
161
packages/ui/src/components/auth/LoginView.tsx
Normal file
@@ -0,0 +1,161 @@
|
||||
import { Component, createSignal, onMount, For, Show } from "solid-js"
|
||||
import { Lock, User, LogIn, ShieldCheck, Cpu } from "lucide-solid"
|
||||
import toast from "solid-toast"
|
||||
|
||||
interface UserRecord {
|
||||
id: string
|
||||
name: string
|
||||
isGuest?: boolean
|
||||
}
|
||||
|
||||
interface LoginViewProps {
|
||||
onLoginSuccess: (user: UserRecord) => void
|
||||
}
|
||||
|
||||
const LoginView: Component<LoginViewProps> = (props) => {
|
||||
const [users, setUsers] = createSignal<UserRecord[]>([])
|
||||
const [selectedUserId, setSelectedUserId] = createSignal<string>("")
|
||||
const [password, setPassword] = createSignal("")
|
||||
const [isLoggingIn, setIsLoggingIn] = createSignal(false)
|
||||
const [isLoadingUsers, setIsLoadingUsers] = createSignal(true)
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const ipcRenderer = (window as any).electron?.ipcRenderer
|
||||
if (ipcRenderer) {
|
||||
const userList = await ipcRenderer.invoke("users:list")
|
||||
setUsers(userList)
|
||||
if (userList.length > 0) {
|
||||
setSelectedUserId(userList[0].id)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch users:", error)
|
||||
toast.error("Failed to load user list")
|
||||
} finally {
|
||||
setIsLoadingUsers(false)
|
||||
}
|
||||
})
|
||||
|
||||
const handleLogin = async (e: Event) => {
|
||||
e.preventDefault()
|
||||
if (!selectedUserId()) return
|
||||
|
||||
setIsLoggingIn(true)
|
||||
try {
|
||||
const ipcRenderer = (window as any).electron?.ipcRenderer
|
||||
if (ipcRenderer) {
|
||||
const result = await ipcRenderer.invoke("users:login", {
|
||||
id: selectedUserId(),
|
||||
password: password(),
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
toast.success(`Welcome back, ${result.user.name}!`)
|
||||
props.onLoginSuccess(result.user)
|
||||
} else {
|
||||
toast.error("Invalid password")
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Login failed:", error)
|
||||
toast.error("Login failed. Please try again.")
|
||||
} finally {
|
||||
setIsLoggingIn(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="fixed inset-0 z-[9999] flex items-center justify-center bg-[#0a0a0a]">
|
||||
{/* Dynamic Background */}
|
||||
<div class="absolute inset-0 overflow-hidden pointer-events-none opacity-20">
|
||||
<div class="absolute -top-[10%] -left-[10%] w-[40%] h-[40%] bg-blue-500/20 blur-[120px] rounded-full animate-pulse" />
|
||||
<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">
|
||||
{/* 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="w-full h-full bg-[#141414] rounded-2xl flex items-center justify-center">
|
||||
<Cpu class="w-10 h-10 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>
|
||||
</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>
|
||||
<select
|
||||
value={selectedUserId()}
|
||||
onInput={(e) => setSelectedUserId(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-pointer"
|
||||
>
|
||||
<Show when={isLoadingUsers()}>
|
||||
<option>Loading identities...</option>
|
||||
</Show>
|
||||
<For each={users()}>
|
||||
{(user) => (
|
||||
<option value={user.id}>
|
||||
{user.name} {user.isGuest ? "(Guest)" : ""}
|
||||
</option>
|
||||
)}
|
||||
</For>
|
||||
</select>
|
||||
<div class="absolute inset-y-0 right-0 pr-4 flex items-center pointer-events-none">
|
||||
<LogIn class="w-4 h-4 text-gray-600 transform rotate-90" />
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoggingIn() || !selectedUserId()}
|
||||
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"
|
||||
>
|
||||
<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>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="mt-8 text-center text-xs text-gray-600">
|
||||
Powered by Antigravity OS v4.5 | Encrypted Connection
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginView
|
||||
Reference in New Issue
Block a user