mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-06 07:54:22 +00:00
Merge pull request #15 from LordMathis/chore/componants-structure
Restructure components
This commit is contained in:
@@ -2,8 +2,8 @@ import React from 'react';
|
|||||||
import { MantineProvider, ColorSchemeScript } from '@mantine/core';
|
import { MantineProvider, ColorSchemeScript } from '@mantine/core';
|
||||||
import { Notifications } from '@mantine/notifications';
|
import { Notifications } from '@mantine/notifications';
|
||||||
import { ModalsProvider } from '@mantine/modals';
|
import { ModalsProvider } from '@mantine/modals';
|
||||||
import Layout from './components/Layout';
|
import Layout from './components/layout/Layout';
|
||||||
import LoginPage from './components/LoginPage';
|
import LoginPage from './components/auth/LoginPage';
|
||||||
import { WorkspaceProvider } from './contexts/WorkspaceContext';
|
import { WorkspaceProvider } from './contexts/WorkspaceContext';
|
||||||
import { ModalProvider } from './contexts/ModalContext';
|
import { ModalProvider } from './contexts/ModalContext';
|
||||||
import { AuthProvider, useAuth } from './contexts/AuthContext';
|
import { AuthProvider, useAuth } from './contexts/AuthContext';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Stack,
|
Stack,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
|
|
||||||
const LoginPage = () => {
|
const LoginPage = () => {
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
@@ -2,8 +2,8 @@ import React from 'react';
|
|||||||
import { Text, Center } from '@mantine/core';
|
import { Text, Center } from '@mantine/core';
|
||||||
import Editor from './Editor';
|
import Editor from './Editor';
|
||||||
import MarkdownPreview from './MarkdownPreview';
|
import MarkdownPreview from './MarkdownPreview';
|
||||||
import { getFileUrl } from '../services/api';
|
import { getFileUrl } from '../../services/api';
|
||||||
import { isImageFile } from '../utils/fileHelpers';
|
import { isImageFile } from '../../utils/fileHelpers';
|
||||||
|
|
||||||
const ContentView = ({
|
const ContentView = ({
|
||||||
activeTab,
|
activeTab,
|
||||||
@@ -5,7 +5,7 @@ import { EditorView, keymap } from '@codemirror/view';
|
|||||||
import { markdown } from '@codemirror/lang-markdown';
|
import { markdown } from '@codemirror/lang-markdown';
|
||||||
import { defaultKeymap } from '@codemirror/commands';
|
import { defaultKeymap } from '@codemirror/commands';
|
||||||
import { oneDark } from '@codemirror/theme-one-dark';
|
import { oneDark } from '@codemirror/theme-one-dark';
|
||||||
import { useWorkspace } from '../contexts/WorkspaceContext';
|
import { useWorkspace } from '../../contexts/WorkspaceContext';
|
||||||
|
|
||||||
const Editor = ({ content, handleContentChange, handleSave, selectedFile }) => {
|
const Editor = ({ content, handleContentChange, handleSave, selectedFile }) => {
|
||||||
const { colorScheme } = useWorkspace();
|
const { colorScheme } = useWorkspace();
|
||||||
@@ -8,8 +8,8 @@ import rehypeReact from 'rehype-react';
|
|||||||
import rehypePrism from 'rehype-prism';
|
import rehypePrism from 'rehype-prism';
|
||||||
import * as prod from 'react/jsx-runtime';
|
import * as prod from 'react/jsx-runtime';
|
||||||
import { notifications } from '@mantine/notifications';
|
import { notifications } from '@mantine/notifications';
|
||||||
import { remarkWikiLinks } from '../utils/remarkWikiLinks';
|
import { remarkWikiLinks } from '../../utils/remarkWikiLinks';
|
||||||
import { useWorkspace } from '../contexts/WorkspaceContext';
|
import { useWorkspace } from '../../contexts/WorkspaceContext';
|
||||||
|
|
||||||
const MarkdownPreview = ({ content, handleFileSelect }) => {
|
const MarkdownPreview = ({ content, handleFileSelect }) => {
|
||||||
const [processedContent, setProcessedContent] = useState(null);
|
const [processedContent, setProcessedContent] = useState(null);
|
||||||
@@ -6,8 +6,8 @@ import {
|
|||||||
IconGitPullRequest,
|
IconGitPullRequest,
|
||||||
IconGitCommit,
|
IconGitCommit,
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import { useModalContext } from '../contexts/ModalContext';
|
import { useModalContext } from '../../contexts/ModalContext';
|
||||||
import { useWorkspace } from '../contexts/WorkspaceContext';
|
import { useWorkspace } from '../../contexts/WorkspaceContext';
|
||||||
|
|
||||||
const FileActions = ({ handlePullChanges, selectedFile }) => {
|
const FileActions = ({ handlePullChanges, selectedFile }) => {
|
||||||
const { settings } = useWorkspace();
|
const { settings } = useWorkspace();
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Group, Text } from '@mantine/core';
|
import { Group, Text } from '@mantine/core';
|
||||||
import UserMenu from './UserMenu';
|
import UserMenu from '../navigation/UserMenu';
|
||||||
import WorkspaceSwitcher from './WorkspaceSwitcher';
|
import WorkspaceSwitcher from '../navigation/WorkspaceSwitcher';
|
||||||
import Settings from './Settings';
|
import WorkspaceSettings from '../settings/workspace/WorkspaceSettings';
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
return (
|
return (
|
||||||
@@ -14,7 +14,7 @@ const Header = () => {
|
|||||||
<WorkspaceSwitcher />
|
<WorkspaceSwitcher />
|
||||||
<UserMenu />
|
<UserMenu />
|
||||||
</Group>
|
</Group>
|
||||||
<Settings />
|
<WorkspaceSettings />
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -3,9 +3,9 @@ import { AppShell, Container, Loader, Center } from '@mantine/core';
|
|||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
import Sidebar from './Sidebar';
|
import Sidebar from './Sidebar';
|
||||||
import MainContent from './MainContent';
|
import MainContent from './MainContent';
|
||||||
import { useFileNavigation } from '../hooks/useFileNavigation';
|
import { useFileNavigation } from '../../hooks/useFileNavigation';
|
||||||
import { useFileList } from '../hooks/useFileList';
|
import { useFileList } from '../../hooks/useFileList';
|
||||||
import { useWorkspace } from '../contexts/WorkspaceContext';
|
import { useWorkspace } from '../../contexts/WorkspaceContext';
|
||||||
|
|
||||||
const Layout = () => {
|
const Layout = () => {
|
||||||
const { currentWorkspace, loading: workspaceLoading } = useWorkspace();
|
const { currentWorkspace, loading: workspaceLoading } = useWorkspace();
|
||||||
@@ -2,15 +2,15 @@ import React, { useState, useCallback, useMemo } from 'react';
|
|||||||
import { Tabs, Breadcrumbs, Group, Box, Text, Flex } from '@mantine/core';
|
import { Tabs, Breadcrumbs, Group, Box, Text, Flex } from '@mantine/core';
|
||||||
import { IconCode, IconEye, IconPointFilled } from '@tabler/icons-react';
|
import { IconCode, IconEye, IconPointFilled } from '@tabler/icons-react';
|
||||||
|
|
||||||
import ContentView from './ContentView';
|
import ContentView from '../editor/ContentView';
|
||||||
import CreateFileModal from './modals/CreateFileModal';
|
import CreateFileModal from '../modals/file/CreateFileModal';
|
||||||
import DeleteFileModal from './modals/DeleteFileModal';
|
import DeleteFileModal from '../modals/file/DeleteFileModal';
|
||||||
import CommitMessageModal from './modals/CommitMessageModal';
|
import CommitMessageModal from '../modals/git/CommitMessageModal';
|
||||||
|
|
||||||
import { useFileContent } from '../hooks/useFileContent';
|
import { useFileContent } from '../../hooks/useFileContent';
|
||||||
import { useFileOperations } from '../hooks/useFileOperations';
|
import { useFileOperations } from '../../hooks/useFileOperations';
|
||||||
import { useGitOperations } from '../hooks/useGitOperations';
|
import { useGitOperations } from '../../hooks/useGitOperations';
|
||||||
import { useWorkspace } from '../contexts/WorkspaceContext';
|
import { useWorkspace } from '../../contexts/WorkspaceContext';
|
||||||
|
|
||||||
const MainContent = ({ selectedFile, handleFileSelect, loadFileList }) => {
|
const MainContent = ({ selectedFile, handleFileSelect, loadFileList }) => {
|
||||||
const [activeTab, setActiveTab] = useState('source');
|
const [activeTab, setActiveTab] = useState('source');
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Box } from '@mantine/core';
|
import { Box } from '@mantine/core';
|
||||||
import FileActions from './FileActions';
|
import FileActions from '../files/FileActions';
|
||||||
import FileTree from './FileTree';
|
import FileTree from '../files/FileTree';
|
||||||
import { useGitOperations } from '../hooks/useGitOperations';
|
import { useGitOperations } from '../../hooks/useGitOperations';
|
||||||
import { useWorkspace } from '../contexts/WorkspaceContext';
|
import { useWorkspace } from '../../contexts/WorkspaceContext';
|
||||||
|
|
||||||
const Sidebar = ({ selectedFile, handleFileSelect, files, loadFileList }) => {
|
const Sidebar = ({ selectedFile, handleFileSelect, files, loadFileList }) => {
|
||||||
const { settings } = useWorkspace();
|
const { settings } = useWorkspace();
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
PasswordInput,
|
||||||
|
Group,
|
||||||
|
Button,
|
||||||
|
} from '@mantine/core';
|
||||||
|
|
||||||
|
const DeleteAccountModal = ({ opened, onClose, onConfirm }) => {
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
title="Delete Account"
|
||||||
|
centered
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<Text c="red" fw={500}>
|
||||||
|
Warning: This action cannot be undone
|
||||||
|
</Text>
|
||||||
|
<Text size="sm">
|
||||||
|
Please enter your password to confirm account deletion.
|
||||||
|
</Text>
|
||||||
|
<PasswordInput
|
||||||
|
label="Current Password"
|
||||||
|
placeholder="Enter your current password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.currentTarget.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Group justify="flex-end" mt="md">
|
||||||
|
<Button variant="default" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
onClick={() => {
|
||||||
|
onConfirm(password);
|
||||||
|
setPassword('');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Delete Account
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeleteAccountModal;
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Modal,
|
||||||
|
Text,
|
||||||
|
Button,
|
||||||
|
Group,
|
||||||
|
Stack,
|
||||||
|
PasswordInput,
|
||||||
|
} from '@mantine/core';
|
||||||
|
|
||||||
|
const EmailPasswordModal = ({ opened, onClose, onConfirm, email }) => {
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
opened={opened}
|
||||||
|
onClose={onClose}
|
||||||
|
title="Confirm Password"
|
||||||
|
centered
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
<Stack>
|
||||||
|
<Text size="sm">
|
||||||
|
Please enter your password to confirm changing your email to: {email}
|
||||||
|
</Text>
|
||||||
|
<PasswordInput
|
||||||
|
label="Current Password"
|
||||||
|
placeholder="Enter your current password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.currentTarget.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Group justify="flex-end" mt="md">
|
||||||
|
<Button variant="default" onClick={onClose}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onConfirm(password);
|
||||||
|
setPassword('');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Confirm
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EmailPasswordModal;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Modal, TextInput, Button, Group, Box } from '@mantine/core';
|
import { Modal, TextInput, Button, Group, Box } from '@mantine/core';
|
||||||
import { useModalContext } from '../../contexts/ModalContext';
|
import { useModalContext } from '../../../contexts/ModalContext';
|
||||||
|
|
||||||
const CreateFileModal = ({ onCreateFile }) => {
|
const CreateFileModal = ({ onCreateFile }) => {
|
||||||
const [fileName, setFileName] = useState('');
|
const [fileName, setFileName] = useState('');
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Modal, Text, Button, Group } from '@mantine/core';
|
import { Modal, Text, Button, Group } from '@mantine/core';
|
||||||
import { useModalContext } from '../../contexts/ModalContext';
|
import { useModalContext } from '../../../contexts/ModalContext';
|
||||||
|
|
||||||
const DeleteFileModal = ({ onDeleteFile, selectedFile }) => {
|
const DeleteFileModal = ({ onDeleteFile, selectedFile }) => {
|
||||||
const { deleteFileModalVisible, setDeleteFileModalVisible } =
|
const { deleteFileModalVisible, setDeleteFileModalVisible } =
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Modal, TextInput, Button, Group, Box } from '@mantine/core';
|
import { Modal, TextInput, Button, Group, Box } from '@mantine/core';
|
||||||
import { useModalContext } from '../../contexts/ModalContext';
|
import { useModalContext } from '../../../contexts/ModalContext';
|
||||||
|
|
||||||
const CommitMessageModal = ({ onCommitAndPush }) => {
|
const CommitMessageModal = ({ onCommitAndPush }) => {
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Modal, TextInput, Button, Group, Box } from '@mantine/core';
|
import { Modal, TextInput, Button, Group, Box } from '@mantine/core';
|
||||||
import { useModalContext } from '../../contexts/ModalContext';
|
import { useModalContext } from '../../../contexts/ModalContext';
|
||||||
import { createWorkspace } from '../../services/api';
|
import { createWorkspace } from '../../../services/api';
|
||||||
import { notifications } from '@mantine/notifications';
|
import { notifications } from '@mantine/notifications';
|
||||||
|
|
||||||
const CreateWorkspaceModal = ({ onWorkspaceCreated }) => {
|
const CreateWorkspaceModal = ({ onWorkspaceCreated }) => {
|
||||||
@@ -9,8 +9,8 @@ import {
|
|||||||
Divider,
|
Divider,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconUser, IconLogout, IconSettings } from '@tabler/icons-react';
|
import { IconUser, IconLogout, IconSettings } from '@tabler/icons-react';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../../contexts/AuthContext';
|
||||||
import AccountSettings from './AccountSettings';
|
import AccountSettings from '../settings/account/AccountSettings';
|
||||||
|
|
||||||
const UserMenu = () => {
|
const UserMenu = () => {
|
||||||
const [accountSettingsOpened, setAccountSettingsOpened] = useState(false);
|
const [accountSettingsOpened, setAccountSettingsOpened] = useState(false);
|
||||||
@@ -15,10 +15,10 @@ import {
|
|||||||
useMantineTheme,
|
useMantineTheme,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { IconFolders, IconSettings, IconFolderPlus } from '@tabler/icons-react';
|
import { IconFolders, IconSettings, IconFolderPlus } from '@tabler/icons-react';
|
||||||
import { useWorkspace } from '../contexts/WorkspaceContext';
|
import { useWorkspace } from '../../contexts/WorkspaceContext';
|
||||||
import { useModalContext } from '../contexts/ModalContext';
|
import { useModalContext } from '../../contexts/ModalContext';
|
||||||
import { listWorkspaces } from '../services/api';
|
import { listWorkspaces } from '../../services/api';
|
||||||
import CreateWorkspaceModal from './modals/CreateWorkspaceModal';
|
import CreateWorkspaceModal from '../modals/workspace/CreateWorkspaceModal';
|
||||||
|
|
||||||
const WorkspaceSwitcher = () => {
|
const WorkspaceSwitcher = () => {
|
||||||
const { currentWorkspace, switchWorkspace } = useWorkspace();
|
const { currentWorkspace, switchWorkspace } = useWorkspace();
|
||||||
10
frontend/src/components/settings/AccordionControl.jsx
Normal file
10
frontend/src/components/settings/AccordionControl.jsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Accordion, Title } from '@mantine/core';
|
||||||
|
|
||||||
|
const AccordionControl = ({ children }) => (
|
||||||
|
<Accordion.Control>
|
||||||
|
<Title order={4}>{children}</Title>
|
||||||
|
</Accordion.Control>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default AccordionControl;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useReducer, useRef } from 'react';
|
import React, { useState, useReducer, useRef, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Modal,
|
Modal,
|
||||||
Badge,
|
Badge,
|
||||||
@@ -7,14 +7,15 @@ import {
|
|||||||
Title,
|
Title,
|
||||||
Stack,
|
Stack,
|
||||||
Accordion,
|
Accordion,
|
||||||
TextInput,
|
|
||||||
PasswordInput,
|
|
||||||
Box,
|
|
||||||
Text,
|
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { notifications } from '@mantine/notifications';
|
import { notifications } from '@mantine/notifications';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../../../contexts/AuthContext';
|
||||||
import { useProfileSettings } from '../hooks/useProfileSettings';
|
import { useProfileSettings } from '../../../hooks/useProfileSettings';
|
||||||
|
import EmailPasswordModal from '../../modals/account/EmailPasswordModal';
|
||||||
|
import SecuritySettings from './SecuritySettings';
|
||||||
|
import ProfileSettings from './ProfileSettings';
|
||||||
|
import DangerZoneSettings from './DangerZoneSettings';
|
||||||
|
import AccordionControl from '../AccordionControl';
|
||||||
|
|
||||||
// Reducer for managing settings state
|
// Reducer for managing settings state
|
||||||
const initialState = {
|
const initialState = {
|
||||||
@@ -53,196 +54,15 @@ function settingsReducer(state, action) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Password confirmation modal for email changes
|
|
||||||
const EmailPasswordModal = ({ opened, onClose, onConfirm, email }) => {
|
|
||||||
const [password, setPassword] = useState('');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
opened={opened}
|
|
||||||
onClose={onClose}
|
|
||||||
title="Confirm Password"
|
|
||||||
centered
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
<Stack>
|
|
||||||
<Text size="sm">
|
|
||||||
Please enter your password to confirm changing your email to: {email}
|
|
||||||
</Text>
|
|
||||||
<PasswordInput
|
|
||||||
label="Current Password"
|
|
||||||
placeholder="Enter your current password"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.currentTarget.value)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<Group justify="flex-end" mt="md">
|
|
||||||
<Button variant="default" onClick={onClose}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
onConfirm(password);
|
|
||||||
setPassword('');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Confirm
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Delete account confirmation modal
|
|
||||||
const DeleteAccountModal = ({ opened, onClose, onConfirm }) => {
|
|
||||||
const [password, setPassword] = useState('');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
opened={opened}
|
|
||||||
onClose={onClose}
|
|
||||||
title="Delete Account"
|
|
||||||
centered
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
<Stack>
|
|
||||||
<Text c="red" fw={500}>
|
|
||||||
Warning: This action cannot be undone
|
|
||||||
</Text>
|
|
||||||
<Text size="sm">
|
|
||||||
Please enter your password to confirm account deletion.
|
|
||||||
</Text>
|
|
||||||
<PasswordInput
|
|
||||||
label="Current Password"
|
|
||||||
placeholder="Enter your current password"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.currentTarget.value)}
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<Group justify="flex-end" mt="md">
|
|
||||||
<Button variant="default" onClick={onClose}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color="red"
|
|
||||||
onClick={() => {
|
|
||||||
onConfirm(password);
|
|
||||||
setPassword('');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Delete Account
|
|
||||||
</Button>
|
|
||||||
</Group>
|
|
||||||
</Stack>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const AccordionControl = ({ children }) => (
|
|
||||||
<Accordion.Control>
|
|
||||||
<Title order={4}>{children}</Title>
|
|
||||||
</Accordion.Control>
|
|
||||||
);
|
|
||||||
|
|
||||||
const ProfileSettings = ({ settings, onInputChange }) => (
|
|
||||||
<Box>
|
|
||||||
<Stack spacing="md">
|
|
||||||
<TextInput
|
|
||||||
label="Display Name"
|
|
||||||
value={settings.displayName || ''}
|
|
||||||
onChange={(e) => onInputChange('displayName', e.currentTarget.value)}
|
|
||||||
placeholder="Enter display name"
|
|
||||||
/>
|
|
||||||
<TextInput
|
|
||||||
label="Email"
|
|
||||||
value={settings.email || ''}
|
|
||||||
onChange={(e) => onInputChange('email', e.currentTarget.value)}
|
|
||||||
placeholder="Enter email"
|
|
||||||
/>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
const SecuritySettings = ({ settings, onInputChange }) => {
|
|
||||||
const [confirmPassword, setConfirmPassword] = useState('');
|
|
||||||
const [error, setError] = useState('');
|
|
||||||
|
|
||||||
const handlePasswordChange = (field, value) => {
|
|
||||||
if (field === 'confirmNewPassword') {
|
|
||||||
setConfirmPassword(value);
|
|
||||||
// Check if passwords match when either password field changes
|
|
||||||
if (value !== settings.newPassword) {
|
|
||||||
setError('Passwords do not match');
|
|
||||||
} else {
|
|
||||||
setError('');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
onInputChange(field, value);
|
|
||||||
// Check if passwords match when either password field changes
|
|
||||||
if (field === 'newPassword' && value !== confirmPassword) {
|
|
||||||
setError('Passwords do not match');
|
|
||||||
} else if (value === confirmPassword) {
|
|
||||||
setError('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box>
|
|
||||||
<Stack spacing="md">
|
|
||||||
<PasswordInput
|
|
||||||
label="Current Password"
|
|
||||||
value={settings.currentPassword || ''}
|
|
||||||
onChange={(e) =>
|
|
||||||
handlePasswordChange('currentPassword', e.currentTarget.value)
|
|
||||||
}
|
|
||||||
placeholder="Enter current password"
|
|
||||||
/>
|
|
||||||
<PasswordInput
|
|
||||||
label="New Password"
|
|
||||||
value={settings.newPassword || ''}
|
|
||||||
onChange={(e) =>
|
|
||||||
handlePasswordChange('newPassword', e.currentTarget.value)
|
|
||||||
}
|
|
||||||
placeholder="Enter new password"
|
|
||||||
/>
|
|
||||||
<PasswordInput
|
|
||||||
label="Confirm New Password"
|
|
||||||
value={confirmPassword}
|
|
||||||
onChange={(e) =>
|
|
||||||
handlePasswordChange('confirmNewPassword', e.currentTarget.value)
|
|
||||||
}
|
|
||||||
placeholder="Confirm new password"
|
|
||||||
error={error}
|
|
||||||
/>
|
|
||||||
<Text size="xs" c="dimmed">
|
|
||||||
Password must be at least 8 characters long. Leave password fields
|
|
||||||
empty if you don't want to change it.
|
|
||||||
</Text>
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const DangerZone = ({ onDeleteClick }) => (
|
|
||||||
<Box>
|
|
||||||
<Button color="red" variant="light" onClick={onDeleteClick} fullWidth>
|
|
||||||
Delete Account
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
|
|
||||||
const AccountSettings = ({ opened, onClose }) => {
|
const AccountSettings = ({ opened, onClose }) => {
|
||||||
const { user, logout, refreshUser } = useAuth();
|
const { user, refreshUser } = useAuth();
|
||||||
const { loading, updateProfile, deleteAccount } = useProfileSettings();
|
const { loading, updateProfile } = useProfileSettings();
|
||||||
const [state, dispatch] = useReducer(settingsReducer, initialState);
|
const [state, dispatch] = useReducer(settingsReducer, initialState);
|
||||||
const isInitialMount = useRef(true);
|
const isInitialMount = useRef(true);
|
||||||
const [deleteModalOpened, setDeleteModalOpened] = useState(false);
|
|
||||||
const [emailModalOpened, setEmailModalOpened] = useState(false);
|
const [emailModalOpened, setEmailModalOpened] = useState(false);
|
||||||
|
|
||||||
// Initialize settings on mount
|
// Initialize settings on mount
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (isInitialMount.current && user) {
|
if (isInitialMount.current && user) {
|
||||||
isInitialMount.current = false;
|
isInitialMount.current = false;
|
||||||
const settings = {
|
const settings = {
|
||||||
@@ -333,15 +153,6 @@ const AccountSettings = ({ opened, onClose }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (password) => {
|
|
||||||
const result = await deleteAccount(password);
|
|
||||||
if (result.success) {
|
|
||||||
setDeleteModalOpened(false);
|
|
||||||
onClose();
|
|
||||||
logout();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
@@ -404,7 +215,7 @@ const AccountSettings = ({ opened, onClose }) => {
|
|||||||
<Accordion.Item value="danger">
|
<Accordion.Item value="danger">
|
||||||
<AccordionControl>Danger Zone</AccordionControl>
|
<AccordionControl>Danger Zone</AccordionControl>
|
||||||
<Accordion.Panel>
|
<Accordion.Panel>
|
||||||
<DangerZone onDeleteClick={() => setDeleteModalOpened(true)} />
|
<DangerZoneSettings />
|
||||||
</Accordion.Panel>
|
</Accordion.Panel>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
@@ -430,12 +241,6 @@ const AccountSettings = ({ opened, onClose }) => {
|
|||||||
onConfirm={handleEmailConfirm}
|
onConfirm={handleEmailConfirm}
|
||||||
email={state.localSettings.email}
|
email={state.localSettings.email}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DeleteAccountModal
|
|
||||||
opened={deleteModalOpened}
|
|
||||||
onClose={() => setDeleteModalOpened(false)}
|
|
||||||
onConfirm={handleDelete}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Box, Button, Text } from '@mantine/core';
|
||||||
|
import DeleteAccountModal from '../../modals/account/DeleteAccountModal';
|
||||||
|
import { useAuth } from '../../../contexts/AuthContext';
|
||||||
|
import { useProfileSettings } from '../../../hooks/useProfileSettings';
|
||||||
|
|
||||||
|
const DangerZoneSettings = () => {
|
||||||
|
const { logout } = useAuth();
|
||||||
|
const { deleteAccount } = useProfileSettings();
|
||||||
|
const [deleteModalOpened, setDeleteModalOpened] = useState(false);
|
||||||
|
|
||||||
|
const handleDelete = async (password) => {
|
||||||
|
const result = await deleteAccount(password);
|
||||||
|
if (result.success) {
|
||||||
|
setDeleteModalOpened(false);
|
||||||
|
logout();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box mb="md">
|
||||||
|
<Text size="sm" mb="sm" c="dimmed">
|
||||||
|
Once you delete your account, there is no going back. Please be certain.
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
color="red"
|
||||||
|
variant="light"
|
||||||
|
onClick={() => setDeleteModalOpened(true)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
Delete Account
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<DeleteAccountModal
|
||||||
|
opened={deleteModalOpened}
|
||||||
|
onClose={() => setDeleteModalOpened(false)}
|
||||||
|
onConfirm={handleDelete}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DangerZoneSettings;
|
||||||
23
frontend/src/components/settings/account/ProfileSettings.jsx
Normal file
23
frontend/src/components/settings/account/ProfileSettings.jsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Box, Stack, TextInput } from '@mantine/core';
|
||||||
|
|
||||||
|
const ProfileSettings = ({ settings, onInputChange }) => (
|
||||||
|
<Box>
|
||||||
|
<Stack spacing="md">
|
||||||
|
<TextInput
|
||||||
|
label="Display Name"
|
||||||
|
value={settings.displayName || ''}
|
||||||
|
onChange={(e) => onInputChange('displayName', e.currentTarget.value)}
|
||||||
|
placeholder="Enter display name"
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label="Email"
|
||||||
|
value={settings.email || ''}
|
||||||
|
onChange={(e) => onInputChange('email', e.currentTarget.value)}
|
||||||
|
placeholder="Enter email"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ProfileSettings;
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Box, PasswordInput, Stack, Text } from '@mantine/core';
|
||||||
|
|
||||||
|
const SecuritySettings = ({ settings, onInputChange }) => {
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
|
const handlePasswordChange = (field, value) => {
|
||||||
|
if (field === 'confirmNewPassword') {
|
||||||
|
setConfirmPassword(value);
|
||||||
|
// Check if passwords match when either password field changes
|
||||||
|
if (value !== settings.newPassword) {
|
||||||
|
setError('Passwords do not match');
|
||||||
|
} else {
|
||||||
|
setError('');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onInputChange(field, value);
|
||||||
|
// Check if passwords match when either password field changes
|
||||||
|
if (field === 'newPassword' && value !== confirmPassword) {
|
||||||
|
setError('Passwords do not match');
|
||||||
|
} else if (value === confirmPassword) {
|
||||||
|
setError('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Stack spacing="md">
|
||||||
|
<PasswordInput
|
||||||
|
label="Current Password"
|
||||||
|
value={settings.currentPassword || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
handlePasswordChange('currentPassword', e.currentTarget.value)
|
||||||
|
}
|
||||||
|
placeholder="Enter current password"
|
||||||
|
/>
|
||||||
|
<PasswordInput
|
||||||
|
label="New Password"
|
||||||
|
value={settings.newPassword || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
handlePasswordChange('newPassword', e.currentTarget.value)
|
||||||
|
}
|
||||||
|
placeholder="Enter new password"
|
||||||
|
/>
|
||||||
|
<PasswordInput
|
||||||
|
label="Confirm New Password"
|
||||||
|
value={confirmPassword}
|
||||||
|
onChange={(e) =>
|
||||||
|
handlePasswordChange('confirmNewPassword', e.currentTarget.value)
|
||||||
|
}
|
||||||
|
placeholder="Confirm new password"
|
||||||
|
error={error}
|
||||||
|
/>
|
||||||
|
<Text size="xs" c="dimmed">
|
||||||
|
Password must be at least 8 characters long. Leave password fields
|
||||||
|
empty if you don't want to change it.
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SecuritySettings;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Text, Switch, Group, Box, Title } from '@mantine/core';
|
import { Text, Switch, Group, Box, Title } from '@mantine/core';
|
||||||
import { useWorkspace } from '../../contexts/WorkspaceContext';
|
import { useWorkspace } from '../../../contexts/WorkspaceContext';
|
||||||
|
|
||||||
const AppearanceSettings = ({ themeSettings, onThemeChange }) => {
|
const AppearanceSettings = ({ themeSettings, onThemeChange }) => {
|
||||||
const { colorScheme, updateColorScheme } = useWorkspace();
|
const { colorScheme, updateColorScheme } = useWorkspace();
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Box, Button, Title } from '@mantine/core';
|
import { Box, Button, Title } from '@mantine/core';
|
||||||
import DeleteWorkspaceModal from '../modals/DeleteWorkspaceModal';
|
import DeleteWorkspaceModal from '../../modals/workspace/DeleteWorkspaceModal';
|
||||||
import { useWorkspace } from '../../contexts/WorkspaceContext';
|
import { useWorkspace } from '../../../contexts/WorkspaceContext';
|
||||||
import { useModalContext } from '../../contexts/ModalContext';
|
import { useModalContext } from '../../../contexts/ModalContext';
|
||||||
|
|
||||||
const DangerZoneSettings = () => {
|
const DangerZoneSettings = () => {
|
||||||
const { currentWorkspace, workspaces, deleteCurrentWorkspace } =
|
const { currentWorkspace, workspaces, deleteCurrentWorkspace } =
|
||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
Stack,
|
Stack,
|
||||||
PasswordInput,
|
PasswordInput,
|
||||||
Group,
|
Group,
|
||||||
Title,
|
|
||||||
Grid,
|
Grid,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
|
|
||||||
@@ -9,13 +9,14 @@ import {
|
|||||||
Accordion,
|
Accordion,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { notifications } from '@mantine/notifications';
|
import { notifications } from '@mantine/notifications';
|
||||||
import { useWorkspace } from '../contexts/WorkspaceContext';
|
import { useWorkspace } from '../../../contexts/WorkspaceContext';
|
||||||
import AppearanceSettings from './settings/AppearanceSettings';
|
import AppearanceSettings from './AppearanceSettings';
|
||||||
import EditorSettings from './settings/EditorSettings';
|
import EditorSettings from './EditorSettings';
|
||||||
import GitSettings from './settings/GitSettings';
|
import GitSettings from './GitSettings';
|
||||||
import GeneralSettings from './settings/GeneralSettings';
|
import GeneralSettings from './GeneralSettings';
|
||||||
import { useModalContext } from '../contexts/ModalContext';
|
import { useModalContext } from '../../../contexts/ModalContext';
|
||||||
import DangerZoneSettings from './settings/DangerZoneSettings';
|
import DangerZoneSettings from './DangerZoneSettings';
|
||||||
|
import AccordionControl from '../AccordionControl';
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
localSettings: {},
|
localSettings: {},
|
||||||
@@ -53,13 +54,7 @@ function settingsReducer(state, action) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccordionControl = ({ children }) => (
|
const WorkspaceSettings = () => {
|
||||||
<Accordion.Control>
|
|
||||||
<Title order={4}>{children}</Title>
|
|
||||||
</Accordion.Control>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Settings = () => {
|
|
||||||
const { currentWorkspace, updateSettings } = useWorkspace();
|
const { currentWorkspace, updateSettings } = useWorkspace();
|
||||||
const { settingsModalVisible, setSettingsModalVisible } = useModalContext();
|
const { settingsModalVisible, setSettingsModalVisible } = useModalContext();
|
||||||
const [state, dispatch] = useReducer(settingsReducer, initialState);
|
const [state, dispatch] = useReducer(settingsReducer, initialState);
|
||||||
@@ -121,7 +116,7 @@ const Settings = () => {
|
|||||||
<Modal
|
<Modal
|
||||||
opened={settingsModalVisible}
|
opened={settingsModalVisible}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
title={<Title order={2}>Settings</Title>}
|
title={<Title order={2}>Workspace Settings</Title>}
|
||||||
centered
|
centered
|
||||||
size="lg"
|
size="lg"
|
||||||
>
|
>
|
||||||
@@ -228,4 +223,4 @@ const Settings = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Settings;
|
export default WorkspaceSettings;
|
||||||
Reference in New Issue
Block a user