├── .DS_Store
├── .babelrc
├── .gitignore
├── LICENSE
├── README.md
├── assets
└── random-dog.jpg
├── build
└── bundle.js
├── client
├── .DS_Store
├── App.jsx
├── StateProvider.js
├── components
│ ├── .DS_Store
│ ├── ARCHIVE FILES - PETSPERSECTIOn
│ │ ├── .DS_Store
│ │ ├── AddPostForm.jsx
│ │ ├── Bucketlist.jsx
│ │ ├── BucketlistItemDisplay.jsx
│ │ ├── CompletedBucketlistItem.jsx
│ │ ├── CreateListItem.jsx
│ │ ├── Header.jsx
│ │ ├── Login.jsx
│ │ ├── Signup.jsx
│ │ ├── UncompletedBucketlistItem.jsx
│ │ ├── feed.jsx
│ │ └── profile.jsx
│ ├── ChatPage
│ │ ├── .DS_Store
│ │ ├── ChatBox
│ │ │ ├── .DS_Store
│ │ │ ├── Chat
│ │ │ │ ├── .DS_Store
│ │ │ │ ├── ChatBox.jsx
│ │ │ │ ├── ChatBoxHeader.jsx
│ │ │ │ ├── InputContainer.jsx
│ │ │ │ └── Message.jsx
│ │ │ └── ChatContainer.jsx
│ │ ├── ChatPage.jsx
│ │ ├── PartnerInfo
│ │ │ ├── PartnerInfo.jsx
│ │ │ └── PartnerInfo
│ │ │ │ ├── Avatar.jsx
│ │ │ │ ├── PartnerActivitiesList.jsx
│ │ │ │ ├── PartnerInfoItem.jsx
│ │ │ │ └── PartnerPetInfoItem.jsx
│ │ └── UserList
│ │ │ ├── .DS_Store
│ │ │ ├── UserItem
│ │ │ └── UserItem.jsx
│ │ │ └── UserList.jsx
│ ├── Header
│ │ ├── .DS_Store
│ │ ├── Header.jsx
│ │ └── UserInfo
│ │ │ └── UserInfo.jsx
│ ├── Login
│ │ ├── FacebookLogin.jsx
│ │ └── Login.jsx
│ └── ProfilePage
│ │ ├── .DS_Store
│ │ ├── Activities
│ │ ├── .DS_Store
│ │ ├── Activities.jsx
│ │ └── ActivityItem
│ │ │ ├── .DS_Store
│ │ │ └── ActivityItem.jsx
│ │ ├── Profile
│ │ └── Profile.jsx
│ │ └── ProfilePage.jsx
├── index.js
├── main.js
├── styled-items.js
└── styles.css
├── index.html
├── package-lock.json
├── package.json
├── server
├── controllers
│ ├── MatchesController.js
│ └── UserController.js
├── models
│ ├── UserModels.js
│ └── activitiesModels.js
├── routers
│ └── users.js
└── server.js
├── utils
└── messages.js
└── webpack.config.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoffeeAndWoof/Woof/275aba4dded583aa8fef595a543dcc88776735da/.DS_Store
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env",
4 | "@babel/preset-react"
5 | ],
6 | "plugins": [
7 | [
8 | "@babel/plugin-proposal-class-properties",
9 | {
10 | "loose": true
11 | }
12 | ]
13 | ]
14 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Yeti-Crab-Squad
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # yeti-crab-professionals
2 | Coffee&Woof
3 | * An activities match dating site for dogs!
4 | (1) Login with your facebook picture!
5 |
6 |
7 |
8 | Routes
9 |
10 | /api/login
11 | * POST
12 | * request - FB login object
13 | * response - object with user data
14 |
15 | /api/getUserData/userID
16 | * GET
17 | * put userID string from mongo in params
18 | * response - object with user data
19 |
20 | /api/updateUserData/userID
21 | * PUT
22 | * put userID string from mongo in params
23 | * request - user object with updated user data
24 | * response - object with user data
25 |
26 |
27 | ******* STRETCH FEATURE *******
28 |
29 | /api/deleteUser/userID
30 | * DELETE
31 | * put userID string from mongo in params
32 |
--------------------------------------------------------------------------------
/assets/random-dog.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoffeeAndWoof/Woof/275aba4dded583aa8fef595a543dcc88776735da/assets/random-dog.jpg
--------------------------------------------------------------------------------
/client/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoffeeAndWoof/Woof/275aba4dded583aa8fef595a543dcc88776735da/client/.DS_Store
--------------------------------------------------------------------------------
/client/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, useEffect } from 'react';
2 | import {
3 | BrowserRouter as Router,
4 | Route,
5 | Switch,
6 | Redirect,
7 | } from 'react-router-dom';
8 | import styled from 'styled-components';
9 |
10 | import Header from './components/Header/Header.jsx';
11 | import Login from './components/Login/Login.jsx';
12 | import ProfilePage from './components/ProfilePage/ProfilePage.jsx';
13 | import { ChatPage } from './components/ChatPage/ChatPage.jsx';
14 | import { StateProvider, useStateValue } from './StateProvider';
15 | import styledItems from './styled-items';
16 |
17 | const RouterDiv = styled.div`
18 | width: 85%;
19 | background-color: ${styledItems.white};
20 | min-height: 1000px;
21 | display: flex;
22 | justify-content: center;
23 | `;
24 |
25 | const StyledDiv = styled.div`
26 | width: 100%;
27 | display: flex;
28 | flex-direction: column;
29 | align-items: center;
30 | `;
31 |
32 | const App = () => {
33 | // define initialState here as an object
34 | const initialState = {
35 | userInfo: {
36 | _id: 0,
37 | fullName: '',
38 | firstName: '',
39 | location: '',
40 | age: '',
41 | avatarUrl: '',
42 | activities: {},
43 | email: '',
44 | },
45 | petInfo: {
46 | name: '',
47 | age: '',
48 | breed: '',
49 | size: '',
50 | avatarUrl: '',
51 | },
52 | partnerInfo: {
53 | _id: 564345532,
54 | fullName: 'Gary Slootskiy',
55 | firstName: 'Gary',
56 | location: 'New York, NY',
57 | age: '30',
58 | avatarUrl: '',
59 | activities: {},
60 | petInfo: {
61 | name: 'Tully',
62 | age: '7',
63 | breed: 'Labrador',
64 | size: 'Medium',
65 | avatarUrl: '',
66 | },
67 | },
68 | chatItems: {},
69 | matchList: [],
70 | mainMatch: false,
71 | loggedIn: false,
72 | };
73 |
74 | const reducer = (state, action) => {
75 | let userInfo;
76 | let petInfo;
77 |
78 | switch (action.type) {
79 | case 'clickLogin':
80 | userInfo = Object.assign({}, state.userInfo);
81 | petInfo = Object.assign({}, state.petInfo);
82 | userInfo._id = action.id;
83 | userInfo.fullName = action.full_name;
84 | userInfo.firstName = action.first_name;
85 | userInfo.email = action.email;
86 | userInfo.avatarUrl = action.profile_img;
87 | userInfo.age = action.user_age;
88 | userInfo.location = action.location;
89 | petInfo.name = action.dog_name;
90 | petInfo.avatarUrl = action.dog_image;
91 | petInfo.age = action.dog_age;
92 | petInfo.size = action.dog_size;
93 | petInfo.breed = action.dog_breed;
94 | userInfo.activities = {};
95 | action.preferred_activities.forEach((activity) => {
96 | userInfo.activities[activity.activity] = activity.description;
97 | });
98 | // console.log('userInfo:',userInfo);
99 | // console.log('petInfo:',petInfo);
100 | return {
101 | ...state,
102 | userInfo,
103 | petInfo,
104 | loggedIn: true,
105 | };
106 |
107 | case 'addActivity':
108 | userInfo = Object.assign({}, state.userInfo);
109 | userInfo.activities[action.activity] = `i like ${action.activity}`;
110 | return {
111 | ...state,
112 | userInfo,
113 | };
114 |
115 | case 'removeActivity':
116 | userInfo = Object.assign({}, state.userInfo);
117 | delete userInfo.activities[action.activity];
118 | return {
119 | ...state,
120 | userInfo,
121 | };
122 |
123 | case 'updateMatches':
124 | userInfo = Object.assign({}, state.userInfo);
125 | petInfo = Object.assign({}, state.petInfo);
126 | userInfo.location = action.userInfo.location;
127 | userInfo.age = action.userInfo.age;
128 | userInfo.activities = action.userInfo.activities;
129 | petInfo.name = action.petInfo.name;
130 | petInfo.age = action.petInfo.age;
131 | petInfo.breed = action.petInfo.breed;
132 | petInfo.size = action.petInfo.size;
133 | petInfo.avatarUrl = action.petInfo.avatarUrl;
134 | console.log("save profile");
135 |
136 | let matchList = action.matchList;
137 | console.log('Hello',matchList);
138 | return {
139 | ...state,
140 | matchList,
141 | userInfo,
142 | petInfo,
143 | };
144 |
145 | case 'saveProfile':
146 | userInfo = Object.assign({}, state.userInfo);
147 | petInfo = Object.assign({}, state.petInfo);
148 | userInfo.location = action.userInfo.location;
149 | userInfo.age = action.userInfo.age;
150 | userInfo.activities = action.userInfo.activities;
151 | petInfo.name = action.petInfo.name;
152 | petInfo.age = action.petInfo.age;
153 | petInfo.breed = action.petInfo.breed;
154 | petInfo.size = action.petInfo.size;
155 | petInfo.avatarUrl = action.petInfo.avatarUrl;
156 | console.log("save profile");
157 | return {
158 | ...state,
159 | userInfo,
160 | petInfo,
161 | };
162 |
163 | default:
164 | return state;
165 | }
166 | };
167 |
168 | return (
169 | // CONTEXT API: everything inside of StateProvider will now be able to access state
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 | );
185 | };
186 |
187 | export default App;
188 |
--------------------------------------------------------------------------------
/client/StateProvider.js:
--------------------------------------------------------------------------------
1 | import React, {Component, createContext, useContext, useReducer} from 'react';
2 |
3 | // CONTEXT API CODE
4 | export const StateContext = createContext();
5 |
6 | // result of useReducer hook is passed on to the Provider =>
7 | // ...so it becomes available in any component in the app component tree
8 | export const StateProvider = ({reducer, initialState, children}) =>(
9 |
10 | {children}
11 |
12 | );
13 |
14 | // callback function so we can shorten our useContext call....
15 | export const useStateValue = () => useContext(StateContext);
16 |
--------------------------------------------------------------------------------
/client/components/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoffeeAndWoof/Woof/275aba4dded583aa8fef595a543dcc88776735da/client/components/.DS_Store
--------------------------------------------------------------------------------
/client/components/ARCHIVE FILES - PETSPERSECTIOn/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoffeeAndWoof/Woof/275aba4dded583aa8fef595a543dcc88776735da/client/components/ARCHIVE FILES - PETSPERSECTIOn/.DS_Store
--------------------------------------------------------------------------------
/client/components/ARCHIVE FILES - PETSPERSECTIOn/AddPostForm.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function AddPostForm(props) {
4 | return (
5 |
6 |
{props.state.listItem}
7 |
8 |
14 |
15 |
21 |
22 |
23 |
24 |
29 |
30 |
36 |
37 |
40 |
41 | );
42 |
43 | }
44 |
45 | export default AddPostForm;
46 |
--------------------------------------------------------------------------------
/client/components/ARCHIVE FILES - PETSPERSECTIOn/Bucketlist.jsx:
--------------------------------------------------------------------------------
1 | import React, {useState, useEffect} from "react";
2 | import BucketlistItemDisplay from "./BucketlistItemDisplay.jsx";
3 | import CreateListItem from "./CreateListItem.jsx";
4 | import Header from "./Header.jsx";
5 |
6 | function Bucketlist(props) {
7 | const [newItem, setNewItem] = useState("");
8 | const [bucketlistItems, setBucketlistItems] = useState([]);
9 |
10 | useEffect(() => {
11 | fetch("/api/listItems")
12 | .then((res) => res.json())
13 | .then((data) => {
14 | setBucketlistItems(data);
15 | console.log(bucketlistItems.concat(...data));
16 | });
17 | }, []);
18 |
19 |
20 | function handleCheckedOffClick(listItem) {
21 | const updatedItem = {
22 | listItem: listItem,
23 | isChecked: true,
24 | hasPost: false
25 | }
26 |
27 | fetch(`/api/listItems/${listItem}`,{
28 | method: 'PUT',
29 | headers: {
30 | 'Content-Type': 'application/json'
31 | },
32 | body: JSON.stringify(updatedItem)
33 | })
34 | .then(res => res.json())
35 | .then(listItemData => {
36 | console.log("",listItemData)
37 | console.log("PRE",bucketlistItems)
38 | fetch('/api/listItems')
39 | .then(res => res.json())
40 | .then(data => {
41 | setBucketlistItems(data)
42 | console.log("POST",bucketlistItems)
43 |
44 | })
45 |
46 |
47 | // setState(state.isChecked = true, state.hasPost = false)
48 | })
49 | .catch(error => {
50 | console.log(error)
51 | })
52 | }
53 |
54 | function handleNewItemClick() {
55 | const newListItem = {
56 | listItem: newItem,
57 | };
58 |
59 | fetch(`/api/listItems`, {
60 | method: "POST",
61 | headers: {
62 | "Content-Type": "application/json",
63 | },
64 | body: JSON.stringify(newListItem),
65 | })
66 | .then((res) => res.json())
67 | .then((data) => {
68 | setNewItem("");
69 | fetch("/api/listItems")
70 | .then((res) => res.json())
71 | .then((data) => {
72 | setBucketlistItems(data);
73 | console.log(bucketlistItems.concat(...data));
74 | });
75 | });
76 |
77 | }
78 |
79 | function handleNewItemChange(e) {
80 | e.preventDefault();
81 | let newItem = e.target.value;
82 |
83 | return setNewItem(newItem);
84 | }
85 |
86 | return (
87 | <>
88 |
89 |
90 |
95 | {bucketlistItems.map((item) => {
96 | return ;
97 | })}
98 |
99 | >
100 | );
101 |
102 | }
103 |
104 | export default Bucketlist;
105 |
--------------------------------------------------------------------------------
/client/components/ARCHIVE FILES - PETSPERSECTIOn/BucketlistItemDisplay.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useState, useEffect } from 'react'
3 | import AddPostForm from './AddPostForm.jsx'
4 | import CompletedBucketlistItem from './CompletedBucketlistItem.jsx'
5 | import UncompletedBucketlistItem from './UncompletedBucketlistItem.jsx'
6 |
7 | function BucketlistItemDisplay(props) {
8 | const [state, setState] = useState(props.item);
9 | const [newPost, setNewPost] = useState('')
10 |
11 | const [images, setImages] = useState([])
12 | const [imagesChange, setImagesChange] = useState('')
13 |
14 | const [date, setDateChange] = useState('')
15 | const [postDescription, setPostDescription] = useState('')
16 | const [YTLink, setYTLink] = useState('')
17 | const [GMLink, setGMLink] = useState('')
18 |
19 |
20 |
21 | // function handleCheckedOffClick(listItem) {
22 | // const updatedItem = {
23 | // listItem: listItem,
24 | // isChecked: true,
25 | // hasPost: false
26 | // }
27 |
28 | // fetch(`/api/listItems/${listItem}`,{
29 | // method: 'PUT',
30 | // headers: {
31 | // 'Content-Type': 'application/json'
32 | // },
33 | // body: JSON.stringify(updatedItem)
34 | // })
35 | // .then(res => res.json())
36 | // .then(data => {
37 |
38 | // // setState(state.isChecked = true, state.hasPost = false)
39 | // })
40 | // .catch(error => {
41 | // console.log(error)
42 | // })
43 | // }
44 |
45 | function handleDateChange(e) {
46 | e.preventDefault()
47 | const newValue = e.target.value;
48 | setDateChange(newValue)
49 | }
50 |
51 | function handleBodyOfPostChange(e) {
52 | e.preventDefault()
53 | const newValue = e.target.value;
54 |
55 | setPostDescription(newValue)
56 | }
57 |
58 | function handleYoutubeURLChange(e) {
59 | e.preventDefault()
60 | const newValue = e.target.value;
61 |
62 | setYTLink(newValue)
63 | }
64 |
65 | // function handleEmbedGoogleMaps(e) {
66 | // // NOT SURE YET HOW TO DO THIS ONE!!
67 | // e.preventDefault()
68 | // const newValue = e.target.value;
69 |
70 | // setGMLink(newValue)
71 | // }
72 |
73 | function handleSubmitPostClick(listItem) {
74 | const fullLink = `https://www.google.com/maps/embed/v1/place?key=AIzaSyCyw8Q7SqmZ8RbKT6HgInw5Bcp93emrlNU&q=${GMLink}`
75 |
76 | console.log("LIST ITEM!!!!",listItem)
77 | const newPost = {
78 | listItem: listItem,
79 | dateCompleted: date,
80 | postDescription: postDescription,
81 | youtubeLink: YTLink,
82 | location: fullLink,
83 | images: images
84 | }
85 |
86 | fetch('/api/posts/', {
87 | method: 'POST',
88 | headers: {
89 | 'Content-Type': 'application/json'
90 | },
91 | body: JSON.stringify(newPost)
92 | })
93 | .then(res => res.json())
94 | .then(data => {
95 |
96 | console.log(data)
97 | // setState(...state, state.hasPost:true)
98 |
99 | const updatedItem = {
100 | listItem: listItem,
101 | isChecked: true,
102 | hasPost: true
103 | }
104 |
105 | fetch(`/api/listItems/${listItem}`, {
106 | method: 'PUT',
107 | headers: {
108 | 'Content-Type': 'application/json'
109 | },
110 | body: JSON.stringify(updatedItem)
111 | })
112 | .then(res => res.json())
113 | .then(data => {
114 |
115 | fetch('/api/listItems')
116 | .then(res => res.json())
117 | .then(data => {
118 | setState(data)
119 | console.log("POST",bucketlistItems)
120 |
121 | })
122 | console.log('Have updated list item, HAS POST is TRUE')
123 | })
124 |
125 | })
126 | .catch(error => {
127 | console.log(error)
128 | })
129 |
130 | }
131 |
132 | function handleGoogleLinkChange(e) {
133 | e.preventDefault()
134 | const newValue = e.target.value;
135 |
136 | setGMLink(newValue)
137 |
138 | }
139 |
140 | function handleAddImagesChange(e){
141 | e.preventDefault()
142 | const newValue = e.target.value;
143 | setImagesChange(newValue)
144 | }
145 |
146 | function handleAddImagesClick() {
147 | setImages([...images, imagesChange])
148 | // setImagesChange('')
149 |
150 | }
151 |
152 | // function handleDeleteClick(listItem) {
153 |
154 | // }
155 |
156 |
157 | // console.log(state)
158 | if (state.isChecked && !state.hasPost) {
159 | // console.log('THIS IS STATE',state)
160 |
161 | return (
162 |
186 | )
187 | } else if (state.isChecked) {
188 | return (
189 |
195 | )
196 | } else {
197 |
198 | return (
199 |
205 | )
206 | }
207 |
208 | }
209 |
210 | export default BucketlistItemDisplay;
211 |
212 |
213 |
--------------------------------------------------------------------------------
/client/components/ARCHIVE FILES - PETSPERSECTIOn/CompletedBucketlistItem.jsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from "react";
2 |
3 | function CompletedBucketlistItem(props) {
4 | const [displayPost, setDisplayPost] = useState(false);
5 | const [postData, setPostData] = useState({});
6 |
7 | function showPost(listItem) {
8 | console.log(listItem);
9 | fetch(`/api/posts/${listItem}`)
10 | .then((res) => res.json())
11 | .then((data) => {
12 | // console.log("POST DATQ",data)
13 | setPostData(data);
14 | setDisplayPost(true);
15 | });
16 | }
17 |
18 | if (!displayPost) {
19 | return (
20 |
21 |
22 |
{props.listItem}
23 | {props.dateCompleted}
24 |
25 |
26 |
27 |
28 | );
29 | } else {
30 | return (
31 |
32 |
{postData.listItem}
33 |
{postData.dateCompleted}
34 |
{postData.postDescription}
35 | {console.log("POST DATAaaa", postData)}
36 | {postData.images.map((image, index) => {
37 | return

;
38 | })}
39 |
44 |
45 |
46 | );
47 | }
48 | }
49 |
50 | export default CompletedBucketlistItem;
51 |
--------------------------------------------------------------------------------
/client/components/ARCHIVE FILES - PETSPERSECTIOn/CreateListItem.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function CreateListItem(props) {
4 | return (
5 |
6 |
7 |
13 |
16 |
17 | );
18 | }
19 |
20 | export default CreateListItem;
21 |
--------------------------------------------------------------------------------
/client/components/ARCHIVE FILES - PETSPERSECTIOn/Header.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Link} from "react-router-dom";
3 |
4 | const Header = (props) => {
5 | return (
6 |
18 | );
19 | };
20 | export default Header;
21 |
--------------------------------------------------------------------------------
/client/components/ARCHIVE FILES - PETSPERSECTIOn/Login.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Redirect } from "react-router-dom";
3 | import { Link } from "react-router-dom";
4 | import "../styles.css";
5 |
6 | class Login extends Component {
7 | constructor() {
8 | super();
9 | this.state = {
10 | loggedIn: false,
11 | failedLogin: false,
12 | //object -- all the info
13 | };
14 |
15 | this.validate = this.validate.bind(this);
16 | }
17 |
18 | validate() {
19 | const username = document.getElementById("username").value;
20 | const password = document.getElementById("password").value;
21 |
22 | const body = {
23 | username,
24 | password,
25 | };
26 |
27 | // console.log("This is outside of the POST", body)
28 |
29 | fetch("/api/pet/login", {
30 | method: "POST",
31 | headers: {
32 | "Content-Type": "application/json",
33 | },
34 | body: JSON.stringify(body),
35 | })
36 | .then((res) => res.json())
37 | .then((data) => {
38 | this.setState({
39 | loggedIn: true,
40 | });
41 | })
42 | .catch((err) => console.log(`Error: ${err} `));
43 | }
44 |
45 | render() {
46 | if (this.state.loggedIn) {
47 | return ;
48 | }
49 |
50 | return (
51 |
52 |
Petrospective!
53 |
78 |
79 | );
80 | }
81 | }
82 |
83 | export default Login;
84 |
--------------------------------------------------------------------------------
/client/components/ARCHIVE FILES - PETSPERSECTIOn/Signup.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Redirect } from "react-router-dom";
3 | import { Link } from "react-router-dom";
4 |
5 | class Signup extends Component {
6 | constructor() {
7 | super();
8 | this.state = {
9 | loggedIn: false,
10 | failedLogin: false,
11 | };
12 |
13 | this.validate = this.validate.bind(this);
14 | }
15 |
16 | validate() {
17 | const bio = document.getElementById("bio").value;
18 | const username = document.getElementById("signupUsername").value;
19 | const password = document.getElementById("signupPassword").value;
20 | const name = document.getElementById("name").value;
21 | const age = document.getElementById("age").value;
22 | const profilePicture = "this is a src for am img";
23 |
24 | const body = {
25 | username,
26 | password,
27 | profilePicture,
28 | age,
29 | bio,
30 | name,
31 | };
32 |
33 | fetch("/api/pet/signup", {
34 | method: "POST",
35 | headers: {
36 | "Content-Type": "application/json",
37 | },
38 | body: JSON.stringify(body),
39 | })
40 | .then((res) => {
41 | console.log(res);
42 | })
43 | .then((data) => {
44 | this.setState({
45 | loggedIn: true,
46 | });
47 | })
48 | .catch((err) => console.log(`Error: ${err} `));
49 | }
50 |
51 | render() {
52 | if (this.state.loggedIn) {
53 | return ;
54 | }
55 |
56 | return (
57 |
102 | );
103 | }
104 | }
105 |
106 | export default Signup;
107 |
--------------------------------------------------------------------------------
/client/components/ARCHIVE FILES - PETSPERSECTIOn/UncompletedBucketlistItem.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function UncompletedBucketlistItem(props) {
4 | return (
5 |
6 |
{props.listItem}
7 | props.handleCheckedOffClick(props.listItem)}
9 | type='checkbox'
10 | />
11 |
12 | );
13 |
14 | }
15 |
16 | export default UncompletedBucketlistItem;
17 |
--------------------------------------------------------------------------------
/client/components/ARCHIVE FILES - PETSPERSECTIOn/feed.jsx:
--------------------------------------------------------------------------------
1 | import React, {useState, useEffect} from "react";
2 | import {BrowserRouter as Router, Route, Switch} from "react-router-dom";
3 | import Header from "./Header.jsx";
4 |
5 | const Feed = (props) => {
6 | const [posts, setPosts] = useState([]);
7 |
8 | useEffect(() => {
9 | fetch("http://localhost:3000/api/posts")
10 | .then((res) => res.json())
11 | .then((completeItems) => {
12 | setPosts(posts.concat(...completeItems));
13 | });
14 | }, []);
15 |
16 | return (
17 |
18 |
19 | {/*
*/}
20 | {posts.map((post) => {
21 | {
22 | console.log(post);
23 | }
24 | return ;
25 | })}
26 |
27 | );
28 | };
29 |
30 | //API_KEY = AIzaSyCyw8Q7SqmZ8RbKT6HgInw5Bcp93emrlNU
31 | function RenderPost(props) {
32 | return (
33 | <>
34 |
35 |
36 |
{props.post.listItem}
37 | {props.post.dateCompleted}
38 |
39 |
40 |
45 |
46 |
{props.post.postDescription}
47 |
51 | {props.post.images.map((image) => {
52 | return (
53 |

58 | );
59 | })}
60 |
61 |
62 | >
63 | );
64 | }
65 |
66 | export default Feed;
67 |
--------------------------------------------------------------------------------
/client/components/ARCHIVE FILES - PETSPERSECTIOn/profile.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { Redirect } from "react-router-dom";
3 | import { Link } from "react-router-dom";
4 | import "../styles.css";
5 |
6 | const Profile = (props) => {
7 | return (
8 |
9 |
10 |
Your Profile
11 |
12 |
Line 1
13 |
Line 2
14 |
Line 3
15 |
16 |
17 |
18 | );
19 | }
20 |
21 | export default Profile;
--------------------------------------------------------------------------------
/client/components/ChatPage/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoffeeAndWoof/Woof/275aba4dded583aa8fef595a543dcc88776735da/client/components/ChatPage/.DS_Store
--------------------------------------------------------------------------------
/client/components/ChatPage/ChatBox/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoffeeAndWoof/Woof/275aba4dded583aa8fef595a543dcc88776735da/client/components/ChatPage/ChatBox/.DS_Store
--------------------------------------------------------------------------------
/client/components/ChatPage/ChatBox/Chat/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoffeeAndWoof/Woof/275aba4dded583aa8fef595a543dcc88776735da/client/components/ChatPage/ChatBox/Chat/.DS_Store
--------------------------------------------------------------------------------
/client/components/ChatPage/ChatBox/Chat/ChatBox.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import styled from 'styled-components';
3 | import styledItems from '../../../../styled-items';
4 |
5 | export const ChatBoxStyle = styled.div`
6 | border: 1px solid #5AC4FF;
7 | background-color: ${styledItems.secondaryBlue};
8 | min-height: 60%;
9 | max-height: 60%;
10 | overflow: auto;
11 | margin: 1rem;
12 | border-radius: 5px;
13 | `;
14 |
15 | export function ChatBox({ chat }) {
16 | console.log({chat});
17 | return (
18 |
19 | {chat}
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/client/components/ChatPage/ChatBox/Chat/ChatBoxHeader.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | // CONTEXT API IMPORT
4 | import { useStateValue , StateContext , createContext} from '../../../../StateProvider';
5 | import styledItems from '../../../../styled-items';
6 |
7 | const StyledHeader = styled.div`
8 | color: ${styledItems.darkGray};
9 | font-size: 1.5em;
10 | display: flex;
11 | justify-content: flex-end;
12 | align-items: center;
13 | `;
14 |
15 | const StyledImg = styled.img`
16 | width: 36px;
17 | height: 36px;
18 | margin: 10px;
19 | border-radius: 18px;
20 | border: 1px solid ${styledItems.darkGray}
21 | `;
22 |
23 | export function ChatBoxHeader(props) {
24 | const selectedPartner = props.selectedPartner;
25 | const nameString = `${selectedPartner.firstName} & ${selectedPartner.petInfo.name}`;
26 | const userAvatar = selectedPartner.avatarUrl;
27 | const petAvatar = selectedPartner.petInfo.avatarUrl;
28 | return
{nameString};
29 | }
30 |
--------------------------------------------------------------------------------
/client/components/ChatPage/ChatBox/Chat/InputContainer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import styledItems from '../../../../styled-items';
4 |
5 | export const InputStyle = styled.div`
6 | text-align: center;
7 | margin: .7em;
8 | `;
9 |
10 | const StyledInput = styled.input`
11 | padding: 10px;
12 | width: 80%;
13 | `;
14 |
15 | const StyledButton = styled.button`
16 | background-color: #5AC4FF;
17 | color: #fff;
18 | height: 2rem;
19 | width: 4rem;
20 | border-radius: 5px;
21 | border: 1px solid #2cb4fd;
22 | margin-left: 1rem;
23 | box-shadow: 1px 1px 1px #5AC4FF;
24 | &:hover {
25 | cursor: pointer;
26 | background-color: ${styledItems.lightGray};
27 | color: ${styledItems.darkGray};
28 | }
29 | `;
30 |
31 | export function InputContainer() {
32 | return (
33 |
34 |
35 |
39 |
40 |
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/client/components/ChatPage/ChatBox/Chat/Message.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | // CONTEXT API IMPORT
4 | import { useStateValue , StateContext , createContext} from '../../../../StateProvider';
5 | import styledItems from '../../../../styled-items';
6 |
7 | const StyledText = styled.div`
8 | color: ${styledItems.darkGray};
9 | font-size: 1.2em;
10 | display: flex;
11 | justify-content: flex-end;
12 | align-items: center;
13 | `;
14 |
15 | const StyledPartnerText = styled.div`
16 | color: ${styledItems.darkGray};
17 | font-size: 1.2em;
18 | display: flex;
19 | justify-content: flex-start;
20 | align-items: center;
21 | `;
22 |
23 | const StyledImg = styled.img`
24 | width: 30px;
25 | height: 30px;
26 | margin: 6px;
27 | border-radius: 15px;
28 | border: 1px solid ${styledItems.darkGray}
29 | `;
30 |
31 | const StyledTime = styled.span`
32 | font-size: .08em;
33 | color: ${styledItems.lightGray};
34 | `;
35 |
36 |
37 | export function Message({ messageInfo, selectedPartner, currentId }) {
38 | const [{ userInfo , petInfo }, dispatch] = useStateValue();
39 | const timeString = messageInfo.time
40 | const textString = `${messageInfo.text}`;
41 | const partnerAvatar = selectedPartner.avatarUrl;
42 | const partnerPetAvatar = selectedPartner.petInfo.avatarUrl;
43 | const userAvatar = userInfo.avatarUrl;
44 | const userPetAvatar = petInfo.avatarUrl;
45 |
46 | let userItem =
{userInfo.firstName}: {textString}{timeString};
47 | let partnerItem =
{timeString}{selectedPartner.firstName}: {textString}
48 | let chatItem = userItem;
49 | if (messageInfo.userID != currentId) chatItem = partnerItem;
50 | return (
51 | //
52 | //
{messageInfo.username}
53 | //
{messageInfo.time}
54 | //
{messageInfo.text}
55 | //
56 |
57 |
{chatItem}
58 |
59 | );
60 |
61 |
62 | // return
{nameString};
63 | }
64 |
65 |
66 | {/*
)
37 | }
38 | setComponentMessages(messageList);
39 | // const div = document.createElement('div');
40 | // div.classList.add('message');
41 | // div.innerHTML = `${message.username} ${message.time}
${message.text}
`;
42 | // document.querySelector('.chat-messages').appendChild(div);
43 | }
44 |
45 |
46 |
47 | useEffect(() => {
48 | const chatForm = document.getElementById('chat-form');
49 | const chatMessages = document.querySelector('.chat-box');
50 |
51 | const socket = io.connect('http://localhost:3000');
52 |
53 | // Message from server
54 |
55 | socket.on('message', (message) => {
56 | console.log("message: ", message);
57 | outputMessage(message);
58 |
59 | // Scroll down
60 | chatMessages.scrollTop = chatMessages.scrollHeight;
61 | });
62 |
63 | chatForm.addEventListener('submit', (e) => {
64 | e.preventDefault();
65 |
66 | // Get message text
67 | const msg = e.target.elements.msg.value;
68 |
69 | const send = formatMessage(userInfo.firstName, msg, userInfo._id);
70 | console.log(send);
71 | // Emitting a message to the server
72 | socket.emit('chatMessage', send);
73 |
74 | // Clear input
75 | e.target.elements.msg.value = '';
76 | e.target.elements.msg.focus();
77 | });
78 | }, []);
79 |
80 | return (
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | );
92 | }
93 |
--------------------------------------------------------------------------------
/client/components/ChatPage/ChatPage.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { UserList } from './UserList/UserList.jsx';
5 | import { ChatContainer } from './ChatBox/ChatContainer.jsx';
6 | import { useStateValue , StateContext } from '../../StateProvider';
7 |
8 | const ChatPageStyle = styled.div`
9 | display: flex;
10 | min-height: 80%;
11 | min-width: 100%;
12 | margin: 0 10px;
13 | `;
14 |
15 | export function ChatPage() {
16 | //use context to pull down matchlist
17 | const [{ userInfo, petInfo, matchList}, dispatch] = useStateValue();
18 | console.log("MATCHLIST HERE, ", matchList);
19 | // const matchList = [
20 | // {
21 | // _id: 564345532,
22 | // firstName: 'Gary',
23 | // location: 'Los Angeles, CA',
24 | // age: '28',
25 | // avatarUrl: 'https://picsum.photos/id/117/300/150',
26 | // activities: {
27 | // coffee: 'i like starbucks',
28 | // hiking: 'not much but sure',
29 | // walking: 'why?',
30 | // },
31 | // petInfo: {
32 | // name: 'Chico',
33 | // age: '2',
34 | // breed: 'Pitbull Terrier Mix',
35 | // size: 'Medium',
36 | // avatarUrl: 'https://picsum.photos/id/237/300/150/',
37 | // },
38 | // },
39 | // {
40 | // _id: 1007,
41 | // firstName: 'Kevin',
42 | // location: 'Los Angeles, CA',
43 | // age: '28',
44 | // avatarUrl: 'https://picsum.photos/id/197/300/150/',
45 | // activities: {
46 | // xcv: 'i like starbucks',
47 | // asdf: 'test',
48 | // },
49 | // petInfo: {
50 | // name: 'Chachi',
51 | // age: '2',
52 | // breed: 'Pitbull Terrier Mix',
53 | // size: 'Medium',
54 | // avatarUrl: 'https://picsum.photos/id/222/300/150/',
55 | // },
56 | // },
57 | // {
58 | // _id: 3001,
59 | // firstName: 'John',
60 | // location: 'Los Angeles, CA',
61 | // age: '28',
62 | // avatarUrl: 'https://picsum.photos/id/111/300/150/',
63 | // activities: { asdf: 'i like starbucks' },
64 | // petInfo: {
65 | // name: 'Luna',
66 | // age: '2',
67 | // breed: 'Kitty Kat',
68 | // size: 'Tiny thang',
69 | // avatarUrl: 'https://picsum.photos/id/217/300/150/',
70 | // },
71 | // },
72 | // ];
73 |
74 | const [selectedPartner, setSelectedPartner] = useState(null);
75 | if (selectedPartner) {
76 | return (
77 |
78 |
83 |
84 |
85 | );
86 | } else {
87 | return (
88 |
89 |
94 | {/* empty divs for placeholders for styling */}
95 |
96 |
97 |
98 | );
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/client/components/ChatPage/PartnerInfo/PartnerInfo.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { Avatar } from './PartnerInfo/Avatar.jsx';
4 | import { PartnerActivitiesList } from './PartnerInfo/PartnerActivitiesList.jsx';
5 | import { PartnerInfoItem } from './PartnerInfo/PartnerInfoItem.jsx';
6 | import { PartnerPetInfoItem } from './PartnerInfo/PartnerPetInfoItem.jsx';
7 |
8 | const PartnerInfoStyle = styled.div`
9 | min-height: 100%;
10 | min-width: 40%;
11 | display: flex;
12 | flex-direction: column;
13 | align-items: center;
14 | border: 2px solid #aedfff;
15 | `;
16 |
17 | export function PartnerInfo({ selectedPartner }) {
18 | const {
19 | firstName,
20 | location,
21 | age,
22 | // avatarUrl,
23 | activities,
24 | petInfo,
25 | } = selectedPartner;
26 | const partnerInfo = { firstName, location, age };
27 | console.log("activity", activities);
28 |
29 | return (
30 |
31 |
32 | {/* */}
33 |
34 | {/* */}
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/client/components/ChatPage/PartnerInfo/PartnerInfo/Avatar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | const AvatarStyles = styled.div`
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | min-width: 90%;
9 | width: 90%:
10 | min-height: 200px;
11 | max-height: 150px;
12 | padding: 10px;
13 | transition: transform 0.4s;
14 | `;
15 |
16 | const AvatarImgStyles = styled.img`
17 | max-width: 50px;
18 | max-height: 50px;
19 |
20 | &:hover {
21 | transform: scale(1.4) rotate(0.1deg);
22 | transition: 0.6s ease-in;
23 | cursor: pointer;
24 | }
25 | `;
26 |
27 | export function Avatar({ url }) {
28 | return (
29 |
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/client/components/ChatPage/PartnerInfo/PartnerInfo/PartnerActivitiesList.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | const PartnerActivitiesListStyles = styled.div`
5 | display: flex;
6 | flex-direction: column;
7 | align-items: center;
8 | width: 70%;
9 | height: 150px;
10 | background-color: #5ac4ff;
11 | margin-top: 10px;
12 | overflow: auto;
13 | margin: 10px;
14 |
15 | h3 {
16 | margin-top: 10px;
17 | }
18 |
19 | div {
20 | display: flex;
21 | width: 100%;
22 | max-width: 100%;
23 | justify-content: space-evenly;
24 | white-space: wrap;
25 |
26 | div {
27 | margin: 5px 0;
28 | }
29 | }
30 | `;
31 |
32 | export function PartnerActivitiesList({ activities }) {
33 | const activitiesJSX = [];
34 | for (const activity in activities) {
35 | activitiesJSX.push(
36 |
37 |
{formatActivity(activity)}:
38 |
{activities[activity]}
39 |
40 | );
41 | }
42 | return (
43 |
44 | Activities:
45 | {activitiesJSX}
46 |
47 | );
48 | }
49 |
50 | function formatActivity(str) {
51 | return str[0].toUpperCase() + str.slice(1).toLowerCase();
52 | }
53 |
--------------------------------------------------------------------------------
/client/components/ChatPage/PartnerInfo/PartnerInfo/PartnerInfoItem.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | const PartnerInfoItemStyles = styled.div`
5 | display: flex;
6 | flex-direction: column;
7 | justify-content: center;
8 | align-items: center;
9 | width: 70%;
10 | height: 150px;
11 | background-color: #5ac4ff;
12 | overflow: scroll;
13 | margin: 10px;
14 | `;
15 |
16 | export function PartnerInfoItem({ partnerInfo }) {
17 | const { firstName, age, location } = partnerInfo;
18 | return (
19 |
20 | {firstName}
21 | Location: {location}
22 | Age: {age} years old
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/client/components/ChatPage/PartnerInfo/PartnerInfo/PartnerPetInfoItem.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | const PartnerPetInfoStyles = styled.div`
5 | display: flex;
6 | flex-direction: column;
7 | justify-content: center;
8 | align-items: center;
9 | width: 70%;
10 | height: 150px;
11 | background-color: #5ac4ff;
12 | overflow: scroll;
13 | margin: 10px;
14 | `;
15 |
16 | export function PartnerPetInfoItem({ petInfo }) {
17 | const { name, age, breed, size } = petInfo;
18 | return (
19 |
20 | {name}
21 | Age: {age} years old
22 | Breed: {breed}
23 | Size: {size}
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/client/components/ChatPage/UserList/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoffeeAndWoof/Woof/275aba4dded583aa8fef595a543dcc88776735da/client/components/ChatPage/UserList/.DS_Store
--------------------------------------------------------------------------------
/client/components/ChatPage/UserList/UserItem/UserItem.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | const UserItemStyle = styled.div`
5 | display: flex;
6 | align-items: center;
7 | border: 1px solid #acacac;
8 | margin: 5px;
9 | min-height: 60px;
10 | max-height: 60px;
11 | background-color: ${({ isCurrentMatchedPartner }) =>
12 | isCurrentMatchedPartner ? '#5ac4ff' : 'transparent'};
13 |
14 | &:hover {
15 | background-color: #aedfff;
16 | transition: 1.5s all ease-out;
17 | cursor: pointer;
18 | }
19 | `;
20 |
21 | const AvatarStyle = styled.img`
22 | max-height: 40px;
23 | min-height: 40px;
24 | max-width: 40px;
25 | min-width: 40px;
26 | border-radius: 50%;
27 | width: 60%;
28 |
29 | &:hover {
30 | transform: scale(1.4);
31 | transition: 0.3s ease-in;
32 | }
33 | `;
34 |
35 | const LeftAvatarStyle = styled.div`
36 | padding-left: 50px;
37 | width: 60%;
38 | display: flex;
39 | justify-content: center;
40 | `;
41 |
42 | const RightAvatarStyle = styled.div`
43 | padding-right: 50px;
44 | width: 40%;
45 | display: flex;
46 | justify-content: space-evenly;
47 | padding: 0 10px;
48 | transition: transform 0.4s;
49 | `;
50 |
51 | const PartnersNameStyle = styled.h4`
52 | font-size: 1.3rem;
53 | color: #636363;
54 | `;
55 |
56 | export function UserItem({ user, setSelectedPartner, selectedPartner }) {
57 | const {firstName, petInfo, avatarUrl } = user;
58 | // const {firstName } = user;
59 | console.log(JSON.stringify(user));
60 |
61 | //check to see if user is the selectedPartner;
62 | let isCurrentMatchedPartner;
63 | if (user && selectedPartner) {
64 | isCurrentMatchedPartner = selectedPartner._id === user._id;
65 | }
66 |
67 | return (
68 |
69 |
70 | setSelectedPartner(user)}>
71 | {firstName} & {petInfo.name}
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | );
81 | }
82 |
--------------------------------------------------------------------------------
/client/components/ChatPage/UserList/UserList.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import { UserItem } from './UserItem/UserItem.jsx';
4 |
5 | const UserListStyle = styled.div`
6 | border: 2px solid #aedfff;
7 | min-height: 100%;
8 | min-width: 25%;
9 | max-width: 25%;
10 | overflow: scroll;
11 | `;
12 |
13 | export function UserList({ matchList, setSelectedPartner, selectedPartner }) {
14 | console.log("matchList",matchList);
15 |
16 | const displayMatchList = matchList.map((user, idx) => {
17 | return (
18 |
26 | );
27 | });
28 |
29 | return {displayMatchList};
30 | }
31 |
--------------------------------------------------------------------------------
/client/components/Header/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoffeeAndWoof/Woof/275aba4dded583aa8fef595a543dcc88776735da/client/components/Header/.DS_Store
--------------------------------------------------------------------------------
/client/components/Header/Header.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import styled from 'styled-components';
4 | // CONTEXT API IMPORT
5 | import { useStateValue, StateContext } from '../../StateProvider';
6 | import styledItems from '../../styled-items';
7 | import UserInfo from './UserInfo/UserInfo.jsx';
8 |
9 | const HeaderBG = styled.div`
10 | background-color: ${styledItems.primaryBlue};
11 | height: 130px;
12 | padding: 20px;
13 | display: flex;
14 | justify-content: space-between;
15 | width: 100%;
16 | `;
17 |
18 | const Logo = styled.div`
19 | font-size: 2em;
20 | color: ${styledItems.darkGray};
21 | font-weight: 100;
22 | `;
23 |
24 | const BlackText = styled.span`
25 | color: ${styledItems.black};
26 | font-weight: 800;
27 | `;
28 |
29 | const Header = () => {
30 | return (
31 |
32 |
33 | Coffee&Woof
34 |
35 |
36 |
37 | );
38 | };
39 |
40 | export default Header;
41 |
--------------------------------------------------------------------------------
/client/components/Header/UserInfo/UserInfo.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Link, Redirect} from "react-router-dom";
3 | import styled from 'styled-components';
4 | import { useStateValue , StateContext } from '../../../StateProvider';
5 | import styledItems from '../../../styled-items';
6 | import FacebookLogin from 'react-facebook-login/dist/facebook-login-render-props'
7 |
8 | const Register = styled.div`
9 | color: ${styledItems.darkGray};
10 | font-size: 1.4em;
11 | `;
12 |
13 | const OuterDiv = styled.div`
14 | margin-right: 100px;
15 | &:hover {
16 | cursor: pointer;
17 | }
18 | `;
19 |
20 | const StyledImg = styled.img`
21 | width: 30px;
22 | height: 30px;
23 | border-radius: 15px;
24 | margin: 10px;
25 | `;
26 |
27 | const OverallDiv = styled.div`
28 | display: flex;
29 | flex-direction: column;
30 | `;
31 |
32 | const LineDiv = styled.div`
33 | display: flex;
34 | align-items: center;
35 | `;
36 |
37 |
38 | const UserInfo = () => {
39 | // CONTEXT API, RELEVENT STATE ELEMENTS
40 | const [{ userInfo, petInfo }, dispatch] = useStateValue();
41 |
42 | // FACEBOOK RESPONSE
43 | const responseFacebook = response => {
44 | if (response.accessToken) {
45 | // send user data to DB
46 | fetch('/api/login', {
47 | method: 'POST',
48 | headers: {
49 | "Content-Type": "Application/JSON",
50 | },
51 | body: JSON.stringify(response)
52 | })
53 | .then((res) => res.json())
54 | .then((data) => {
55 | // data contains email, first_name, full_name, profile_img
56 | console.log('response from server', data);
57 | const info = data[0];
58 | dispatch({
59 | type: 'clickLogin',
60 | id: info._id,
61 | full_name: info.full_name,
62 | first_name: info.first_name,
63 | email: info.email,
64 | profile_img: info.profile_img,
65 | user_age: info.user_age,
66 | location: info.location,
67 | dog_name: info.dog_name,
68 | dog_image: info.dog_image,
69 | dog_age: info.dog_age,
70 | dog_size: info.dog_size,
71 | dog_breed: info.dog_breed,
72 | preferred_activities: info.preferred_activities,
73 | });
74 | })
75 | .catch((err) => console.log('POST: FB info to DB ERROR: ', err));
76 |
77 | // send user to destination
78 | // console.log(`You're logged in ${response.name}, ${response.email}, ${response.picture.data.url}`);
79 | }
80 | }
81 |
82 | const modifyDirection = () => {
83 | redirection = ;
84 | }
85 |
86 | // CONDITIONAL RENDERING OF CORNER DIV
87 | let cornerDiv;
88 | let redirection;
89 | if(userInfo.fullName == '') {
90 | cornerDiv =
91 | (login with facebook)}
96 | />;
97 | } else if(petInfo.name == '') {
98 | cornerDiv = {userInfo.firstName};
99 | redirection = ;
100 | } else {
101 | //console.log(JSON.stringify(userInfo));
102 | cornerDiv = {userInfo.firstName}{petInfo.name};
103 | redirection = ;
104 | }
105 |
106 | return (
107 |
108 | {cornerDiv}
109 | {redirection}
110 |
111 | );
112 | }
113 |
114 | export default UserInfo;
--------------------------------------------------------------------------------
/client/components/Login/FacebookLogin.jsx:
--------------------------------------------------------------------------------
1 | import { useHistory } from 'react-router-dom';
2 | import React, { useState, useEffect } from 'react';
3 | import FacebookLogin from "react-facebook-login";
4 |
5 | const FBLogin = props => {
6 | const [isLoggedIn, setIsLoggedIn] = useState(false);
7 | const [name, setName] = useState('');
8 | const [email, setEmail] = useState('');
9 | const [picture, setPicture] = useState('');
10 | //const history = useHistory();
11 |
12 | //let fbContent;
13 | const handleClick = event => 0;
14 |
15 | const responseFacebook = response => {
16 | if (response.accessToken) {
17 | setIsLoggedIn(true);
18 | setEmail(response.email);
19 | setName(response.name);
20 | setPicture(response.picture);
21 | console.log(`Logging in`);
22 | // send user data to DB
23 | fetch('/api/login', {
24 | method: 'POST',
25 | headers: {
26 | "Content-Type": "Application/JSON",
27 | },
28 | body: JSON.stringify(response)
29 | })
30 | .then((res) => res.json())
31 | .then((data) => {
32 | console.log('response from server', data);
33 | })
34 | .catch((err) => console.log('POST: FB info to DB ERROR: ', err));
35 | // send user to destination
36 | console.log(`You're logged in ${response.name}`);
37 | }
38 | }
39 |
40 | return (
41 |
48 | );
49 | }
50 |
51 | export default FBLogin;
--------------------------------------------------------------------------------
/client/components/Login/Login.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Link} from "react-router-dom";
3 | import styled from 'styled-components';
4 | // CONTEXT API IMPORT
5 | import { useStateValue , StateContext } from '../../StateProvider';
6 | import styledItems from '../../styled-items';
7 |
8 | const StyledDiv = styled.div`
9 | width: 100%;
10 | display: flex;
11 | justify-content: center;
12 | align-items: center;
13 | `;
14 |
15 | const StyledText = styled.div`
16 | width: 300px;
17 | color: ${styledItems.darkGray};
18 | font-size: 2em;
19 | text-align: center;
20 | `;
21 |
22 | const Login = () => {
23 | // CONTEXT API, RELEVENT STATE ELEMENTS
24 | const [{ userName }, dispatch] = useStateValue();
25 | return (
26 |
27 |
28 | Meet your pup's new best friend today....
29 |
30 |
31 | );
32 | }
33 |
34 | export default Login;
--------------------------------------------------------------------------------
/client/components/ProfilePage/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoffeeAndWoof/Woof/275aba4dded583aa8fef595a543dcc88776735da/client/components/ProfilePage/.DS_Store
--------------------------------------------------------------------------------
/client/components/ProfilePage/Activities/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoffeeAndWoof/Woof/275aba4dded583aa8fef595a543dcc88776735da/client/components/ProfilePage/Activities/.DS_Store
--------------------------------------------------------------------------------
/client/components/ProfilePage/Activities/Activities.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Link} from "react-router-dom";
3 | import styled from 'styled-components';
4 | // CONTEXT API IMPORT
5 | import { useStateValue , StateContext } from '../../../StateProvider';
6 | import ActivityItem from './ActivityItem/ActivityItem.jsx';
7 | import styledItems from '../../../styled-items';
8 |
9 | const OuterDiv = styled.div`
10 | width: 35%;
11 | align-self: flex-start;
12 | background-color: ${styledItems.lightGray};
13 | min-height: 600px;
14 | margin: 25px;
15 | padding: 20px;
16 | `;
17 | const Heading = styled.div`
18 | font-size: 1.6em;
19 | color: ${styledItems.darkGray};
20 | `;
21 |
22 | const stockActivities = [
23 | 'Coffee',
24 | 'Hiking',
25 | 'Birdwatch',
26 | 'Running',
27 | 'Fetch',
28 | 'Swimming',
29 | 'Grooming',
30 | 'Beach',
31 | ];
32 |
33 | const activitiesList = [];
34 |
35 |
36 | const Activities = (props) => {
37 |
38 | // stockActivities.forEach((activity, index) => {
39 | // const item = ;
40 | // activitiesList.push(item);
41 | // });
42 |
43 | // CONTEXT API, RELEVENT STATE ELEMENTS
44 | const [{ userName }, dispatch] = useStateValue();
45 | return (
46 |
47 | Activities
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | );
58 | }
59 |
60 | export default Activities;
--------------------------------------------------------------------------------
/client/components/ProfilePage/Activities/ActivityItem/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CoffeeAndWoof/Woof/275aba4dded583aa8fef595a543dcc88776735da/client/components/ProfilePage/Activities/ActivityItem/.DS_Store
--------------------------------------------------------------------------------
/client/components/ProfilePage/Activities/ActivityItem/ActivityItem.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {Link} from "react-router-dom";
3 | import styled from 'styled-components';
4 | // CONTEXT API IMPORT
5 | import { useStateValue , StateContext , createContext} from '../../../../StateProvider';
6 | import styledItems from '../../../../styled-items';
7 |
8 | const OuterDiv = styled.div`
9 | display: flex;
10 | flex-direction: column;
11 | margin: 25px;
12 | `;
13 |
14 | const ButtonDiv = styled.div`
15 | width: 40%;
16 | height: 50px;
17 | border: 1px solid ${styledItems.darkGray};
18 | border-radius: 2px;
19 | background-color: ${styledItems.secondaryBlue};
20 | display: flex;
21 | justify-content: center;
22 | align-items: center;
23 | color: ${styledItems.darkGray};
24 | &:hover {
25 | background-color: ${styledItems.primaryBlue};
26 | color: ${styledItems.white};
27 | cursor: pointer;
28 | }
29 | `;
30 |
31 | const StyledForm = styled.form`
32 | margin: 0;
33 | font-size: 1em;
34 | `;
35 |
36 | const StyledInput = styled.input`
37 | padding: 5px;
38 | margin-top: 4px;
39 | width: 100%;
40 | border: 1px solid ${styledItems.darkGray};
41 | border-radius: 4px;
42 | color: ${styledItems.darkGray}:
43 | `;
44 |
45 | const ActivityItem = (props) => {
46 | // CONTEXT API, RELEVENT STATE ELEMENTS
47 | const activityName = props.activity;
48 | const actId = props.id;
49 | const [{ userInfo }, dispatch] = useStateValue();
50 |
51 | let formItem;
52 | if(activityName in userInfo.activities) {
53 | formItem =
54 |
55 |
56 | ;
57 | }
58 |
59 | const activityClick = () => {
60 | if(activityName in userInfo.activities) {
61 | dispatch({
62 | type: 'removeActivity',
63 | activity: activityName,
64 | })
65 | } else {
66 | dispatch({
67 | type: 'addActivity',
68 | activity: activityName,
69 | })
70 | }
71 | }
72 |
73 | return (
74 |
75 | activityClick()}>
76 | {activityName}
77 |
78 | {formItem}
79 |
80 | );
81 | }
82 |
83 | export default ActivityItem;
--------------------------------------------------------------------------------
/client/components/ProfilePage/Profile/Profile.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Link} from "react-router-dom";
3 | import styled from 'styled-components';
4 | // CONTEXT API IMPORT
5 | import { useStateValue , StateContext } from '../../../StateProvider';
6 | import styledItems from '../../../styled-items';
7 |
8 | const OuterDiv = styled.div`
9 | width: 100;
10 | align-self: flex-start;
11 | background-color: ${styledItems.white};
12 | border: 3px solid ${styledItems.lightGray};
13 | margin: 25px;
14 | padding: 20px;
15 | `;
16 | const Heading = styled.div`
17 | font-size: 1.4em;
18 | color: ${styledItems.darkGray};
19 | `;
20 | const LesserText = styled.div`
21 | font-size: .8em;
22 | color: ${styledItems.white};
23 | `;
24 |
25 | const StyledImg = styled.img`
26 | width: 50px;
27 | height: 50px;
28 | border-radius: 25px;
29 | `;
30 |
31 | const HumanInfo = styled.div`
32 | display: flex;
33 | justify-content: space-around;
34 | align-items: center;
35 | `;
36 |
37 | const CreatureTextArea = styled.div`
38 | display: flex;
39 | flex-direction: column;
40 | align-items: center;
41 | `;
42 |
43 | const HumanTextDesc = styled.div`
44 | display: flex;
45 | flex-direction: column;
46 | align-items: center;
47 | padding: 20px;
48 | margin-top: 10px;
49 | background-color: ${styledItems.primaryBlue};
50 | color: ${styledItems.white};
51 | `;
52 |
53 | const StyledForm = styled.form`
54 | margin: 0;
55 | font-size: 1em;
56 | display: flex;
57 | align-items: center;
58 | justify-content: center;
59 | `;
60 |
61 | const StyledInputLeft = styled.input`
62 | padding: 5px;
63 | margin-top: 4px;
64 | border: none;
65 | background-color: ${styledItems.primaryBlue};
66 | color: ${styledItems.white};
67 | text-align: left;
68 | width: 100px;
69 | border-bottom: 1px solid white;
70 | `;
71 |
72 | const StyledInputRight = styled.input`
73 | padding: 5px;
74 | margin-top: 4px;
75 | border: none;
76 | background-color: ${styledItems.primaryBlue};
77 | color: ${styledItems.white};
78 | text-align: right;
79 | width: 60px;
80 | border-bottom: 1px solid white;
81 | `;
82 |
83 | const StyledInputCenter = styled.input`
84 | padding: 5px;
85 | margin-top: 4px;
86 | border: none;
87 | background-color: ${styledItems.primaryBlue};
88 | color: ${styledItems.white};
89 | text-align: center;
90 | width: 100px;
91 | border-bottom: 1px solid white;
92 | `;
93 |
94 | const DogInfo = styled.div`
95 | display: flex;
96 | justify-content: space-around;
97 | align-items: center;
98 | `;
99 |
100 | const DogNameInput = styled.input`
101 | padding: 5px;
102 | margin-top: 4px;
103 | border: none;
104 | font-size: 1.4em;
105 | background-color: ${styledItems.white};
106 | color: ${styledItems.darkGray};
107 | text-align: center;
108 | `;
109 |
110 | const Profile = (props) => {
111 | // CONTEXT API, RELEVENT STATE ELEMENTS
112 | const [{ userInfo , petInfo }, dispatch] = useStateValue();
113 | console.log(userInfo);
114 | return (
115 |
116 |
117 |
118 |
119 | {userInfo.fullName}
120 |
121 | {userInfo.email}
122 |
123 |
124 | years old
125 |
126 |
127 | I live in
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 | );
159 | }
160 |
161 | export default Profile;
--------------------------------------------------------------------------------
/client/components/ProfilePage/ProfilePage.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from "react";
2 | import {Link} from "react-router-dom";
3 | import styled from 'styled-components';
4 | // CONTEXT API IMPORT
5 | import { useStateValue , StateContext } from '../../StateProvider';
6 | import styledItems from '../../styled-items';
7 | import Activities from './Activities/Activities.jsx';
8 | import Profile from './Profile/Profile.jsx';
9 |
10 | const OuterDiv = styled.div`
11 | width: 100%;
12 | display: flex;
13 | justify-content: space-between;
14 | height: 100%;
15 | `;
16 |
17 | const RightDiv = styled.div`
18 | display: flex;
19 | flex-direction: column;
20 | height: 100%;
21 | width: 40%;
22 | `;
23 |
24 | const SaveButton = styled.div`
25 | font-size: 1.7em;
26 | padding: 10px;
27 | width: 180px;
28 | border: 1px solid ${styledItems.darkGray};
29 | border-radius: 2px;
30 | background-color: ${styledItems.primaryBlue};
31 | display: flex;
32 | justify-content: center;
33 | align-items: center;
34 | color: ${styledItems.white};
35 | justify-self: flex-end;
36 | text-decoration: none;
37 | &:hover {
38 | background-color: ${styledItems.darkGray};
39 | color: ${styledItems.secondaryBlue};
40 | cursor: pointer;
41 | }
42 | `;
43 |
44 | class ProfilePage extends Component {
45 | constructor (props) {
46 | super(props);
47 | this.state = {
48 | userLocation: '',
49 | userAge: '',
50 | petName: '',
51 | petAge: '',
52 | petBreed: '',
53 | petSize: '',
54 | petAvatar: '',
55 | Coffee: '',
56 | Hiking: '',
57 | Birdwatch: '',
58 | Running: '',
59 | Fetch: '',
60 | Swimming: '',
61 | Grooming: '',
62 | Beach: '',
63 | }
64 | this.handleChange = this.handleChange.bind(this);
65 | }
66 |
67 | handleChange(event) {
68 | const value = event.target.value;
69 | this.setState({
70 | ...this.state,
71 | [event.target.name]: value,
72 | })
73 | }
74 |
75 | componentDidMount() {
76 | const [{ userInfo, petInfo }, dispatch] = this.context;
77 | console.log("LOG TEST",userInfo);
78 | this.setState({
79 | userLocation: userInfo.location,
80 | userAge: userInfo.age,
81 | petName: petInfo.name,
82 | petAge: petInfo.age,
83 | petBreed: petInfo.breed,
84 | petSize: petInfo.size,
85 | petAvatar: petInfo.avatarUrl,
86 | Coffee: userInfo.activities.Coffee || '',
87 | Hiking: userInfo.activities.Hiking || '',
88 | Birdwatch: userInfo.activities.Birdwatch || '',
89 | Running: userInfo.activities.Running || '',
90 | Fetch: userInfo.activities.Fetch || '',
91 | Swimming: userInfo.activities.Swimming || '',
92 | Grooming: userInfo.activities.Grooming || '',
93 | Beach: userInfo.activities.Beach || '',
94 | })
95 | }
96 |
97 | static contextType = StateContext;
98 | render() {
99 | const [{ userInfo, petInfo}, dispatch] = this.context;
100 |
101 | const saveProfile = () => {
102 | const stockActivities = [
103 | 'Coffee',
104 | 'Hiking',
105 | 'Birdwatch',
106 | 'Running',
107 | 'Fetch',
108 | 'Swimming',
109 | 'Grooming',
110 | 'Beach',
111 | ];
112 |
113 | userInfo.location = this.state.userLocation;
114 | userInfo.age = this.state.userAge;
115 | userInfo.activities = {};
116 |
117 | petInfo.name = this.state.petName,
118 | petInfo.age = this.state.petAge,
119 | petInfo.breed = this.state.petBreed,
120 | petInfo.size = this.state.petSize,
121 | petInfo.avatarUrl = this.state.petAvatar,
122 |
123 | stockActivities.forEach(activity => {
124 | if(this.state[activity] != '') {
125 | userInfo.activities[activity] = this.state[activity];
126 | }
127 | })
128 | // console.log(this.state);
129 | // console.log(stockActivities);
130 | // console.log(userInfo);
131 |
132 | // dispatch({
133 | // type: 'saveProfile',
134 | // userInfo,
135 | // petInfo,
136 | // });
137 |
138 | // convert state to mongoDB format
139 | const mongoObj = {};
140 | mongoObj.first_name = userInfo.firstName;
141 | mongoObj.full_name = userInfo.fullName;
142 | mongoObj.email = userInfo.email;
143 | mongoObj.profile_img = userInfo.avatarUrl;
144 | mongoObj.user_age = userInfo.age;
145 | mongoObj.location = userInfo.location;
146 | mongoObj.dog_name = petInfo.name;
147 | mongoObj.dog_image = petInfo.avatarUrl;
148 | mongoObj.dog_age = petInfo.age;
149 | mongoObj.dog_size = petInfo.size;
150 | mongoObj.dog_breed = petInfo.breed;
151 | mongoObj.preferred_activities = [];
152 | Object.entries(userInfo.activities).forEach(([activity, description]) => {
153 | mongoObj.preferred_activities.push({'activity': activity, 'description': description })
154 | });
155 | // console.log(mongoObj);
156 |
157 | // console.log('===========>', userInfo._id)
158 | // send user data & pet data to DB
159 | fetch(`/api/updateUserData/${userInfo._id}`, {
160 | method: 'PUT',
161 | headers: {
162 | "Content-Type": "Application/JSON",
163 | },
164 | body: JSON.stringify(mongoObj)
165 | })
166 | .then((res) => res.json())
167 | .then((data) => {
168 | const currentUserId = userInfo._id;
169 | console.log('response from server', data);
170 | const matchList = [];
171 | data.forEach(match => {
172 | if(match._id != currentUserId) {
173 | const userObject = {
174 | _id: match._id,
175 | firstName: match.first_name || '',
176 | fullName: match.full_name || '',
177 | location: match.location || '',
178 | age: match.user_age || '',
179 | avatarUrl: match.profile_img || '',
180 | activities: {},
181 | petInfo: {
182 | name: match.dog_name || '',
183 | age: match.dog_age || '',
184 | breed: match.dog_breed || '',
185 | size: match.dog_size || '',
186 | avatarUrl: match.dog_image || '',
187 | },
188 | }
189 | match.preferred_activities.forEach(activity => {
190 | userObject.activities[activity.activity] = activity.description;
191 | });
192 | matchList.push(userObject);
193 | };
194 | });
195 | console.log('HELLO');
196 | dispatch({
197 | type: 'updateMatches',
198 | matchList,
199 | userInfo,
200 | petInfo,
201 | });
202 | })
203 | .catch((err) => console.log('POST: PROFILE INFO to DB ERROR: ', err));
204 | }
205 |
206 | return (
207 |
208 |
209 |
210 |
211 | {saveProfile()}}>Save
212 |
213 |
214 | );
215 | }
216 | }
217 |
218 | export default ProfilePage;
--------------------------------------------------------------------------------
/client/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import App from './App.jsx';
4 | import './styles.css';
5 |
6 |
7 | // const chatForm = document.getElementById('chat-form');
8 | // const chatMessages = document.querySelector('.chat-messages');
9 |
10 | // // Get username and room from URL
11 | // // const { username, room } = Qs.parse(location.search, {
12 | // // ignoreQueryPrefix: true
13 | // // });
14 |
15 | // // console.log(username, room);
16 |
17 | // const socket = io.connect('http://localhost:3000');
18 |
19 | // // socket.emit('joinRoom', ({ username, room }) => {
20 | // // console.log(username, room)
21 | // // });
22 |
23 | // // Message from server
24 | // socket.on('message', message => {
25 | // console.log(message);
26 | // outputMessage(message);
27 |
28 | // // Scroll down
29 | // chatMessages.scrollTop = chatMessages.scrollHeight;
30 | // })
31 |
32 | // chatForm.addEventListener('submit', (e)=> {
33 | // e.preventDefault();
34 |
35 | // // Get message text
36 | // const msg = e.target.elements.msg.value;
37 |
38 | // // Emitting a message to the server
39 | // socket.emit('chatMessage', msg);
40 |
41 | // // Clear input
42 | // e.target.elements.msg.value = '';
43 | // e.target.elements.msg.focus();
44 | // });
45 |
46 |
47 |
48 | // // Output message to DOM
49 | // function outputMessage(message) {
50 | // const div = document.createElement('div');
51 | // div.classList.add('message');
52 | // div.innerHTML = `${message.username} ${message.time}
${message.text}
`;
53 | // document.querySelector('.chat-messages').appendChild(div);
54 | // }
55 |
56 |
57 | render(, document.getElementById('app'));
58 |
--------------------------------------------------------------------------------
/client/main.js:
--------------------------------------------------------------------------------
1 | const chatForm = document.getElementById('chat-form');
2 | const chatMessages = document.querySelector('.chat-messages');
3 |
4 | // Get username and room from URL
5 | // const { username, room } = Qs.parse(location.search, {
6 | // ignoreQueryPrefix: true
7 | // });
8 |
9 | // console.log(username, room);
10 |
11 | const socket = io();
12 |
13 | // socket.emit('joinRoom', ({ username, room }) => {
14 | // console.log(username, room)
15 | // });
16 |
17 | // Message from server
18 | socket.on('message', message => {
19 | console.log(message);
20 | outputMessage(message);
21 |
22 | // Scroll down
23 | chatMessages.scrollTop = chatMessages.scrollHeight;
24 | })
25 |
26 | chatForm.addEventListener('submit', (e)=> {
27 | e.preventDefault();
28 |
29 | // Get message text
30 | const msg = e.target.elements.msg.value;
31 |
32 | // Emitting a message to the server
33 | socket.emit('chatMessage', msg);
34 |
35 | // Clear input
36 | e.target.elements.msg.value = '';
37 | e.target.elements.msg.focus();
38 | });
39 |
40 |
41 |
42 | // Output message to DOM
43 | function outputMessage(message) {
44 | const div = document.createElement('div');
45 | div.classList.add('message');
46 | div.innerHTML = `${message.username} ${message.time}
${message.text}
`;
47 | document.querySelector('.chat-messages').appendChild(div);
48 | }
49 |
--------------------------------------------------------------------------------
/client/styled-items.js:
--------------------------------------------------------------------------------
1 | const styledItems = {
2 | primaryBlue: '#5AC4FF',
3 | secondaryBlue: '#F3F8FF',
4 | black: '#000000',
5 | darkGray: '#4A4A4A',
6 | lightGray: '#FAFAFA',
7 | white: '#FFFFFF',
8 | };
9 |
10 | export default styledItems;
--------------------------------------------------------------------------------
/client/styles.css:
--------------------------------------------------------------------------------
1 | /* @import url("https://fonts.googleapis.com/css2?family=Fredoka+One&display=swap"); */
2 | /* @import url("https://fonts.googleapis.com/css2?family=Black+Ops+One&family=Monoton&family=Shrikhand&display=swap"); */
3 | @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');
4 |
5 | /* :root {
6 | /* --yellow: #ffefa0;
7 | --light-orange: #ffd57e;
8 | --orange: #fca652;
9 | --red: #ac4b1c;
10 |
11 | //our colors
12 | --primaryBlue: #5AC4FF;
13 | --secondaryBlue: #F3F8FF;
14 | --black: #222;
15 | --white: #fff;
16 | --darkGray: #4A4A4A;
17 | --lightGray: #575757;
18 | }
19 | */
20 |
21 | * {
22 | box-sizing: border-box;
23 | margin: 0;
24 | padding: 0;
25 | font-family: 'Roboto';
26 | color: black;
27 | /* color: #636363; */
28 | }
29 |
30 | #app {
31 | width: 100%;
32 | }
33 |
34 | body {
35 | background-color: #f3f8ff;
36 | display: flex;
37 | justify-content: center;
38 | width: 100%;
39 | }
40 |
41 | #msg {
42 | height: 2rem;
43 | width: 15rem;
44 | border-radius: 5px;
45 | border: 1px solid black;
46 | }
47 |
48 | /* .btn {
49 | background-color: #5AC4FF;
50 | color: #fff;
51 | height: 2rem;
52 | width: 4rem;
53 | border-radius: 5px;
54 | border: 1px solid #2cb4fd;
55 | margin-left: 1rem;
56 | box-shadow: 1px 1px 1px #5AC4FF;
57 | } */
58 |
59 | /* OLD SITE STYLING */
60 | /*
61 | * {
62 | box-sizing: border-box;
63 | margin: 0;
64 | padding: 0;
65 | font-family: "Shrikhand", "Arial Narrow", Arial, sans-serif;
66 | color: black;
67 | }
68 | a {
69 | text-decoration: none;
70 | }
71 |
72 | li {
73 | list-style: none;
74 | }
75 | #header-nav {
76 | padding: 20px;
77 | border: 2px solid #ffd57e;
78 | background-color: #ffd57e;
79 | }
80 | #header-nav nav ul {
81 | display: flex;
82 | justify-content: flex-start;
83 | }
84 | #header nav ul li a {
85 | font-size: 1.2rem;
86 | }
87 | #header-nav nav ul li {
88 | margin-right: 10px;
89 | }
90 |
91 | .form-container {
92 | width: 500px;
93 | height: 500px;
94 | margin: 0 auto;
95 | display: flex;
96 | flex-direction: column;
97 | justify-content: center;
98 | align-items: center;
99 | border: 1px solid #222;
100 | border-radius: 6px;
101 | box-shadow: 10px 10px 5px 0px rgba(220, 220, 220, 1);
102 | }
103 |
104 | .input-container {
105 | display: flex;
106 | flex-direction: column;
107 | align-items: center;
108 | }
109 |
110 | .form-container h1 {
111 | font-family: "Shrikhand", cursive;
112 | margin-bottom: 10px;
113 | }
114 |
115 | .input-group input {
116 | margin-top: 5px;
117 | margin-bottom: 5px;
118 | height: 30px;
119 | padding: 0px 10px;
120 | font-size: 16px;
121 | border-radius: 4px;
122 | }
123 |
124 | .login-btn {
125 | background-color: #fca652;
126 | border: 2px solid #fca652;
127 | margin-bottom: 10px;
128 | border-radius: 5px;
129 | cursor: pointer;
130 | }
131 |
132 | .login-btn:hover {
133 | background-color: #ffd57e;
134 | border: 2px solid #ffd57e;
135 | }
136 | .profile {
137 | margin-top: 5px;
138 | display: flex;
139 | flex-direction: column;
140 | align-items: flex-start;
141 | width: fit-content;
142 | }
143 |
144 | .login-btn {
145 | font-size: 1.25em;
146 | display: flex;
147 | flex-grow: 1;
148 | width: 100%;
149 | margin-top: 0.25em;
150 | padding: 0.25em;
151 | align-items: flex-start;
152 | justify-content: center;
153 | }
154 | .feed-container {
155 | margin: 20px auto;
156 | width: 1000px;
157 | border: 1px solid black;
158 | padding: 20px;
159 | }
160 | .title-date {
161 | display: flex;
162 | flex-direction: row;
163 | justify-content: space-between;
164 | margin-bottom: 10px;
165 | /* align-items: flex-start;
166 | }
167 | .title-date h3 {
168 | align-self: center;
169 | }
170 |
171 | .image-container {
172 | margin-top: 20px;
173 | }
174 |
175 | #create-list-item {
176 | padding: 20px 20px 20px 0;
177 | }
178 | #create-list-item label {
179 | margin-right: 10px;
180 | }
181 |
182 | #create-list-item input {
183 | margin-right: 10px;
184 | }
185 |
186 | #bucket-list-container {
187 | width: 100%;
188 | margin: 0 auto;
189 | padding: 20px;
190 | }
191 |
192 | .uncompleted-post-form {
193 | display: flex;
194 | flex-direction: column;
195 | width: 800px;
196 | margin-bottom: 20px;
197 | }
198 |
199 | .completed-list-item,
200 | .uncompleted-list-item {
201 | display: flex;
202 | flex-direction: row;
203 | justify-content: space-between;
204 | width: 800px;
205 | align-items: center;
206 | }
207 |
208 | .uncompleted-post-form button {
209 | margin-bottom: 20px;
210 | } */
211 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
14 |
15 | Coffee&Woof
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "petrospective",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "jest --verbose",
8 | "start": "nodemon server/server.js",
9 | "build": "webpack",
10 | "dev": "concurrently \"webpack-dev-server --open --mode development\" \"nodemon server/server.js\""
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/andrew-lovato/petrospective.git"
15 | },
16 | "author": "",
17 | "license": "ISC",
18 | "bugs": {
19 | "url": "https://github.com/andrew-lovato/petrospective/issues"
20 | },
21 | "homepage": "https://github.com/andrew-lovato/petrospective#readme",
22 | "dependencies": {
23 | "bcrypt": "^5.0.0",
24 | "bcryptjs": "^2.4.3",
25 | "body-parser": "^1.19.0",
26 | "cookie-parser": "^1.4.5",
27 | "cors": "^2.8.5",
28 | "enzyme": "^3.11.0",
29 | "express": "^4.17.1",
30 | "moment": "^2.29.1",
31 | "mongoose": "^5.10.7",
32 | "oauth": "^0.9.15",
33 | "react": "^16.13.1",
34 | "react-dom": "^16.13.1",
35 | "react-facebook-login": "^4.1.1",
36 | "react-router": "^5.2.0",
37 | "socket.io": "^2.3.0",
38 | "styled-components": "^5.2.0"
39 | },
40 | "devDependencies": {
41 | "@babel/core": "^7.11.6",
42 | "@babel/preset-env": "^7.11.5",
43 | "@babel/preset-react": "^7.10.4",
44 | "@babel/plugin-proposal-class-properties": "^7.0.0",
45 | "babel-loader": "^8.1.0",
46 | "concurrently": "^5.3.0",
47 | "cross-env": "^7.0.2",
48 | "css-loader": "^4.3.0",
49 | "fs": "0.0.1-security",
50 | "isomorphic-fetch": "^3.0.0",
51 | "nodemon": "^2.0.4",
52 | "path": "^0.12.7",
53 | "react-router-dom": "^5.2.0",
54 | "sass": "^1.26.11",
55 | "sass-loader": "^10.0.2",
56 | "style-loader": "^1.3.0",
57 | "supertest": "^5.0.0",
58 | "w": "^1.1.0",
59 | "webpack": "^4.44.2",
60 | "webpack-cli": "^3.3.12",
61 | "webpack-dev-server": "^3.11.0"
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/server/controllers/MatchesController.js:
--------------------------------------------------------------------------------
1 | const User = require('../models/UserModels');
2 | const Activities = require('../models/ActivitiesModels');
3 |
4 | const matchesController = {};
5 |
6 | // FINDING AN ACTIVITIES MATCH FOR THE USER BASED ON USER ID.
7 |
8 | matchesController.userActivities = (req, res, next) => {
9 | // console.log(`Requesting for user data`);
10 | const { data } = res.locals;
11 | // console.log('data ---> ', res.locals)
12 | User.findById(data._id, (err, user) => {
13 | if (err) {
14 | return next({ err });
15 | }
16 | const activities = user.preferred_activities;
17 | // Getting an array of the user's activities
18 | const activitiesArray = activities.map((activity) => {
19 | return activity.activity;
20 | });
21 | res.locals.activities = activitiesArray;
22 | // console.log(res.locals.activities)
23 | // console.log(res.locals.activities);
24 | next();
25 | });
26 | };
27 |
28 | matchesController.getUniqueIds = (req, res, next) => {
29 | // console.log(`Requesting for activities array`);
30 | const { activities } = res.locals;
31 |
32 | // Changing all activities to lowercase
33 | const activitiesUpdate = activities.map((activity) => activity.toLowerCase())
34 |
35 | // const matchesArray = []
36 | Activities.find({ name: { $in: activitiesUpdate } })
37 | .then((users) => {
38 | // Loop through array of activities to extract the ids. Expect to receive an array of IDs that are not duplicates
39 | // console.log('users -----> ', users);
40 | const arrayOfIds = [];
41 | users.forEach((activity) => {
42 | arrayOfIds.push(activity.users);
43 | });
44 | // Return a flattened array of IDs with no duplicates. Save result to res.locals
45 | const flattenArrayId = arrayOfIds.flat()
46 |
47 | const result = [];
48 | for (let index = 0; index < flattenArrayId.length; index += 1) {
49 | if (!result.includes(flattenArrayId[index].toString())) result.push(flattenArrayId[index].toString())
50 | }
51 | // console.log(arrayOfIds)
52 | // const uniqueIds = [...new Set(flattenArrayId)];
53 | // console.log('uniqueIds1 -->', uniqueIds);
54 | res.locals.uniqueIds = result;
55 | // console.log('uniqueIDs --> ', res.locals.uniqueIds);
56 | next();
57 | })
58 | .catch((err) => {
59 | console.log(err);
60 | });
61 | }
62 |
63 | //Return list of users with this array
64 | matchesController.returnMatches = (req, res, next) => {
65 | // console.log(`Returning Matches`);
66 | const { uniqueIds } = res.locals;
67 | // console.log(res.locals.uniqueIds);
68 | // console.log(typeof res.locals.uniqueIds[0].toString());
69 |
70 | // // const idString = uniqueIds.map((id) => id.toString())
71 | // // console.log(idString)
72 |
73 | //Find users based on input ID
74 | User.find({
75 | _id: { $in: uniqueIds },
76 | })
77 | .then((data) => {
78 | console.log(data);
79 | res.locals.matchingUsersInfo = data
80 | next()
81 | })
82 | .catch((err) => {
83 | console.log(err);
84 | });
85 | }
86 |
87 |
88 | module.exports = matchesController;
--------------------------------------------------------------------------------
/server/controllers/UserController.js:
--------------------------------------------------------------------------------
1 | const Activities = require('../models/ActivitiesModels');
2 | const User = require('../models/UserModels');
3 |
4 | const userController = {};
5 |
6 | userController.getUserData = async (req, res, next) => {
7 | // get the user id from params
8 | const { id } = req.params;
9 | console.log('Send data for user ID:', id);
10 | // verify that user name is valid
11 | if (!id) {
12 | return next({
13 | log: 'UserController.getUserData ERROR: userName is undefined',
14 | message: { err: 'UserController.getUserData ERROR: Check server logs for details' },
15 | });
16 | }
17 | const result = await User.findById(id).exec();
18 | res.locals.data = result;
19 | return next();
20 | };
21 |
22 |
23 | userController.updateUserData = async (req, res, next) => {
24 |
25 | const { id } = req.params;
26 | console.log('updating id:', id);
27 | const userData = req.body;
28 | if (!id) {
29 | return next({
30 | log: 'UserController.updateUserData ERROR: userName is undefined',
31 | message: { err: 'UserController.updateUserData ERROR: Check server logs for details' },
32 | });
33 | }
34 |
35 | // contains the fields from req.body
36 | const queryResult = await User.findByIdAndUpdate(id, userData, {new: true}); // new true sends back the updated user obj
37 | // update the activites collection
38 | res.locals.data = queryResult;
39 | const onlyActivities = queryResult.preferred_activities.map(obj => {
40 | return obj.activity.toLowerCase();
41 | });
42 | Activities.updateMany({name: {$in: onlyActivities}}, { $push: {users: queryResult._id}})
43 | return next();
44 | };
45 |
46 | userController.deleteUserData = async (req, res, next) => {
47 | // get the user id from params
48 | const { id } = req.params;
49 | console.log('Delete user ID:', id);
50 | // verify that user name is valid
51 | if (!id) {
52 | return next({
53 | log: 'UserController.deleteUserData ERROR: userName is undefined',
54 | message: { err: 'UserController.deleteUserData ERROR: Check server logs for details' },
55 | });
56 | }
57 | const queryResult = await User.deleteOne({_id: id}).exec();
58 | res.locals.data = queryResult;
59 | return next();
60 | };
61 |
62 | userController.createUser = async (req, res, next) => {
63 | const { name, email } = req.body;
64 | const picURL = req.body.picture.data.url;
65 |
66 | // check if user already exists
67 | console.log('==========>', email);
68 | const doesUserExist = await User.find({ email });
69 |
70 | // only create new document if user does not exist
71 | if (doesUserExist.length === 0) {
72 | console.log('============> user does not exist');
73 | const userData = {
74 | full_name: name,
75 | first_name: name.split(' ')[0],
76 | email,
77 | profile_img: picURL
78 | };
79 | const newUser = new User(userData);
80 | newUser.save((e, r) => {
81 | if (e) console.log(e);
82 | const result = fetchUserData({ email });
83 | result.then(result => {
84 | res.locals.data = result;
85 | next();
86 | })
87 | });
88 | } else {
89 | res.locals.data = doesUserExist;
90 | next();
91 | }
92 | }
93 |
94 | const fetchUserData = async (userData) => {
95 | const result = await User.find(userData);
96 | return result;
97 | }
98 |
99 |
100 |
101 |
102 | module.exports = userController;
103 |
--------------------------------------------------------------------------------
/server/models/UserModels.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const MONGO_URI = 'mongodb+srv://woof:codesmith123@woof.qaamj.mongodb.net/woof?retryWrites=true&w=majority';
4 |
5 | mongoose
6 | .connect(MONGO_URI, {
7 | useNewUrlParser: true,
8 | useUnifiedTopology: true,
9 | })
10 | .then(() => console.log('CONNECTED TO MONGO DB'))
11 | .catch((err) => console.log(err));
12 |
13 |
14 |
15 | const Schema = mongoose.Schema;
16 |
17 | // SAVING USER SCHEMA FOR MONGODB
18 | const usersSchema = new Schema({
19 | full_name: { type: String, required: true },
20 | first_name: String,
21 | email: String,
22 | profile_img: String,
23 | user_age: Number,
24 | location: String,
25 | dog_name: String,
26 | dog_image: String,
27 | dog_age: Number,
28 | dog_size: String,
29 | dog_breed: String,
30 | preferred_activities: [{ activity: String, description: String }],
31 | //timestamps: true,
32 | });
33 |
34 | const User = mongoose.model('Woof-users', usersSchema);
35 |
36 |
37 | module.exports = User;
38 |
39 | // preferred_activities: [{activity: "coffee", description: "i like it black"}, {activity: "beach", description: "a day under the sun"}, {activity: "hiking", description: "conversation time for our pets"}]
40 |
41 | // /login
42 | // /register
43 | // / setProfile
44 | // / main
45 |
--------------------------------------------------------------------------------
/server/models/activitiesModels.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Schema = mongoose.Schema;
3 |
4 | // SAVING ACTIVITIES SCHEMA FOR MONGODB
5 | const activitiesSchema = new Schema({
6 | name: String,
7 | users: [{ type: Schema.Types.ObjectId, ref: 'users' }],
8 | });
9 |
10 | module.exports = mongoose.model('activities', activitiesSchema);
11 |
12 | // coffee: [{user_id: 524535, description: "i like black coffee"}]
13 |
--------------------------------------------------------------------------------
/server/routers/users.js:
--------------------------------------------------------------------------------
1 | const { Router } = require('express');
2 | const express = require('express');
3 | const userController = require('../controllers/UserController');
4 | const matchesController = require('../controllers/MatchesController');
5 | const router = express.Router();
6 |
7 | router.post('/login',
8 | userController.createUser,
9 | (req, res) => res.status(200).json(res.locals.data)
10 | );
11 |
12 | router.get('/getUserData/:id',
13 | userController.getUserData,
14 | (req, res) => res.status(200).json(res.locals.data)
15 | );
16 |
17 | router.delete('/deleteUser/:id',
18 | userController.deleteUserData,
19 | (req, res) => res.status(200).json(res.locals.data)
20 | );
21 |
22 | router.put(
23 | '/updateUserData/:id',
24 | userController.updateUserData,
25 | matchesController.userActivities,
26 | matchesController.getUniqueIds,
27 | matchesController.returnMatches,
28 | (req, res) => res.status(200).json(res.locals.matchingUsersInfo)
29 | );
30 |
31 | module.exports = router;
32 |
33 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const express = require('express');
3 | const socketio = require('socket.io');
4 | const bodyParser = require("body-parser");
5 | const cookieParser = require('cookie-parser');
6 | const mongoose = require('mongoose');
7 | const usersRouter = require("./routers/users");
8 | const formatMessage = require('../utils/messages');
9 |
10 | // SETTING UP SERVER
11 | const app = express();
12 | const PORT = 3000;
13 | const server = app.listen(PORT, () => {
14 | console.log(`LISTENING ON PORT: ${PORT}`);
15 | });
16 | const io = socketio(server);
17 |
18 | // ESTABLISH CONNECTION TO DATABASE
19 | const MONGO_URI = 'mongodb+srv://woof:codesmith123@woof.qaamj.mongodb.net/woof?retryWrites=true&w=majority';
20 |
21 | mongoose
22 | .connect(MONGO_URI, {
23 | useNewUrlParser: true,
24 | useUnifiedTopology: true,
25 | })
26 | .then(() => console.log('CONNECTED TO MONGO DB'))
27 | .catch((err) => console.log(err));
28 |
29 | // SET UP
30 | app.use(express.json());
31 | app.use(express.urlencoded({ extended: true }));
32 | app.use(bodyParser.json());
33 | app.use(bodyParser.urlencoded({ extended: true }));
34 | app.use(cookieParser());
35 |
36 |
37 | app.use(express.static(path.join(__dirname, '../client/')));
38 |
39 |
40 | app.use("/api", usersRouter);
41 |
42 | // DIRECT ALL INCOMING TRAFFIC TO HOMEPAGE
43 | app.use('/', (req, res) => {
44 | res.sendFile(path.join(__dirname, '../index.html'));
45 | });
46 |
47 | // GLOBAL ERROR HANDLER
48 | app.use((err, req, res, next) => {
49 | const defaultErr = {
50 | log: 'Express error: Unknown middleware',
51 | status: 500,
52 | message: { err: 'An error occurred' },
53 | };
54 | const errorObj = { ...defaultErr, err };
55 |
56 | console.log('ERROR LOG => ', errorObj.log);
57 | return res.status(errorObj.status).json(errorObj.message);
58 | });
59 |
60 | // Run when a client connects
61 | const botName = 'WoofBot';
62 | // Run when a client connects
63 | io.on('connection', socket => {
64 | console.log("A USER CONNECTED!");
65 |
66 | // socket.on('joinRoom', ({ username, room }) => {
67 | // Welcome current user
68 | console.log(botName);
69 | socket.emit('message', formatMessage(botName, 'Welcome to WoofChat!', 1))
70 |
71 | // Broadcast when a user connects
72 | socket.broadcast.emit('message', formatMessage(botName, 'A user has joined the chat!', 1));
73 | // });
74 |
75 | // Listen for chat message
76 | socket.on('chatMessage', ({ username, text, userID}) => {
77 | io.emit('message', formatMessage(username, text, userID));
78 | });
79 |
80 | // Runs when client disconnects
81 | socket.on('disconnect', () => {
82 | io.emit('message', formatMessage(botName, 'A user has left the chat!', 1));
83 | });
84 | });
85 | // STARTUP LOGS
86 | // console.log(
87 | // 'Remember to check your .env file if you cannot connect to the database'
88 | // );
89 | // console.log(`Server is listening at http://localhost:${PORT}`);
90 | // console.log(`Client is live at http://localhost:8080`);
91 |
--------------------------------------------------------------------------------
/utils/messages.js:
--------------------------------------------------------------------------------
1 | const moment = require('moment');
2 |
3 | function formatMessage(username, text, userID) {
4 | return {
5 | userID,
6 | username,
7 | text,
8 | time: moment().format('h:mm a')
9 | }
10 | }
11 |
12 | module.exports = formatMessage;
13 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 |
4 | module.exports = {
5 | mode: process.env.NODE_ENV,
6 | entry: './client/index.js',
7 | output: {
8 | path: path.resolve(__dirname, 'build'),
9 | filename: 'bundle.js',
10 | publicPath: '/'
11 | },
12 |
13 | devServer: {
14 | contentBase: path.resolve(__dirname, "build"),
15 | historyApiFallback: true,
16 | headers: {
17 | "Access-Control-Allow-Origin": "*"
18 | },
19 | proxy: {
20 | '/' : {
21 | target: 'http://localhost:3000',
22 | secure: false
23 | }
24 | },
25 | port: 8080
26 | },
27 | module: {
28 | rules: [
29 | {
30 | test: /\.jsx?/,
31 | exclude: /(node_modules)/,
32 | use: [{
33 | loader: 'babel-loader',
34 | options: {
35 | presets: ['@babel/preset-env', '@babel/preset-react'],
36 | }
37 | }]
38 | },
39 | {
40 | test: /\.css$/, //changed to specify css instead of previous "catch all" test regex item including scss
41 | exclude: /(node_modules)/,
42 | use: ['style-loader', 'css-loader', 'sass-loader'],
43 | }
44 | ]
45 | }
46 | };
47 |
--------------------------------------------------------------------------------