feat: extract shared account components (Profile, Password, MFA, Passkey)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
88
ui/src/components/account/PasswordChangeSection.tsx
Normal file
88
ui/src/components/account/PasswordChangeSection.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { useState } from 'react';
|
||||
import { errorMessage } from '../../api/client';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
FormField,
|
||||
Input,
|
||||
useToast,
|
||||
} from '@cameleer/design-system';
|
||||
import { useChangePassword } from '../../api/account-hooks';
|
||||
import styles from '../../styles/platform.module.css';
|
||||
|
||||
export function PasswordChangeSection() {
|
||||
const { toast } = useToast();
|
||||
const changePassword = useChangePassword();
|
||||
|
||||
const [currentPassword, setCurrentPassword] = useState('');
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
|
||||
async function handleChangePassword(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
if (newPassword.length < 8) {
|
||||
toast({ title: 'Password must be at least 8 characters', variant: 'error' });
|
||||
return;
|
||||
}
|
||||
if (newPassword !== confirmPassword) {
|
||||
toast({ title: 'Passwords do not match', variant: 'error' });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await changePassword.mutateAsync({ currentPassword, newPassword });
|
||||
toast({ title: 'Password changed successfully', variant: 'success' });
|
||||
setCurrentPassword('');
|
||||
setNewPassword('');
|
||||
setConfirmPassword('');
|
||||
} catch (err) {
|
||||
toast({ title: 'Failed to change password', description: errorMessage(err), variant: 'error' });
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card title="Change Password">
|
||||
<p className={styles.description} style={{ marginTop: 0 }}>
|
||||
Update your login password. Minimum 8 characters.
|
||||
</p>
|
||||
<form onSubmit={handleChangePassword} style={{ display: 'flex', flexDirection: 'column', gap: 16, marginTop: 12 }}>
|
||||
<FormField label="Current password" htmlFor="current-pw">
|
||||
<Input
|
||||
id="current-pw"
|
||||
type="password"
|
||||
value={currentPassword}
|
||||
onChange={(e) => setCurrentPassword(e.target.value)}
|
||||
placeholder="Enter current password"
|
||||
required
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="New password" htmlFor="new-pw">
|
||||
<Input
|
||||
id="new-pw"
|
||||
type="password"
|
||||
value={newPassword}
|
||||
onChange={(e) => setNewPassword(e.target.value)}
|
||||
placeholder="Enter new password"
|
||||
required
|
||||
minLength={8}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Confirm password" htmlFor="confirm-pw">
|
||||
<Input
|
||||
id="confirm-pw"
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
placeholder="Confirm new password"
|
||||
required
|
||||
minLength={8}
|
||||
/>
|
||||
</FormField>
|
||||
<div>
|
||||
<Button type="submit" variant="primary" loading={changePassword.isPending}>
|
||||
Change Password
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user