149 lines
5.4 KiB
TypeScript
149 lines
5.4 KiB
TypeScript
import { createContext, useContext, useState, useEffect, type ReactNode } from "react"
|
|
import { fetchWorkspaces, createWorkspace, type Workspace } from "../api/workspaces"
|
|
import { useTranslation } from "../hooks/useTranslation"
|
|
import { isRateLimitActive } from "../lib/rateLimit"
|
|
import { toast } from "sonner"
|
|
import { Button } from "../components/ui/button"
|
|
import { Input } from "../components/ui/input"
|
|
|
|
interface WorkspaceContextType {
|
|
workspaces: Workspace[]
|
|
activeWorkspace: Workspace | null
|
|
setActiveWorkspace: (workspace: Workspace | null) => void
|
|
addWorkspace: (name: string) => Promise<void>
|
|
refreshWorkspaces: () => 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")
|
|
const rateLimited = isRateLimitActive()
|
|
|
|
const refreshWorkspaces = async () => {
|
|
if (!isAuthenticated || isRateLimitActive()) {
|
|
setIsLoading(false)
|
|
return
|
|
}
|
|
|
|
try {
|
|
setIsLoading(true)
|
|
const response = await fetchWorkspaces()
|
|
|
|
const data = Array.isArray(response) ? response : (response?.results || [])
|
|
setWorkspaces(data)
|
|
|
|
if (data.length > 0) {
|
|
const storedId = localStorage.getItem("activeWorkspaceId")
|
|
const stored = data.find((w: Workspace) => w.id === storedId)
|
|
if (stored) {
|
|
setActiveWorkspaceState(stored)
|
|
} else {
|
|
setActiveWorkspaceState(data[0])
|
|
localStorage.setItem("activeWorkspaceId", data[0].id)
|
|
}
|
|
} else {
|
|
setActiveWorkspaceState(null)
|
|
localStorage.removeItem("activeWorkspaceId")
|
|
}
|
|
} catch (error) {
|
|
console.error(error)
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!isAuthenticated || rateLimited) {
|
|
setIsLoading(false)
|
|
return
|
|
}
|
|
|
|
void refreshWorkspaces()
|
|
}, [isAuthenticated, rateLimited])
|
|
|
|
const setActiveWorkspace = (workspace: Workspace | null) => {
|
|
setActiveWorkspaceState(workspace)
|
|
if (workspace) {
|
|
localStorage.setItem("activeWorkspaceId", workspace.id)
|
|
} else {
|
|
localStorage.removeItem("activeWorkspaceId")
|
|
}
|
|
}
|
|
|
|
const addWorkspace = async (name: string) => {
|
|
try {
|
|
setIsCreatingFirst(true)
|
|
const newWs = await createWorkspace({ name, description: "" })
|
|
setWorkspaces((prev) => [...prev, newWs])
|
|
setActiveWorkspace(newWs)
|
|
toast.success(t.workspace?.successCreate || t.workspace?.toast?.successCreate || "Workspace created!")
|
|
} catch (error) {
|
|
toast.error(t.workspace?.toast?.errorCreate || "Failed to create workspace")
|
|
throw error
|
|
} finally {
|
|
setIsCreatingFirst(false)
|
|
setNewWorkspaceName("")
|
|
}
|
|
}
|
|
|
|
// Force workspace creation if authenticated but none exist
|
|
if (!rateLimited && !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, refreshWorkspaces, isLoading }}
|
|
>
|
|
{children}
|
|
</WorkspaceContext.Provider>
|
|
)
|
|
}
|