hyper-tuner/hyper-tuner-cloud

View on GitHub
src/pages/auth/Profile.tsx

Summary

Maintainability
D
3 days
Test Coverage
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;