diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ec0ac53d2c2a40011e4ce94e98b4bf794fa7f55d..3b3ed1f6ef0fb9b317dfbe9041f064907c12533a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,6 +18,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^4.8.0", + "react-router-dom": "^6.10.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" } @@ -3512,6 +3513,14 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@remix-run/router": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz", + "integrity": "sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg==", + "engines": { + "node": ">=14" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -14827,6 +14836,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.10.0.tgz", + "integrity": "sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ==", + "dependencies": { + "@remix-run/router": "1.5.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.10.0.tgz", + "integrity": "sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg==", + "dependencies": { + "@remix-run/router": "1.5.0", + "react-router": "6.10.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -20111,6 +20150,11 @@ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==" }, + "@remix-run/router": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.5.0.tgz", + "integrity": "sha512-bkUDCp8o1MvFO+qxkODcbhSqRa6P2GXgrGZVpt0dCXNW2HCSCqYI0ZoAqEOSAjRWmmlKcYgFvN4B4S+zo/f8kg==" + }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -28196,6 +28240,23 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, + "react-router": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.10.0.tgz", + "integrity": "sha512-Nrg0BWpQqrC3ZFFkyewrflCud9dio9ME3ojHCF/WLsprJVzkq3q3UeEhMCAW1dobjeGbWgjNn/PVF6m46ANxXQ==", + "requires": { + "@remix-run/router": "1.5.0" + } + }, + "react-router-dom": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.10.0.tgz", + "integrity": "sha512-E5dfxRPuXKJqzwSe/qGcqdwa18QiWC6f3H3cWXM24qj4N0/beCIf/CWTipop2xm7mR0RCS99NnaqPNjHtrAzCg==", + "requires": { + "@remix-run/router": "1.5.0", + "react-router": "6.10.0" + } + }, "react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 0064481c8791078de4108473e6291a054361bd2d..96b2bd3f4643213ee47d1cb9193ebfe0fa572612 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^4.8.0", + "react-router-dom": "^6.10.0", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, diff --git a/frontend/public/content/cheers.jpg b/frontend/public/content/cheers.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8e9df451c8701e5233fc3f49080d44e7b5d91be8 Binary files /dev/null and b/frontend/public/content/cheers.jpg differ diff --git a/frontend/src/App.js b/frontend/src/App.js index 0a46df4128cc00da5994979cdd1f637f42d74338..f92680dc94810a838e994c767aa08308469d28b6 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1,12 +1,9 @@ import axios from 'axios' import React, {useState, useEffect} from 'react' -import './styles.css' +import styles from './styles/styles.module.css' import Landing from './components/Landing' import Post from './components/Post' -import SignUp from './components/SignUp' -import Login from './components/Login' - - +import Header from './components/Header' function App() { const [posts,setPosts] = useState([]) @@ -26,18 +23,20 @@ const loadPosts = () => { loadPosts() + },[]) return ( <> +<Header/> <Landing/> <Post/> - <div className=''> + <div> <h1>POSTS</h1> - <div className=''>{posts.map((post) => { + <div>{posts.map((post) => { return( - <div className='posts-container' key={post._id}> - <img className='img' src={`http://localhost:5000/uploads/${post.photo}`}/> + <div className={styles.posts_container} key={post._id}> + <img className={styles.img} src={`http://localhost:5000/uploads/${post.photo}`}/> <div> <h1>{post.name}</h1> <p>{post.description}</p> @@ -47,8 +46,7 @@ const loadPosts = () => { ) })}</div> </div> - <SignUp/> - <Login/> + </> ); } diff --git a/frontend/src/components/Header.js b/frontend/src/components/Header.js index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..feb3438bbbbfc7ecabc732e6daadcf525b954a86 100644 --- a/frontend/src/components/Header.js +++ b/frontend/src/components/Header.js @@ -0,0 +1,17 @@ +import React from 'react' +import {Link} from 'react-router-dom' +import styles from '../styles/header.module.css' +const Header = () => { + return ( + <div className={styles.header}> + <Link to='/register'> + <div className={styles.button}>Register</div> + </Link> + <Link to='/login'> + <div className={styles.button}>Login</div> + </Link> + </div> + ) +} + +export default Header \ No newline at end of file diff --git a/frontend/src/components/Landing.js b/frontend/src/components/Landing.js index 2009c0bb25b6778a4637f8cdff66d8655989184b..630750db785baf49534a4e64e5c1ecfd8b084fc3 100644 --- a/frontend/src/components/Landing.js +++ b/frontend/src/components/Landing.js @@ -1,48 +1,48 @@ import React from 'react' -import '../styles.css' +import styles from '../styles/styles.module.css' const Landing = () => { return ( <> - <div className='Header'> - <div className='text-container'> + <div className={styles.Header}> + <div className={styles.text_container}> <h1>Welcome to Liqour Buddy!</h1> <p>A project by Ludvig Damberg and Ludvig Hillert for Linköping University. </p> <p>Scroll down to see a checklist for our project and test the current features! Keep in mind that this is a project under developement, some features might not work or have any response. ðŸ¹ðŸ» </p> </div> - <div className='image-container'> + <div className={styles.image_container}> <img alt='' src='../../content/landing.jpg'/> </div> </div> - <div className='Header2'> - <div className='card'> + <div className={styles.Header2}> + <div className={styles.card}> <h1>Functionality ðŸº</h1> - <ul className='list'> - <li className='item'>Creating Posts ✅ </li> - <li className='item'>Account Sign Up </li> - <li className='item'>Logging In </li> - <li className='item'>Authentication Checking </li> + <ul className={styles.list}> + <li className={styles.item}>Creating Posts ✅ </li> + <li className={styles.item}>Account Sign Up ✅ </li> + <li className={styles.item}>Logging In ✅</li> + <li className={styles.item}>Authentication Checking </li> </ul> </div> - <div className='card'> + <div className={styles.card}> <h1>PagesðŸº</h1> - <ul className='list'> - <li className='item'>Home </li> - <li className='item'>About </li> - <li className='item'>Discover </li> - <li className='item'>Log In and Sign Up </li> - <li className='item'>Crate Post ✅</li> + <ul className={styles.list}> + <li className={styles.item}>Home </li> + <li className={styles.item}>About </li> + <li className={styles.item}>Discover </li> + <li className={styles.item}>Log In and Sign Up </li> + <li className={styles.item}>Crate Post ✅</li> </ul> </div> - <div className='card'> + <div className={styles.card}> <h1>User ExperienceðŸº</h1> - <ul className='list'> - <li className='item'>Scrolling Triggers </li> - <li className='item'>Hover Interactions </li> - <li className='item'>Instructional Interface </li> - <li className='item'>Easy Usage </li> - <li className='item'>Consistent Theme </li> + <ul className={styles.list}> + <li className={styles.item}>Scrolling Triggers </li> + <li className={styles.item}>Hover Interactions </li> + <li className={styles.item}>Instructional Interface </li> + <li className={styles.item}>Easy Usage </li> + <li className={styles.item}>Consistent Theme </li> </ul> </div> diff --git a/frontend/src/components/Login.js b/frontend/src/components/Login.js index c9f973aa139661c58a196b12da70e2df66414ff0..be4382fdede3601b91701b9fe4442beeff7b33fd 100644 --- a/frontend/src/components/Login.js +++ b/frontend/src/components/Login.js @@ -1,17 +1,32 @@ import React from 'react' -import {useState} from 'react' +import {useState, useRef, useEffect} from 'react' import axios from 'axios' const Login = () => { - + const userRef = useRef(); + const errRef = useRef(); + const[password,setPassword] = useState("") const[email,setEmail] = useState("") + const [errMsg, setErrMsg] = useState(''); + const [success, setSuccess] = useState(false); + + // useEffect(()=>{ + // userRef.current.focus(); + + // }, []) + + useEffect(() => { + setErrMsg(''); + }, [email, password]) - const Login = () => { + + const handleLogin = () => { console.log({email,password}) + setSuccess(true); axios.post("http://localhost:5000/login", {email, password}) .then((res) => { @@ -21,12 +36,42 @@ const Login = () => { } return ( + <> + {success ? ( + <section> + <h1>You are logged in!</h1> + <br /> + + </section> + ) : ( <div> - <input type='text' placeholder='Email' onChange={(e) => setEmail(e.target.value)}/> - <input type='text' placeholder='Password' onChange={(e) => setPassword(e.target.value)}/> - <button onClick={Login}>Sign up</button> + <p ref={errRef} className = {errMsg ? "errmsg" : "offscreen"} + aria-live = "assertive">{errMsg}</p> + <h1>Sign In</h1> + <label htmlFor = "Email">Email: </label> + <input + type='text' + id='Email' + ref ={userRef} + autocomplete = "off" + onChange={(e) => setEmail(e.target.value)} + value = {email} + required/> + + <label htmlFor = "password">Password: </label> + <input + type='text' + id="password" + ref ={userRef} + onChange={(e) => setPassword(e.target.value)} + value = {password} + required/> + + <button onClick={handleLogin}>Sign in</button> </div> + )} + </> ) } diff --git a/frontend/src/components/SignUp.js b/frontend/src/components/SignUp.js index e7cd7be5c6fe35158ffc5b627f1c4b0316e7ca26..6fe05e2e04bdc8e02da6e576cfbe5f9ae6896954 100644 --- a/frontend/src/components/SignUp.js +++ b/frontend/src/components/SignUp.js @@ -1,15 +1,39 @@ import React from 'react' -import {useState} from 'react' +import {useState, useRef, useEffect} from 'react' import axios from 'axios' +const USER_REGEX = /^[a-zA-Z][a-zA-Z0-9-_]{3,23}$/; + const SignUp = () => { + const userRef = useRef(); + const errRef = useRef(); + + const[username,setUsername] = useState("") const[password,setPassword] = useState("") const[email,setEmail] = useState("") - const Signup = () => { + const [validName, setValidName] = useState(false); + + const [errMsg, setErrMsg] = useState(''); + const [success, setSuccess] = useState(false); + + useEffect(() => { + setErrMsg(''); + }, [username, password]) + + useEffect(() => { + const result = USER_REGEX.test(username); + + setValidName(result); +}, [username]) + + + + const handleSignup = () => { + setSuccess(true); console.log({email,username,password}) @@ -27,13 +51,50 @@ const SignUp = () => { } return ( +<> + {success ? ( + <section> + <h1>Success!</h1> + <p> + <a href="#">Sign In</a> + </p> + </section> + ) : ( + <div> - <input type='text' placeholder='Email' onChange={(e) => setEmail(e.target.value)}/> - <input type='text' placeholder='Username' onChange={(e) => setUsername(e.target.value)}/> - <input type='text' placeholder='Password' onChange={(e) => setPassword(e.target.value)}/> - <button onClick={Signup}>Sign up</button> + <h1>Register</h1> + + <label htmlFor="email">Email:</label> + <input type='text' + placeholder='Email' + ref={userRef} + autoComplete="off" + onChange={(e) => setEmail(e.target.value)} + /> + <label htmlFor="username">Username:</label> + <input type='text' + placeholder='Username' + ref={userRef} + autoComplete="off" + onChange={(e) => setUsername(e.target.value)} + required + /> + + + <label htmlFor="password">Password:</label> + <input type='password' + placeholder='Password' + onChange={(e) => setPassword(e.target.value)} + value={password}/> + + <button disabled={!validName ? true : false} + onClick={handleSignup}> + Sign up + </button> </div> + )} + </> ) } diff --git a/frontend/src/index.js b/frontend/src/index.js index 371254590eeead924d47e1bd1c2f69205fb48199..dd6a8e6b717d93f0212a977c0057c43aad1c10cb 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -2,12 +2,31 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; +import Register from './pages/Register'; +import Login from './pages/Login'; +import { RouterProvider, createBrowserRouter, Route } from 'react-router-dom'; + +const router =createBrowserRouter([ + { + path: '/', + element: <App/> + }, + { + path: '/login', + element: <Login/> + }, + { + path: '/register', + element: <Register/> + }, +]) const root = ReactDOM.createRoot(document.getElementById('root')); root.render( - <App /> - + <RouterProvider router={router}/> + + ); // If you want to start measuring performance in your app, pass a function diff --git a/frontend/src/pages/Login.js b/frontend/src/pages/Login.js new file mode 100644 index 0000000000000000000000000000000000000000..de53aa68e4fd854e46a86458c80f103a9e66f5e9 --- /dev/null +++ b/frontend/src/pages/Login.js @@ -0,0 +1,21 @@ +import React from 'react' +import styles from '../styles/login.module.css' + + +const Login = () => { + + return ( + <div className={styles.parent}> + <div className={styles.left}> + <h1>LOGIN TO START SHARING THE FUN!</h1> + </div> + <div className={styles.right}> + <div className={styles.image_container}> + <img src='../content/cheers.jpg'/> + </div> + </div> + </div> + ) +} + +export default Login \ No newline at end of file diff --git a/frontend/src/pages/Register.js b/frontend/src/pages/Register.js new file mode 100644 index 0000000000000000000000000000000000000000..4110e55c848928c7c21fb7a81304cce399f50e99 --- /dev/null +++ b/frontend/src/pages/Register.js @@ -0,0 +1,12 @@ +import React from 'react' + + +const Register = () => { + return ( + <> + + </> + ) +} + +export default Register \ No newline at end of file diff --git a/frontend/src/styles/header.module.css b/frontend/src/styles/header.module.css new file mode 100644 index 0000000000000000000000000000000000000000..d69e8b4fe16f4f483b85cad0f413e6957b61b917 --- /dev/null +++ b/frontend/src/styles/header.module.css @@ -0,0 +1,48 @@ +.header{ +height: 10vh; +margin: 0; +padding: 10px; +display: flex; +justify-content: end; +} +.button { + appearance: none; + background-color: rgb(255, 240, 153); + border: 0.125em solid #383838; + border-radius: 0.9375em; + box-sizing: border-box; + color: #383838; + cursor: pointer; + display: inline-block; + font-size: 16px; + font-weight: 600; + line-height: normal; + margin: 0; + min-height: 3.75em; + min-width: 0; + outline: none; + padding: 1em 2.3em; + text-align: center; + transition: all 300ms cubic-bezier(.23, 1, 0.32, 1); + user-select: none; + -webkit-user-select: none; + touch-action: manipulation; + will-change: transform; + margin: 5px; + } + + .button:disabled { + pointer-events: none; + } + + .button:hover { + color: #fff; + background-color: #1A1A1A; + box-shadow: rgba(0, 0, 0, 0.25) 0 8px 15px; + transform: translateY(-2px); + } + + .button:active { + box-shadow: none; + transform: translateY(0); + } \ No newline at end of file diff --git a/frontend/src/styles/login.module.css b/frontend/src/styles/login.module.css new file mode 100644 index 0000000000000000000000000000000000000000..854bda70c89b73e967394b9869e731c34cc022ae --- /dev/null +++ b/frontend/src/styles/login.module.css @@ -0,0 +1,31 @@ +.parent{ + display: flex; + height: 100vh; +} +.left{ + width: 40%; + background: rgb(49, 117, 121); +} +.left h1{ + margin: 0; + color: rgb(255, 240, 153); +} +.right{ + width: 60%; + height: 100vh; + background: black; +} +.image_container { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; +} + +.image_container img{ + max-width: 150%; + max-height: 150%; + object-fit: cover; +} \ No newline at end of file diff --git a/frontend/src/styles.css b/frontend/src/styles/styles.module.css similarity index 94% rename from frontend/src/styles.css rename to frontend/src/styles/styles.module.css index 4c2d6d5ce10700e0b5fb6829748ed221df22a5f1..60db6e528b044fce8acac3d39807b7040fa6ed68 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles/styles.module.css @@ -7,7 +7,7 @@ input{ font-family: 'Poppins', sans-serif; } .Header{ - height: 100vh; + height: 90vh; display: flex; justify-content: center; align-items: center; @@ -25,7 +25,7 @@ input{ color: rgb(255, 240, 153); font-size: 350%; } -.pick-file { +.pick_file { color: rgb(67, 1, 128); font-size: 35px; margin:0; @@ -36,7 +36,7 @@ input{ } -.image-container { +.image_container { width: 500px; /* set width of image container */ height: 500px; /* set height of image container */ border-radius: 50%; /* make image container circular */ @@ -47,14 +47,14 @@ input{ } -.image-container img { +.image_container img { height: 100%; width: 100%; object-fit: cover; } -.text-container{ +.text_container{ display: flex; flex-direction: column; justify-content: center; @@ -96,7 +96,7 @@ object-fit: cover; margin-bottom: 15px; } -.post-container{ +.post_container{ min-height: 35vh; margin: auto; width: 50vw; @@ -111,7 +111,7 @@ object-fit: cover; box-shadow: 0 8px 23px 0 rgba(0, 0, 0, 0.2); } -.post-input1{ +.post_input1{ border: none; background-color: rgb(11, 58, 58); width: 300px; @@ -122,7 +122,7 @@ object-fit: cover; margin-top: 15px; } -.post-input2{ +.post_input2{ border: none; background-color: rgb(11, 58, 58); width: 1000px; @@ -132,7 +132,7 @@ object-fit: cover; margin: 10px; } -.post-container button{ +.post_container button{ background: rgb(150, 172, 110); color: white; font-weight: bold; @@ -144,7 +144,7 @@ object-fit: cover; font-family: 'Poppins', sans-serif; border-radius: 5px; } -.posts-container{ +.posts_container{ min-height: 35vh; margin: auto; margin-top: 30px;