File size: 3,976 Bytes
eb60b6b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
} from "react";
import { useLocation, useNavigate, useParams } from "react-router-dom";
import {
  createWorkspace as createWorkspaceRequest,
  fetchWorkspaces,
  getSession,
  updateWorkspace as updateWorkspaceRequest,
  workspaceApiFetch,
  type CreateWorkspaceInput,
  type UpdateWorkspaceInput,
  type Workspace,
} from "../lib/api";
import { resolveRoutedWorkspace, switchWorkspacePath, workspacePath } from "./workspaceRoutes";

interface WorkspaceContextValue {
  workspaces: Workspace[];
  activeWorkspace: Workspace | null;
  requestedSlug?: string;
  loading: boolean;
  error: string | null;
  notFound: boolean;
  canManageWorkspaces: boolean;
  refreshWorkspaces: () => Promise<Workspace[]>;
  switchWorkspace: (slug: string) => void;
  createWorkspace: (input: CreateWorkspaceInput) => Promise<Workspace>;
  updateWorkspace: (input: UpdateWorkspaceInput) => Promise<Workspace>;
  apiFetch: <T>(path: string, options?: RequestInit) => Promise<T>;
}

const WorkspaceContext = createContext<WorkspaceContextValue | null>(null);

export function WorkspaceProvider({ children }: { children: React.ReactNode }) {
  const { workspaceSlug } = useParams();
  const location = useLocation();
  const navigate = useNavigate();
  const session = getSession();
  const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  const refreshWorkspaces = useCallback(async () => {
    setLoading(true);
    setError(null);
    try {
      const items = await fetchWorkspaces();
      setWorkspaces(items);
      return items;
    } catch (reason) {
      setError(reason instanceof Error ? reason.message : "Unable to load workspaces");
      return [];
    } finally {
      setLoading(false);
    }
  }, []);

  useEffect(() => {
    void refreshWorkspaces();
  }, [refreshWorkspaces]);

  const activeWorkspace = useMemo(
    () => resolveRoutedWorkspace(workspaces, workspaceSlug),
    [workspaceSlug, workspaces],
  );
  const notFound = !loading && Boolean(workspaceSlug) && !activeWorkspace;

  const switchWorkspace = useCallback(
    (slug: string) => navigate(switchWorkspacePath(location.pathname, slug)),
    [location.pathname, navigate],
  );

  const createWorkspace = useCallback(
    async (input: CreateWorkspaceInput) => {
      const created = await createWorkspaceRequest(input);
      await refreshWorkspaces();
      navigate(workspacePath(created.slug));
      return created;
    },
    [navigate, refreshWorkspaces],
  );

  const updateWorkspace = useCallback(
    async (input: UpdateWorkspaceInput) => {
      if (!activeWorkspace) throw new Error("No active workspace");
      const updated = await updateWorkspaceRequest(activeWorkspace.slug, input);
      await refreshWorkspaces();
      return updated;
    },
    [activeWorkspace, refreshWorkspaces],
  );

  const scopedFetch = useCallback(
    <T,>(path: string, options: RequestInit = {}) => {
      if (!activeWorkspace || activeWorkspace.archived) {
        return Promise.reject(new Error("No active workspace"));
      }
      return workspaceApiFetch<T>(activeWorkspace, path, options);
    },
    [activeWorkspace],
  );

  return (
    <WorkspaceContext.Provider
      value={{
        workspaces,
        activeWorkspace,
        requestedSlug: workspaceSlug,
        loading,
        error,
        notFound,
        canManageWorkspaces: Boolean(session?.isSystemPat),
        refreshWorkspaces,
        switchWorkspace,
        createWorkspace,
        updateWorkspace,
        apiFetch: scopedFetch,
      }}
    >
      {children}
    </WorkspaceContext.Provider>
  );
}

export function useWorkspace(): WorkspaceContextValue {
  const value = useContext(WorkspaceContext);
  if (!value) throw new Error("useWorkspace must be used inside WorkspaceProvider");
  return value;
}