Migrate ModalContext and WorkspaceContext

This commit is contained in:
2025-05-08 21:42:13 +02:00
parent 1a06c31705
commit 14b1a46508
5 changed files with 160 additions and 115 deletions

View File

@@ -1,11 +1,6 @@
import { API_BASE_URL } from '@/types/authApi'; import { API_BASE_URL } from '@/types/authApi';
import { apiCall } from './api'; import { apiCall } from './api';
import { import { isWorkspace, Workspace } from '@/types/workspace';
DeleteWorkspaceResponse,
isWorkspace,
LastWorkspaceNameResponse,
Workspace,
} from '@/types/workspace';
/** /**
* listWorkspaces fetches the list of workspaces * listWorkspaces fetches the list of workspaces
@@ -97,12 +92,12 @@ export const updateWorkspace = async (
/** /**
* deleteWorkspace deletes the workspace with the given name * deleteWorkspace deletes the workspace with the given name
* @param workspaceName - The name of the workspace to delete * @param workspaceName - The name of the workspace to delete
* @returns {Promise<DeleteWorkspaceResponse>} A promise that resolves to the response object * @returns {Promise<string>} A promise that resolves to the next workspace name to switch to
* @throws {Error} If the API call fails or returns an invalid response * @throws {Error} If the API call fails or returns an invalid response
*/ */
export const deleteWorkspace = async ( export const deleteWorkspace = async (
workspaceName: string workspaceName: string
): Promise<DeleteWorkspaceResponse> => { ): Promise<string> => {
const response = await apiCall( const response = await apiCall(
`${API_BASE_URL}/workspaces/${encodeURIComponent(workspaceName)}`, `${API_BASE_URL}/workspaces/${encodeURIComponent(workspaceName)}`,
{ {
@@ -113,23 +108,22 @@ export const deleteWorkspace = async (
if (!('nextWorkspaceName' in data)) { if (!('nextWorkspaceName' in data)) {
throw new Error('Invalid delete workspace response received from API'); 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 * getLastWorkspaceName fetches the last workspace name
* @returns {Promise<LastWorkspaceNameResponse>} A promise that resolves to the last workspace name * @returns {Promise<string>} A promise that resolves to the last workspace name
* @throws {Error} If the API call fails or returns an invalid response * @throws {Error} If the API call fails or returns an invalid response
*/ */
export const getLastWorkspaceName = export const getLastWorkspaceName = async (): Promise<string> => {
async (): Promise<LastWorkspaceNameResponse> => {
const response = await apiCall(`${API_BASE_URL}/workspaces/last`); const response = await apiCall(`${API_BASE_URL}/workspaces/last`);
const data = await response.json(); const data = await response.json();
if (!('lastWorkspaceName' in data)) { if (!('lastWorkspaceName' in data)) {
throw new Error('Invalid last workspace name response received from API'); throw new Error('Invalid last workspace name response received from API');
} }
return data as LastWorkspaceNameResponse; return data.lastWorkspaceName as string;
}; };
/** /**
* updateLastWorkspaceName updates the last workspace name * updateLastWorkspaceName updates the last workspace name

View File

@@ -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 (
<ModalContext.Provider value={value}>{children}</ModalContext.Provider>
);
};
export const useModalContext = () => useContext(ModalContext);

View File

@@ -0,0 +1,62 @@
import React, { createContext, useContext, useState, ReactNode } from 'react';
interface ModalContextType {
newFileModalVisible: boolean;
setNewFileModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
deleteFileModalVisible: boolean;
setDeleteFileModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
commitMessageModalVisible: boolean;
setCommitMessageModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
settingsModalVisible: boolean;
setSettingsModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
switchWorkspaceModalVisible: boolean;
setSwitchWorkspaceModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
createWorkspaceModalVisible: boolean;
setCreateWorkspaceModalVisible: React.Dispatch<React.SetStateAction<boolean>>;
}
// Create the context with a default undefined value
const ModalContext = createContext<ModalContextType | null>(null);
interface ModalProviderProps {
children: ReactNode;
}
export const ModalProvider: React.FC<ModalProviderProps> = ({ 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 (
<ModalContext.Provider value={value}>{children}</ModalContext.Provider>
);
};
export const useModalContext = (): ModalContextType => {
const context = useContext(ModalContext);
if (context === null) {
throw new Error('useModalContext must be used within a ModalProvider');
}
return context;
};

View File

@@ -4,28 +4,53 @@ import React, {
useState, useState,
useEffect, useEffect,
useCallback, useCallback,
ReactNode,
} from 'react'; } from 'react';
import { useMantineColorScheme } from '@mantine/core'; import { MantineColorScheme, useMantineColorScheme } from '@mantine/core';
import { notifications } from '@mantine/notifications'; import { notifications } from '@mantine/notifications';
import { import {
fetchLastWorkspaceName, getLastWorkspaceName,
getWorkspace, getWorkspace,
updateWorkspace, updateWorkspace,
updateLastWorkspaceName, updateLastWorkspaceName,
deleteWorkspace, deleteWorkspace,
listWorkspaces, listWorkspaces,
} from '../api/git'; } from '@/api/workspace';
import { DEFAULT_WORKSPACE_SETTINGS } from '../utils/constants'; 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<Workspace>) => Promise<void>;
loading: boolean;
colorScheme: MantineColorScheme;
updateColorScheme: (newTheme: MantineColorScheme) => void;
switchWorkspace: (workspaceName: string) => Promise<void>;
deleteCurrentWorkspace: () => Promise<void>;
}
export const WorkspaceProvider = ({ children }) => { const WorkspaceContext = createContext<WorkspaceContextType | null>(null);
const [currentWorkspace, setCurrentWorkspace] = useState(null);
const [workspaces, setWorkspaces] = useState([]); interface WorkspaceProviderProps {
const [loading, setLoading] = useState(true); children: ReactNode;
}
export const WorkspaceProvider: React.FC<WorkspaceProviderProps> = ({
children,
}) => {
const [currentWorkspace, setCurrentWorkspace] = useState<Workspace | null>(
null
);
const [workspaces, setWorkspaces] = useState<Workspace[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const { colorScheme, setColorScheme } = useMantineColorScheme(); const { colorScheme, setColorScheme } = useMantineColorScheme();
const loadWorkspaces = useCallback(async () => { const loadWorkspaces = useCallback(async (): Promise<Workspace[]> => {
try { try {
const workspaceList = await listWorkspaces(); const workspaceList = await listWorkspaces();
setWorkspaces(workspaceList); setWorkspaces(workspaceList);
@@ -41,11 +66,12 @@ export const WorkspaceProvider = ({ children }) => {
} }
}, []); }, []);
const loadWorkspaceData = useCallback(async (workspaceName) => { const loadWorkspaceData = useCallback(
async (workspaceName: string): Promise<void> => {
try { try {
const workspace = await getWorkspace(workspaceName); const workspace = await getWorkspace(workspaceName);
setCurrentWorkspace(workspace); setCurrentWorkspace(workspace);
setColorScheme(workspace.theme); setColorScheme(workspace.theme as MantineColorScheme);
} catch (error) { } catch (error) {
console.error('Failed to load workspace data:', error); console.error('Failed to load workspace data:', error);
notifications.show({ notifications.show({
@@ -54,9 +80,11 @@ export const WorkspaceProvider = ({ children }) => {
color: 'red', color: 'red',
}); });
} }
}, []); },
[]
);
const loadFirstAvailableWorkspace = useCallback(async () => { const loadFirstAvailableWorkspace = useCallback(async (): Promise<void> => {
try { try {
const allWorkspaces = await listWorkspaces(); const allWorkspaces = await listWorkspaces();
if (allWorkspaces.length > 0) { if (allWorkspaces.length > 0) {
@@ -75,9 +103,9 @@ export const WorkspaceProvider = ({ children }) => {
}, []); }, []);
useEffect(() => { useEffect(() => {
const initializeWorkspace = async () => { const initializeWorkspace = async (): Promise<void> => {
try { try {
const { lastWorkspaceName } = await fetchLastWorkspaceName(); const lastWorkspaceName = await getLastWorkspaceName();
if (lastWorkspaceName) { if (lastWorkspaceName) {
await loadWorkspaceData(lastWorkspaceName); await loadWorkspaceData(lastWorkspaceName);
} else { } else {
@@ -95,7 +123,8 @@ export const WorkspaceProvider = ({ children }) => {
initializeWorkspace(); initializeWorkspace();
}, []); }, []);
const switchWorkspace = useCallback(async (workspaceName) => { const switchWorkspace = useCallback(
async (workspaceName: string): Promise<void> => {
try { try {
setLoading(true); setLoading(true);
await updateLastWorkspaceName(workspaceName); await updateLastWorkspaceName(workspaceName);
@@ -111,9 +140,11 @@ export const WorkspaceProvider = ({ children }) => {
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, []); },
[]
);
const deleteCurrentWorkspace = useCallback(async () => { const deleteCurrentWorkspace = useCallback(async (): Promise<void> => {
if (!currentWorkspace) return; if (!currentWorkspace) return;
try { try {
@@ -129,10 +160,12 @@ export const WorkspaceProvider = ({ children }) => {
} }
// Delete workspace and get the next workspace ID // 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 // Load the new workspace data
await loadWorkspaceData(response.nextWorkspaceName); await loadWorkspaceData(nextWorkspaceName);
notifications.show({ notifications.show({
title: 'Success', title: 'Success',
@@ -152,7 +185,7 @@ export const WorkspaceProvider = ({ children }) => {
}, [currentWorkspace]); }, [currentWorkspace]);
const updateSettings = useCallback( const updateSettings = useCallback(
async (newSettings) => { async (newSettings: Partial<Workspace>): Promise<void> => {
if (!currentWorkspace) return; if (!currentWorkspace) return;
try { try {
@@ -177,13 +210,13 @@ export const WorkspaceProvider = ({ children }) => {
); );
const updateColorScheme = useCallback( const updateColorScheme = useCallback(
(newTheme) => { (newTheme: MantineColorScheme): void => {
setColorScheme(newTheme); setColorScheme(newTheme);
}, },
[setColorScheme] [setColorScheme]
); );
const value = { const value: WorkspaceContextType = {
currentWorkspace, currentWorkspace,
workspaces, workspaces,
settings: currentWorkspace || DEFAULT_WORKSPACE_SETTINGS, settings: currentWorkspace || DEFAULT_WORKSPACE_SETTINGS,
@@ -202,9 +235,9 @@ export const WorkspaceProvider = ({ children }) => {
); );
}; };
export const useWorkspace = () => { export const useWorkspace = (): WorkspaceContextType => {
const context = useContext(WorkspaceContext); const context = useContext(WorkspaceContext);
if (context === undefined) { if (!context) {
throw new Error('useWorkspace must be used within a WorkspaceProvider'); throw new Error('useWorkspace must be used within a WorkspaceProvider');
} }
return context; return context;

View File

@@ -1,13 +1,5 @@
import { Theme } from './theme'; import { Theme } from './theme';
export interface DeleteWorkspaceResponse {
nextWorkspaceName: string;
}
export interface LastWorkspaceNameResponse {
lastWorkspaceName: string;
}
export interface WorkspaceSettings { export interface WorkspaceSettings {
theme: Theme; theme: Theme;
autoSave: boolean; autoSave: boolean;