hyper-tuner-cloud/src/pages/auth/Profile.tsx

263 lines
6.8 KiB
TypeScript

import {
useCallback,
useEffect,
useState,
} from 'react';
import { useNavigate } from 'react-router-dom';
import {
Form,
Input,
Button,
Divider,
Alert,
Space,
List,
} from 'antd';
import {
UserOutlined,
MailOutlined,
LockOutlined,
} from '@ant-design/icons';
import validateMessages from './validateMessages';
import { useAuth } from '../../contexts/AuthContext';
import {
restrictedPage,
sendingEmailVerificationFailed,
emailVerificationSent,
profileUpdateSuccess,
profileUpdateFailed,
passwordUpdateSuccess,
passwordUpdateFailed,
} from './notifications';
import { Routes } from '../../routes';
import {
passwordRules,
requiredRules,
} from '../../utils/form';
const { Item } = Form;
const MAX_LIST_SIZE = 10;
const parseLogEvent = (raw: string) => {
const split = raw.split('.');
return [split[0], split[2], split[4]].join(' ');
};
const Profile = () => {
const [formProfile] = Form.useForm();
const [formPassword] = Form.useForm();
const {
currentUser,
sendEmailVerification,
updateUsername,
updatePassword,
getSessions,
getLogs,
} = useAuth();
const navigate = useNavigate();
const [isVerificationSent, setIsVerificationSent] = useState(false);
const [isSendingVerification, setIsSendingVerification] = useState(false);
const [isProfileLoading, setIsProfileLoading] = useState(false);
const [isPasswordLoading, setIsPasswordLoading] = useState(false);
const [sessions, setSessions] = useState<string[]>([]);
const [logs, setLogs] = useState<string[]>([]);
const resendEmailVerification = async () => {
setIsSendingVerification(true);
setIsVerificationSent(true);
try {
await sendEmailVerification();
emailVerificationSent();
} catch (error) {
sendingEmailVerificationFailed(error as Error);
setIsVerificationSent(false);
} finally {
setIsSendingVerification(false);
}
};
const fetchLogs = useCallback(async () => getLogs()
.then((list) => setLogs(list.logs.slice(0, MAX_LIST_SIZE).map((log) => [
new Date(log.time * 1000).toLocaleString(),
parseLogEvent(log.event),
log.clientName,
log.clientEngineVersion,
log.osName,
log.deviceName,
log.countryName,
log.ip,
].join(' | ')))), [getLogs]);
const onUpdateProfile = async ({ username }: { username: string }) => {
setIsProfileLoading(true);
try {
await updateUsername(username);
profileUpdateSuccess();
fetchLogs();
} catch (error) {
profileUpdateFailed(error as Error);
} finally {
setIsProfileLoading(false);
}
};
const onUpdatePassword = async ({ password, oldPassword }: { password: string, oldPassword: string }) => {
setIsPasswordLoading(true);
try {
await updatePassword(password, oldPassword);
passwordUpdateSuccess();
fetchLogs();
formPassword.resetFields();
} catch (error) {
passwordUpdateFailed(error as Error);
} finally {
setIsPasswordLoading(false);
}
};
useEffect(() => {
if (currentUser) {
getSessions()
.then((list) => setSessions(list.sessions.slice(0, MAX_LIST_SIZE).map((ses) => [
ses.clientName,
ses.osName,
ses.deviceName,
ses.countryName,
ses.ip,
].join(' | '))));
fetchLogs();
return;
}
restrictedPage();
navigate(Routes.LOGIN);
}, [currentUser, fetchLogs, getLogs, getSessions, navigate]);
return (
<>
<div className="auth-container">
{!currentUser?.emailVerification && (<>
<Divider>Email verification</Divider>
<Space direction="vertical" style={{ width: '100%' }} size="large">
<Alert message="Your email address is not verified!" type="error" showIcon />
<Button
type="primary"
style={{ width: '100%' }}
icon={<MailOutlined />}
disabled={isVerificationSent}
loading={isSendingVerification}
onClick={resendEmailVerification}
>
Resend verification
</Button>
</Space>
</>)}
<Divider>Your Profile</Divider>
<Form
validateMessages={validateMessages}
form={formProfile}
onFinish={onUpdateProfile}
fields={[
{
name: 'username',
value: currentUser?.name,
},
{
name: 'email',
value: currentUser?.email,
},
]}
>
<Item
name="username"
rules={requiredRules}
hasFeedback
>
<Input
prefix={<UserOutlined />}
placeholder="Username"
autoComplete="name"
/>
</Item>
<Item name="email">
<Input prefix={<MailOutlined />} placeholder="Email" disabled />
</Item>
<Item>
<Button
type="primary"
htmlType="submit"
style={{ width: '100%' }}
icon={<UserOutlined />}
loading={isProfileLoading}
>
Update
</Button>
</Item>
</Form>
<Divider>Password</Divider>
<Form
validateMessages={validateMessages}
form={formPassword}
onFinish={onUpdatePassword}
>
<Item
name="oldPassword"
rules={requiredRules}
hasFeedback
>
<Input.Password
placeholder="Old password"
autoComplete="current-password"
prefix={<LockOutlined />}
/>
</Item>
<Item
name="password"
rules={passwordRules}
hasFeedback
>
<Input.Password
placeholder="New password"
autoComplete="new-password"
prefix={<LockOutlined />}
/>
</Item>
<Item>
<Button
type="primary"
htmlType="submit"
style={{ width: '100%' }}
icon={<LockOutlined />}
loading={isPasswordLoading}
>
Change
</Button>
</Item>
</Form>
</div>
<div className="large-container">
<Divider>Active sessions</Divider>
<List
size="small"
bordered
dataSource={sessions}
renderItem={item => <List.Item>{item}</List.Item>}
loading={sessions.length === 0}
/>
<Divider>Audit logs</Divider>
<List
size="small"
bordered
dataSource={logs}
renderItem={item => <List.Item>{item}</List.Item>}
loading={logs.length === 0}
/>
</div>
</>
);
};
export default Profile;