├── .gitignore ├── frontend ├── public │ ├── manifest.json │ └── index.html ├── src │ ├── components │ │ ├── SearchBar │ │ │ ├── SearchBar.css │ │ │ └── index.js │ │ ├── Landing │ │ │ ├── index.js │ │ │ └── Landing.css │ │ ├── context │ │ │ ├── SearchContext.js │ │ │ ├── modal.css │ │ │ └── Modal.js │ │ ├── SearchResults │ │ │ ├── SearchResults.css │ │ │ └── index.js │ │ ├── LoginFormModal │ │ │ ├── DemoUser.js │ │ │ ├── index.js │ │ │ ├── LoginForm.css │ │ │ └── LoginForm.js │ │ ├── SignupFormModal │ │ │ ├── index.js │ │ │ ├── SignupForm.css │ │ │ └── SignUpForm.js │ │ ├── Footer │ │ │ └── index.js │ │ ├── UserHaunting │ │ │ ├── CreateUserHauntingModal.js │ │ │ ├── UserHaunting.css │ │ │ └── index.js │ │ ├── CreateUserHaunting │ │ │ ├── index.js │ │ │ ├── CreateUserHauntingModal.js │ │ │ └── CreateUserHaunting.css │ │ ├── HauntingCard │ │ │ ├── HauntingCard.css │ │ │ └── index.js │ │ ├── Navigation │ │ │ ├── ProfileButton.js │ │ │ └── index.js │ │ ├── HauntingProfile │ │ │ ├── index.js │ │ │ └── HauntingProfile.css │ │ └── BookingDates │ │ │ └── index.js │ ├── store │ │ ├── index.js │ │ ├── hauntings.js │ │ ├── csrf.js │ │ ├── userHauntings.js │ │ └── session.js │ ├── index.js │ ├── App.js │ └── index.css ├── .gitignore ├── package.json ├── README.md └── .eslintcache ├── backend ├── .sequelizerc ├── config │ ├── index.js │ └── database.js ├── bin │ └── www ├── db │ ├── models │ │ ├── userhaunting.js │ │ ├── index.js │ │ ├── haunting.js │ │ └── user.js │ ├── migrations │ │ ├── 20210126173505-price.js │ │ ├── 20210122042207-create-user.js │ │ ├── 20210126043124-create-user-haunting.js │ │ └── 20210126042127-create-haunting.js │ └── seeders │ │ ├── 20210221042321-seedUser.js │ │ └── 20210221042342-seedHauntings.js ├── utils │ ├── validation.js │ └── auth.js ├── routes │ ├── api │ │ ├── hauntings.js │ │ ├── userHauntings.js │ │ ├── users.js │ │ ├── index.js │ │ └── session.js │ └── index.js ├── package.json ├── app.js └── awsS3.js ├── README.md └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | build 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React Template", 3 | "name": "Create React App Template", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "theme_color": "#000000", 7 | "background_color": "#ffffff" 8 | } 9 | -------------------------------------------------------------------------------- /backend/.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | config: path.resolve('config', 'database.js'), 5 | 'models-path': path.resolve('db', 'models'), 6 | 'seeders-path': path.resolve('db', 'seeders'), 7 | 'migrations-path': path.resolve('db', 'migrations'), 8 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scarebnb 2 | 3 | 4 | ## What is it? 5 | * A spooky airbnb clone full of haunts to visit...if you dare. 6 | 7 | ## View the live site! 8 | * https://scarebnb-react.herokuapp.com/ 9 | 10 | 11 | ## View this page's wiki! 12 | * https://github.com/CJNewcomer/scarebnb/wiki 13 | 14 | 15 | -------------------------------------------------------------------------------- /frontend/src/components/SearchBar/SearchBar.css: -------------------------------------------------------------------------------- 1 | .search__container { 2 | display: flex; 3 | align-content: center; 4 | border: 0.5px solid #333333; 5 | border-radius: 5px; 6 | background-color: whitesmoke; 7 | } 8 | 9 | .search__text { 10 | font-family: 'Esteban', serif; 11 | } 12 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /frontend/src/components/Landing/index.js: -------------------------------------------------------------------------------- 1 | import HauntingCard from '../HauntingCard'; 2 | import './Landing.css'; 3 | 4 | function Landing() { 5 | return ( 6 | <> 7 |
8 |
9 | 10 |
11 |
12 | 13 | 14 | ) 15 | 16 | } 17 | 18 | export default Landing; -------------------------------------------------------------------------------- /backend/config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | environment: process.env.NODE_ENV || 'development', 3 | port: process.env.PORT || 5000, 4 | db: { 5 | username: process.env.DB_USERNAME, 6 | password: process.env.DB_PASSWORD, 7 | database: process.env.DB_DATABASE, 8 | host: process.env.DB_HOST, 9 | }, 10 | jwtConfig: { 11 | secret: process.env.JWT_SECRET, 12 | expiresIn: process.env.JWT_EXPIRES_IN, 13 | }, 14 | }; -------------------------------------------------------------------------------- /backend/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { port } = require('../config'); 3 | 4 | const app = require('../app'); 5 | const db = require('../db/models'); 6 | 7 | db.sequelize 8 | .authenticate() 9 | .then(() => { 10 | console.log('Database connection success! Sequelize is ready to use...'); 11 | 12 | app.listen(port, () => console.log(`Listening on port ${port}...`)); 13 | }) 14 | .catch((err) => { 15 | console.log('Database connection failure.'); 16 | console.error(err); 17 | }); -------------------------------------------------------------------------------- /frontend/src/components/context/SearchContext.js: -------------------------------------------------------------------------------- 1 | import { createContext, useState, useContext } from 'react'; 2 | 3 | const SearchContext = createContext(); 4 | 5 | export const useSearchContext = () => useContext(SearchContext); 6 | 7 | export default function SearchProvider({children}) { 8 | const [input, setInput] = useState(''); 9 | 10 | return ( 11 | 12 | { children } 13 | 14 | ); 15 | } -------------------------------------------------------------------------------- /frontend/src/components/SearchResults/SearchResults.css: -------------------------------------------------------------------------------- 1 | .search__wrapper { 2 | /* display: table; */ 3 | width: 80%; 4 | margin: auto; 5 | height: 100%; 6 | /* margin-left: 30px; */ 7 | } 8 | 9 | p.search__results { 10 | font-size: 25px; 11 | font-weight: 300; 12 | letter-spacing: 1px; 13 | text-decoration: underline; 14 | text-decoration-color: #333333; 15 | } 16 | 17 | div.search { 18 | display: flex; 19 | flex-wrap: wrap; 20 | justify-content: space-between; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /frontend/src/components/context/modal.css: -------------------------------------------------------------------------------- 1 | #modal { 2 | position: fixed; 3 | top: 0; 4 | right: 0; 5 | left: 0; 6 | bottom: 0; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | } 11 | 12 | #modal-background { 13 | position: fixed; 14 | top: 0; 15 | right: 0; 16 | left: 0; 17 | bottom: 0; 18 | text-align: center; 19 | background-color: rgba(0, 0, 0, 0.7); 20 | } 21 | 22 | #modal-content { 23 | position: absolute; 24 | background-color:white; 25 | text-align: center; 26 | padding: 35px; 27 | } -------------------------------------------------------------------------------- /backend/db/models/userhaunting.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | const UserHaunting = sequelize.define('UserHaunting', { 4 | userId: DataTypes.INTEGER, 5 | hauntingId: DataTypes.INTEGER, 6 | statusId: DataTypes.INTEGER, 7 | bookingStartDate: DataTypes.DATE, 8 | bookingEndDate: DataTypes.DATE, 9 | rating: DataTypes.INTEGER, 10 | comment: DataTypes.TEXT 11 | }, {}); 12 | UserHaunting.associate = function(models) { 13 | // associations can be defined here 14 | }; 15 | return UserHaunting; 16 | }; -------------------------------------------------------------------------------- /frontend/src/components/LoginFormModal/DemoUser.js: -------------------------------------------------------------------------------- 1 | import { useDispatch } from 'react-redux'; 2 | 3 | import { demoLogin } from '../../store/session'; 4 | import './LoginForm.css'; 5 | 6 | const DemoUser = () => { 7 | const dispatch = useDispatch(); 8 | 9 | const loginDemoUser = async () => { 10 | dispatch(demoLogin()); 11 | }; 12 | return ( 13 |
14 | 15 |
16 | ) 17 | } 18 | 19 | export default DemoUser; -------------------------------------------------------------------------------- /frontend/src/components/LoginFormModal/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Modal } from '../../components/context/Modal'; 3 | import LoginForm from './LoginForm'; 4 | 5 | function LoginFormModal() { 6 | const [showModal, setShowModal] = useState(false); 7 | 8 | return ( 9 | <> 10 | 11 | {showModal && ( 12 | setShowModal(false)}> 13 | 14 | 15 | )} 16 | 17 | ); 18 | } 19 | 20 | export default LoginFormModal; -------------------------------------------------------------------------------- /backend/config/database.js: -------------------------------------------------------------------------------- 1 | const config = require('./index'); 2 | const db = config.db; 3 | const username = db.username; 4 | const password = db.password; 5 | const database = db.database; 6 | const host = db.host; 7 | module.exports = { 8 | development: { 9 | username, 10 | password, 11 | database, 12 | host, 13 | dialect: 'postgres', 14 | seederStorage: 'sequelize' 15 | }, 16 | production: { 17 | use_env_variable: 'DATABASE_URL', 18 | dialect: 'postgres', 19 | dialectOptions: { 20 | ssl: { 21 | require: true, 22 | rejectUnauthorized: false 23 | } 24 | }, 25 | seederStorage: 'sequelize' 26 | } 27 | }; -------------------------------------------------------------------------------- /frontend/src/components/SignupFormModal/index.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | import {Modal} from '../context/Modal'; 4 | import SignupForm from './SignUpForm'; 5 | 6 | const SignupFormModal = () => { 7 | const [showModal, setShowModal] = useState(false); 8 | 9 | return ( 10 | <> 11 | 12 | {showModal && ( 13 | setShowModal(false)}> 14 | 15 | 16 | )} 17 | 18 | ); 19 | }; 20 | 21 | export default SignupFormModal; -------------------------------------------------------------------------------- /backend/db/migrations/20210126173505-price.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | /* 6 | Add altering commands here. 7 | Return a promise to correctly handle asynchronicity. 8 | 9 | Example: 10 | */ 11 | return queryInterface.addColumn('Hauntings', 'price', { 12 | type: Sequelize.INTEGER, 13 | allowNull: false }); 14 | }, 15 | 16 | down: (queryInterface, Sequelize) => { 17 | /* 18 | Add reverting commands here. 19 | Return a promise to correctly handle asynchronicity. 20 | 21 | Example: 22 | */ 23 | return queryInterface.removeColumn('Hauntings', 'price'); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /backend/utils/validation.js: -------------------------------------------------------------------------------- 1 | const { validationResult } = require('express-validator'); 2 | 3 | // middleware for formatting errors from express-validator middleware 4 | // (to customize, see express-validator's documentation) 5 | const handleValidationErrors = (req, _res, next) => { 6 | const validationErrors = validationResult(req); 7 | 8 | if (!validationErrors.isEmpty()) { 9 | const errors = validationErrors 10 | .array() 11 | .map((error) => `${error.msg}`); 12 | 13 | const err = Error('Bad request.'); 14 | err.errors = errors; 15 | err.status = 400; 16 | err.title = 'Bad request.'; 17 | next(err); 18 | } 19 | next(); 20 | }; 21 | 22 | module.exports = { 23 | handleValidationErrors, 24 | }; -------------------------------------------------------------------------------- /backend/routes/api/hauntings.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const { Haunting } = require('../../db/models'); 5 | const asyncHandler = require('express-async-handler'); 6 | 7 | 8 | router.get('/:id(\\d+)', asyncHandler(async(req, res) => { 9 | const id = parseInt(req.params.id, 10); 10 | // console.log(id) 11 | const haunting = await Haunting.findOne({ 12 | where: { 13 | id, 14 | } 15 | }); 16 | // console.log(haunting) 17 | res.json({haunting}); 18 | })); 19 | 20 | router.get('/', asyncHandler(async(req, res) => { 21 | const hauntings = await Haunting.findAll(); 22 | // console.log(hauntings); 23 | res.json({hauntings}); 24 | })), 25 | 26 | 27 | module.exports = router; -------------------------------------------------------------------------------- /backend/routes/api/userHauntings.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const { UserHaunting } = require('../../db/models'); 5 | const asyncHandler = require('express-async-handler'); 6 | 7 | router.post('/', asyncHandler(async (req, res) => { 8 | const {userId, bookingStartDate, bookingEndDate, active} = req.body; 9 | const userHaunting = await UserHaunting.create({ userId, bookingStartDate, bookingEndDate, active}); 10 | 11 | return res.json({userHaunting}); 12 | })) 13 | 14 | router.get('/:id', asyncHandler(async (req, res) => { 15 | const userHauntings = await PromiseRejectionEvent.findAll({ 16 | where: { 17 | userId: req.params.id 18 | } 19 | }) 20 | return res.json({userHauntings}); 21 | })); 22 | 23 | module.exports = router; -------------------------------------------------------------------------------- /frontend/src/components/SearchBar/index.js: -------------------------------------------------------------------------------- 1 | import { useHistory } from 'react-router-dom'; 2 | 3 | import { useSearchContext } from '../context/SearchContext'; 4 | 5 | import './SearchBar.css'; 6 | 7 | const SearchBar = () => { 8 | const history = useHistory(); 9 | 10 | const {input, setInput} = useSearchContext(); 11 | 12 | const onSearch = (e) => { 13 | e.preventDefault(); 14 | history.push(`/search?result1=${input}`) 15 | }; 16 | 17 | return ( 18 |
19 |
20 | setInput(e.target.value)} placeholder='Search...if you dare' className='search__text'/> 21 |
22 |
23 | ) 24 | } 25 | 26 | export default SearchBar; -------------------------------------------------------------------------------- /frontend/src/components/Footer/index.js: -------------------------------------------------------------------------------- 1 | 2 | function Footer() { 3 | return ( 4 |
5 |
6 |

Created by Courtney J. Newcomer {new Date().getFullYear()}

7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 | ) 18 | } 19 | export default Footer; 20 | 21 | 22 | -------------------------------------------------------------------------------- /frontend/src/components/UserHaunting/CreateUserHauntingModal.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { Modal } from '../context/Modal'; 3 | 4 | import UserHaunting from './index' 5 | 6 | 7 | const CreateUserHauntingModal = () => { 8 | const [ showModal, setShowModal] = useState(false); 9 | 10 | const handleClose = () => setShowModal(false); 11 | 12 | return ( 13 | <> 14 | 18 | {showModal && ( 19 | setShowModal(false)}> 20 | 21 | 22 | )} 23 | 24 | ) 25 | } 26 | 27 | 28 | export default CreateUserHauntingModal; -------------------------------------------------------------------------------- /frontend/src/components/Landing/Landing.css: -------------------------------------------------------------------------------- 1 | div.main { 2 | width: 100%; 3 | height: 100%; 4 | display: block; 5 | position: relative; 6 | } 7 | 8 | div.main ::after { 9 | content: ""; 10 | background: url("https://www.ft.com/__origami/service/image/v2/images/raw/http%3A%2F%2Fcom.ft.imagepublish.upp-prod-us.s3.amazonaws.com%2F86be13f0-e59f-11e9-9743-db5a370481bc?fit=scale-down&source=next&width=700"); 11 | background-repeat: no-repeat; 12 | background-attachment: fixed; 13 | background-position: center; 14 | background-size: cover; 15 | width: 100%; 16 | opacity: 0.2; 17 | top: 0; 18 | left: 0; 19 | bottom: 0; 20 | right: 0; 21 | position: absolute; 22 | z-index: -1; 23 | } 24 | 25 | .main__layout { 26 | display: flex; 27 | justify-content: space-between; 28 | flex-wrap: wrap; 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /frontend/src/components/CreateUserHaunting/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Modal } from '../context/Modal' 3 | 4 | import CreateUserHaunting from './CreateUserHauntingModal'; 5 | 6 | const CreateUserHauntingModal = () =>{ 7 | const [showModal, setShowModal] = useState(false); 8 | 9 | const handleClose = () => setShowModal(false); 10 | 11 | return ( 12 | <> 13 | 18 | {showModal && ( 19 | setShowModal(false)}> 20 | 21 | 22 | )} 23 | 24 | ) 25 | } 26 | 27 | export default CreateUserHauntingModal; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scarebnb", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "heroku-postbuild": "npm run build --prefix frontend", 8 | "install": "npm --prefix backend install backend && npm --prefix frontend install frontend", 9 | "dev:backend": "npm install --prefix backend start", 10 | "dev:frontend": "npm install --prefix frontend start", 11 | "sequelize": "npm run --prefix backend sequelize", 12 | "sequelize-cli": "npm run --prefix backend sequelize-cli", 13 | "start": "npm start --prefix backend" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/CJNewcomer/scarebnb.git" 18 | }, 19 | "author": "", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/CJNewcomer/scarebnb/issues" 23 | }, 24 | "homepage": "https://github.com/CJNewcomer/scarebnb#readme", 25 | "dependencies": { 26 | "lodash": "^4.17.21" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/components/UserHaunting/UserHaunting.css: -------------------------------------------------------------------------------- 1 | .form__container { 2 | margin: auto; 3 | display: flex; 4 | flex-direction: column; 5 | padding: 4rem; 6 | background-color: #333; 7 | } 8 | 9 | .form__content { 10 | display: flex; 11 | justify-content: space-between; 12 | flex-direction: column; 13 | } 14 | 15 | .form__title { 16 | color: #333; 17 | font-family: 'Playfair Display SC', serif; 18 | font-size: 25px; 19 | align-self: center; 20 | margin-top: -.5rem; 21 | letter-spacing: 1.2px; 22 | } 23 | 24 | .error-list { 25 | margin: auto; 26 | list-style: none; 27 | color: rgb(143, 18, 18); 28 | text-align: center; 29 | padding: .5rem rem; 30 | } 31 | 32 | .form__input { 33 | margin: .2rem; 34 | } 35 | 36 | .form__button-container { 37 | align-self: center; 38 | margin: 2rem 1rem 1rem 1rem; 39 | } 40 | 41 | .form__button { 42 | padding: .7rem; 43 | background-color: #333; 44 | border-radius: 5px; 45 | border: none; 46 | } -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | React Template 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /frontend/src/components/LoginFormModal/LoginForm.css: -------------------------------------------------------------------------------- 1 | .form__login{ 2 | margin:auto; 3 | display: flex; 4 | flex-direction: column; 5 | padding: 4rem; 6 | background-color: whitesmoke; 7 | } 8 | 9 | .form__content{ 10 | display: flex; 11 | justify-content: space-between; 12 | flex-direction: column; 13 | } 14 | 15 | .error-list { 16 | margin: auto; 17 | list-style: none; 18 | color: rgb(143, 18, 18); 19 | text-align: center; 20 | padding: .5rem rem; 21 | } 22 | 23 | label { 24 | padding: 5px; 25 | font-family: 'Playfair Display SC', serif; 26 | letter-spacing: 1px; 27 | } 28 | 29 | .form__input { 30 | margin: .2rem; 31 | } 32 | 33 | .form__input-text { 34 | color: #333333; 35 | border-radius: 5px; 36 | border: .15px solid rgb(11,12,15); 37 | padding: .3rem; 38 | } 39 | 40 | .form__button { 41 | align-self: center; 42 | } 43 | 44 | .form__button-submit { 45 | padding: 5px 20px; 46 | background-color: rgb(197,198,200); 47 | border-radius: 5px; 48 | } 49 | -------------------------------------------------------------------------------- /frontend/src/components/context/Modal.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useRef, useState, useEffect } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './modal.css'; 4 | 5 | const ModalContext = React.createContext(); 6 | 7 | export function ModalProvider({ children }) { 8 | const modalRef = useRef(); 9 | const [value, setValue] = useState(); 10 | 11 | useEffect(() => { 12 | setValue(modalRef.current); 13 | }, []) 14 | 15 | return ( 16 | <> 17 | 18 | {children} 19 | 20 |
21 | 22 | ); 23 | } 24 | 25 | export function Modal({ onClose, children }) { 26 | const modalNode = useContext(ModalContext); 27 | if (!modalNode) return null; 28 | 29 | return ReactDOM.createPortal( 30 |