diff --git a/package-lock.json b/package-lock.json index fd1740021..81073e10e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "cors": "2.8.5", "css-loader": "6.7.1", "express": "4.18.1", - "jest-dom": "4.0.0", + "graphql": "^16.6.0", "moment": "2.29.4", "mutationobserver-shim": "0.3.7", "react": "18.2.0", @@ -29,7 +29,7 @@ "@babel/plugin-transform-runtime": "7.18.10", "@babel/preset-env": "7.18.10", "@babel/preset-react": "7.18.6", - "@testing-library/jest-dom": "5.16.5", + "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "13.3.0", "@types/jest": "28.1.8", "babel-loader": "8.2.5", @@ -6800,6 +6800,14 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, + "node_modules/graphql": { + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz", + "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -8554,12 +8562,6 @@ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } }, - "node_modules/jest-dom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jest-dom/-/jest-dom-4.0.0.tgz", - "integrity": "sha512-gBxYZlZB1Jgvf2gP2pRfjjUWF8woGBHj/g5rAQgFPB/0K2atGuhVcPO+BItyjWeKg9zM+dokgcMOH01vrWVMFA==", - "deprecated": "🚨 jest-dom has moved to @testing-library/jest-dom. Please uninstall jest-dom and install @testing-library/jest-dom instead, or use an older version of jest-dom. Learn more about this change here: https://github.com/testing-library/dom-testing-library/issues/260 Thanks! :)" - }, "node_modules/jest-each": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", @@ -19177,6 +19179,11 @@ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "dev": true }, + "graphql": { + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.6.0.tgz", + "integrity": "sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==" + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -20424,11 +20431,6 @@ "detect-newline": "^3.0.0" } }, - "jest-dom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jest-dom/-/jest-dom-4.0.0.tgz", - "integrity": "sha512-gBxYZlZB1Jgvf2gP2pRfjjUWF8woGBHj/g5rAQgFPB/0K2atGuhVcPO+BItyjWeKg9zM+dokgcMOH01vrWVMFA==" - }, "jest-each": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", diff --git a/package.json b/package.json index 925c4018c..5759b4254 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "@babel/plugin-transform-runtime": "7.18.10", "@babel/preset-env": "7.18.10", "@babel/preset-react": "7.18.6", - "@testing-library/jest-dom": "5.16.5", + "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "13.3.0", "@types/jest": "28.1.8", "babel-loader": "8.2.5", @@ -38,7 +38,7 @@ "cors": "2.8.5", "css-loader": "6.7.1", "express": "4.18.1", - "jest-dom": "4.0.0", + "graphql": "^16.6.0", "moment": "2.29.4", "mutationobserver-shim": "0.3.7", "react": "18.2.0", diff --git a/src/App.css b/src/App.css index 74b5e0534..820a0283b 100644 --- a/src/App.css +++ b/src/App.css @@ -1,5 +1,9 @@ +@import url(https://fonts.googleapis.com/css?family=Lato); + +/* APP */ .App { text-align: center; + } .App-logo { @@ -13,21 +17,80 @@ } } + + .App-header { - background-color: #282c34; - min-height: 100vh; + background-color: white; + min-height: 10vh; display: flex; - flex-direction: column; + flex-direction: row; align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); + justify-content: space-between; + font-size: 20px; + border-bottom: solid; +} +.App-link { color: white; + text-decoration: none; + text-transform: uppercase; + padding: 20px; + background-color: #282c34; + margin: 5px; } -.App-link { - color: #61dafb; +/* login */ +input{ + background-color: #282c34; + width:100%; + height: 5vh; + color:white; } +@media (max-width: 863px) { + .App-header{ + color:pink; + font-size: 15px; + /* justify-content: start; */ + } + .App-link{ + padding: 15px; + justify-content: space-around; + } +} +@media (max-width: 701px) { + .App-header{ + color:green; + font-size: 15px; + /* flex-direction: column; */ + } + .App-link{ + padding: 15px; + justify-content: space-around; + } + form{ + font-size: 15px; + } + h1{ + font-size:25px; + } + h2{ + font-size:20px; + } + input{ + height:3vh + } +} + @media (max-width: 643px) { + .App-header{ + color:pink; + justify-content:space-around; + } + .App-link{ + padding: 10px; + } +} + + @keyframes App-logo-spin { from { transform: rotate(0deg); @@ -36,3 +99,57 @@ transform: rotate(360deg); } } + + +.Link-container { + justify-content: flex-end; +} + + +/* LOGIN */ +form{ + width:100%; + display:flex; + flex-direction: column; + align-items: center; +} + + +/* .Login-comp{ + display:flex; +} */ + +.Login-container{ + display:flex; + flex-direction: column; + align-items: center; + +} + +label{ + display:flex; + justify-content: center; + /* align-items: center; */ + font-family: Lato; + text-transform: uppercase; + +} + +/* fix width */ +.Submit-button{ + color: white; + text-decoration: none; + text-transform: uppercase; + padding: 10px; + background-color: #282c34; + margin: 5px; + width: 20%; +} + + + +.Friends { + text-align: left; +} + + diff --git a/src/App.js b/src/App.js index 1b971d1f6..25f8b5942 100644 --- a/src/App.js +++ b/src/App.js @@ -1,12 +1,76 @@ import React from 'react'; import './App.css'; -import { BrowserRouter as Router, Route} from 'react-router-dom'; +import { + Routes, + Route, + Link, + Navigate} from 'react-router-dom'; + +import Login from './components/Login'; +import FriendList from './components/FriendsList'; +import AddFriend from './components/AddFriend'; +import Logout from './components/Logout'; +import PrivateRoutes from './components/PrivateRoutes'; + function App() { + return (
-

Client Auth Project

-
+
+

Friends Database

+
+ + Login. + + + Friends List. + + + Add Friends. + + + Logout. + +
+
+ + + }> + } + exact path="/friends/add" + /> + } + exact path="/friends" + /> + + } + exact path="/login" + /> + } + exact path="/logout" + /> + {/* displays Login comp */} + } + exact path="/" + /> + + + ); } diff --git a/src/components/AddFriend.js b/src/components/AddFriend.js new file mode 100644 index 000000000..80ff549d9 --- /dev/null +++ b/src/components/AddFriend.js @@ -0,0 +1,90 @@ +import React, {useState} from 'react'; +import axios from 'axios'; +//instead of useHistory +import { useNavigate } from "react-router-dom"; + +const AddFriend = () => { + //instead of useHistory bc v6 + const navigate = useNavigate(); + + //state for each label info. look at inspect - data to check properties in the objects and write it below + const [form, setForm] =useState({ + name: '', + age: '', + email: '' + }); + + const handleChange = (e) => { + setForm({ + ...form, + [e.target.name]:e.target.value + }); + } + + //check state + console.log('form state', form); + + //handle submit - add to form + const handleSubmit = (e) => { + console.log('here') + e.preventDefault(); + const token = localStorage.getItem('token'); + axios + .post( + 'http://localhost:9000/api/friends', + form, + {headers: { + authorization: token + } + }) + .then(resp=>{ + console.log('ADDED NEW',resp); + navigate('/friends/add'); + }) + .catch(err=>{ + console.log('ADDING ERROR', err) + }) + } + + + + return( +
+

ADDFRIEND

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ ); + + } + + export default AddFriend; \ No newline at end of file diff --git a/src/components/FriendsList.js b/src/components/FriendsList.js new file mode 100644 index 000000000..abac8edf9 --- /dev/null +++ b/src/components/FriendsList.js @@ -0,0 +1,47 @@ +import React, {useState, useEffect} from 'react'; +import axios from 'axios'; + + +export default function FriendList() { + const [friends, setFriends] =useState([]); + + useEffect(()=>{ + const token = localStorage.getItem('token'); + axios + .get( + 'http://localhost:9000/api/friends', + { headers: { + authorization:token + }} + ) + .then(resp=>{ + console.log('LOGGED IN', resp); + setFriends(resp.data); + }) + .catch(err=>{ + console.log(err) + }) + },[]); + + return( +
+

FRIENDLIST

+ +
+ ); + } \ No newline at end of file diff --git a/src/components/Login.js b/src/components/Login.js new file mode 100644 index 000000000..acc3642ce --- /dev/null +++ b/src/components/Login.js @@ -0,0 +1,73 @@ +import React, {useState} from "react"; +import axios from "axios"; +import { defaultResponse } from "msw"; +//instead of useHistory +import { useNavigate } from "react-router-dom"; + + +export default function Login(){ + //instead of useHistory bc v6 + const navigate = useNavigate(); + const [formValues, setFormValues] = useState({ + username:"", + password:"" + }); + +//when we type, it'll handle the change +const handleChange = (e) => { + setFormValues({ + ...formValues, + [e.target.name]: e.target.value + }) +} +//to check +console.log(formValues); + + +//make our call +const handleSubmit= (e) =>{ + e.preventDefault(); + console.log('Login formValues:', formValues) + axios + .post('http://localhost:9000/api/login', formValues) + .then(resp=> { + console.log('TOKEN RESPONSE', resp); + localStorage.setItem('token', resp.data.token); + navigate('/friends'); + }) + .catch(err=>{ + console.log(err) + }) + +} + + +return( + // className="Login-comp" +
+

LOGIN

+
+
+ + +
+
+ + +
+ +
+
+ +); + +} \ No newline at end of file diff --git a/src/components/Logout.js b/src/components/Logout.js new file mode 100644 index 000000000..cc27f24dc --- /dev/null +++ b/src/components/Logout.js @@ -0,0 +1,32 @@ +import React, { useEffect } from "react"; +import axios from 'axios'; +import { useNavigate } from "react-router-dom"; + + +export default function Logout() { + const navigate = useNavigate(); + useEffect(()=>{ + const token=localStorage.getItem('token'); + + axios.post('http://localhost:9000/api/logout', + //empty body bc logged out + {}, + {headers: { + authorization: token + }}) + .then(resp => { + console.log('LOGOUT resp:', resp); + localStorage.removeItem('token'); + navigate('/login') + }) + .catch(err=>{ + console.log('LOGOUT err:', err) + + },[]) + }) + return( +
+

You Are Logged Out

+
+ ) +} \ No newline at end of file diff --git a/src/components/PrivateRoutes.js b/src/components/PrivateRoutes.js new file mode 100644 index 000000000..58f3ab484 --- /dev/null +++ b/src/components/PrivateRoutes.js @@ -0,0 +1,13 @@ +import React from "react"; +import {Outlet, Navigate} from 'react-router-dom'; + +const PrivateRoutes = () => { + console.log('localStorage.getItem(token):', localStorage.getItem('token')) + + const token = localStorage.getItem('token') + return ( + token ? : + ) +} + +export default PrivateRoutes \ No newline at end of file diff --git a/src/index.js b/src/index.js index 97c1d279f..9d8d41860 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ import App from './App' const root = createRoot(document.getElementById('root')) root.render( + // so you don't need to wrap the Routes inside App with a diff --git a/src/style.css b/src/style.css index a0b4452b1..8a027777c 100644 --- a/src/style.css +++ b/src/style.css @@ -9,4 +9,6 @@ html, body, #root { label { display:block; -} \ No newline at end of file +} + +