add workspace navbar status + creation modal

This commit is contained in:
2026-03-12 09:20:25 +08:00
parent bc099512db
commit 94489a7769
11 changed files with 648 additions and 16 deletions

View File

@@ -0,0 +1,129 @@
import { createContext, useContext, useState, useEffect, ReactNode } from "react"
import { fetchWorkspaces, createWorkspace, type Workspace } from "../api/workspaces"
import { useTranslation } from "../hooks/useTranslation"
import { toast } from "sonner"
import { Button } from "../components/ui/button"
interface WorkspaceContextType {
workspaces: Workspace[]
activeWorkspace: Workspace | null
setActiveWorkspace: (workspace: Workspace) => void
addWorkspace: (name: string) => Promise<void>
isLoading: boolean
}
const WorkspaceContext = createContext<WorkspaceContextType | undefined>(undefined)
export const useWorkspace = () => {
const context = useContext(WorkspaceContext)
if (!context) throw new Error("useWorkspace must be used within a WorkspaceProvider")
return context
}
export const WorkspaceProvider = ({ children }: { children: ReactNode }) => {
const { t } = useTranslation()
const [workspaces, setWorkspaces] = useState<Workspace[]>([])
const [activeWorkspace, setActiveWorkspaceState] = useState<Workspace | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [newWorkspaceName, setNewWorkspaceName] = useState("")
const [isCreatingFirst, setIsCreatingFirst] = useState(false)
const isAuthenticated = !!localStorage.getItem("accessToken")
useEffect(() => {
if (!isAuthenticated) {
setIsLoading(false)
return
}
const loadWorkspaces = async () => {
try {
const data = await fetchWorkspaces()
setWorkspaces(data)
if (data.length > 0) {
const storedId = localStorage.getItem("activeWorkspaceId")
const stored = data.find((w) => w.id === storedId)
if (stored) {
setActiveWorkspaceState(stored)
} else {
setActiveWorkspaceState(data[0])
localStorage.setItem("activeWorkspaceId", data[0].id)
}
}
} catch (error) {
console.error(error)
} finally {
setIsLoading(false)
}
}
loadWorkspaces()
}, [isAuthenticated])
const setActiveWorkspace = (workspace: Workspace) => {
setActiveWorkspaceState(workspace)
localStorage.setItem("activeWorkspaceId", workspace.id)
}
const addWorkspace = async (name: string) => {
try {
setIsCreatingFirst(true)
const newWs = await createWorkspace(name)
setWorkspaces((prev) => [...prev, newWs])
setActiveWorkspace(newWs)
toast.success(t.workspace?.createSuccess || "Workspace created!")
} catch (error) {
toast.error(t.workspace?.createError || "Failed to create workspace")
throw error
} finally {
setIsCreatingFirst(false)
setNewWorkspaceName("")
}
}
// Force workspace creation if authenticated but none exist
if (!isLoading && isAuthenticated && workspaces.length === 0) {
return (
<div className="min-h-screen flex items-center justify-center bg-slate-50 dark:bg-slate-900 px-4">
<div className="w-full max-w-md bg-white dark:bg-slate-800 p-8 rounded-xl shadow-lg border border-slate-200 dark:border-slate-700">
<h2 className="text-2xl font-bold text-slate-900 dark:text-white mb-2">
{t.workspace?.noWorkspaceTitle || "Welcome!"}
</h2>
<p className="text-slate-600 dark:text-slate-400 mb-6">
{t.workspace?.noWorkspaceDesc || "Please create your first workspace."}
</p>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
{t.workspace?.nameLabel || "Workspace Name"}
</label>
<input
type="text"
value={newWorkspaceName}
onChange={(e) => setNewWorkspaceName(e.target.value)}
placeholder={t.workspace?.namePlaceholder || "e.g. My Company"}
className="w-full px-4 py-2 rounded-md border border-slate-300 dark:border-slate-600 bg-transparent text-slate-900 dark:text-white focus:ring-2 focus:ring-blue-500 outline-none"
/>
</div>
<Button
onClick={() => addWorkspace(newWorkspaceName)}
disabled={!newWorkspaceName.trim() || isCreatingFirst}
className="w-full bg-blue-600 hover:bg-blue-700 text-white"
>
{isCreatingFirst ? "..." : t.workspace?.submit || "Create"}
</Button>
</div>
</div>
</div>
)
}
return (
<WorkspaceContext.Provider
value={{ workspaces, activeWorkspace, setActiveWorkspace, addWorkspace, isLoading }}
>
{children}
</WorkspaceContext.Provider>
)
}