mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-15 11:00:47 +02:00
# Description of Changes Ages ago I made #4835 to try and fix all the `any` type usage in the system but never got it finished, and there were just too many to review and ensure it still worked. There's even more now. My new tactic is to fix folder by folder. This fixes the `any` typing in the `saas/` folder, and also enables `no-unnecessary-type-assertion`, which really helps reduce pointless `as` casts that AI generates when the type is already known. I hope to expand both of these to the rest of the folders soon, but one folder is better than none.
124 lines
4.6 KiB
TypeScript
124 lines
4.6 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Button, PasswordInput, Group, Alert, LoadingOverlay, Modal, Divider } from '@mantine/core';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useAuth } from '@app/auth/UseSession';
|
|
import { supabase } from '@app/auth/supabase';
|
|
import { Z_INDEX_OVER_SETTINGS_MODAL } from '@app/styles/zIndex';
|
|
|
|
const PasswordSecurity: React.FC = () => {
|
|
const { t } = useTranslation();
|
|
const { refreshSession } = useAuth();
|
|
|
|
const [opened, setOpened] = useState(false);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [newPassword, setNewPassword] = useState('');
|
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [success, setSuccess] = useState<string | null>(null);
|
|
const [didUpdate, setDidUpdate] = useState(false);
|
|
|
|
const handleChangePassword = async () => {
|
|
if (!newPassword || !confirmPassword) {
|
|
setError(t('signup.pleaseFillAllFields', 'Please fill in all fields'));
|
|
return;
|
|
}
|
|
if (newPassword.length < 6) {
|
|
setError(t('signup.passwordTooShort', 'Password must be at least 6 characters long'));
|
|
return;
|
|
}
|
|
if (newPassword !== confirmPassword) {
|
|
setError(t('signup.passwordsDoNotMatch', 'Passwords do not match'));
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setIsLoading(true);
|
|
setError(null);
|
|
setSuccess(null);
|
|
|
|
// Update to the new password directly
|
|
const { error: updateError } = await supabase.auth.updateUser({ password: newPassword });
|
|
if (updateError) {
|
|
setError(updateError.message);
|
|
return;
|
|
}
|
|
|
|
setSuccess(t('login.passwordUpdatedSuccess', 'Your password has been updated successfully.'));
|
|
setNewPassword('');
|
|
setConfirmPassword('');
|
|
setDidUpdate(true);
|
|
|
|
// Replace form with success text, then close after 2s
|
|
setTimeout(() => {
|
|
// refresh session after closing to avoid UI jank
|
|
void refreshSession();
|
|
setOpened(false);
|
|
setDidUpdate(false);
|
|
}, 2000);
|
|
} catch (e: unknown) {
|
|
setError(e instanceof Error ? e.message : 'Failed to change password');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div style={{ position: 'relative' }}>
|
|
<LoadingOverlay visible={isLoading} />
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<div>
|
|
<h3 style={{ margin: 0, color: 'var(--mantine-color-text)', fontSize: '1rem' }}>
|
|
{t('config.account.security.title', 'Passwords & Security')}
|
|
</h3>
|
|
<p style={{ margin: '0.25rem 0 1rem 0', color: 'var(--mantine-color-dimmed)', fontSize: '0.875rem' }}>
|
|
{t('config.account.security.description', 'Manage your password and security settings.')}
|
|
</p>
|
|
</div>
|
|
<Button type="button" onClick={() => setOpened(true)} variant="filled">
|
|
{t('config.account.security.changePassword', 'Change password')}
|
|
</Button>
|
|
</div>
|
|
|
|
<Modal opened={opened} onClose={() => setOpened(false)} centered title={t('config.account.security.changePassword', 'Change password')} zIndex={Z_INDEX_OVER_SETTINGS_MODAL}>
|
|
{error && (
|
|
<Alert color="red" mb="md">{error}</Alert>
|
|
)}
|
|
|
|
{didUpdate ? (
|
|
<Alert color="green" mb="md">{success || t('login.passwordUpdatedSuccess', 'Your password has been updated successfully.')}</Alert>
|
|
) : (
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
|
<PasswordInput
|
|
label={t('account.newPassword', 'New Password')}
|
|
placeholder={t('account.newPassword', 'New Password')}
|
|
value={newPassword}
|
|
onChange={(e) => setNewPassword(e.currentTarget.value)}
|
|
/>
|
|
<PasswordInput
|
|
label={t('account.confirmNewPassword', 'Confirm New Password')}
|
|
placeholder={t('account.confirmNewPassword', 'Confirm New Password')}
|
|
value={confirmPassword}
|
|
onChange={(e) => setConfirmPassword(e.currentTarget.value)}
|
|
/>
|
|
|
|
<Divider my="sm" />
|
|
<Group justify="flex-end">
|
|
<Button type="button" variant="default" onClick={() => setOpened(false)}>
|
|
{t('common.cancel', 'Cancel')}
|
|
</Button>
|
|
<Button type="button" onClick={handleChangePassword} loading={isLoading}>
|
|
{t('config.account.security.update', 'Update password')}
|
|
</Button>
|
|
</Group>
|
|
</div>
|
|
)}
|
|
</Modal>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default PasswordSecurity;
|
|
|
|
|