diff --git a/frontend/src/App.js b/frontend/src/App.js
index 99177c3..2b5cd4d 100644
--- a/frontend/src/App.js
+++ b/frontend/src/App.js
@@ -1,31 +1,16 @@
-import React, { useState, useEffect } from 'react';
+import React from 'react';
import { GeistProvider, CssBaseline, Page, useToasts } from '@geist-ui/core';
import Header from './components/Header';
import MainContent from './components/MainContent';
import { useFileManagement } from './hooks/useFileManagement';
-import { fetchUserSettings, lookupFileByName } from './services/api';
+import { SettingsProvider, useSettings } from './contexts/SettingsContext';
+import { lookupFileByName } from './services/api';
import './App.scss';
-function App() {
- const [themeType, setThemeType] = useState('light');
- const [userId, setUserId] = useState(1);
- const [settings, setSettings] = useState({ gitEnabled: false });
+function AppContent() {
+ const { settings, loading } = useSettings();
const { setToast } = useToasts();
- useEffect(() => {
- const loadUserSettings = async () => {
- try {
- const fetchedSettings = await fetchUserSettings(userId);
- setSettings(fetchedSettings.settings);
- setThemeType(fetchedSettings.settings.theme);
- } catch (error) {
- console.error('Failed to load user settings:', error);
- }
- };
-
- loadUserSettings();
- }, [userId]);
-
const {
content,
files,
@@ -39,18 +24,14 @@ function App() {
pullLatestChanges,
} = useFileManagement(settings.gitEnabled);
- const handleThemeChange = (newTheme) => {
- setThemeType(newTheme);
- };
-
const handleLinkClick = async (filename) => {
try {
const filePaths = await lookupFileByName(filename);
if (filePaths.length === 1) {
handleFileSelect(filePaths[0]);
} else if (filePaths.length > 1) {
- setFileOptions(filePaths.map((path) => ({ label: path, value: path })));
- setFileSelectionModalVisible(true);
+ // Handle multiple file options (you may want to show a modal or dropdown)
+ console.log('Multiple files found:', filePaths);
} else {
setToast({ text: `File "${filename}" not found`, type: 'error' });
}
@@ -63,11 +44,15 @@ function App() {
}
};
+ if (loading) {
+ return
Loading...
;
+ }
+
return (
-
+
-
+
+
+
+ );
+}
+
export default App;
diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js
index cbac3aa..44d6de5 100644
--- a/frontend/src/components/Header.js
+++ b/frontend/src/components/Header.js
@@ -2,9 +2,11 @@ import React, { useState } from 'react';
import { Page, Text, User, Button, Spacer } from '@geist-ui/core';
import { Settings as SettingsIcon } from '@geist-ui/icons';
import Settings from './Settings';
+import { useSettings } from '../contexts/SettingsContext';
-const Header = ({ currentTheme, onThemeChange }) => {
+const Header = () => {
const [settingsVisible, setSettingsVisible] = useState(false);
+ const { settings } = useSettings();
const openSettings = () => setSettingsVisible(true);
const closeSettings = () => setSettingsVisible(false);
@@ -16,12 +18,7 @@ const Header = ({ currentTheme, onThemeChange }) => {
} onClick={openSettings} />
-
+
);
};
diff --git a/frontend/src/components/MainContent.js b/frontend/src/components/MainContent.js
index 91beee6..f193bee 100644
--- a/frontend/src/components/MainContent.js
+++ b/frontend/src/components/MainContent.js
@@ -16,6 +16,7 @@ import CommitMessageModal from './modals/CommitMessageModal';
import ContentView from './ContentView';
import { commitAndPush, saveFileContent, deleteFile } from '../services/api';
import { isImageFile } from '../utils/fileHelpers';
+import { useSettings } from '../contexts/SettingsContext';
const MainContent = ({
content,
@@ -25,7 +26,6 @@ const MainContent = ({
onFileSelect,
onContentChange,
onSave,
- settings,
pullLatestChanges,
onLinkClick,
lookupFileByName,
@@ -33,6 +33,7 @@ const MainContent = ({
const [activeTab, setActiveTab] = useState('source');
const { type: themeType } = useTheme();
const { setToast } = useToasts();
+ const { settings } = useSettings();
const [newFileModalVisible, setNewFileModalVisible] = useState(false);
const [newFileName, setNewFileName] = useState('');
const [deleteFileModalVisible, setDeleteFileModalVisible] = useState(false);
diff --git a/frontend/src/components/Settings.js b/frontend/src/components/Settings.js
index 2ab0c0b..a5e1928 100644
--- a/frontend/src/components/Settings.js
+++ b/frontend/src/components/Settings.js
@@ -1,75 +1,88 @@
-import React, { useState, useEffect, useCallback } from 'react';
+import React, { useReducer, useEffect, useCallback, useRef } from 'react';
import { Modal, Spacer, useTheme, Dot, useToasts } from '@geist-ui/core';
-import { saveUserSettings, fetchUserSettings } from '../services/api';
+import { useSettings } from '../contexts/SettingsContext';
import AppearanceSettings from './settings/AppearanceSettings';
import EditorSettings from './settings/EditorSettings';
import GitSettings from './settings/GitSettings';
-const Settings = ({ visible, onClose, currentTheme, onThemeChange }) => {
- const theme = useTheme();
- const { setToast } = useToasts();
- const [settings, setSettings] = useState({
- autoSave: false,
- gitEnabled: false,
- gitUrl: '',
- gitUser: '',
- gitToken: '',
- gitAutoCommit: false,
- gitCommitMsgTemplate: '',
- });
- const [themeSettings, setThemeSettings] = useState(currentTheme);
- const [originalSettings, setOriginalSettings] = useState({});
- const [originalTheme, setOriginalTheme] = useState(currentTheme);
- const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
- const [isInitialized, setIsInitialized] = useState(false);
+const initialState = {
+ localSettings: {},
+ initialSettings: {},
+ hasUnsavedChanges: false,
+};
- const loadSettings = useCallback(async () => {
- try {
- const userSettings = await fetchUserSettings(1); // Assuming user ID 1 for now
- const { theme, ...otherSettings } = userSettings.settings;
- setSettings(otherSettings);
- setThemeSettings(theme);
- setOriginalSettings(otherSettings);
- setOriginalTheme(theme);
- setHasUnsavedChanges(false);
- setIsInitialized(true);
- } catch (error) {
- console.error('Failed to load user settings:', error);
+function settingsReducer(state, action) {
+ console.log('Reducer action:', action.type, action.payload); // Debug log
+ switch (action.type) {
+ case 'INIT_SETTINGS':
+ return {
+ ...state,
+ localSettings: action.payload,
+ initialSettings: action.payload,
+ hasUnsavedChanges: false,
+ };
+ case 'UPDATE_LOCAL_SETTINGS':
+ const newLocalSettings = { ...state.localSettings, ...action.payload };
+ const hasChanges =
+ JSON.stringify(newLocalSettings) !==
+ JSON.stringify(state.initialSettings);
+ return {
+ ...state,
+ localSettings: newLocalSettings,
+ hasUnsavedChanges: hasChanges,
+ };
+ case 'MARK_SAVED':
+ return {
+ ...state,
+ initialSettings: state.localSettings,
+ hasUnsavedChanges: false,
+ };
+ case 'RESET':
+ return {
+ ...state,
+ localSettings: state.initialSettings,
+ hasUnsavedChanges: false,
+ };
+ default:
+ return state;
+ }
+}
+
+const Settings = ({ visible, onClose }) => {
+ const { settings, updateSettings, updateTheme } = useSettings();
+ const { setToast } = useToasts();
+ const [state, dispatch] = useReducer(settingsReducer, initialState);
+ const isInitialMount = useRef(true);
+ const updateThemeTimeoutRef = useRef(null);
+
+ useEffect(() => {
+ if (isInitialMount.current) {
+ isInitialMount.current = false;
+ dispatch({ type: 'INIT_SETTINGS', payload: settings });
}
+ }, [settings]);
+
+ const handleInputChange = useCallback((key, value) => {
+ dispatch({ type: 'UPDATE_LOCAL_SETTINGS', payload: { [key]: value } });
}, []);
- useEffect(() => {
- if (!isInitialized) {
- loadSettings();
+ const handleThemeChange = useCallback(() => {
+ const newTheme = state.localSettings.theme === 'dark' ? 'light' : 'dark';
+ dispatch({ type: 'UPDATE_LOCAL_SETTINGS', payload: { theme: newTheme } });
+
+ // Debounce the theme update
+ if (updateThemeTimeoutRef.current) {
+ clearTimeout(updateThemeTimeoutRef.current);
}
- }, [isInitialized, loadSettings]);
-
- useEffect(() => {
- const settingsChanged =
- JSON.stringify(settings) !== JSON.stringify(originalSettings);
- const themeChanged = themeSettings !== originalTheme;
- setHasUnsavedChanges(settingsChanged || themeChanged);
- }, [settings, themeSettings, originalSettings, originalTheme]);
-
- const handleInputChange = (key, value) => {
- setSettings((prev) => ({ ...prev, [key]: value }));
- };
-
- const handleThemeChange = () => {
- const newTheme = themeSettings === 'dark' ? 'light' : 'dark';
- setThemeSettings(newTheme);
- onThemeChange(newTheme);
- };
+ updateThemeTimeoutRef.current = setTimeout(() => {
+ updateTheme(newTheme);
+ }, 0);
+ }, [state.localSettings.theme, updateTheme]);
const handleSubmit = async () => {
try {
- await saveUserSettings({
- userId: 1, // Assuming user ID 1 for now
- settings: { ...settings, theme: themeSettings },
- });
- setOriginalSettings(settings);
- setOriginalTheme(themeSettings);
- setHasUnsavedChanges(false);
+ await updateSettings(state.localSettings);
+ dispatch({ type: 'MARK_SAVED' });
setToast({ text: 'Settings saved successfully', type: 'success' });
onClose();
} catch (error) {
@@ -81,36 +94,66 @@ const Settings = ({ visible, onClose, currentTheme, onThemeChange }) => {
}
};
+ const handleClose = useCallback(() => {
+ if (state.hasUnsavedChanges) {
+ const confirmClose = window.confirm(
+ 'You have unsaved changes. Are you sure you want to close without saving?'
+ );
+ if (confirmClose) {
+ updateTheme(state.initialSettings.theme); // Revert theme if not saved
+ dispatch({ type: 'RESET' });
+ onClose();
+ }
+ } else {
+ onClose();
+ }
+ }, [
+ state.hasUnsavedChanges,
+ state.initialSettings.theme,
+ updateTheme,
+ onClose,
+ ]);
+
+ useEffect(() => {
+ return () => {
+ if (updateThemeTimeoutRef.current) {
+ clearTimeout(updateThemeTimeoutRef.current);
+ }
+ };
+ }, []);
+
+ console.log('State:', state); // Debugging log
+
return (
-
+
Settings
- {hasUnsavedChanges && (
+ {state.hasUnsavedChanges && (
)}
handleInputChange('autoSave', value)}
/>
-
+
Cancel
Save Changes
diff --git a/frontend/src/contexts/SettingsContext.js b/frontend/src/contexts/SettingsContext.js
new file mode 100644
index 0000000..5caf883
--- /dev/null
+++ b/frontend/src/contexts/SettingsContext.js
@@ -0,0 +1,66 @@
+import React, { createContext, useState, useContext, useEffect } from 'react';
+import { fetchUserSettings, saveUserSettings } from '../services/api';
+
+const SettingsContext = createContext();
+
+export const useSettings = () => {
+ return useContext(SettingsContext);
+};
+
+export const SettingsProvider = ({ children }) => {
+ const [settings, setSettings] = useState({
+ theme: 'light',
+ autoSave: false,
+ gitEnabled: false,
+ gitUrl: '',
+ gitUser: '',
+ gitToken: '',
+ gitAutoCommit: false,
+ gitCommitMsgTemplate: '',
+ });
+
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ const loadSettings = async () => {
+ try {
+ const userSettings = await fetchUserSettings(1); // Assuming user ID 1 for now
+ setSettings(userSettings.settings);
+ } catch (error) {
+ console.error('Failed to load user settings:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ loadSettings();
+ }, []);
+
+ const updateSettings = async (newSettings) => {
+ try {
+ await saveUserSettings({
+ userId: 1, // Assuming user ID 1 for now
+ settings: newSettings,
+ });
+ setSettings(newSettings);
+ } catch (error) {
+ console.error('Failed to save settings:', error);
+ throw error;
+ }
+ };
+
+ const updateTheme = (newTheme) => {
+ setSettings((prevSettings) => ({
+ ...prevSettings,
+ theme: newTheme,
+ }));
+ };
+
+ return (
+
+ {children}
+
+ );
+};