diff --git a/app/src/api/workspace.ts b/app/src/api/workspace.ts index 135fbfc..318523b 100644 --- a/app/src/api/workspace.ts +++ b/app/src/api/workspace.ts @@ -1,11 +1,6 @@ import { API_BASE_URL } from '@/types/authApi'; import { apiCall } from './api'; -import { - DeleteWorkspaceResponse, - isWorkspace, - LastWorkspaceNameResponse, - Workspace, -} from '@/types/workspace'; +import { isWorkspace, Workspace } from '@/types/workspace'; /** * listWorkspaces fetches the list of workspaces @@ -97,12 +92,12 @@ export const updateWorkspace = async ( /** * deleteWorkspace deletes the workspace with the given name * @param workspaceName - The name of the workspace to delete - * @returns {Promise} A promise that resolves to the response object + * @returns {Promise} A promise that resolves to the next workspace name to switch to * @throws {Error} If the API call fails or returns an invalid response */ export const deleteWorkspace = async ( workspaceName: string -): Promise => { +): Promise => { const response = await apiCall( `${API_BASE_URL}/workspaces/${encodeURIComponent(workspaceName)}`, { @@ -113,23 +108,22 @@ export const deleteWorkspace = async ( if (!('nextWorkspaceName' in data)) { throw new Error('Invalid delete workspace response received from API'); } - return data as DeleteWorkspaceResponse; + return data.nextWorkspaceName as string; }; /** * getLastWorkspaceName fetches the last workspace name - * @returns {Promise} A promise that resolves to the last workspace name + * @returns {Promise} A promise that resolves to the last workspace name * @throws {Error} If the API call fails or returns an invalid response */ -export const getLastWorkspaceName = - async (): Promise => { - const response = await apiCall(`${API_BASE_URL}/workspaces/last`); - const data = await response.json(); - if (!('lastWorkspaceName' in data)) { - throw new Error('Invalid last workspace name response received from API'); - } - return data as LastWorkspaceNameResponse; - }; +export const getLastWorkspaceName = async (): Promise => { + const response = await apiCall(`${API_BASE_URL}/workspaces/last`); + const data = await response.json(); + if (!('lastWorkspaceName' in data)) { + throw new Error('Invalid last workspace name response received from API'); + } + return data.lastWorkspaceName as string; +}; /** * updateLastWorkspaceName updates the last workspace name diff --git a/app/src/contexts/ModalContext.jsx b/app/src/contexts/ModalContext.jsx deleted file mode 100644 index 4865e6a..0000000 --- a/app/src/contexts/ModalContext.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, { createContext, useContext, useState } from 'react'; - -const ModalContext = createContext(); - -export const ModalProvider = ({ children }) => { - const [newFileModalVisible, setNewFileModalVisible] = useState(false); - const [deleteFileModalVisible, setDeleteFileModalVisible] = useState(false); - const [commitMessageModalVisible, setCommitMessageModalVisible] = - useState(false); - const [settingsModalVisible, setSettingsModalVisible] = useState(false); - const [switchWorkspaceModalVisible, setSwitchWorkspaceModalVisible] = - useState(false); - const [createWorkspaceModalVisible, setCreateWorkspaceModalVisible] = - useState(false); - - const value = { - newFileModalVisible, - setNewFileModalVisible, - deleteFileModalVisible, - setDeleteFileModalVisible, - commitMessageModalVisible, - setCommitMessageModalVisible, - settingsModalVisible, - setSettingsModalVisible, - switchWorkspaceModalVisible, - setSwitchWorkspaceModalVisible, - createWorkspaceModalVisible, - setCreateWorkspaceModalVisible, - }; - - return ( - {children} - ); -}; - -export const useModalContext = () => useContext(ModalContext); diff --git a/app/src/contexts/ModalContext.tsx b/app/src/contexts/ModalContext.tsx new file mode 100644 index 0000000..bd2c813 --- /dev/null +++ b/app/src/contexts/ModalContext.tsx @@ -0,0 +1,62 @@ +import React, { createContext, useContext, useState, ReactNode } from 'react'; + +interface ModalContextType { + newFileModalVisible: boolean; + setNewFileModalVisible: React.Dispatch>; + deleteFileModalVisible: boolean; + setDeleteFileModalVisible: React.Dispatch>; + commitMessageModalVisible: boolean; + setCommitMessageModalVisible: React.Dispatch>; + settingsModalVisible: boolean; + setSettingsModalVisible: React.Dispatch>; + switchWorkspaceModalVisible: boolean; + setSwitchWorkspaceModalVisible: React.Dispatch>; + createWorkspaceModalVisible: boolean; + setCreateWorkspaceModalVisible: React.Dispatch>; +} + +// Create the context with a default undefined value +const ModalContext = createContext(null); + +interface ModalProviderProps { + children: ReactNode; +} + +export const ModalProvider: React.FC = ({ children }) => { + const [newFileModalVisible, setNewFileModalVisible] = useState(false); + const [deleteFileModalVisible, setDeleteFileModalVisible] = useState(false); + const [commitMessageModalVisible, setCommitMessageModalVisible] = + useState(false); + const [settingsModalVisible, setSettingsModalVisible] = useState(false); + const [switchWorkspaceModalVisible, setSwitchWorkspaceModalVisible] = + useState(false); + const [createWorkspaceModalVisible, setCreateWorkspaceModalVisible] = + useState(false); + + const value: ModalContextType = { + newFileModalVisible, + setNewFileModalVisible, + deleteFileModalVisible, + setDeleteFileModalVisible, + commitMessageModalVisible, + setCommitMessageModalVisible, + settingsModalVisible, + setSettingsModalVisible, + switchWorkspaceModalVisible, + setSwitchWorkspaceModalVisible, + createWorkspaceModalVisible, + setCreateWorkspaceModalVisible, + }; + + return ( + {children} + ); +}; + +export const useModalContext = (): ModalContextType => { + const context = useContext(ModalContext); + if (context === null) { + throw new Error('useModalContext must be used within a ModalProvider'); + } + return context; +}; diff --git a/app/src/contexts/WorkspaceContext.jsx b/app/src/contexts/WorkspaceContext.tsx similarity index 55% rename from app/src/contexts/WorkspaceContext.jsx rename to app/src/contexts/WorkspaceContext.tsx index af23277..047b5b5 100644 --- a/app/src/contexts/WorkspaceContext.jsx +++ b/app/src/contexts/WorkspaceContext.tsx @@ -4,28 +4,53 @@ import React, { useState, useEffect, useCallback, + ReactNode, } from 'react'; -import { useMantineColorScheme } from '@mantine/core'; +import { MantineColorScheme, useMantineColorScheme } from '@mantine/core'; import { notifications } from '@mantine/notifications'; import { - fetchLastWorkspaceName, + getLastWorkspaceName, getWorkspace, updateWorkspace, updateLastWorkspaceName, deleteWorkspace, listWorkspaces, -} from '../api/git'; -import { DEFAULT_WORKSPACE_SETTINGS } from '../utils/constants'; +} from '@/api/workspace'; +import { + Workspace, + DEFAULT_WORKSPACE_SETTINGS, + WorkspaceSettings, +} from '@/types/workspace'; -const WorkspaceContext = createContext(); +interface WorkspaceContextType { + currentWorkspace: Workspace | null; + workspaces: Workspace[]; + settings: Workspace | typeof DEFAULT_WORKSPACE_SETTINGS; + updateSettings: (newSettings: Partial) => Promise; + loading: boolean; + colorScheme: MantineColorScheme; + updateColorScheme: (newTheme: MantineColorScheme) => void; + switchWorkspace: (workspaceName: string) => Promise; + deleteCurrentWorkspace: () => Promise; +} -export const WorkspaceProvider = ({ children }) => { - const [currentWorkspace, setCurrentWorkspace] = useState(null); - const [workspaces, setWorkspaces] = useState([]); - const [loading, setLoading] = useState(true); +const WorkspaceContext = createContext(null); + +interface WorkspaceProviderProps { + children: ReactNode; +} + +export const WorkspaceProvider: React.FC = ({ + children, +}) => { + const [currentWorkspace, setCurrentWorkspace] = useState( + null + ); + const [workspaces, setWorkspaces] = useState([]); + const [loading, setLoading] = useState(true); const { colorScheme, setColorScheme } = useMantineColorScheme(); - const loadWorkspaces = useCallback(async () => { + const loadWorkspaces = useCallback(async (): Promise => { try { const workspaceList = await listWorkspaces(); setWorkspaces(workspaceList); @@ -41,22 +66,25 @@ export const WorkspaceProvider = ({ children }) => { } }, []); - const loadWorkspaceData = useCallback(async (workspaceName) => { - try { - const workspace = await getWorkspace(workspaceName); - setCurrentWorkspace(workspace); - setColorScheme(workspace.theme); - } catch (error) { - console.error('Failed to load workspace data:', error); - notifications.show({ - title: 'Error', - message: 'Failed to load workspace data', - color: 'red', - }); - } - }, []); + const loadWorkspaceData = useCallback( + async (workspaceName: string): Promise => { + try { + const workspace = await getWorkspace(workspaceName); + setCurrentWorkspace(workspace); + setColorScheme(workspace.theme as MantineColorScheme); + } catch (error) { + console.error('Failed to load workspace data:', error); + notifications.show({ + title: 'Error', + message: 'Failed to load workspace data', + color: 'red', + }); + } + }, + [] + ); - const loadFirstAvailableWorkspace = useCallback(async () => { + const loadFirstAvailableWorkspace = useCallback(async (): Promise => { try { const allWorkspaces = await listWorkspaces(); if (allWorkspaces.length > 0) { @@ -75,9 +103,9 @@ export const WorkspaceProvider = ({ children }) => { }, []); useEffect(() => { - const initializeWorkspace = async () => { + const initializeWorkspace = async (): Promise => { try { - const { lastWorkspaceName } = await fetchLastWorkspaceName(); + const lastWorkspaceName = await getLastWorkspaceName(); if (lastWorkspaceName) { await loadWorkspaceData(lastWorkspaceName); } else { @@ -95,25 +123,28 @@ export const WorkspaceProvider = ({ children }) => { initializeWorkspace(); }, []); - const switchWorkspace = useCallback(async (workspaceName) => { - try { - setLoading(true); - await updateLastWorkspaceName(workspaceName); - await loadWorkspaceData(workspaceName); - await loadWorkspaces(); - } catch (error) { - console.error('Failed to switch workspace:', error); - notifications.show({ - title: 'Error', - message: 'Failed to switch workspace', - color: 'red', - }); - } finally { - setLoading(false); - } - }, []); + const switchWorkspace = useCallback( + async (workspaceName: string): Promise => { + try { + setLoading(true); + await updateLastWorkspaceName(workspaceName); + await loadWorkspaceData(workspaceName); + await loadWorkspaces(); + } catch (error) { + console.error('Failed to switch workspace:', error); + notifications.show({ + title: 'Error', + message: 'Failed to switch workspace', + color: 'red', + }); + } finally { + setLoading(false); + } + }, + [] + ); - const deleteCurrentWorkspace = useCallback(async () => { + const deleteCurrentWorkspace = useCallback(async (): Promise => { if (!currentWorkspace) return; try { @@ -129,10 +160,12 @@ export const WorkspaceProvider = ({ children }) => { } // Delete workspace and get the next workspace ID - const response = await deleteWorkspace(currentWorkspace.name); + const nextWorkspaceName: string = await deleteWorkspace( + currentWorkspace.name + ); // Load the new workspace data - await loadWorkspaceData(response.nextWorkspaceName); + await loadWorkspaceData(nextWorkspaceName); notifications.show({ title: 'Success', @@ -152,7 +185,7 @@ export const WorkspaceProvider = ({ children }) => { }, [currentWorkspace]); const updateSettings = useCallback( - async (newSettings) => { + async (newSettings: Partial): Promise => { if (!currentWorkspace) return; try { @@ -177,13 +210,13 @@ export const WorkspaceProvider = ({ children }) => { ); const updateColorScheme = useCallback( - (newTheme) => { + (newTheme: MantineColorScheme): void => { setColorScheme(newTheme); }, [setColorScheme] ); - const value = { + const value: WorkspaceContextType = { currentWorkspace, workspaces, settings: currentWorkspace || DEFAULT_WORKSPACE_SETTINGS, @@ -202,9 +235,9 @@ export const WorkspaceProvider = ({ children }) => { ); }; -export const useWorkspace = () => { +export const useWorkspace = (): WorkspaceContextType => { const context = useContext(WorkspaceContext); - if (context === undefined) { + if (!context) { throw new Error('useWorkspace must be used within a WorkspaceProvider'); } return context; diff --git a/app/src/types/workspace.ts b/app/src/types/workspace.ts index b88c041..15ec4f2 100644 --- a/app/src/types/workspace.ts +++ b/app/src/types/workspace.ts @@ -1,13 +1,5 @@ import { Theme } from './theme'; -export interface DeleteWorkspaceResponse { - nextWorkspaceName: string; -} - -export interface LastWorkspaceNameResponse { - lastWorkspaceName: string; -} - export interface WorkspaceSettings { theme: Theme; autoSave: boolean;