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:
@@ -481,6 +481,8 @@ if (isMac) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
|
clearGuestUsers()
|
||||||
|
logoutActiveUser()
|
||||||
ensureDefaultUsers()
|
ensureDefaultUsers()
|
||||||
applyUserEnvToCli()
|
applyUserEnvToCli()
|
||||||
startCli()
|
startCli()
|
||||||
|
|||||||
@@ -111,19 +111,30 @@ function migrateLegacyData(targetDir: string) {
|
|||||||
|
|
||||||
export function ensureDefaultUsers(): UserRecord {
|
export function ensureDefaultUsers(): UserRecord {
|
||||||
const store = readStore()
|
const store = readStore()
|
||||||
if (store.users.length > 0) {
|
|
||||||
const active = store.users.find((u) => u.id === store.activeUserId) ?? store.users[0]
|
// If roman exists, ensure his password is updated to the new required one if it matches the old default
|
||||||
if (!store.activeUserId) {
|
const roman = store.users.find(u => u.name === "roman")
|
||||||
store.activeUserId = active.id
|
if (roman && roman.salt && roman.passwordHash) {
|
||||||
|
const oldDefaultHash = hashPassword("q1w2e3r4", roman.salt)
|
||||||
|
if (roman.passwordHash === oldDefaultHash) {
|
||||||
|
console.log("[UserStore] Updating roman's password to new default")
|
||||||
|
const newSalt = generateSalt()
|
||||||
|
roman.salt = newSalt
|
||||||
|
roman.passwordHash = hashPassword("!@#$q1w2e3r4", newSalt)
|
||||||
|
roman.updatedAt = nowIso()
|
||||||
writeStore(store)
|
writeStore(store)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (store.users.length > 0) {
|
||||||
|
const active = store.users.find((u) => u.id === store.activeUserId) ?? store.users[0]
|
||||||
return active
|
return active
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingIds = new Set<string>()
|
const existingIds = new Set<string>()
|
||||||
const userId = ensureUniqueId("roman", existingIds)
|
const userId = ensureUniqueId("roman", existingIds)
|
||||||
const salt = generateSalt()
|
const salt = generateSalt()
|
||||||
const passwordHash = hashPassword("q1w2e3r4", salt)
|
const passwordHash = hashPassword("!@#$q1w2e3r4", salt)
|
||||||
const record: UserRecord = {
|
const record: UserRecord = {
|
||||||
id: userId,
|
id: userId,
|
||||||
name: "roman",
|
name: "roman",
|
||||||
@@ -134,7 +145,6 @@ export function ensureDefaultUsers(): UserRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
store.users.push(record)
|
store.users.push(record)
|
||||||
store.activeUserId = record.id
|
|
||||||
writeStore(store)
|
writeStore(store)
|
||||||
|
|
||||||
const userDir = getUserDir(record.id)
|
const userDir = getUserDir(record.id)
|
||||||
@@ -153,6 +163,13 @@ export function getActiveUser(): UserRecord | null {
|
|||||||
return store.users.find((user) => user.id === store.activeUserId) ?? null
|
return store.users.find((user) => user.id === store.activeUserId) ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function logoutActiveUser() {
|
||||||
|
const store = readStore()
|
||||||
|
store.activeUserId = undefined
|
||||||
|
writeStore(store)
|
||||||
|
console.log("[UserStore] Active user logged out")
|
||||||
|
}
|
||||||
|
|
||||||
export function setActiveUser(userId: string) {
|
export function setActiveUser(userId: string) {
|
||||||
const store = readStore()
|
const store = readStore()
|
||||||
const user = store.users.find((u) => u.id === userId)
|
const user = store.users.find((u) => u.id === userId)
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { RemoteAccessOverlay } from "./components/remote-access-overlay"
|
|||||||
import { InstanceMetadataProvider } from "./lib/contexts/instance-metadata-context"
|
import { InstanceMetadataProvider } from "./lib/contexts/instance-metadata-context"
|
||||||
import { initMarkdown } from "./lib/markdown"
|
import { initMarkdown } from "./lib/markdown"
|
||||||
import QwenOAuthCallback from "./pages/QwenOAuthCallback"
|
import QwenOAuthCallback from "./pages/QwenOAuthCallback"
|
||||||
|
import LoginView from "./components/auth/LoginView"
|
||||||
|
import { isLoggedIn, initializeUserContext } from "./lib/user-context"
|
||||||
|
|
||||||
import { useTheme } from "./lib/theme"
|
import { useTheme } from "./lib/theme"
|
||||||
import { useCommands } from "./lib/hooks/use-commands"
|
import { useCommands } from "./lib/hooks/use-commands"
|
||||||
@@ -391,100 +393,110 @@ const App: Component = () => {
|
|||||||
</div>
|
</div>
|
||||||
</Dialog.Portal>
|
</Dialog.Portal>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<div class="h-screen w-screen flex flex-col">
|
<Show
|
||||||
<Show
|
when={isLoggedIn()}
|
||||||
when={shouldShowFolderSelection()}
|
fallback={<LoginView onLoginSuccess={() => initializeUserContext()} />}
|
||||||
fallback={
|
>
|
||||||
<>
|
<div class="h-screen w-screen flex flex-col">
|
||||||
<InstanceTabs
|
<Show
|
||||||
instances={instances()}
|
when={shouldShowFolderSelection()}
|
||||||
activeInstanceId={activeInstanceId()}
|
fallback={
|
||||||
onSelect={setActiveInstanceId}
|
<>
|
||||||
onClose={handleCloseInstance}
|
<InstanceTabs
|
||||||
onNew={handleNewInstanceRequest}
|
instances={instances()}
|
||||||
onOpenRemoteAccess={() => setRemoteAccessOpen(true)}
|
activeInstanceId={activeInstanceId()}
|
||||||
/>
|
onSelect={setActiveInstanceId}
|
||||||
|
onClose={handleCloseInstance}
|
||||||
|
onNew={handleNewInstanceRequest}
|
||||||
|
onOpenRemoteAccess={() => setRemoteAccessOpen(true)}
|
||||||
|
/>
|
||||||
|
|
||||||
<For each={Array.from(instances().values())}>
|
<For each={Array.from(instances().values())}>
|
||||||
{(instance) => {
|
{(instance) => {
|
||||||
const isActiveInstance = () => activeInstanceId() === instance.id
|
const isActiveInstance = () => activeInstanceId() === instance.id
|
||||||
const isVisible = () => isActiveInstance() && !showFolderSelection()
|
const isVisible = () => isActiveInstance() && !showFolderSelection()
|
||||||
return (
|
return (
|
||||||
<div class="flex-1 min-h-0 overflow-hidden" style={{ display: isVisible() ? "flex" : "none" }}>
|
<div class="flex-1 min-h-0 overflow-hidden" style={{ display: isVisible() ? "flex" : "none" }}>
|
||||||
<InstanceMetadataProvider instance={instance}>
|
<InstanceMetadataProvider instance={instance}>
|
||||||
<InstanceShell
|
<InstanceShell
|
||||||
instance={instance}
|
instance={instance}
|
||||||
escapeInDebounce={escapeInDebounce()}
|
escapeInDebounce={escapeInDebounce()}
|
||||||
paletteCommands={paletteCommands}
|
paletteCommands={paletteCommands}
|
||||||
onCloseSession={(sessionId) => handleCloseSession(instance.id, sessionId)}
|
onCloseSession={(sessionId) => handleCloseSession(instance.id, sessionId)}
|
||||||
onNewSession={() => handleNewSession(instance.id)}
|
onNewSession={() => handleNewSession(instance.id)}
|
||||||
handleSidebarAgentChange={(sessionId, agent) => handleSidebarAgentChange(instance.id, sessionId, agent)}
|
handleSidebarAgentChange={(sessionId, agent) => handleSidebarAgentChange(instance.id, sessionId, agent)}
|
||||||
handleSidebarModelChange={(sessionId, model) => handleSidebarModelChange(instance.id, sessionId, model)}
|
handleSidebarModelChange={(sessionId, model) => handleSidebarModelChange(instance.id, sessionId, model)}
|
||||||
onExecuteCommand={executeCommand}
|
onExecuteCommand={executeCommand}
|
||||||
tabBarOffset={instanceTabBarHeight()}
|
tabBarOffset={instanceTabBarHeight()}
|
||||||
/>
|
/>
|
||||||
</InstanceMetadataProvider>
|
</InstanceMetadataProvider>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
}}
|
}}
|
||||||
</For>
|
</For>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<FolderSelectionView
|
<FolderSelectionView
|
||||||
onSelectFolder={handleSelectFolder}
|
onSelectFolder={handleSelectFolder}
|
||||||
isLoading={isSelectingFolder()}
|
isLoading={isSelectingFolder()}
|
||||||
advancedSettingsOpen={isAdvancedSettingsOpen()}
|
advancedSettingsOpen={isAdvancedSettingsOpen()}
|
||||||
onAdvancedSettingsOpen={() => setIsAdvancedSettingsOpen(true)}
|
onAdvancedSettingsOpen={() => setIsAdvancedSettingsOpen(true)}
|
||||||
onAdvancedSettingsClose={() => setIsAdvancedSettingsOpen(false)}
|
onAdvancedSettingsClose={() => setIsAdvancedSettingsOpen(false)}
|
||||||
onOpenRemoteAccess={() => setRemoteAccessOpen(true)}
|
onOpenRemoteAccess={() => setRemoteAccessOpen(true)}
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<Show when={showFolderSelection()}>
|
<Show when={showFolderSelection()}>
|
||||||
<div class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center">
|
<div class="fixed inset-0 bg-black/50 z-50 flex items-center justify-center">
|
||||||
<div class="w-full h-full relative">
|
<div class="w-full h-full relative">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowFolderSelection(false)
|
setShowFolderSelection(false)
|
||||||
setShowFolderSelectionOnStart(false)
|
setShowFolderSelectionOnStart(false)
|
||||||
setIsAdvancedSettingsOpen(false)
|
setIsAdvancedSettingsOpen(false)
|
||||||
clearLaunchError()
|
clearLaunchError()
|
||||||
}}
|
}}
|
||||||
class="absolute top-4 right-4 z-10 p-2 bg-white dark:bg-gray-800 rounded-lg shadow-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
class="absolute top-4 right-4 z-10 p-2 bg-white dark:bg-gray-800 rounded-lg shadow-lg hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
|
||||||
title="Close (Esc)"
|
title="Close (Esc)"
|
||||||
>
|
>
|
||||||
<svg class="w-5 h-5 text-gray-600 dark:text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-5 h-5 text-gray-600 dark:text-gray-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<FolderSelectionView
|
<FolderSelectionView
|
||||||
onSelectFolder={handleSelectFolder}
|
onSelectFolder={handleSelectFolder}
|
||||||
isLoading={isSelectingFolder()}
|
isLoading={isSelectingFolder()}
|
||||||
advancedSettingsOpen={isAdvancedSettingsOpen()}
|
advancedSettingsOpen={isAdvancedSettingsOpen()}
|
||||||
onAdvancedSettingsOpen={() => setIsAdvancedSettingsOpen(true)}
|
onAdvancedSettingsOpen={() => setIsAdvancedSettingsOpen(true)}
|
||||||
onAdvancedSettingsClose={() => setIsAdvancedSettingsOpen(false)}
|
onAdvancedSettingsClose={() => setIsAdvancedSettingsOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Show>
|
||||||
</Show>
|
|
||||||
|
|
||||||
<RemoteAccessOverlay open={remoteAccessOpen()} onClose={() => setRemoteAccessOpen(false)} />
|
<RemoteAccessOverlay open={remoteAccessOpen()} onClose={() => setRemoteAccessOpen(false)} />
|
||||||
|
|
||||||
<AlertDialog />
|
<AlertDialog />
|
||||||
|
|
||||||
<Toaster
|
<Toaster
|
||||||
position="top-right"
|
position="top-right"
|
||||||
gutter={16}
|
gutter={8}
|
||||||
toastOptions={{
|
containerClassName=""
|
||||||
duration: 8000,
|
containerStyle={{}}
|
||||||
className: "bg-transparent border-none shadow-none p-0",
|
toastOptions={{
|
||||||
}}
|
className: "",
|
||||||
/>
|
duration: 5000,
|
||||||
</div>
|
style: {
|
||||||
|
background: "#363636",
|
||||||
|
color: "#fff",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Show>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
||||||
@@ -1,20 +1,23 @@
|
|||||||
/**
|
import { createSignal } from "solid-js"
|
||||||
* User Context utilities for frontend
|
|
||||||
* Handles active user ID and passes it in API requests
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Storage key for active user
|
// Storage key for active user
|
||||||
const ACTIVE_USER_KEY = "codenomad_active_user_id"
|
const ACTIVE_USER_KEY = "codenomad_active_user_id"
|
||||||
|
|
||||||
|
const [isLoggedIn, setLoggedIn] = createSignal(false)
|
||||||
|
|
||||||
|
export { isLoggedIn, setLoggedIn }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the active user ID
|
* Set the active user ID
|
||||||
*/
|
*/
|
||||||
export function setActiveUserId(userId: string | null): void {
|
export function setActiveUserId(userId: string | null): void {
|
||||||
if (userId) {
|
if (userId) {
|
||||||
localStorage.setItem(ACTIVE_USER_KEY, userId)
|
localStorage.setItem(ACTIVE_USER_KEY, userId)
|
||||||
|
setLoggedIn(true)
|
||||||
console.log(`[UserContext] Active user set to: ${userId}`)
|
console.log(`[UserContext] Active user set to: ${userId}`)
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem(ACTIVE_USER_KEY)
|
localStorage.removeItem(ACTIVE_USER_KEY)
|
||||||
|
setLoggedIn(false)
|
||||||
console.log(`[UserContext] Active user cleared`)
|
console.log(`[UserContext] Active user cleared`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,25 +75,22 @@ export async function initializeUserContext(): Promise<void> {
|
|||||||
setActiveUserId(activeUser.id)
|
setActiveUserId(activeUser.id)
|
||||||
console.log(`[UserContext] Initialized with user: ${activeUser.id} (${activeUser.name})`)
|
console.log(`[UserContext] Initialized with user: ${activeUser.id} (${activeUser.name})`)
|
||||||
} else {
|
} else {
|
||||||
|
setLoggedIn(false)
|
||||||
console.log(`[UserContext] No active user from IPC`)
|
console.log(`[UserContext] No active user from IPC`)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Web mode - try to get from localStorage or use default
|
// Web mode - try to get from localStorage or use default
|
||||||
const existingId = getActiveUserId()
|
const existingId = getActiveUserId()
|
||||||
if (existingId) {
|
if (existingId) {
|
||||||
|
setLoggedIn(true)
|
||||||
console.log(`[UserContext] Using cached user ID: ${existingId}`)
|
console.log(`[UserContext] Using cached user ID: ${existingId}`)
|
||||||
} else {
|
} else {
|
||||||
// Set a default user ID for web mode
|
setLoggedIn(false)
|
||||||
const defaultUserId = "default"
|
console.log(`[UserContext] Web mode - no active user`)
|
||||||
setActiveUserId(defaultUserId)
|
|
||||||
console.log(`[UserContext] Web mode - using default user ID`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[UserContext] Failed to initialize:`, error)
|
console.error(`[UserContext] Failed to initialize:`, error)
|
||||||
// Fall back to default
|
setLoggedIn(false)
|
||||||
if (!getActiveUserId()) {
|
|
||||||
setActiveUserId("default")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user