Add theme support to user settings and related components

This commit is contained in:
2025-10-28 23:14:45 +01:00
parent 3926954b74
commit efdc42cbd7
16 changed files with 158 additions and 35 deletions

View File

@@ -89,6 +89,7 @@ const AccountSettings: React.FC<AccountSettingsProps> = ({
email: user.email,
currentPassword: '',
newPassword: '',
theme: user.theme,
};
dispatch({
type: SettingsActionType.INIT_SETTINGS,
@@ -107,6 +108,13 @@ const AccountSettings: React.FC<AccountSettingsProps> = ({
});
};
const handleThemeChange = (theme: string): void => {
dispatch({
type: SettingsActionType.UPDATE_LOCAL_SETTINGS,
payload: { theme } as UserProfileSettings,
});
};
const handleSubmit = async (): Promise<void> => {
const updates: UserProfileSettings = {};
const needsPasswordConfirmation =
@@ -117,6 +125,14 @@ const AccountSettings: React.FC<AccountSettingsProps> = ({
updates.displayName = state.localSettings.displayName || '';
}
// Add theme if changed
if (
state.localSettings.theme &&
state.localSettings.theme !== state.initialSettings.theme
) {
updates.theme = state.localSettings.theme;
}
// Handle password change
if (state.localSettings.newPassword) {
if (!state.localSettings.currentPassword) {
@@ -216,6 +232,7 @@ const AccountSettings: React.FC<AccountSettingsProps> = ({
<ProfileSettings
settings={state.localSettings}
onInputChange={handleInputChange}
onThemeChange={handleThemeChange}
/>
</Accordion.Panel>
</Accordion.Item>

View File

@@ -1,36 +1,70 @@
import React from 'react';
import { Box, Stack, TextInput } from '@mantine/core';
import type { UserProfileSettings } from '@/types/models';
import { Box, Stack, TextInput, Group, Text, Switch } from '@mantine/core';
import { IconMoon, IconSun } from '@tabler/icons-react';
import { useAuth } from '@/contexts/AuthContext';
import { Theme, type UserProfileSettings } from '@/types/models';
interface ProfileSettingsProps {
settings: UserProfileSettings;
onInputChange: (key: keyof UserProfileSettings, value: string) => void;
onThemeChange?: (theme: Theme) => void;
}
const ProfileSettings: React.FC<ProfileSettingsProps> = ({
settings,
onInputChange,
}) => (
<Box>
<Stack gap="md">
<TextInput
label="Display Name"
type="text"
value={settings.displayName || ''}
onChange={(e) => onInputChange('displayName', e.currentTarget.value)}
placeholder="Enter display name"
data-testid="display-name-input"
/>
<TextInput
label="Email"
type="email"
value={settings.email || ''}
onChange={(e) => onInputChange('email', e.currentTarget.value)}
placeholder="Enter email"
data-testid="email-input"
/>
</Stack>
</Box>
);
onThemeChange,
}) => {
const { user } = useAuth();
const currentTheme = settings.theme || user?.theme || Theme.Dark;
const handleThemeToggle = () => {
const newTheme = currentTheme === Theme.Dark ? Theme.Light : Theme.Dark;
if (onThemeChange) {
onThemeChange(newTheme);
}
};
return (
<Box>
<Stack gap="md">
<TextInput
label="Display Name"
type="text"
value={settings.displayName || ''}
onChange={(e) => onInputChange('displayName', e.currentTarget.value)}
placeholder="Enter display name"
data-testid="display-name-input"
/>
<TextInput
label="Email"
type="email"
value={settings.email || ''}
onChange={(e) => onInputChange('email', e.currentTarget.value)}
placeholder="Enter email"
data-testid="email-input"
/>
<Group justify="space-between" align="flex-start">
<div>
<Text size="sm" fw={500}>
Default Theme
</Text>
<Text size="xs" c="dimmed">
Sets the default theme for new workspaces
</Text>
</div>
<Switch
checked={currentTheme === Theme.Dark}
onChange={handleThemeToggle}
size="lg"
onLabel={<IconMoon size={16} />}
offLabel={<IconSun size={16} />}
data-testid="theme-toggle"
/>
</Group>
</Stack>
</Box>
);
};
export default ProfileSettings;

View File

@@ -3,7 +3,7 @@ import { render as rtlRender, screen, fireEvent } from '@testing-library/react';
import React from 'react';
import { MantineProvider } from '@mantine/core';
import AdminDashboard from './AdminDashboard';
import { UserRole, type User } from '@/types/models';
import { UserRole, Theme, type User } from '@/types/models';
// Mock the auth context
const mockCurrentUser: User = {
@@ -11,6 +11,7 @@ const mockCurrentUser: User = {
email: 'admin@example.com',
displayName: 'Admin User',
role: UserRole.Admin,
theme: Theme.Dark,
createdAt: '2024-01-01T00:00:00Z',
lastWorkspaceId: 1,
};

View File

@@ -8,7 +8,7 @@ import {
import React from 'react';
import { MantineProvider } from '@mantine/core';
import AdminUsersTab from './AdminUsersTab';
import { UserRole, type User } from '@/types/models';
import { UserRole, Theme, type User } from '@/types/models';
// Mock the user admin hook
const mockCreate = vi.fn();
@@ -123,6 +123,7 @@ describe('AdminUsersTab', () => {
email: 'admin@example.com',
displayName: 'Admin User',
role: UserRole.Admin,
theme: Theme.Dark,
createdAt: '2024-01-01T00:00:00Z',
lastWorkspaceId: 1,
};
@@ -134,6 +135,7 @@ describe('AdminUsersTab', () => {
email: 'editor@example.com',
displayName: 'Editor User',
role: UserRole.Editor,
theme: Theme.Dark,
createdAt: '2024-01-15T00:00:00Z',
lastWorkspaceId: 2,
},
@@ -142,6 +144,7 @@ describe('AdminUsersTab', () => {
email: 'viewer@example.com',
displayName: 'Viewer User',
role: UserRole.Viewer,
theme: Theme.Dark,
createdAt: '2024-02-01T00:00:00Z',
lastWorkspaceId: 3,
},