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

252 lines
7.1 KiB
TypeScript

import {
useEffect,
useState,
} from 'react';
import {
generatePath,
useNavigate,
} from 'react-router-dom';
import {
Form,
Input,
Button,
Divider,
Alert,
Space,
List,
Pagination,
Typography,
} from 'antd';
import {
UserOutlined,
MailOutlined,
ArrowRightOutlined,
EditOutlined,
GlobalOutlined,
EyeOutlined,
} from '@ant-design/icons';
import validateMessages from './validateMessages';
import { useAuth } from '../../contexts/AuthContext';
import {
restrictedPage,
sendingEmailVerificationFailed,
emailVerificationSent,
profileUpdateSuccess,
profileUpdateFailed,
} from './notifications';
import { Routes } from '../../routes';
import { usernameRules } from '../../utils/form';
import { formatTime } from '../../utils/time';
import useDb from '../../hooks/useDb';
import { aspirationMapper } from '../../utils/tune/mappers';
import { TunesResponse } from '../../@types/pocketbase-types';
const { Item } = Form;
const tunePath = (tuneId: string) => generatePath(Routes.TUNE_TUNE, { tuneId });
const Profile = () => {
const [formProfile] = Form.useForm();
const {
currentUser,
sendEmailVerification,
updateUsername,
refreshUser,
} = useAuth();
const navigate = useNavigate();
const { getUserTunes } = useDb();
const [isVerificationSent, setIsVerificationSent] = useState(false);
const [isSendingVerification, setIsSendingVerification] = useState(false);
const [isProfileLoading, setIsProfileLoading] = useState(false);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [total, setTotal] = useState(0);
const [isTunesLoading, setIsTunesLoading] = useState(false);
const [tunesDataSource, setTunesDataSource] = useState<TunesResponse[]>([]);
const goToEdit = (tuneId: string) => navigate(generatePath(Routes.UPLOAD_WITH_TUNE_ID, {
tuneId,
}));
const resendEmailVerification = async () => {
setIsSendingVerification(true);
setIsVerificationSent(true);
try {
await sendEmailVerification();
emailVerificationSent();
} catch (error) {
sendingEmailVerificationFailed(error as Error);
setIsVerificationSent(false);
} finally {
setIsSendingVerification(false);
}
};
const onUpdateProfile = async ({ username }: { username: string }) => {
setIsProfileLoading(true);
try {
await updateUsername(username);
profileUpdateSuccess();
refreshUser();
} catch (error) {
profileUpdateFailed(error as Error);
} finally {
setIsProfileLoading(false);
}
};
const loadData = async () => {
setIsTunesLoading(true);
try {
const { items, totalItems } = await getUserTunes(currentUser!.id, page, pageSize);
setTotal(totalItems);
const mapped = items.map((tune) => ({
...tune,
key: tune.tuneId,
year: tune.year,
displacement: `${tune.displacement}l`,
aspiration: aspirationMapper[tune.aspiration],
updated: formatTime(tune.updated),
}));
setTunesDataSource(mapped as any);
} catch (error) {
// request cancelled
} finally {
setIsTunesLoading(false);
}
};
useEffect(() => {
if (!currentUser) {
restrictedPage();
navigate(Routes.LOGIN);
return;
}
refreshUser().then((user) => {
if (currentUser === null || user === null) {
restrictedPage();
navigate(Routes.LOGIN);
}
});
loadData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [page]);
return (
<>
<div className="auth-container">
{!currentUser?.verified && (<>
<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>
<Space direction="vertical" style={{ width: '100%' }} size="large">
<Form
validateMessages={validateMessages}
form={formProfile}
onFinish={onUpdateProfile}
fields={[
{
name: 'username',
value: currentUser?.username,
},
{
name: 'email',
value: currentUser?.email,
},
]}
>
<Item
name="username"
rules={usernameRules}
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>
</Space>
</div>
<div className="small-container">
<Divider>Your tunes</Divider>
<List
dataSource={tunesDataSource}
loading={isTunesLoading}
renderItem={(tune) => (
<List.Item
actions={[
tune.visibility === 'public' ? <GlobalOutlined /> : <EyeOutlined />,
<Button icon={<EditOutlined />} onClick={() => goToEdit(tune.tuneId)} />,
<Button icon={<ArrowRightOutlined />} onClick={() => navigate(tunePath(tune.tuneId))} />,
]}
>
<Space direction="vertical">
<List.Item.Meta
title={<>
{tune.vehicleName} <Typography.Text code>{tune.signature}</Typography.Text>
</>}
description={<>
{tune.engineMake}, {tune.engineCode}, {tune.displacement}, {tune.aspiration}
</>}
/>
<div>
<Typography.Text italic>{tune.updated}</Typography.Text>
</div>
</Space>
</List.Item>
)}
footer={
<div style={{ textAlign: 'right' }}>
<Pagination
style={{ marginTop: 10 }}
pageSize={pageSize}
current={page}
total={total}
onChange={(newPage, newPageSize) => {
setIsTunesLoading(true);
setPage(newPage);
setPageSize(newPageSize);
}}
/>
</div>
}
/>
</div>
</>
);
};
export default Profile;