Source

pages/Auth/Auth.js

// NPM module imports
import React, { Component } from "react";

// File imports
import * as authStages from "./authStages";

// Image imports
import logo from "../../assets/logos/tma-logo-white.png";

// Component imports
import AuthCard from "../../components/AuthCards/AuthCard/AuthCard";
import ProfileCard from "../../components/AuthCards/ProfileCard/ProfileCard";
import LoadingHUD from "../../components/Spinners/LoadingHUD/LoadingHUD";
import AlertBar from "../../components/AlertBar/AlertBar";

// Style imports
import styles from "./Auth.module.scss";

/**
 * Renders the Auth component.
 * This component handles both the sign up and sign in auth flows.
 * @extends {Component}
 * @component
 * @category Auth
 * @author Dan Levy <danlevy124@gmail.com>
 */
class Auth extends Component {
    /**
     * Auth component state
     * @property {module:authStages} authStage - The current auth stage (see authStages enum)
     * @property {number} windowInnerHeight - The inner height of the window (used to resize the component)
     * @property {boolean} isLoading - Indicates whether the component is in a loading state
     * @property {object} alertData - Data used to display an alert
     * @property {module:alertBarTypes} alertData.type - The type of alert bar to show
     * @property {string} alertData.heading - The alert heading
     * @property {string} alertData.message - The alert message
     */
    state = {
        authStage: authStages.SIGN_IN,
        windowInnerHeight: window.innerHeight,
        isLoading: false,
        alertData: null,
    };

    /**
     * Indicates if the component is mounted.
     * Used for asynchronous tasks.
     * @see https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html
     */
    _isMounted = false;

    /**
     * Starts the auth flow.
     * Starts a window resize listener.
     */
    componentDidMount() {
        this._isMounted = true;
        window.addEventListener("resize", this.resizeWindow);
    }

    /**
     * Removes the window resize listener
     */
    componentWillUnmount() {
        this._isMounted = false;
        window.removeEventListener("resize", this.resizeWindow);
    }

    /**
     * Updates state when the inner height of the window changes
     * @function
     */
    resizeWindow = () => {
        if (this._isMounted)
            this.setState({ windowInnerHeight: window.innerHeight });
    };

    /**
     * Updates loading state
     * @function
     */
    setLoadingHandler = (isLoading) => {
        if (this._isMounted) this.setState({ isLoading });
    };

    /**
     * Sets alertData in state when a new alert is triggered
     * @function
     * @param {alertBarTypes} - The type of alert bar to show
     * @param {string} - The alert heading
     * @param {string} - The alert message
     */
    showAlertHandler = (type, heading, message) => {
        if (this._isMounted)
            this.setState({
                alertData: { type, heading, message },
            });
    };

    /**
     * Sets alertData in state to null in state when the alert disappears
     * @function
     */
    alertIsDoneHandler = () => {
        if (this._isMounted) this.setState({ alertData: null });
    };

    /**
     * Moves to the next auth stage when the current stage is complete.
     * If the flow is done, signals to Redux that the flow is done (sign in or sign up).
     * @function
     * @param {module:authStages} - The auth stage that is complete
     */
    authFlowStageDoneHandler = (stage) => {
        if (this._isMounted && stage === authStages.SIGN_UP) {
            this.setState({ authStage: authStages.PROFILE });
        }
    };

    /**
     * Switches to the opposite auth flow.
     * If the current auth flow is sign in, switch to sign up, and vice versa.
     * @function
     */
    switchAuthFlowHandler = () => {
        if (this._isMounted) {
            if (this.state.authStage === authStages.SIGN_IN) {
                this.setState({ authStage: authStages.SIGN_UP });
            } else {
                this.setState({ authStage: authStages.SIGN_IN });
            }
        }
    };

    /**
     * Gets the correct auth card based on the current auth stage
     * @function
     * @returns {object} An auth card (JSX)
     */
    getAuthCard = () => {
        // Selects the Auth Card and auth info to display based on the auth stage
        switch (this.state.authStage) {
            case authStages.SIGN_IN:
            case authStages.SIGN_UP:
                // Both sign in and sign up stages use the same card
                return (
                    <AuthCard
                        setLoading={this.setLoadingHandler}
                        showAlert={this.showAlertHandler}
                        done={this.authFlowStageDoneHandler}
                        authStage={this.state.authStage}
                        switchAuthFlow={this.switchAuthFlowHandler}
                    />
                );
            case authStages.PROFILE:
                return (
                    <ProfileCard
                        setLoading={this.setLoadingHandler}
                        showAlert={this.showAlertHandler}
                        done={this.authFlowStageDoneHandler}
                    />
                );
            default:
                return null;
        }
    };

    /**
     * Renders the Auth component
     * @returns {object} The JSX to render
     */
    render() {
        return (
            <div
                className={styles.auth}
                style={{ minHeight: `${this.state.windowInnerHeight}px` }}
            >
                {this.state.isLoading ? (
                    <LoadingHUD message="Loading..." />
                ) : null}
                {this.state.alertData ? (
                    <AlertBar
                        type={this.state.alertData.type}
                        heading={this.state.alertData.heading}
                        message={this.state.alertData.message}
                        done={this.alertIsDoneHandler}
                    />
                ) : null}
                <div className={styles.authContainer}>
                    <div className={styles.authInfo}>
                        <img
                            className={styles.authInfoLogo}
                            src={logo}
                            alt="Music Assistant Logo"
                        />
                        <h1 className={styles.authInfoHeading}>
                            The Music Assistant
                        </h1>
                        <h2 className={styles.authInfoSubheading}>
                            A smarter way to sing
                        </h2>
                    </div>
                    {this.getAuthCard()}
                </div>
            </div>
        );
    }
}

export default Auth;