tutorbookapp/tutorbook

View on GitHub
components/login/index.tsx

Summary

Maintainability
A
1 hr
Test Coverage
import { FormEvent, useCallback, useEffect, useMemo, useState } from 'react';
import Router, { useRouter } from 'next/router';
import axios, { AxiosError } from 'axios';
import NProgress from 'nprogress';
import { TextField } from '@rmwc/textfield';
import to from 'await-to-js';
import useTranslation from 'next-translate/useTranslation';

import Button from 'components/button';

import { APIErrorJSON } from 'lib/model/error';
import getLocation from 'lib/utils/location';
import { period } from 'lib/utils';
import { loginWithGoogle } from 'lib/firebase/login';
import useTrack from 'lib/hooks/track';

import styles from './login.module.scss';

export default function Login(): JSX.Element {
  const { query } = useRouter();
  const { t } = useTranslation();

  const [error, setError] = useState<string>('');
  const [loading, setLoading] = useState<boolean>(false);

  useEffect(() => {
    if (!loading) {
      NProgress.done();
    } else {
      NProgress.start();
      setError('');
    }
  }, [loading]);
  useEffect(() => {
    if (error) setLoading(false);
  }, [error]);

  const track = useTrack();
  const redirect = useMemo(
    () => decodeURIComponent((query.href as string) || 'overview'),
    [query.href]
  );

  const [email, setEmail] = useState<string>('');
  const withEmail = useCallback(
    async (evt: FormEvent) => {
      evt.preventDefault();
      setLoading(true);
      localStorage.setItem('email', email);
      track('Email Login Started', { email });
      const [locationErr, location] = await to(getLocation());
      if (locationErr) {
        track('Email Login Errored', { error: period(locationErr.message) });
        return setError(period(locationErr.message));
      }
      const [err] = await to(
        axios.post('/api/login', { email, location, redirect })
      );
      if (err) {
        const e = (err as AxiosError<APIErrorJSON>).response?.data || err;
        track('Email Login Errored', { error: period(e.message) });
        return setError(period(e.message));
      }
      return Router.push({
        pathname: '/awaiting-confirm',
        query: { email, href: query.href },
      });
    },
    [track, email, redirect, query.href]
  );

  const withGoogle = useCallback(async () => {
    setLoading(true);
    track('Google Login Started');
    const [err] = await to(loginWithGoogle());
    if (err) {
      track('Google Login Errored', { error: period(err.message) });
      return setError(period(err.message));
    }
    return Router.push(redirect);
  }, [track, redirect]);

  return (
    <div className={styles.wrapper}>
      <div className={styles.content}>
        <h2>Welcome</h2>
        <Button
          onClick={withGoogle}
          label='Continue with Google'
          disabled={loading}
          google
          raised
          arrow
        />
        <div className={styles.divider}>
          <span>Or, sign in with your email</span>
        </div>
        <form onSubmit={withEmail}>
          <TextField
            label='Your email address'
            placeholder='Ex: you@domain.com'
            disabled={loading}
            value={email}
            onChange={(evt) => setEmail(evt.currentTarget.value)}
            type='email'
            required
            outlined
          />
          <Button label='Continue with email' disabled={loading} raised arrow />
        </form>
        {!!error && (
          <div data-cy='error' className={styles.error}>
            {t('login:error', { error })}
          </div>
        )}
      </div>
    </div>
  );
}