ahbeng/NUSMods

View on GitHub
website/src/views/errors/ModuleNotFoundPage.tsx

Summary

Maintainability
A
0 mins
Test Coverage
import { PureComponent } from 'react';
import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import classnames from 'classnames';
import * as Sentry from '@sentry/browser';

import { ModuleCode } from 'types/modules';
import RandomKawaii from 'views/components/RandomKawaii';
import Title from 'views/components/Title';
import { fetchAllModuleArchive } from 'actions/moduleBank';
import { MODULE_CODE_REGEX } from 'utils/modules';
import LoadingSpinner from 'views/components/LoadingSpinner';
import { availableArchive, isArchiveLoading } from 'selectors/timetables';
import { moduleArchive } from 'views/routes/paths';
import { State } from 'types/state';
import styles from './ErrorPage.scss';

type OwnProps = {
  tryArchive: boolean;
  moduleCode: ModuleCode;
};

type Props = OwnProps & {
  isLoading: boolean;
  availableArchive: string[];
  fetchModuleArchive: (str: string) => Promise<unknown>;
};

export class ModuleNotFoundPageComponent extends PureComponent<Props> {
  override componentDidMount() {
    Sentry.withScope(() => {
      Sentry.captureMessage('404 - Module Not Found');
    });

    // If we think this is a module, try checking for archived modules
    if (this.props.moduleCode.match(MODULE_CODE_REGEX) && this.props.tryArchive) {
      this.props.fetchModuleArchive(this.props.moduleCode);
    }
  }

  override render() {
    const { moduleCode, isLoading } = this.props;

    if (isLoading) {
      return <LoadingSpinner />;
    }

    return (
      <div className={styles.container}>
        <Title>Course Not Found</Title>

        {this.props.availableArchive.length ? (
          <>
            <div className={styles.header}>
              <RandomKawaii size={100} />
            </div>

            <h1 className={classnames('h3', styles.header)}>
              <span className={styles.expr}>{moduleCode}</span> is not currently offered
            </h1>

            <p>However, it was previously offered in these academic years</p>

            <div className={styles.buttons}>
              {this.props.availableArchive.map((year) => (
                <Link
                  className="btn btn-outline-primary"
                  to={moduleArchive(moduleCode, year)}
                  key={year}
                >
                  AY
                  {year} archive
                </Link>
              ))}
            </div>

            <p>
              Otherwise, if this is not what you are looking for,{' '}
              <Link to="/">go back to nusmods.com</Link> or{' '}
              <Link to="/courses">try the course finder</Link>.
            </p>
          </>
        ) : (
          <>
            <h1 className={styles.heading}>
              <span className={styles.bigCharacter}>4</span>
              <RandomKawaii aria-label="0" title="0" size={100} />
              <span className={styles.bigCharacter}>4</span>
            </h1>

            <h2>Oops, course {moduleCode} not found.</h2>

            <p>
              This usually means you have a typo in the course code, or the course is not offered
              this year.
            </p>

            <div className={styles.buttons}>
              <button
                type="button"
                className="btn btn-outline-primary"
                onClick={() => Sentry.showReportDialog()}
              >
                {moduleCode} should be here
              </button>
              <Link className="btn btn-primary" to="/">
                Bring me home
              </Link>
            </div>
          </>
        )}
      </div>
    );
  }
}

export default connect(
  (state: State, ownProps: OwnProps) => ({
    isLoading: isArchiveLoading(state, ownProps.moduleCode),
    availableArchive: availableArchive(state, ownProps.moduleCode),
  }),
  { fetchModuleArchive: fetchAllModuleArchive },
)(ModuleNotFoundPageComponent);