src/pages/auth/Profile.tsx
import {
ArrowRightOutlined,
EditOutlined,
EyeOutlined,
GlobalOutlined,
MailOutlined,
UserOutlined,
} from '@ant-design/icons';
import { Alert, Button, Divider, Form, Input, List, Pagination, Space, Typography } from 'antd';
import { useEffect, useState } from 'react';
import { generatePath, useNavigate } from 'react-router-dom';
import { TunesResponse, TunesVisibilityOptions } from '../../@types/pocketbase-types';
import TuneTag from '../../components/TuneTag';
import { useAuth } from '../../contexts/AuthContext';
import useDb from '../../hooks/useDb';
import { Routes } from '../../routes';
import { usernameRules } from '../../utils/form';
import { formatTime } from '../../utils/time';
import { aspirationMapper } from '../../utils/tune/mappers';
import {
emailVerificationSent,
profileUpdateFailed,
profileUpdateSuccess,
restrictedPage,
sendingEmailVerificationFailed,
} from './notifications';
import validateMessages from './validateMessages';
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);
setTunesDataSource(items);
} catch (_error) {
// request cancelled
} finally {
setIsTunesLoading(false);
}
};
useEffect(() => {
if (!currentUser) {
restrictedPage();
navigate(Routes.LOGIN);
return;
}
refreshUser().then((user) => {
if (user === null) {
restrictedPage();
navigate(Routes.LOGIN);
}
});
loadData();
}, [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 === TunesVisibilityOptions.public ? (
<GlobalOutlined />
) : (
<EyeOutlined />
),
<Button
key="edit"
icon={<EditOutlined />}
onClick={() => {
goToEdit(tune.tuneId);
}}
/>,
<Button
key="view"
icon={<ArrowRightOutlined />}
onClick={() => {
navigate(tunePath(tune.tuneId));
}}
/>,
]}
>
<Space direction="vertical">
<List.Item.Meta
title={
<Space direction="vertical">
{tune.vehicleName}
<TuneTag tag={tune.tags} />
<Typography.Text italic>{tune.signature}</Typography.Text>
</Space>
}
description={
<>
{tune.engineMake}, {tune.engineCode}, {tune.displacement}l,{' '}
{aspirationMapper[tune.aspiration]}
</>
}
/>
<div>
<Typography.Text italic>{formatTime(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;