mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-05 15:44:21 +00:00
Migrate account settings
This commit is contained in:
@@ -16,24 +16,38 @@ import SecuritySettings from './SecuritySettings';
|
||||
import ProfileSettings from './ProfileSettings';
|
||||
import DangerZoneSettings from './DangerZoneSettings';
|
||||
import AccordionControl from '../AccordionControl';
|
||||
import {
|
||||
SettingsActionType,
|
||||
UserProfileSettings,
|
||||
ProfileSettingsState,
|
||||
SettingsAction,
|
||||
} from '../../../types/settings';
|
||||
|
||||
interface AccountSettingsProps {
|
||||
opened: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
// Reducer for managing settings state
|
||||
const initialState = {
|
||||
const initialState: ProfileSettingsState = {
|
||||
localSettings: {},
|
||||
initialSettings: {},
|
||||
hasUnsavedChanges: false,
|
||||
};
|
||||
|
||||
function settingsReducer(state, action) {
|
||||
function settingsReducer(
|
||||
state: ProfileSettingsState,
|
||||
action: SettingsAction<UserProfileSettings>
|
||||
): ProfileSettingsState {
|
||||
switch (action.type) {
|
||||
case 'INIT_SETTINGS':
|
||||
case SettingsActionType.INIT_SETTINGS:
|
||||
return {
|
||||
...state,
|
||||
localSettings: action.payload,
|
||||
initialSettings: action.payload,
|
||||
localSettings: action.payload || {},
|
||||
initialSettings: action.payload || {},
|
||||
hasUnsavedChanges: false,
|
||||
};
|
||||
case 'UPDATE_LOCAL_SETTINGS':
|
||||
case SettingsActionType.UPDATE_LOCAL_SETTINGS:
|
||||
const newLocalSettings = { ...state.localSettings, ...action.payload };
|
||||
const hasChanges =
|
||||
JSON.stringify(newLocalSettings) !==
|
||||
@@ -43,7 +57,7 @@ function settingsReducer(state, action) {
|
||||
localSettings: newLocalSettings,
|
||||
hasUnsavedChanges: hasChanges,
|
||||
};
|
||||
case 'MARK_SAVED':
|
||||
case SettingsActionType.MARK_SAVED:
|
||||
return {
|
||||
...state,
|
||||
initialSettings: state.localSettings,
|
||||
@@ -54,33 +68,45 @@ function settingsReducer(state, action) {
|
||||
}
|
||||
}
|
||||
|
||||
const AccountSettings = ({ opened, onClose }) => {
|
||||
const AccountSettings: React.FC<AccountSettingsProps> = ({
|
||||
opened,
|
||||
onClose,
|
||||
}) => {
|
||||
const { user, refreshUser } = useAuth();
|
||||
const { loading, updateProfile } = useProfileSettings();
|
||||
const [state, dispatch] = useReducer(settingsReducer, initialState);
|
||||
const isInitialMount = useRef(true);
|
||||
const [emailModalOpened, setEmailModalOpened] = useState(false);
|
||||
const isInitialMount = useRef<boolean>(true);
|
||||
const [emailModalOpened, setEmailModalOpened] = useState<boolean>(false);
|
||||
|
||||
// Initialize settings on mount
|
||||
useEffect(() => {
|
||||
if (isInitialMount.current && user) {
|
||||
isInitialMount.current = false;
|
||||
const settings = {
|
||||
const settings: UserProfileSettings = {
|
||||
displayName: user.displayName,
|
||||
email: user.email,
|
||||
currentPassword: '',
|
||||
newPassword: '',
|
||||
};
|
||||
dispatch({ type: 'INIT_SETTINGS', payload: settings });
|
||||
dispatch({
|
||||
type: SettingsActionType.INIT_SETTINGS,
|
||||
payload: settings,
|
||||
});
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const handleInputChange = (key, value) => {
|
||||
dispatch({ type: 'UPDATE_LOCAL_SETTINGS', payload: { [key]: value } });
|
||||
const handleInputChange = (
|
||||
key: keyof UserProfileSettings,
|
||||
value: string
|
||||
): void => {
|
||||
dispatch({
|
||||
type: SettingsActionType.UPDATE_LOCAL_SETTINGS,
|
||||
payload: { [key]: value } as UserProfileSettings,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const updates = {};
|
||||
const handleSubmit = async (): Promise<void> => {
|
||||
const updates: UserProfileSettings = {};
|
||||
const needsPasswordConfirmation =
|
||||
state.localSettings.email !== state.initialSettings.email;
|
||||
|
||||
@@ -113,10 +139,10 @@ const AccountSettings = ({ opened, onClose }) => {
|
||||
}
|
||||
}
|
||||
|
||||
const result = await updateProfile(updates);
|
||||
if (result.success) {
|
||||
const updatedUser = await updateProfile(updates);
|
||||
if (updatedUser) {
|
||||
await refreshUser();
|
||||
dispatch({ type: 'MARK_SAVED' });
|
||||
dispatch({ type: SettingsActionType.MARK_SAVED });
|
||||
onClose();
|
||||
}
|
||||
} else {
|
||||
@@ -125,17 +151,20 @@ const AccountSettings = ({ opened, onClose }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleEmailConfirm = async (password) => {
|
||||
const updates = {
|
||||
const handleEmailConfirm = async (password: string): Promise<void> => {
|
||||
const updates: UserProfileSettings = {
|
||||
...state.localSettings,
|
||||
currentPassword: password,
|
||||
};
|
||||
|
||||
// Remove any undefined/empty values
|
||||
Object.keys(updates).forEach((key) => {
|
||||
if (updates[key] === undefined || updates[key] === '') {
|
||||
delete updates[key];
|
||||
const typedKey = key as keyof UserProfileSettings;
|
||||
if (updates[typedKey] === undefined || updates[typedKey] === '') {
|
||||
delete updates[typedKey];
|
||||
}
|
||||
});
|
||||
|
||||
// Remove keys that haven't changed
|
||||
if (updates.displayName === state.initialSettings.displayName) {
|
||||
delete updates.displayName;
|
||||
@@ -144,10 +173,10 @@ const AccountSettings = ({ opened, onClose }) => {
|
||||
delete updates.email;
|
||||
}
|
||||
|
||||
const result = await updateProfile(updates);
|
||||
if (result.success) {
|
||||
const updatedUser = await updateProfile(updates);
|
||||
if (updatedUser) {
|
||||
await refreshUser();
|
||||
dispatch({ type: 'MARK_SAVED' });
|
||||
dispatch({ type: SettingsActionType.MARK_SAVED });
|
||||
setEmailModalOpened(false);
|
||||
onClose();
|
||||
}
|
||||
@@ -162,7 +191,7 @@ const AccountSettings = ({ opened, onClose }) => {
|
||||
centered
|
||||
size="lg"
|
||||
>
|
||||
<Stack spacing="xl">
|
||||
<Stack gap="xl">
|
||||
{state.hasUnsavedChanges && (
|
||||
<Badge color="yellow" variant="light">
|
||||
Unsaved Changes
|
||||
@@ -172,7 +201,7 @@ const AccountSettings = ({ opened, onClose }) => {
|
||||
<Accordion
|
||||
defaultValue={['profile', 'security', 'danger']}
|
||||
multiple
|
||||
styles={(theme) => ({
|
||||
styles={(theme: any) => ({
|
||||
control: {
|
||||
paddingTop: theme.spacing.md,
|
||||
paddingBottom: theme.spacing.md,
|
||||
@@ -239,7 +268,7 @@ const AccountSettings = ({ opened, onClose }) => {
|
||||
opened={emailModalOpened}
|
||||
onClose={() => setEmailModalOpened(false)}
|
||||
onConfirm={handleEmailConfirm}
|
||||
email={state.localSettings.email}
|
||||
email={state.localSettings.email || ''}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -4,14 +4,14 @@ import DeleteAccountModal from '../../modals/account/DeleteAccountModal';
|
||||
import { useAuth } from '../../../contexts/AuthContext';
|
||||
import { useProfileSettings } from '../../../hooks/useProfileSettings';
|
||||
|
||||
const DangerZoneSettings = () => {
|
||||
const DangerZoneSettings: React.FC = () => {
|
||||
const { logout } = useAuth();
|
||||
const { deleteAccount } = useProfileSettings();
|
||||
const [deleteModalOpened, setDeleteModalOpened] = useState(false);
|
||||
const [deleteModalOpened, setDeleteModalOpened] = useState<boolean>(false);
|
||||
|
||||
const handleDelete = async (password) => {
|
||||
const result = await deleteAccount(password);
|
||||
if (result.success) {
|
||||
const handleDelete = async (password: string): Promise<void> => {
|
||||
const success = await deleteAccount(password);
|
||||
if (success) {
|
||||
setDeleteModalOpened(false);
|
||||
logout();
|
||||
}
|
||||
@@ -1,9 +1,18 @@
|
||||
import React from 'react';
|
||||
import { Box, Stack, TextInput } from '@mantine/core';
|
||||
import { UserProfileSettings } from '../../../types/settings';
|
||||
|
||||
const ProfileSettings = ({ settings, onInputChange }) => (
|
||||
interface ProfileSettingsProps {
|
||||
settings: UserProfileSettings;
|
||||
onInputChange: (key: keyof UserProfileSettings, value: string) => void;
|
||||
}
|
||||
|
||||
const ProfileSettingsComponent: React.FC<ProfileSettingsProps> = ({
|
||||
settings,
|
||||
onInputChange,
|
||||
}) => (
|
||||
<Box>
|
||||
<Stack spacing="md">
|
||||
<Stack gap="md">
|
||||
<TextInput
|
||||
label="Display Name"
|
||||
value={settings.displayName || ''}
|
||||
@@ -20,4 +29,4 @@ const ProfileSettings = ({ settings, onInputChange }) => (
|
||||
</Box>
|
||||
);
|
||||
|
||||
export default ProfileSettings;
|
||||
export default ProfileSettingsComponent;
|
||||
@@ -1,11 +1,22 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box, PasswordInput, Stack, Text } from '@mantine/core';
|
||||
import { UserProfileSettings } from '@/types/settings';
|
||||
|
||||
const SecuritySettings = ({ settings, onInputChange }) => {
|
||||
interface SecuritySettingsProps {
|
||||
settings: UserProfileSettings;
|
||||
onInputChange: (key: keyof UserProfileSettings, value: string) => void;
|
||||
}
|
||||
|
||||
type PasswordField = 'currentPassword' | 'newPassword' | 'confirmNewPassword';
|
||||
|
||||
const SecuritySettings: React.FC<SecuritySettingsProps> = ({
|
||||
settings,
|
||||
onInputChange,
|
||||
}) => {
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const handlePasswordChange = (field, value) => {
|
||||
const handlePasswordChange = (field: PasswordField, value: string) => {
|
||||
if (field === 'confirmNewPassword') {
|
||||
setConfirmPassword(value);
|
||||
// Check if passwords match when either password field changes
|
||||
@@ -27,7 +38,7 @@ const SecuritySettings = ({ settings, onInputChange }) => {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack spacing="md">
|
||||
<Stack gap="md">
|
||||
<PasswordInput
|
||||
label="Current Password"
|
||||
value={settings.currentPassword || ''}
|
||||
23
app/src/types/settings.ts
Normal file
23
app/src/types/settings.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export enum SettingsActionType {
|
||||
INIT_SETTINGS = 'INIT_SETTINGS',
|
||||
UPDATE_LOCAL_SETTINGS = 'UPDATE_LOCAL_SETTINGS',
|
||||
MARK_SAVED = 'MARK_SAVED',
|
||||
}
|
||||
|
||||
export interface UserProfileSettings {
|
||||
displayName?: string;
|
||||
email?: string;
|
||||
currentPassword?: string;
|
||||
newPassword?: string;
|
||||
}
|
||||
|
||||
export interface ProfileSettingsState {
|
||||
localSettings: UserProfileSettings;
|
||||
initialSettings: UserProfileSettings;
|
||||
hasUnsavedChanges: boolean;
|
||||
}
|
||||
|
||||
export interface SettingsAction<T> {
|
||||
type: SettingsActionType;
|
||||
payload?: T;
|
||||
}
|
||||
Reference in New Issue
Block a user