diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js
index 0fbe38d..b9f894a 100644
--- a/frontend/src/components/Header.js
+++ b/frontend/src/components/Header.js
@@ -1,6 +1,6 @@
import React from 'react';
import { Group, Text, Avatar } from '@mantine/core';
-import WorkspaceMenu from './WorkspaceMenu';
+import WorkspaceSwitcher from './WorkspaceSwitcher';
import Settings from './Settings';
const Header = () => {
@@ -10,8 +10,8 @@ const Header = () => {
NovaMD
+
-
diff --git a/frontend/src/components/WorkspaceMenu.js b/frontend/src/components/WorkspaceMenu.js
deleted file mode 100644
index 5beff44..0000000
--- a/frontend/src/components/WorkspaceMenu.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import React from 'react';
-import { Menu, ActionIcon, Text } from '@mantine/core';
-import {
- IconFolders,
- IconFolderPlus,
- IconSwitchHorizontal,
- IconSettings,
- IconTrash,
- IconPencil,
- IconFileExport,
- IconFileImport,
-} from '@tabler/icons-react';
-import { useModalContext } from '../contexts/ModalContext';
-import { useWorkspace } from '../contexts/WorkspaceContext';
-
-const WorkspaceMenu = () => {
- const { setSettingsModalVisible } = useModalContext();
- const { currentWorkspace } = useWorkspace();
-
- const openSettings = () => setSettingsModalVisible(true);
-
- return (
-
- );
-};
-
-export default WorkspaceMenu;
diff --git a/frontend/src/components/WorkspaceSwitcher.js b/frontend/src/components/WorkspaceSwitcher.js
new file mode 100644
index 0000000..196f27f
--- /dev/null
+++ b/frontend/src/components/WorkspaceSwitcher.js
@@ -0,0 +1,136 @@
+import React, { useState } from 'react';
+import {
+ Box,
+ Popover,
+ Stack,
+ Paper,
+ ScrollArea,
+ Group,
+ UnstyledButton,
+ Text,
+ Loader,
+ Center,
+ Button,
+ ActionIcon,
+} from '@mantine/core';
+import { IconFolders, IconSettings, IconFolderPlus } from '@tabler/icons-react';
+import { useWorkspace } from '../contexts/WorkspaceContext';
+import { useModalContext } from '../contexts/ModalContext';
+import { listWorkspaces } from '../services/api';
+
+const WorkspaceSwitcher = () => {
+ const { currentWorkspace, switchWorkspace } = useWorkspace();
+ const { setSettingsModalVisible } = useModalContext();
+ const [workspaces, setWorkspaces] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [popoverOpened, setPopoverOpened] = useState(false);
+
+ const loadWorkspaces = async () => {
+ setLoading(true);
+ try {
+ const list = await listWorkspaces();
+ setWorkspaces(list);
+ } catch (error) {
+ console.error('Failed to load workspaces:', error);
+ }
+ setLoading(false);
+ };
+
+ return (
+
+
+ {
+ setPopoverOpened((o) => !o);
+ if (!popoverOpened) {
+ loadWorkspaces();
+ }
+ }}
+ >
+
+
+
+
+ {currentWorkspace?.name || 'No workspace'}
+
+
+
+
+
+
+
+
+ Switch Workspace
+
+
+
+ {loading ? (
+
+
+
+ ) : (
+ workspaces.map((workspace) => (
+ {
+ switchWorkspace(workspace.id);
+ setPopoverOpened(false);
+ }}
+ >
+
+
+
+
+ {workspace.name}
+
+
+ {new Date(workspace.createdAt).toLocaleDateString()}
+
+
+ {workspace.id === currentWorkspace?.id && (
+ {
+ e.stopPropagation();
+ setSettingsModalVisible(true);
+ setPopoverOpened(false);
+ }}
+ >
+
+
+ )}
+
+
+
+ ))
+ )}
+
+
+ }
+ fullWidth
+ >
+ Create Workspace
+
+
+
+ );
+};
+
+export default WorkspaceSwitcher;
diff --git a/frontend/src/contexts/ModalContext.js b/frontend/src/contexts/ModalContext.js
index 697bdc7..3338c7e 100644
--- a/frontend/src/contexts/ModalContext.js
+++ b/frontend/src/contexts/ModalContext.js
@@ -8,6 +8,8 @@ export const ModalProvider = ({ children }) => {
const [commitMessageModalVisible, setCommitMessageModalVisible] =
useState(false);
const [settingsModalVisible, setSettingsModalVisible] = useState(false);
+ const [switchWorkspaceModalVisible, setSwitchWorkspaceModalVisible] =
+ useState(false);
const value = {
newFileModalVisible,
@@ -18,6 +20,8 @@ export const ModalProvider = ({ children }) => {
setCommitMessageModalVisible,
settingsModalVisible,
setSettingsModalVisible,
+ switchWorkspaceModalVisible,
+ setSwitchWorkspaceModalVisible,
};
return (
diff --git a/frontend/src/contexts/WorkspaceContext.js b/frontend/src/contexts/WorkspaceContext.js
index f228b0c..0006a77 100644
--- a/frontend/src/contexts/WorkspaceContext.js
+++ b/frontend/src/contexts/WorkspaceContext.js
@@ -6,6 +6,7 @@ import React, {
useCallback,
} from 'react';
import { useMantineColorScheme } from '@mantine/core';
+import { notifications } from '@mantine/notifications';
import {
fetchLastWorkspaceId,
fetchWorkspaceSettings,
@@ -22,18 +23,29 @@ export const WorkspaceProvider = ({ children }) => {
const [loading, setLoading] = useState(true);
const { colorScheme, setColorScheme } = useMantineColorScheme();
+ const loadWorkspaceData = useCallback(async (workspaceId) => {
+ try {
+ const workspace = await getWorkspace(workspaceId);
+ setCurrentWorkspace(workspace);
+ const workspaceSettings = await fetchWorkspaceSettings(workspaceId);
+ setSettings(workspaceSettings.settings);
+ setColorScheme(workspaceSettings.settings.theme);
+ } catch (error) {
+ console.error('Failed to load workspace data:', error);
+ notifications.show({
+ title: 'Error',
+ message: 'Failed to load workspace data',
+ color: 'red',
+ });
+ }
+ }, []);
+
useEffect(() => {
- const loadWorkspace = async () => {
+ const initializeWorkspace = async () => {
try {
const { lastWorkspaceId } = await fetchLastWorkspaceId();
if (lastWorkspaceId) {
- const workspace = await getWorkspace(lastWorkspaceId);
- setCurrentWorkspace(workspace);
- const workspaceSettings = await fetchWorkspaceSettings(
- lastWorkspaceId
- );
- setSettings(workspaceSettings.settings);
- setColorScheme(workspaceSettings.settings.theme);
+ await loadWorkspaceData(lastWorkspaceId);
} else {
console.warn('No last workspace found');
}
@@ -44,7 +56,29 @@ export const WorkspaceProvider = ({ children }) => {
}
};
- loadWorkspace();
+ initializeWorkspace();
+ }, []);
+
+ const switchWorkspace = useCallback(async (workspaceId) => {
+ try {
+ setLoading(true);
+ await updateLastWorkspace(workspaceId);
+ await loadWorkspaceData(workspaceId);
+ notifications.show({
+ title: 'Success',
+ message: 'Workspace switched successfully',
+ color: 'green',
+ });
+ } catch (error) {
+ console.error('Failed to switch workspace:', error);
+ notifications.show({
+ title: 'Error',
+ message: 'Failed to switch workspace',
+ color: 'red',
+ });
+ } finally {
+ setLoading(false);
+ }
}, []);
const updateSettings = useCallback(
@@ -78,6 +112,7 @@ export const WorkspaceProvider = ({ children }) => {
loading,
colorScheme,
updateColorScheme,
+ switchWorkspace,
};
return (
diff --git a/frontend/src/hooks/useFileList.js b/frontend/src/hooks/useFileList.js
index 46ca9ec..ef72c47 100644
--- a/frontend/src/hooks/useFileList.js
+++ b/frontend/src/hooks/useFileList.js
@@ -20,7 +20,7 @@ export const useFileList = () => {
console.error('Failed to load file list:', error);
setFiles([]);
}
- }, [currentWorkspace, workspaceLoading]);
+ }, [currentWorkspace]);
return { files, loadFileList };
};
diff --git a/frontend/src/hooks/useFileNavigation.js b/frontend/src/hooks/useFileNavigation.js
index 7c3623d..23ce637 100644
--- a/frontend/src/hooks/useFileNavigation.js
+++ b/frontend/src/hooks/useFileNavigation.js
@@ -38,7 +38,7 @@ export const useFileNavigation = () => {
});
}
},
- [currentWorkspace, handleFileSelect]
+ [currentWorkspace]
);
return { handleLinkClick, selectedFile, isNewFile, handleFileSelect };
diff --git a/frontend/src/hooks/useFileOperations.js b/frontend/src/hooks/useFileOperations.js
index 4b7f70a..0110755 100644
--- a/frontend/src/hooks/useFileOperations.js
+++ b/frontend/src/hooks/useFileOperations.js
@@ -21,7 +21,7 @@ export const useFileOperations = () => {
await handleCommitAndPush(commitMessage);
}
},
- [settings, handleCommitAndPush]
+ [settings]
);
const handleSave = useCallback(