// NPM module imports
import React, { Component } from "react";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import shortid from "shortid";
// Component imports
import MemberCard from "./MemberCard/MemberCard";
import PageHeader from "../PageHeader/PageHeader";
import LoadingContainer from "../Spinners/LoadingContainer/LoadingContainer";
// File imports
import { getChoirMembers } from "../../vendors/AWS/tmaApi";
import firebase from "../../vendors/Firebase/firebase";
import * as memberColorOptions from "./MemberCard/memberCardColorOptions";
import * as alertBarTypes from "../AlertBar/alertBarTypes";
import { choirMembersError } from "../../vendors/Firebase/logs";
// Style imports
import styles from "./ChoirMembers.module.scss";
/**
* Renders the ChoirMembers component.
* Shows all choir member profiles.
* @extends {Component}
* @component
* @category ChoirMembers
* @author Dan Levy <danlevy124@gmail.com>
*/
class ChoirMembers extends Component {
/**
* ChoirMembers component state
* @property {boolean} isLoading - Indicates if the component is in a loading state
* @property {array} members - A list of choir members
*/
state = {
isLoading: true,
members: null,
};
/**
* Indicates if the component is mounted.
* Used for asynchronous tasks.
* @see https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html
*/
_isMounted = false;
/**
* Sets _isMounted to true
* Gets the choir members
*/
componentDidMount() {
this._isMounted = true;
// Gets the choir members
this.getMembers();
}
/**
* Sets _isMounted to false
*/
componentWillUnmount() {
this._isMounted = false;
}
/**
* Gets the choir members from the server.
* Updates state with the members.
* @function
*/
getMembers = () => {
getChoirMembers({ choirId: this.props.choirId })
.then((response) => {
// Updates state
if (this._isMounted)
this.setState({ isLoading: false, members: response.data });
// Gets each member's profile picture (this is async and will update the correct card when the picture comes in)
this.getMembersProfilePictures();
})
.catch((error) => {
// Logs an error
choirMembersError(
error.response.status,
error.response.data,
"[ChoirMembers/getMembers]"
);
// Shows an error alert
this.props.showAlert(
alertBarTypes.ERROR,
"Error",
error.response.data
);
// Updates state
if (this._isMounted) this.setState({ isLoading: false });
});
};
/**
* Gets each member's profile picture url.
* Updates state with the url.
* @function
*/
getMembersProfilePictures = () => {
if (this.state.members) {
for (let i = 0; i < this.state.members.length; i++) {
// Get the member (deep copy)
const member = { ...this.state.members[i] };
if (member.has_picture) {
// Gets the member's picture url from Firebase storage
firebase
.storage()
.ref()
.child(
`users/${member.person_id}/profile_picture_200x200`
)
.getDownloadURL()
.then((url) => {
// Updates state with the url
member.picture_url = url;
const members = [...this.state.members];
members[i] = member;
this.setState({ members });
});
}
}
}
};
/**
* Gets an array of admin member cards and an array of student member cards
* @function
* @returns {object} An object containing an array of admin MemberCard components and an array of student MemberCard components
*/
getMemberCards = () => {
const admins = [];
const students = [];
if (this.state.members) {
// Creates a member card for each member of the choir
for (let i = 0; i < this.state.members.length; i++) {
// Gets the current member
const member = this.state.members[i];
if (member.member_type === "admin") {
// Creates an admin member card
admins.push(
this.createMemberCard(member, memberColorOptions.GREEN)
);
} else {
// Creates a student member card
students.push(
this.createMemberCard(member, memberColorOptions.ORANGE)
);
}
}
}
return { admins, students };
};
/**
* Creates a member card component
* @function
* @param {object} member - The member data used to create the member card
* @param {string} color - The color to use for the card
* @returns {object} A MemberCard component
*/
createMemberCard(member, color) {
return (
<MemberCard
key={shortid.generate()}
name={`${member.first_name} ${member.last_name}`}
roles={member.member_role}
profilePictureSrc={member.picture_url}
color={color}
/>
);
}
/**
* Renders the ChoirMembers component
*/
render() {
const { admins, students } = this.getMemberCards();
// The component to display (loading or cards)
let component;
if (this.state.isLoading) {
// Display a loading spinner
component = <LoadingContainer message="Loading members..." />;
} else {
// Display the choir cards
component = (
<div>
<h1 className={styles.choirMembersMemberGroupHeading}>
Administrators
</h1>
<div className={styles.choirMembersCards}>{admins}</div>
<h1 className={styles.choirMembersMemberGroupHeading}>
Students
</h1>
<div className={styles.choirMembersCards}>{students}</div>
</div>
);
}
// Returns the JSX to render
return (
<div className={styles.choirMembers}>
<PageHeader
heading={`${this.props.choirName} Members`}
shouldDisplayBackButton={true}
backButtonTitle={"Choir Selection"}
/>
{component}
</div>
);
}
}
// Prop types for the ChoirMembers component
ChoirMembers.propTypes = {
/**
* The id of the selected choir
*/
choirId: PropTypes.string.isRequired,
/**
* The name of the selected choir
*/
choirName: PropTypes.string.isRequired,
/**
* Shows an alert
*/
showAlert: PropTypes.func.isRequired,
};
/**
* Gets the current state from Redux and passes parts of it to the ChoirMembers component as props.
* This function is used only by the react-redux connect function.
* @memberof ChoirMembers
* @param {object} state - The Redux state
* @returns {object} Redux state properties used in the ChoirMembers component
*/
const mapStateToProps = (state) => {
return {
choirId: state.choirs.selectedChoirId,
choirName: state.choirs.selectedChoirName,
};
};
export default connect(mapStateToProps)(ChoirMembers);
Source