├── .gitignore
├── client
├── src
│ ├── components
│ │ ├── Forms
│ │ │ ├── LoginForm.js
│ │ │ ├── RegisterForm.js
│ │ │ ├── SearchBar.js
│ │ │ ├── ListingForm.js
│ │ │ └── AdvancedSearchForm.js
│ │ ├── Layout
│ │ │ ├── Spinner.gif
│ │ │ ├── Footer.js
│ │ │ ├── Spinner.js
│ │ │ ├── ImageGallery.js
│ │ │ ├── PageNotFound.js
│ │ │ ├── Notification.js
│ │ │ ├── Countdown.js
│ │ │ ├── PaginationButtons.js
│ │ │ └── Navbar.js
│ │ ├── Listing
│ │ │ ├── ListingsCardRow.js
│ │ │ ├── ListingCard.js
│ │ │ ├── EditListingPage.js
│ │ │ ├── ListingPage.js
│ │ │ └── CreateListingPage.js
│ │ ├── Routing
│ │ │ ├── PrivateRoute.js
│ │ │ └── Routes.js
│ │ ├── Listings
│ │ │ ├── ListItem.js
│ │ │ └── ListingsPage.js
│ │ ├── Bids
│ │ │ ├── BidItem.js
│ │ │ └── ViewBidsModal.js
│ │ ├── Dashboard
│ │ │ ├── Dashboard.js
│ │ │ ├── YourReviewsPage.js
│ │ │ ├── YourListingsPage.js
│ │ │ └── BiddingHistoryPage.js
│ │ ├── Profile
│ │ │ ├── ViewReviewsModal.js
│ │ │ ├── ProfilePage.js
│ │ │ └── EditProfilePage.js
│ │ ├── Homepage
│ │ │ └── HomePage.js
│ │ ├── Auth
│ │ │ ├── LoginPage.js
│ │ │ └── RegisterPage.js
│ │ ├── Reviews
│ │ │ ├── ReviewItem.js
│ │ │ ├── CreateReviewModal.js
│ │ │ └── EditReviewModal.js
│ │ └── Report
│ │ │ └── CreateReportModal.js
│ ├── styles
│ │ ├── components
│ │ │ ├── _create-listing-page.scss
│ │ │ ├── _footer.scss
│ │ │ ├── _modal.scss
│ │ │ ├── _forms.scss
│ │ │ ├── _profile-page.scss
│ │ │ ├── _buttons.scss
│ │ │ ├── _listings-page.scss
│ │ │ ├── _navbar.scss
│ │ │ └── _listing-page.scss
│ │ ├── base
│ │ │ ├── _reset.scss
│ │ │ └── _typography.scss
│ │ └── utilities
│ │ │ └── _variables.scss
│ ├── index.js
│ ├── actions
│ │ ├── imageActions.js
│ │ ├── notification.js
│ │ ├── report.js
│ │ ├── user.js
│ │ ├── types.js
│ │ ├── auth.js
│ │ ├── review.js
│ │ └── listing.js
│ ├── setupTests.js
│ ├── utils
│ │ └── setAuthToken.js
│ ├── store.js
│ ├── reducers
│ │ ├── index.js
│ │ ├── notification.js
│ │ ├── user.js
│ │ ├── review.js
│ │ ├── listing.js
│ │ ├── listings.js
│ │ ├── auth.js
│ │ └── reviews.js
│ ├── App.scss
│ └── App.js
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── manifest.json
│ └── index.html
└── package.json
├── utils
├── catchAsync.js
└── appError.js
├── routes
└── api
│ ├── reportRouter.js
│ ├── authRouter.js
│ ├── userRouter.js
│ ├── reviewRouter.js
│ └── listingRouter.js
├── models
├── reportModel.js
├── userModel.js
├── listingModel.js
└── reviewModel.js
├── config
└── db.js
├── controllers
├── reportController.js
├── handlerFactory.js
├── authController.js
├── reviewController.js
├── errorController.js
├── userController.js
└── listingController.js
├── README.md
├── tests
└── backend
│ ├── report.test.js
│ ├── review.test.js
│ ├── auth.test.js
│ ├── user.test.js
│ └── listing.test.js
├── server.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | env
--------------------------------------------------------------------------------
/client/src/components/Forms/LoginForm.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/components/Forms/RegisterForm.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/styles/components/_create-listing-page.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/client/src/styles/base/_reset.scss:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | }
6 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JarrodMalkovic/auction-website-monolith/HEAD/client/public/favicon.ico
--------------------------------------------------------------------------------
/utils/catchAsync.js:
--------------------------------------------------------------------------------
1 | module.exports = fn => {
2 | return (req, res, next) => {
3 | fn(req, res, next).catch(next);
4 | };
5 | };
6 |
--------------------------------------------------------------------------------
/client/src/components/Layout/Spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JarrodMalkovic/auction-website-monolith/HEAD/client/src/components/Layout/Spinner.gif
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(, document.getElementById('root'));
6 |
--------------------------------------------------------------------------------
/client/src/actions/imageActions.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export const addImage = imageData => async dispatch => {
4 | const res = await axios.post('/api/listings/upload/image', imageData);
5 | console.log(res);
6 | };
7 |
--------------------------------------------------------------------------------
/client/src/actions/notification.js:
--------------------------------------------------------------------------------
1 | import { ADD_NOTIFICATION } from './types';
2 |
3 | export function addNotification(message, level) {
4 | return {
5 | type: ADD_NOTIFICATION,
6 | message,
7 | level
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/client/src/styles/utilities/_variables.scss:
--------------------------------------------------------------------------------
1 | // Sizes
2 | $xl-size: 5rem;
3 | $l-size: 3rem;
4 | $m-size: 2rem;
5 | $s-size: 1.2rem;
6 |
7 | // Colours
8 | $gray: #46474a;
9 | $dark-gray: #38393b;
10 | $light-gray: #68696b;
11 | $black: #000;
12 | $white: #fff;
13 |
--------------------------------------------------------------------------------
/client/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom/extend-expect';
6 |
--------------------------------------------------------------------------------
/client/src/utils/setAuthToken.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export const setAuthToken = token => {
4 | if (token) {
5 | console.log('setting');
6 | axios.defaults.headers.common['x-auth-token'] = token;
7 | } else {
8 | delete axios.defaults.headers.common['x-auth-token'];
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/utils/appError.js:
--------------------------------------------------------------------------------
1 | class AppError extends Error {
2 | constructor(message, statusCode) {
3 | super(message);
4 | this.statusCode = statusCode;
5 | this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
6 | this.isOperational = true;
7 | Error.captureStackTrace(this, this.constructor);
8 | }
9 | }
10 |
11 | module.exports = AppError;
12 |
--------------------------------------------------------------------------------
/client/src/styles/components/_footer.scss:
--------------------------------------------------------------------------------
1 | footer {
2 | background-color: $light-gray;
3 | color: $white;
4 | padding: 4rem;
5 | position: absolute;
6 | left: 0;
7 | bottom: 0;
8 | width: 100%;
9 | overflow: hidden;
10 | margin-top: $m-size;
11 | text-align: center;
12 | }
13 |
14 | html {
15 | position: relative;
16 | min-height: 100%;
17 | }
18 |
19 | body {
20 | margin: 0 0 11rem;
21 | }
22 |
--------------------------------------------------------------------------------
/client/src/components/Layout/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Footer = props => {
5 | return (
6 |
12 | );
13 | };
14 |
15 | Footer.propTypes = {};
16 |
17 | export default Footer;
18 |
--------------------------------------------------------------------------------
/client/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import { composeWithDevTools } from 'redux-devtools-extension';
3 | import thunk from 'redux-thunk';
4 | import rootReducer from './reducers/index';
5 |
6 | const initialState = {};
7 |
8 | const middleware = [thunk];
9 |
10 | const store = createStore(
11 | rootReducer,
12 | initialState,
13 | composeWithDevTools(applyMiddleware(...middleware))
14 | );
15 |
16 | export default store;
17 |
--------------------------------------------------------------------------------
/client/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import auth from './auth';
3 | import user from './user';
4 | import reviews from './reviews';
5 | import review from './review';
6 | import listings from './listings';
7 | import listing from './listing';
8 | import notification from './notification';
9 |
10 | export default combineReducers({
11 | auth,
12 | listings,
13 | listing,
14 | user,
15 | reviews,
16 | review,
17 | notification
18 | });
19 |
--------------------------------------------------------------------------------
/client/src/reducers/notification.js:
--------------------------------------------------------------------------------
1 | import { ADD_NOTIFICATION } from '../actions/types';
2 |
3 | export default function notification(state = {}, action) {
4 | switch (action.type) {
5 | case ADD_NOTIFICATION:
6 | return Object.assign({}, state, {
7 | message: action.message,
8 | level: action.level
9 | });
10 |
11 | default:
12 | console.debug('notification reducer :: hit default', action.type);
13 | return state;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/client/src/styles/components/_modal.scss:
--------------------------------------------------------------------------------
1 | .modal {
2 | margin: 75px auto;
3 | padding: 30px 30px;
4 | max-height: 80vh;
5 | overflow-y: auto;
6 | border-radius: 15px;
7 | background: white;
8 | max-width: 102.4rem;
9 | word-wrap: break-word;
10 |
11 | box-shadow: 0 0 30px 0 rgba(0, 0, 0, 0.25);
12 | }
13 |
14 | .overlay {
15 | position: absolute;
16 | top: 0;
17 | left: 0;
18 | right: 0;
19 | bottom: 0;
20 | background-color: rgba(41, 41, 41, 0.8);
21 | }
22 |
--------------------------------------------------------------------------------
/client/src/components/Layout/Spinner.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import SpinnerGif from './Spinner.gif';
3 | import { Helmet } from 'react-helmet';
4 |
5 | const Spinner = () => {
6 | return (
7 |
8 |
9 | Loading.. | Auction
10 |
11 |
12 |

13 |
14 |
15 | );
16 | };
17 |
18 | export default Spinner;
19 |
--------------------------------------------------------------------------------
/routes/api/reportRouter.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 |
4 | const reportController = require('../../controllers/reportController');
5 | const authController = require('../../controllers/authController');
6 |
7 | // @route POST api/report/:id
8 | // @desc Create a report for a user, review or listing
9 | // @access Private
10 | router.post('/:id', authController.authenticate, reportController.createReport);
11 |
12 | router.get('/', reportController.getReports);
13 | module.exports = router;
14 |
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/client/src/components/Listing/ListingsCardRow.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ListingCard from './ListingCard';
4 |
5 | const ListingsCardRow = ({ listings }) => {
6 | console.log(listings);
7 | return listings === null ? (
8 |
Loading..
9 | ) : (
10 |
11 | {listings.map(listing => (
12 |
17 | ))}
18 |
19 | );
20 | };
21 |
22 | ListingsCardRow.propTypes = {};
23 |
24 | export default ListingsCardRow;
25 |
--------------------------------------------------------------------------------
/client/src/components/Layout/ImageGallery.js:
--------------------------------------------------------------------------------
1 | import ImageGallery from 'react-image-gallery';
2 |
3 | const images = [
4 | {
5 | original: 'https://picsum.photos/id/1018/1000/600/',
6 | thumbnail: 'https://picsum.photos/id/1018/250/150/'
7 | },
8 | {
9 | original: 'https://picsum.photos/id/1015/1000/600/',
10 | thumbnail: 'https://picsum.photos/id/1015/250/150/'
11 | },
12 | {
13 | original: 'https://picsum.photos/id/1019/1000/600/',
14 | thumbnail: 'https://picsum.photos/id/1019/250/150/'
15 | }
16 | ];
17 |
18 | class MyGallery extends React.Component {
19 | render() {
20 | return ;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/client/src/styles/components/_forms.scss:
--------------------------------------------------------------------------------
1 | .form-group {
2 | margin-top: $m-size;
3 | margin-bottom: $m-size;
4 | }
5 |
6 | .form .form-group > input[type='text'],
7 | input[type='email'],
8 | input[type='password'],
9 | input[type='textarea'],
10 | input[type='number'],
11 | textarea {
12 | width: 100%;
13 | padding: $s-size $s-size;
14 | margin: 8px 0;
15 | box-sizing: border-box;
16 | border-radius: 4px;
17 | border: 1px solid #ccc;
18 | }
19 |
20 | textarea {
21 | height: 15rem;
22 | color: #555;
23 | font-family: 'Lato';
24 | }
25 | .form {
26 | padding-top: $m-size;
27 | }
28 |
29 | .recaptcha-container,
30 | select {
31 | margin-top: $s-size;
32 | }
33 |
--------------------------------------------------------------------------------
/models/reportModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const ReportSchema = new mongoose.Schema({
4 | reportedBy: {
5 | type: mongoose.Schema.ObjectId,
6 | ref: 'user',
7 | required: true
8 | },
9 | reported: {
10 | type: mongoose.Schema.ObjectId,
11 | required: true,
12 | refPath: 'reportedRef'
13 | },
14 | reason: {
15 | type: String,
16 | required: true
17 | },
18 | reportedRef: {
19 | type: String,
20 | required: true,
21 | enum: ['user', 'Listing', 'review']
22 | },
23 | date: {
24 | type: Date,
25 | default: Date.now()
26 | }
27 | });
28 |
29 | module.exports = Report = mongoose.model('report', ReportSchema);
30 |
--------------------------------------------------------------------------------
/client/src/actions/report.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { addNotification } from './notification';
3 |
4 | export const createReport = (id, reason, reportedRef) => async dispatch => {
5 | try {
6 | const config = {
7 | headers: {
8 | 'Content-Type': 'application/json'
9 | }
10 | };
11 |
12 | const body = {
13 | reason,
14 | reportedRef
15 | };
16 |
17 | await axios.post(`/api/report/${id}`, body, config);
18 | dispatch(addNotification('Report Created Successfully!', 'success'));
19 | } catch (err) {
20 | console.log(`Error: ${err.response.data.message}`);
21 | dispatch(addNotification(err.response.data.message, 'error'));
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/client/src/styles/components/_profile-page.scss:
--------------------------------------------------------------------------------
1 | .profile-top {
2 | background-color: #68696b;
3 | color: #ffffff;
4 | height: 55rem;
5 | text-align: center;
6 | margin-bottom: 15px;
7 | }
8 |
9 | .profile-top > * {
10 | margin: 15px;
11 | color: white;
12 | }
13 |
14 | .round-image {
15 | border-radius: 50%;
16 | }
17 |
18 | .review-item {
19 | padding-top: 20px;
20 | }
21 |
22 | .see-more {
23 | float: right;
24 | }
25 |
26 | .listing-card-title {
27 | h3 {
28 | float: left;
29 | }
30 | }
31 |
32 | .link h3,
33 | .link h4 {
34 | color: #293241;
35 | font-size: 100%;
36 | text-decoration: none;
37 | }
38 |
39 | .small {
40 | padding: 5px;
41 | }
42 |
43 | .right {
44 | float: right;
45 | }
46 |
--------------------------------------------------------------------------------
/client/src/components/Layout/PageNotFound.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Helmet } from 'react-helmet';
4 |
5 | const PageNotFound = props => {
6 | return (
7 |
8 |
9 | Page not found! | Auction
10 |
11 |
12 |
404: Page Not Found
13 |
14 | Sorry, but the page you are looking for was not found. Please make
15 | sure you have typed the current URL correctly.
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | PageNotFound.propTypes = {};
23 |
24 | export default PageNotFound;
25 |
--------------------------------------------------------------------------------
/config/db.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const dotenv = require('dotenv').config({ path: './config.env' });
3 |
4 | const connectDB = async () => {
5 | try {
6 | if (process.env.NODE_ENV === 'test') {
7 | var dbName = 'test';
8 | } else {
9 | var dbName = 'Production';
10 | }
11 |
12 | await mongoose.connect(process.env.MONGO_URI, {
13 | useNewUrlParser: true,
14 | useUnifiedTopology: true,
15 | useCreateIndex: true,
16 | useFindAndModify: false,
17 | dbName
18 | });
19 |
20 | console.log(`MongoDB Connected to ${dbName} DB...`);
21 | } catch (err) {
22 | console.log(err.message);
23 | process.exit(1);
24 | }
25 | };
26 |
27 | module.exports = connectDB;
28 |
--------------------------------------------------------------------------------
/client/src/components/Routing/PrivateRoute.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, Redirect } from 'react-router-dom';
3 | import PropTypes from 'prop-types';
4 | import { connect } from 'react-redux';
5 |
6 | const PrivateRoute = ({
7 | component: Component,
8 | auth: { isAuthenticated, loading },
9 | ...rest
10 | }) => (
11 |
14 | !isAuthenticated && !loading ? (
15 |
16 | ) : (
17 |
18 | )
19 | }
20 | />
21 | );
22 |
23 | PrivateRoute.propTypes = {
24 | auth: PropTypes.object.isRequired
25 | };
26 |
27 | const mapStateToProps = state => ({
28 | auth: state.auth
29 | });
30 |
31 | export default connect(mapStateToProps)(PrivateRoute);
32 |
--------------------------------------------------------------------------------
/client/src/reducers/user.js:
--------------------------------------------------------------------------------
1 | import { GET_USER, CLEAR_USER, USER_ERROR } from '../actions/types';
2 |
3 | const initialState = {
4 | data: null,
5 | loading: true,
6 | errors: null
7 | };
8 |
9 | export default function(state = initialState, action) {
10 | const { type, payload } = action;
11 | switch (type) {
12 | case GET_USER: {
13 | return { ...state, data: payload, loading: false };
14 | }
15 | case CLEAR_USER: {
16 | return {
17 | ...state,
18 | data: null,
19 | errors: null,
20 | loading: true
21 | };
22 | }
23 | case USER_ERROR:
24 | return {
25 | ...state,
26 | data: [],
27 | loading: false,
28 | errors: 'ERROR'
29 | };
30 | default:
31 | return state;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/client/src/App.scss:
--------------------------------------------------------------------------------
1 | // Utilities
2 | @import './styles/utilities/_variables.scss';
3 |
4 | // Base Rules
5 | @import './styles/base/_reset.scss';
6 | @import './styles/base/_typography.scss';
7 |
8 | // Components
9 | @import './styles/components/_listings-page.scss';
10 | @import './styles/components/_navbar.scss';
11 | @import './styles/components/_modal.scss';
12 | @import './styles/components/_forms.scss';
13 | @import './styles/components/_footer.scss';
14 | @import './styles/components/_profile-page.scss';
15 | @import './styles/components/_create-listing-page.scss';
16 | @import './styles/components/_listing-page.scss';
17 | @import './styles/components/_buttons.scss';
18 |
19 | // Third Party Components
20 | @import '~react-image-gallery/styles/scss/image-gallery.scss';
21 | @import '~react-notifications/lib/notifications.css';
22 |
--------------------------------------------------------------------------------
/client/src/reducers/review.js:
--------------------------------------------------------------------------------
1 | import { GET_REVIEW, REVIEW_ERROR, CLEAR_REVIEW } from '../actions/types';
2 |
3 | const initialState = {
4 | data: null,
5 | loading: true,
6 | errors: null
7 | };
8 |
9 | export default function(state = initialState, action) {
10 | const { type, payload } = action;
11 | switch (type) {
12 | case GET_REVIEW:
13 | return {
14 | ...state,
15 | data: payload,
16 | loading: false
17 | };
18 | case REVIEW_ERROR:
19 | return {
20 | ...state,
21 | data: [],
22 | loading: false,
23 | errors: 'ERROR'
24 | };
25 | case CLEAR_REVIEW:
26 | return {
27 | ...state,
28 | data: null,
29 | loading: true,
30 | errors: null
31 | };
32 | default:
33 | return state;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/routes/api/authRouter.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 |
4 | const authController = require('../../controllers/authController');
5 | const userController = require('../../controllers/userController');
6 |
7 | // @route GET api/auth
8 | // @desc Get user by token
9 | // @access Private
10 | router.get('/', authController.authenticate, userController.getMyProfile);
11 |
12 | // @route POST api/auth
13 | // @desc Authenticate user & get token
14 | // @access Public
15 | router.post('/', authController.loginUser);
16 |
17 | // @route POST api/auth/update-password
18 | // @desc Update user password
19 | // @access Private
20 | router.patch(
21 | '/update-password',
22 | authController.authenticate,
23 | authController.updatePassword
24 | );
25 |
26 | module.exports = router;
27 |
--------------------------------------------------------------------------------
/models/userModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const UserSchema = new mongoose.Schema({
4 | name: {
5 | type: String,
6 | required: true
7 | },
8 | email: {
9 | type: String,
10 | required: true,
11 | unique: true
12 | },
13 | password: {
14 | type: String,
15 | required: true
16 | },
17 | avatar: {
18 | type: String
19 | },
20 | ratingsAverage: {
21 | type: Number,
22 | default: 4.5,
23 | min: [1, 'Rating must be above 1.0'],
24 | max: [5, 'Rating must be below 5.0'],
25 | set: val => Math.round(val * 10) / 10
26 | },
27 | date: {
28 | type: Date,
29 | default: Date.now
30 | },
31 | location: {
32 | type: String
33 | },
34 | bio: {
35 | type: String
36 | }
37 | });
38 |
39 | module.exports = User = mongoose.model('user', UserSchema);
40 |
--------------------------------------------------------------------------------
/client/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
3 | import Routes from './components/Routing/Routes';
4 | import Navbar from './components/Layout/Navbar';
5 | import Footer from './components/Layout/Footer';
6 | import Notification from './components/Layout/Notification';
7 | import './App.scss';
8 | import { Provider } from 'react-redux';
9 | import store from './store';
10 |
11 | const App = () => {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | );
26 | };
27 |
28 | export default App;
29 |
--------------------------------------------------------------------------------
/client/src/reducers/listing.js:
--------------------------------------------------------------------------------
1 | import {
2 | GET_LISTING,
3 | CLEAR_LISTING,
4 | DELETE_BID,
5 | LISTING_ERROR
6 | } from '../actions/types';
7 |
8 | const initialState = {
9 | data: null,
10 | loading: true,
11 | errors: null
12 | };
13 |
14 | export default function(state = initialState, action) {
15 | const { type, payload } = action;
16 | switch (type) {
17 | case DELETE_BID:
18 | case GET_LISTING: {
19 | return { ...state, data: payload, loading: false };
20 | }
21 | case CLEAR_LISTING:
22 | return {
23 | ...state,
24 | data: null,
25 | loading: true,
26 | errors: null
27 | };
28 | case LISTING_ERROR:
29 | return {
30 | ...state,
31 | data: [],
32 | loading: false,
33 | errors: 'ERROR'
34 | };
35 | default:
36 | return state;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/controllers/reportController.js:
--------------------------------------------------------------------------------
1 | const Report = require('../models/reportModel');
2 |
3 | const catchAsync = require('./../utils/catchAsync');
4 | const AppError = require('./../utils/appError');
5 |
6 | exports.createReport = catchAsync(async (req, res, next) => {
7 | const { reason, reportedRef } = req.body;
8 |
9 | if (!(reason && reportedRef)) {
10 | return next(new AppError('Missing required fields', 400));
11 | }
12 |
13 | reportedBy = req.user.id;
14 | reported = req.params.id;
15 | report = new Report({
16 | reportedBy,
17 | reported,
18 | reason,
19 | reportedRef
20 | });
21 |
22 | await report.save();
23 |
24 | res.status(200).send(report);
25 | });
26 |
27 | exports.getReports = catchAsync(async (req, res, next) => {
28 | const reports = await Report.find().populate('reported reportedBy');
29 | res.status(200).json({ reports });
30 | });
31 |
--------------------------------------------------------------------------------
/client/src/components/Listing/ListingCard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Link } from 'react-router-dom';
4 |
5 | const ListingCard = ({ listing }) => {
6 | return (
7 |
8 |

16 |
17 |
{listing.title}
18 |
19 |
20 | {(listing.currentPrice / 100).toLocaleString('en-US', {
21 | style: 'currency',
22 | currency: 'AUD'
23 | })}
24 |
25 |
26 | );
27 | };
28 |
29 | ListingCard.propTypes = {};
30 |
31 | export default ListingCard;
32 |
--------------------------------------------------------------------------------
/client/src/styles/base/_typography.scss:
--------------------------------------------------------------------------------
1 | html {
2 | color: #555;
3 | font-family: 'Lato', 'Arial', sans-serif;
4 | font-size: 62.5%;
5 | font-weight: 300;
6 | text-rendering: optimizeLegibility;
7 | }
8 |
9 | .medium-heading {
10 | color: $dark-gray;
11 | font-size: 2rem;
12 | text-decoration: none;
13 | }
14 |
15 | .small-text-white {
16 | color: $white;
17 | font-size: 1.7rem;
18 | }
19 | .small-text {
20 | color: $light-gray;
21 | font-size: 1.7rem;
22 | }
23 |
24 | .small-heading {
25 | color: $dark-gray;
26 | font-size: 1.6rem;
27 | }
28 |
29 | .page-heading {
30 | border-bottom: 0.1rem solid lightgray;
31 | padding-top: 0.6rem;
32 | margin-top: 0.4rem;
33 | padding-bottom: 0.8rem;
34 | margin-bottom: 0.6rem;
35 | }
36 |
37 | .large-heading {
38 | color: $dark-gray;
39 | font-size: 3rem;
40 | }
41 |
42 | .link {
43 | text-decoration: none;
44 | font-size: 1.6rem;
45 | }
46 |
47 | .link:hover {
48 | color: $light-gray;
49 | text-decoration: underline;
50 | }
51 |
--------------------------------------------------------------------------------
/client/src/reducers/listings.js:
--------------------------------------------------------------------------------
1 | import {
2 | GET_LISTINGS,
3 | CLEAR_LISTINGS,
4 | GET_USERS_ACTIVE_LISTINGS,
5 | GET_USERS_INACTIVE_LISTINGS,
6 | GET_WON_LISTINGS,
7 | DELETE_LISTING
8 | } from '../actions/types';
9 |
10 | const initialState = {
11 | data: null,
12 | numListings: null,
13 | loading: true
14 | };
15 |
16 | export default function(state = initialState, action) {
17 | const { type, payload } = action;
18 | switch (type) {
19 | case GET_WON_LISTINGS:
20 | case GET_USERS_INACTIVE_LISTINGS:
21 | case GET_USERS_ACTIVE_LISTINGS:
22 | case GET_LISTINGS:
23 | return {
24 | ...state,
25 | data: payload.data.listings,
26 | numListings: payload.numListings,
27 | loading: false
28 | };
29 |
30 | case CLEAR_LISTINGS:
31 | return {
32 | ...state,
33 | data: null,
34 | loading: true
35 | };
36 | case DELETE_LISTING:
37 | return {
38 | ...state,
39 | data: state.data.filter(listing => listing._id !== payload._id)
40 | };
41 | default:
42 | return state;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Welcome to the Auction
25 |
26 |
27 |
28 |
29 |
30 |