<repo_name>pledovski/MusicFarm<file_sep>/client/src/components/profile-forms/AddRelease.js
import React, { Fragment, useState } from "react";
import { withRouter } from "react-router-dom";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import { addRelease } from "../../actions/release";

const AddRelease = ({ addRelease, history }) => {
  const [formData, setFormData] = useState({
    artist: '',
    title: '',
    label: '',
    format: '',
    country: '',
    releaseDate: '',
    uploadDate: '',
    style: '',
    description: '',
    recordLink: '',
    artwork: ''
  });

  const {
    artist,
    title,
    label,
    format,
    country,
    releaseDate,
    uploadDate,
    style,
    description,
    recordLink,
    artwork
  } = formData;

  const onChange = e =>
    setFormData({ ...formData, [e.target.name]: e.target.value });

  return (
    <Fragment>
      <h1 className="large text-primary">Add Your Release</h1>
      <p className="lead">
        <i className="fas fa-code-branch" /> Add your release here in order to start selling them
      </p>
      <form className="form" onSubmit={e => {
        e.preventDefault();
        addRelease(formData, history)
      }}>
        <div className="form-group">Artist:
          <input
            type="text"
            placeholder="Artist"
            name="artist"
            value={artist}
            onChange={e => onChange(e)}
          />
        </div>
        <div className="form-group">Title:
          <input
            type="text"
            placeholder="Title"
            name="title"
            value={title}
            onChange={e => onChange(e)}
          />
        </div>
        <div className="form-group">Label:
          <input
            type="text"
            placeholder="Label"
            name="label"
            value={label}
            onChange={e => onChange(e)}
          />
        </div>
        <div className="form-group">Format:
          <input
            type="text"
            placeholder="Format"
            name="format"
            value={format}
            onChange={e => onChange(e)}
          />
        </div>
        <div className="form-group">Country:
          <input
            type="text"
            placeholder="Country"
            name="country"
            value={country}
            onChange={e => onChange(e)}
          />
        </div>
        <div className="form-group">Release date:
          <input
            type="text"
            placeholder="Release date"
            name="releaseDate"
            value={releaseDate}
            onChange={e => onChange(e)}
          />
        </div>
        <div className="form-group">Upload date:
          <input
            type="text"
            placeholder="Upload date"
            name="uploadDate"
            value={uploadDate}
            onChange={e => onChange(e)}
            disabled
          />
        </div>
        <div className="form-group">Style:
          <input
            type="text"
            placeholder="Style"
            name="style"
            value={style}
            onChange={e => onChange(e)}
          />
        </div>
        <div className="form-group">Description:
          <input
            type="text"
            placeholder="Description"
            name="description"
            value={description}
            onChange={e => onChange(e)}
          />
        </div>
        <div className="form-group">
          <input
            type="text"
            placeholder="The audio will be here"
            name="recordLink"
            value={recordLink}
            // onChange={e => onChange(e)}
            disabled
          />
        </div>
        <div className="form-group">Drag and Drop the artwork here
          <input
            type="text"
            placeholder="The artwork will be here"
            name="artwork"
            value={artwork}
            // onChange={e => onChange(e)}
            disabled
          />
        </div>

        <input type="submit" className="btn btn-primary my-1" />
        <a className="btn btn-light my-1" href="dashboard.html">
          Go Back
        </a>
      </form>
    </Fragment>
  );
};

AddRelease.propTypes = {
  addRelease: PropTypes.func.isRequired
};

export default connect(
  null,
  { addRelease }
)(withRouter(AddRelease));
<file_sep>/models/Record.js
const mongoose = require("mongoose");

const RecordSchema = new mongoose.Schema({
  user: {
    type: mongoose.Schema.Types.ObjectId,
    required: true,
    ref: "user"
  },
  release: {
    type: mongoose.Schema.Types.ObjectId,
    required: true,
    ref: "rlease"
  },
  artist: {
    type: String,
    required: false
  },
  title: {
    type: String,
    required: false
  },
  lenght: {
    type: String,
    required: false
  },
  uploadDate: {
    type: Date,
    default: Date.now
  },
  file: {}
});

module.exports = Record = mongoose.model("record", RecordSchema);
<file_sep>/client/src/components/releases/AllReleases.js
import React, { Fragment, useEffect } from "react";
import styled from "styled-components";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Spinner from "../../components/layout/Spinner";
import ReleaseItem from "../../components/releases/ReleaseItem";
import { getReleases } from "../../actions/release";

export const AllReleasesStyled = styled.div`
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-gap: 15px;
`;

const AllReleases = ({ getReleases, release: { releases, loading } }) => {
  useEffect(() => {
    getReleases();
  }, [getReleases]);
  return loading ? (
    <Spinner />
  ) : (
    <Fragment>
      <h1 className="large text-primary">Releases</h1>
      <AllReleasesStyled>
        {releases.map(release => (
          <ReleaseItem key={release._id} release={release} />
        ))}
      </AllReleasesStyled>
    </Fragment>
  );
};

AllReleases.propTypes = {
  getReleases: PropTypes.func.isRequired,
  release: PropTypes.object.isRequired
};

const mapStateToProps = state => ({
  release: state.release
});

export default connect(
  mapStateToProps,
  { getReleases }
)(AllReleases);
<file_sep>/client/src/components/dashboard/Dashboard.js
import React, { Fragment, useEffect } from "react";
import styled from "styled-components";
import { Link } from "react-router-dom";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Spinner from "../layout/Spinner";
import DashboardActions from "./DashboardActions";
import ReleaseListItem from "./ReleaseListItem";
import ReleaseItem from "../../components/releases/ReleaseItem";
import { getCurrentProfile, deleteAccount } from "../../actions/profile";
import { getUserReleases } from "../../actions/release";

export const AllReleasesStyled = styled.div`
  display: grid;
  grid-template-columns: 1fr;
  grid-gap: 15px;
`;

const Dashboard = ({
  getCurrentProfile,
  getUserReleases,
  deleteAccount,
  auth: { user },
  profile: { profile, loading },
  release: { releases }
}) => {
  useEffect(() => {
    getCurrentProfile();
    getUserReleases(user._id);
  }, [getCurrentProfile, getUserReleases]);

  return loading && profile === null ? (
    <Spinner />
  ) : (
    <Fragment>
      <h1 className="large text-primary">My Profile</h1>
      <p className="lead">
        <i className=" fas fa-user" /> Welcome {profile && profile.realName}
      </p>
      {profile !== null ? (
        <Fragment>
          <DashboardActions />
          <AllReleasesStyled>
            {releases.map(release => (
              <ReleaseListItem key={release._id} release={release} />
            ))}
          </AllReleasesStyled>
          <div className="my-2">
            <button className="btn btn-danger" onClick={() => deleteAccount()}>
              <i className="fas fa-user-minus" /> Delete my account
            </button>
          </div>
        </Fragment>
      ) : (
        <Fragment>
          <p>You have not setup a profile, please add some info.</p>
          <Link to="/create-profile" className="btn btn-primary my-1">
            Create profile
          </Link>
        </Fragment>
      )}
    </Fragment>
  );
};

Dashboard.propTypes = {
  getCurrentProfile: PropTypes.func.isRequired,
  getUserReleases: PropTypes.func.isRequired,
  deleteAccount: PropTypes.func.isRequired,
  auth: PropTypes.object.isRequired,
  profile: PropTypes.object.isRequired,
  release: PropTypes.object.isRequired
};

const mapStateToProps = state => ({
  auth: state.auth,
  profile: state.profile,
  release: state.release
});

export default connect(
  mapStateToProps,
  { getCurrentProfile, deleteAccount, getUserReleases }
)(Dashboard);
<file_sep>/client/src/components/dashboard/ReleaseListItem.js
import React from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";
import PropTypes from "prop-types";

const Container = styled.div`
  border-radius: 5px 5px 0 0;
  margin-top: 10px;
  width: 100%;
  padding: 2rem;
  display: grid;
  grid-template-columns: 1fr 3fr 1fr;
  align-items: center;
  color: #27117c;
  box-sizing: border-box;
  background-image: linear-gradient(
    to bottom,
    #ebe6f8,
    #ece5f6,
    #ede4f5,
    #eee4f3,
    #efe3f1
  );
`;

const Cover = styled.img`
  width: 75px;
  height: 75px;
  border-radius: 5px;
  margin-right: 1rem;
`;

const Info = styled.div`
  display: grid;
  grid-template-rows: 1fr 1fr;
  align-items: center;
`;

const MainInfo = styled.div`
  display: grid;
  grid-auto-flow: row;
  align-items: start;
`;

const SecondaryInfo = styled.div`
  display: grid;
  grid-auto-flow: row;
  align-items: start;
`;

const Title = styled.p`
  font-size: 1rem;
`;

const Artist = styled.p`
  font-weight: 600;
  font-size: 0.8rem;
  text-transform: uppercase;
`;

const Description = styled.p`
  font-size: 0.8rem;
  font-weight: 400;
`;

const Action = styled.button`
  width: 75px;
  height: 75px;
  border-radius: 5px;
`;

const ReleaseListItem = ({
  release: {
    _id,
    artist,
    title,
    label,
    format,
    country,
    style,
    description,
    records
  }
}) => {
  return (
    <Link to={`/releases/${_id}`}>
      <Container>
        <Cover />
        <Info>
          <MainInfo>
            <Title>{title}</Title>
            <Artist>{artist}</Artist>
          </MainInfo>
          <SecondaryInfo>
            <Description>{description}</Description>
            <Description>{style}</Description>
            <Description>{records.length} records</Description>
          </SecondaryInfo>
        </Info>
        <Action />
      </Container>
    </Link>
  );
};

ReleaseListItem.propTypes = {
  release: PropTypes.object.isRequired
};

export default ReleaseListItem;
<file_sep>/routes/api/users.js
const express = require("express");
const router = express.Router();
const gravatar = require("gravatar");
const bcrypt = require("bcryptjs");
const jwt = require("jsonwebtoken");
const TokenGenerator = require("uuid-token-generator");
const nodemailer = require("nodemailer");
const config = require("config");
const { check, validationResult } = require("express-validator/check");

const User = require("../../models/User");
const Confirmation = require("../../models/Confirmation");

// @route   POST api/users
// @desc    Register user
// @access  Public
router.post(
  "/",
  [
    check("email", "Please include a valid email").isEmail(),
    check(
      "password",
      "Please enter a password with 8 or more characters"
    ).isLength({ min: 8 })
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    const { email, password } = req.body;

    try {
      // See if a user exists
      let user = await User.findOne({ email });

      if (user) {
        return res
          .status(400)
          .json({ errors: [{ msg: "User already exists" }] });
      }
      // Get users gravatar
      const avatar = gravatar.url(email, {
        s: "200",
        r: "ps",
        d: "mm"
      });

      user = new User({
        email,
        avatar,
        password
      });
      // Encrypt password
      const salt = await bcrypt.genSalt(10);

      user.password = await bcrypt.hash(password, salt);

      await user.save();

      const tokgen = new TokenGenerator(256, TokenGenerator.BASE62);

      const confirmationToken = await tokgen.generate();

      // Create a confirmation token for this user
      confirmation = new Confirmation({
        user: user.id,
        token: confirmationToken
      });

      // Save the confirmation token
      await confirmation.save(err => {
        if (err) {
          return res.status(500).json({ errors: [{ msg: err.message }] });
        }

        // Send the email
        const transporter = nodemailer.createTransport({
          service: "gmail",
          auth: {
            user: "<EMAIL>",
            pass: "<PASSWORD>!"
          }
        });
        const mailOptions = {
          from: "<EMAIL>",
          to: user.email,
          subject: "Account Confirmation Token",
          text:
            `Hello ${user.email}` +
            "Please confirm account creation by clicking the link: \nhttp://" +
            req.headers.host +
            "/confirmation/" +
            confirmation.token
        };
        transporter.sendMail(mailOptions, err => {
          if (err) {
            return res.status(500).json({ errors: [{ msg: err.message }] });
          }
          res.status(200).json({
            alert: [
              {
                msg: "A confirmation email has been sent to " + user.email
              }
            ]
          });
        });
      });
    } catch (err) {
      console.log(err.message);
      res.status(500).send("Server error");
    }
  }
);

// @route   GET api/users/confirmation/:token
// @desc    Confirm user email
// @access  Public
router.get("/:token", async (req, res) => {
  try {
  } catch (error) {
    console.log(err.message);
    res.status(500).send("Server error");
  }

  let token = await Confirmation.findOne({ token: req.params.token });

  if (!token)
    return res.status(400).json({
      errors: [
        {
          msg:
            "We were unable to find a valid token. Your token may have expired. Want to send the new one?"
        }
      ]
    });

  // If a token found, find a matching user
  const user = await User.findById({ _id: token.user }).select("-password");

  if (!user)
    return res.status(400).json({
      errors: [
        {
          msg: "We were unable to find a user for this token."
        }
      ]
    });
  if (user.isConfirmed)
    return res.status(400).json({
      // user,
      errors: [
        {
          msg: "Looks your account has already been confirmed. Try to login."
        }
      ]
    });

  // Confirm and save the user
  user.isConfirmed = true;
  user.save(function(err) {
    if (err) {
      return res.status(500).json({
        errors: [
          {
            msg: err.message
          }
        ]
      });
    }
    console.log(user);
    res.status(200).json(user);
  });
});

// @route   POST api/users/confirmation/resend/
// @desc    Resend confirmation email
// @access  Public
router.post(
  "/confirmation/resend",
  [check("email", "Please include a valid email").isEmail()],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    const { email } = req.body;

    try {
      // See if a user exists
      const user = await User.findOne({ email }).select("-password");

      if (!user) {
        return res
          .status(400)
          .json({ errors: [{ msg: "Invalid credentials" }] });
      }

      if (user.isConfirmed)
        return res.status(400).json({
          user: {
            isConfirmed: user.isConfirmed
          },
          errors: [
            {
              msg: "This account has already been confirmed. Please log in."
            }
          ]
        });

      // Create a confirmation token, save it, and send email
      const tokgen = new TokenGenerator(256, TokenGenerator.BASE62);

      const confirmationToken = await tokgen.generate();

      confirmation = new Confirmation({
        user: user.id,
        token: confirmationToken
      });

      // Save the token
      await confirmation.save(function(err) {
        if (err) {
          return res.status(500).json({ errors: { msg: err.message } });
        }

        // Send the email
        const transporter = nodemailer.createTransport({
          service: "gmail",
          auth: {
            user: "<EMAIL>",
            pass: "<PASSWORD>!"
          }
        });
        const mailOptions = {
          from: "<EMAIL>",
          to: user.email,
          subject: "Account Confirmation Token",
          text:
            `Hello ${user.email}` +
            "Please confirm account creation by clicking the link: \nhttp://" +
            req.headers.host +
            "/confirmation/" +
            confirmation.token
        };
        transporter.sendMail(mailOptions, err => {
          if (err) {
            return res.status(500).json({ errors: [{ msg: err.message }] });
          }
          res.status(200).json({
            user: {
              isConfirmed: user.isConfirmed
            },
            alert: [
              {
                msg: "A confirmation email has been sent to " + user.email
              }
            ]
          });
        });
      });
    } catch (err) {
      console.log(err.message);
      res.status(500).send("Server error");
    }
  }
);

module.exports = router;
<file_sep>/client/src/components/profiles/ProfileItem.js
import React from "react";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import Moment from 'react-moment'

const ProfileItem = ({
  profile: {
    user: { _id, avatar },
    realName,
    alias,
    bornAt,
    basedAt,
    dob,
    about
  }
}) => {
  return <div className="profile bg-light">
    <img src={avatar} alt="" className="round-img"/>
    <div>
      <h2>{alias}</h2>
      <p>{realName} from {basedAt}</p>
      <p>Born at {bornAt} in <Moment format='DD/MM/YYYY'>{dob}</Moment></p>
      <p>{about}</p>
      <Link to={`/profile/${_id}`} className="btn btn-primary">
        View Artist
      </Link>
    </div>
  </div>;
};

ProfileItem.propTypes = {
  profile: PropTypes.object.isRequired
};

export default ProfileItem;
<file_sep>/client/src/components/release/Release.js
import React, { Fragment, useEffect } from "react";
import styled from "styled-components";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Spinner from "../../components/layout/Spinner";
import Record from "../../components/release/Record";
import ReleaseItem from "../releases/ReleaseItem";
import { getReleaseById } from "../../actions/release";

const Release = ({ getReleaseById, release: { release, loading }, match }) => {
  useEffect(() => {
    getReleaseById(match.params.id);
  }, [getReleaseById, match.params.id]);
  return loading || release == null ? (
    <Spinner />
  ) : (
    <Fragment>
      <ReleaseItem key={release._id} release={release} />
      <div>
        {release.records.map(record => (
          <Record key={record._id} record={record} />
        ))}
      </div>
    </Fragment>
  );
};

Release.propTypes = {
  getReleaseById: PropTypes.func.isRequired,
  release: PropTypes.object.isRequired
};

const mapStateToProps = state => ({
  release: state.release
});

export default connect(
  mapStateToProps,
  { getReleaseById }
)(Release);
<file_sep>/client/src/components/profile/ProfileAbout.js
import React from "react";
import PropTypes from "prop-types";

const ProfileAbout = ({ profile: { alias, about } }) => {
  return (
    alias &&
    about && (
      <div className="profile-about bg-light p-2">
        <h2 className="text-primary">{alias}'s Bio</h2>
        <p>{about}</p>
      </div>
    )
  );
};

ProfileAbout.propTypes = {};

export default ProfileAbout;
<file_sep>/client/src/components/profile/ProfileTop.js
import React from "react";
import PropTypes from "prop-types";

const ProfileTop = ({
  profile: {
    user: { _id, avatar },
    realName,
    alias,
    dob,
    bornAt,
    basedAt,
    about,
    links
  }
}) => {
  return (
    <div className="profile-top bg-primary p-2">
      <img className="round-img my-1" src={avatar} alt="" />
      <h1 className="large">{realName}</h1>
      <p className="lead">
        aka: <br /> {alias}
      </p>
      <p>
        Based at {basedAt} {bornAt && <span><br/>Originally from {bornAt}</span>}
      </p>
      <div className="icons my-1">
        {links && links.website && (
          <a href={links.website} target="_blank" rel="noopener noreferrer">
            <i className="fas fa-globe fa-2x" />
          </a>
        )}
        {links && links.youtube && (
          <a href={links.youtube} target="_blank" rel="noopener noreferrer">
            <i className="fab fa-youtube fa-2x" />
          </a>
        )}
        {links && links.soundcloud && (
          <a href={links.soundcloud} target="_blank" rel="noopener noreferrer">
            <i className="fab fa-soundcloud fa-2x" />
          </a>
        )}
        {links && links.facebook && (
          <a href="#" target="_blank" rel="noopener noreferrer">
            <i className="fab fa-facebook fa-2x" />
          </a>
        )}
        {links && links.instagram && (
          <a href="#" target="_blank" rel="noopener noreferrer">
            <i className="fab fa-instagram fa-2x" />
          </a>
        )}
      </div>
    </div>
  );
};

ProfileTop.propTypes = {
  profile: PropTypes.object.isRequired
};

export default ProfileTop;
<file_sep>/client/src/components/profile/ProfileReleases.js
import React, { Fragment } from "react";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import Moment from "react-moment";

const ProfileReleases = ({
  release: {
    _id,
    artist,
    title,
    label,
    format,
    country,
    releaseDate,
    uploadDate,
    style,
    description
  }
}) => (
  <Fragment>
    <div>
      <h3>{artist}</h3>
      <p>
        <Link to={`/releases/${_id}`}>
          <strong>{title}</strong>
        </Link>
      </p>
      <p>
        Relased: <Moment format="DD/MM/YYYY">{releaseDate}</Moment>
      </p>
      <p>
        Uploaded: <Moment format="DD/MM/YYYY">{uploadDate}</Moment>
      </p>
      <h4>{label}</h4>
      <p>{format}</p>
      <p>{format}</p>
      <p>{format}</p>
      <p>
        <strong>{format}</strong>
      </p>
    </div>
  </Fragment>
);

ProfileReleases.propTypes = {
  release: PropTypes.object.isRequired
};

export default ProfileReleases;
