├── public ├── favicon.ico ├── img │ └── Arash.jpg └── index.html ├── src ├── Components │ ├── Panel │ │ ├── SideBarLinks │ │ │ ├── SideBarLinks.module.css │ │ │ ├── SideBarLink │ │ │ │ ├── SideBarLink.module.css │ │ │ │ └── SideBarLink.jsx │ │ │ └── SideBarLinks.jsx │ │ ├── UserCard │ │ │ └── UserCard.jsx │ │ ├── UserProfile │ │ │ ├── UserProfile.module.css │ │ │ └── UserProfile.jsx │ │ ├── Panel.module.css │ │ ├── UserChangePassword │ │ │ └── UserChangePassword.jsx │ │ ├── Panel.jsx │ │ └── UserInformation │ │ │ └── UserInformation.jsx │ ├── Forms │ │ ├── FormInput │ │ │ ├── FormInput.module.css │ │ │ └── FormInput.jsx │ │ ├── Forms.module.css │ │ ├── LoginForm │ │ │ └── LoginForm.jsx │ │ └── RegisterForm │ │ │ └── RegisterForm.jsx │ └── Titles │ │ ├── Titles.module.css │ │ └── Titles.jsx ├── index.css ├── index.js ├── utils │ └── storage.js └── App.js ├── .gitignore ├── LICENSE ├── package.json └── README.md /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UndeadMe/React-User-Panel/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/img/Arash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/UndeadMe/React-User-Panel/HEAD/public/img/Arash.jpg -------------------------------------------------------------------------------- /src/Components/Panel/SideBarLinks/SideBarLinks.module.css: -------------------------------------------------------------------------------- 1 | .sidebar-links { 2 | border-radius: 30px; 3 | box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 30px; 4 | } -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;400;500&display=swap'); 2 | 3 | *, html { 4 | font-family: 'Poppins', sans-serif !important; 5 | } -------------------------------------------------------------------------------- /src/Components/Forms/FormInput/FormInput.module.css: -------------------------------------------------------------------------------- 1 | .form-input { 2 | padding: 12px 18px; 3 | box-shadow: none !important; 4 | } 5 | .form-label { 6 | font-size: 0.9rem; 7 | margin-bottom: 10px; 8 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | const root = ReactDOM.createRoot(document.getElementById('root')); 7 | root.render( 8 | 9 | 10 | 11 | ); -------------------------------------------------------------------------------- /src/Components/Titles/Titles.module.css: -------------------------------------------------------------------------------- 1 | .information-heading { 2 | font-size: 1.3rem; 3 | } 4 | .information-heading-text { 5 | font-size: 1rem; 6 | font-weight: lighter; 7 | } 8 | @media screen and (max-width: 441px) { 9 | .information-heading { 10 | font-size: 1.1rem; 11 | margin-bottom: 10px; 12 | } 13 | } -------------------------------------------------------------------------------- /src/Components/Forms/Forms.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | min-height: 100vh !important; 3 | padding: 50px; 4 | } 5 | .submit-btn { 6 | padding: 12px 0; 7 | margin-top: 25px; 8 | } 9 | .form { 10 | width: 50%; 11 | } 12 | @media screen and (max-width: 991px) { 13 | .form { 14 | width: 80%; 15 | } 16 | } 17 | @media screen and (max-width: 575px) { 18 | .form { 19 | width: 90%; 20 | } 21 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .vscode/ -------------------------------------------------------------------------------- /src/Components/Panel/SideBarLinks/SideBarLink/SideBarLink.module.css: -------------------------------------------------------------------------------- 1 | .sidebar-link { 2 | cursor: pointer; 3 | } 4 | .right-arrow-icon { 5 | transition: .4s; 6 | } 7 | .sidebar-link:hover .right-arrow-icon { 8 | transform: translateX(-10px); 9 | } 10 | .right-arrow-icon.active { 11 | transform: translateX(-10px); 12 | } 13 | .sidebar-link-border { 14 | width: 100%; 15 | display: flex; 16 | justify-content: center; 17 | padding: 15px 23px; 18 | } 19 | .sidebar-link-border div { 20 | outline: 1px solid #f1f1f1; 21 | width: 100%; 22 | } -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | React App 11 | 12 | 13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Components/Titles/Titles.jsx: -------------------------------------------------------------------------------- 1 | // import styles of this component 2 | import styles from './Titles.module.css' 3 | // import other pkg 4 | import PropTypes from 'prop-types' 5 | 6 | const Titles = ({ title, text }) => { 7 | return ( 8 | <> 9 |

{title}

10 |

{text}

11 | 12 | ) 13 | } 14 | 15 | // validate component 16 | Titles.propTypes = { 17 | title: PropTypes.string.isRequired, 18 | text: PropTypes.string.isRequired, 19 | } 20 | 21 | export default Titles -------------------------------------------------------------------------------- /src/utils/storage.js: -------------------------------------------------------------------------------- 1 | export const getStorage = storage => { 2 | if (storage === 'id') return localStorage.getItem(storage) 3 | return JSON.parse(localStorage.getItem(storage)) 4 | } 5 | 6 | export const setUserId = id => localStorage.setItem('id', id) 7 | 8 | export const setUserInStorage = (storage, value) => localStorage.setItem(storage, JSON.stringify([ ...value ])) 9 | 10 | export const updateStorage = (users, myUser, login) => { 11 | const myVerifyUserIdx = users.findIndex(user => user.id === myUser.id) 12 | users.splice(myVerifyUserIdx, 1) 13 | if (login) myUser.isLogin = login 14 | users.push(myUser) 15 | setUserInStorage('users', users) 16 | } -------------------------------------------------------------------------------- /src/Components/Panel/UserCard/UserCard.jsx: -------------------------------------------------------------------------------- 1 | // import other component 2 | import UserProfile from '../UserProfile/UserProfile' 3 | import SideBarLinks from '../SideBarLinks/SideBarLinks' 4 | 5 | // import other pkg 6 | import PropTypes from 'prop-types' 7 | 8 | const UserCard = ({ sidebarLinks, username, userBirthday, userEmail, onChangeToggle }) => { 9 | return ( 10 | <> 11 | 12 | 13 | 14 | ) 15 | } 16 | 17 | // validate the component 18 | UserCard.propTypes = { 19 | sidebarLinks: PropTypes.array.isRequired, 20 | username: PropTypes.string.isRequired, 21 | userBirthday: PropTypes.string.isRequired, 22 | userEmail: PropTypes.string.isRequired, 23 | onChangeToggle: PropTypes.func.isRequired 24 | } 25 | 26 | export default UserCard -------------------------------------------------------------------------------- /src/Components/Panel/UserProfile/UserProfile.module.css: -------------------------------------------------------------------------------- 1 | .user-profile { 2 | padding: 20px; 3 | border-radius: 30px; 4 | box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 30px; 5 | } 6 | .user-profile-label { 7 | position: relative; 8 | width: 150px; 9 | height: 150px; 10 | border-radius: 100%; 11 | display: flex; 12 | cursor: pointer; 13 | margin-top: -85px; 14 | outline: 10px solid white; 15 | } 16 | .user-profile-label img { 17 | width: 100%; 18 | object-fit: cover; 19 | border-radius: 100%; 20 | } 21 | .username { 22 | font-size: 1.3rem; 23 | font-weight: bolder; 24 | } 25 | .user-email { 26 | font-size: 0.9rem; 27 | font-weight: lighter; 28 | } 29 | .user-birthday { 30 | font-size: 0.9rem; 31 | } 32 | .profile-icon-box { 33 | position: absolute; 34 | right: 5px; 35 | bottom: 0; 36 | border-radius: 100%; 37 | padding: 5px 4.5px; 38 | outline: 5px solid white; 39 | display: flex; 40 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Arash 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "1", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.2.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "bootstrap": "^5.1.3", 10 | "formik": "^2.2.9", 11 | "iconsax-react": "^0.0.8", 12 | "prop-types": "^15.8.1", 13 | "react": "^18.1.0", 14 | "react-bootstrap": "^2.3.1", 15 | "react-dom": "^18.1.0", 16 | "uuid": "^8.3.2", 17 | "web-vitals": "^2.1.4", 18 | "yup": "^0.32.11" 19 | }, 20 | "overrides": { 21 | "autoprefixer": "10.4.5" 22 | }, 23 | "devDependencies": { 24 | "react-scripts": "5.0.1" 25 | }, 26 | "scripts": { 27 | "start": "react-scripts start", 28 | "build": "react-scripts build", 29 | "test": "react-scripts test", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Components/Panel/Panel.module.css: -------------------------------------------------------------------------------- 1 | body { 2 | position: relative; 3 | } 4 | .panel-wrapper { 5 | width: 100%; 6 | min-height: 100vh; 7 | background-color: #fcfcfc; 8 | } 9 | .container { 10 | width: 83%; 11 | } 12 | .panel { 13 | width: 85%; 14 | z-index: 2; 15 | margin: 100px 0; 16 | } 17 | .panel-column { 18 | border-radius: 30px; 19 | box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 30px; 20 | } 21 | .log-out-btn { 22 | position: absolute; 23 | bottom: 20px; 24 | left: 20px; 25 | width: 140px; 26 | padding: 8px 0; 27 | z-index: 2; 28 | } 29 | .bg-overlay { 30 | position: absolute; 31 | top: 0; 32 | left: 0; 33 | background-image: url(/public/img/Arash.jpg); 34 | background-size: cover; 35 | background-position: center; 36 | background-repeat: no-repeat; 37 | width: 400px; 38 | height: 100%; 39 | } 40 | .bg-overlay::before { 41 | content: ''; 42 | position: absolute; 43 | inset: 0; 44 | width: 100%; 45 | height: 100%; 46 | background-color: rgba(0, 0, 0, 0.3); 47 | } 48 | @media screen and (max-width: 1200px) { 49 | .panel { 50 | width: 100%; 51 | } 52 | } 53 | @media screen and (max-width: 992px) { 54 | .container { 55 | width: 100% ; 56 | padding: 0 30px; 57 | } 58 | } -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { useEffect, useState, useCallback } from 'react'; 2 | 3 | // import bootstrap 4 | import 'bootstrap/dist/css/bootstrap.min.css' 5 | 6 | // import my other component 7 | import RegisterForm from './Components/Forms/RegisterForm/RegisterForm' 8 | import Panel from './Components/Panel/Panel'; 9 | import LoginForm from './Components/Forms/LoginForm/LoginForm'; 10 | 11 | // import utils 12 | import { getStorage } from './utils/storage'; 13 | 14 | const App = () => { 15 | const [toggle, setToggle] = useState(''); 16 | 17 | const changeToggle = (toggle) => setToggle(toggle) 18 | 19 | const checkIsInitStorage = () => getStorage('users') && getStorage('users').length !== 0 20 | 21 | const checkUserIsRegister = useCallback(() => { 22 | if (checkIsInitStorage()) { 23 | const userId = getStorage('id') 24 | const users = getStorage('users') 25 | 26 | const [userRegistered] = users.filter(user => user.id === userId) 27 | 28 | userRegistered.isLogin && changeToggle('panel') 29 | !userRegistered.isLogin && changeToggle('login') 30 | } else changeToggle('register') 31 | }, []) 32 | 33 | useEffect(() => { 34 | checkUserIsRegister() 35 | }, [checkUserIsRegister]) 36 | 37 | 38 | return ( 39 | <> 40 | { toggle === 'register' && } 41 | { toggle === 'login' && } 42 | { toggle === 'panel' && } 43 | 44 | ) 45 | } 46 | 47 | export default App -------------------------------------------------------------------------------- /src/Components/Panel/UserProfile/UserProfile.jsx: -------------------------------------------------------------------------------- 1 | // import styles of this component 2 | import styles from './UserProfile.module.css' 3 | // import other pkgs 4 | import { Import } from 'iconsax-react' 5 | import PropTypes from 'prop-types'; 6 | 7 | const UserProfile = ({ userProfile='img/Arash.jpg', userBirthday, username, userEmail }) => { 8 | const capitalizeText = (text) => { 9 | const firstLetter = text.charAt(0).toUpperCase() 10 | const otherLetters = text.slice(1) 11 | return `${firstLetter}${otherLetters}` 12 | } 13 | 14 | return ( 15 |
16 | 23 |

{capitalizeText(username)}

24 |

🥳 {userBirthday} 🥳

25 |

{capitalizeText(userEmail)}

26 |
27 | ) 28 | } 29 | 30 | // validate the component 31 | UserProfile.propTypes = { 32 | userProfile: PropTypes.string, 33 | username: PropTypes.string.isRequired, 34 | userBirthday: PropTypes.string.isRequired, 35 | userEmail: PropTypes.string.isRequired, 36 | } 37 | 38 | export default UserProfile -------------------------------------------------------------------------------- /src/Components/Panel/SideBarLinks/SideBarLink/SideBarLink.jsx: -------------------------------------------------------------------------------- 1 | // import styles of this component 2 | import styles from './SideBarLink.module.css' 3 | 4 | // import other pkg to use 5 | import { ArrowRight2 } from 'iconsax-react' 6 | import PropTypes from 'prop-types' 7 | 8 | const SideBarLink = ({ id, border, text, icon, href, active, onActive }) => { 9 | return ( 10 | <> 11 |
  • onActive(id)}> 12 | {href ? ( 13 | 14 | {icon} 15 | {text} 16 | 17 | 18 | ) : ( 19 | <> 20 | {icon} 21 | {text} 22 | 23 | 24 | )} 25 |
  • 26 | {border &&
    } 27 | 28 | ) 29 | } 30 | 31 | // validate component 32 | SideBarLink.propTypes = { 33 | id: PropTypes.number.isRequired, 34 | className: PropTypes.string, 35 | border: PropTypes.bool, 36 | text: PropTypes.string.isRequired, 37 | icon: PropTypes.element, 38 | href: PropTypes.string, 39 | active: PropTypes.bool, 40 | onActive: PropTypes.func.isRequired, 41 | } 42 | 43 | export default SideBarLink -------------------------------------------------------------------------------- /src/Components/Panel/SideBarLinks/SideBarLinks.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | // import styles of this component 4 | import styles from './SideBarLinks.module.css' 5 | 6 | // import other component to use 7 | import SideBarLink from "./SideBarLink/SideBarLink"; 8 | 9 | // import other pkg 10 | import PropTypes from 'prop-types' 11 | 12 | const SideBarLinks = ({ sidebarLinks, onChangeToggle }) => { 13 | const [linksState, setLinks] = useState({ 14 | links: [ ...sidebarLinks ] 15 | }) 16 | 17 | const activeLink = (linkId) => { 18 | linksState.links.forEach(link => link.active = false) 19 | 20 | const link = linksState.links.find(link => link.id === linkId) 21 | if (!link.href) { 22 | link.active = true 23 | 24 | setLinks(prev => { 25 | return { 26 | links: [ 27 | ...prev.links 28 | ] 29 | } 30 | }) 31 | 32 | onChangeToggle(link.text.toLowerCase()) 33 | } 34 | } 35 | 36 | return ( 37 |
    38 |
      39 | {linksState.links.map(link => ( 40 | 50 | ))} 51 |
    52 |
    53 | ) 54 | } 55 | 56 | // validate the component 57 | SideBarLinks.propTypes = { 58 | sidebarLinks: PropTypes.array.isRequired, 59 | onChangeToggle: PropTypes.func.isRequired, 60 | } 61 | 62 | export default SideBarLinks -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | - dont see the shit codes ! it's just experiment 2 | # User Panel Usgin React ✨🔥 3 | a few days ago I decided to do this project for myself as practice.This user panel created by react js and I used Formik and Yup for easier input validation 🙂🎈 and I just wanted to focus on logic so I used react bootstrap to design the whole pages. 4 | 5 | # pages 6 | login 7 | - register 8 | - user panel 9 | 10 | # Login page 11 | ![Screenshot 2022-06-02 122209](https://user-images.githubusercontent.com/89915857/171581524-e5ae72f8-1a5e-4878-a339-703278d1462f.png) 12 | as I said I didn't want to focous on the design and design take my all of times so It's a simple page for the login. 13 | ## Login Inputs 14 | - Username 15 | - Email 16 | - Password 17 | # Register Page 18 | ![Screenshot 2022-06-02 122457](https://user-images.githubusercontent.com/89915857/171582086-05fc7f34-5131-464b-b468-c6b7e6a83a78.png) 19 | as you know this page is for user register and you all of the inputs handled by the yup and formik and it maked me so easy and relax for the validation. 20 | ## register inputs 21 | - Username 22 | - Email 23 | - Birthday 24 | - Password 25 | - Confirm Password 26 | 27 | # User Panel 28 | ![Screenshot 2022-06-02 124331](https://user-images.githubusercontent.com/89915857/171585603-4845b892-8fff-49f3-b855-d8685e83468c.png) 29 | this page is for the users and all the data has to be dynamic. users can change their information by information page and change password page. 30 | 31 | ## Information Page 32 | ![Screenshot 2022-06-02 131603](https://user-images.githubusercontent.com/89915857/171592246-cce8f8ab-1e63-4f5f-b8ec-39f94ef09b3e.png) 33 | 34 | 35 | ## Change Password Page 36 | ![Screenshot 2022-06-02 131611](https://user-images.githubusercontent.com/89915857/171592343-fc263921-41a8-4db5-adfc-1adc6e935a04.png) 37 | 38 | 39 | # Components Structure 40 | ``` 41 | ├───Forms 42 | │ ├───FormInput 43 | │ ├───LoginForm 44 | │ └───RegisterForm 45 | ├───Panel 46 | │ ├───SideBarLinks 47 | │ │ └───SideBarLink 48 | │ ├───UserCard 49 | │ ├───UserChangePassword 50 | │ ├───UserInformation 51 | │ └───UserProfile 52 | └───Titles 53 | ``` 54 | 55 | # Features 56 | - [x] Users can change their information 57 | - [x] simple ui design 58 | - [x] responsive 59 | - [ ] users can upload their own profiles and banner 60 | - [x] Users can set their first name and last name 61 | -------------------------------------------------------------------------------- /src/Components/Forms/FormInput/FormInput.jsx: -------------------------------------------------------------------------------- 1 | import { PureComponent } from 'react' 2 | 3 | // import styles of this component 4 | import styles from "./FormInput.module.css" 5 | // import react bootstrap components 6 | import { FormGroup, FormLabel, FormControl, FormText } from 'react-bootstrap' 7 | // import pkgs 8 | import PropTypes from 'prop-types' 9 | 10 | class FormInput extends PureComponent { 11 | render() { 12 | // props 13 | const { 14 | inpClass='', 15 | className='', 16 | controlId, 17 | name, 18 | text, 19 | placeholder, 20 | type="text", 21 | as, 22 | errMsg, 23 | successMsg, 24 | valid, 25 | invalid, 26 | size='md', 27 | value, 28 | onChange, 29 | xs, 30 | sm, 31 | md, 32 | lg, 33 | } = this.props 34 | 35 | return ( 36 | 37 | {text} 38 | 51 | {invalid && { errMsg }} 52 | {valid && { successMsg }} 53 | 54 | ) 55 | } 56 | } 57 | 58 | // validate this component 59 | FormInput.propTypes = { 60 | inpClass: PropTypes.string, 61 | className: PropTypes.string, 62 | controlId: PropTypes.string.isRequired, 63 | name: PropTypes.string.isRequired, 64 | text: PropTypes.string.isRequired, 65 | placeholder: PropTypes.string.isRequired, 66 | type: PropTypes.string, 67 | as: PropTypes.elementType, 68 | errMsg: PropTypes.string.isRequired, 69 | successMsg: PropTypes.string.isRequired, 70 | valid: PropTypes.bool.isRequired, 71 | invalid: PropTypes.bool.isRequired, 72 | size: PropTypes.string, 73 | value: PropTypes.string.isRequired, 74 | onChange: PropTypes.func.isRequired 75 | } 76 | 77 | export default FormInput -------------------------------------------------------------------------------- /src/Components/Panel/UserChangePassword/UserChangePassword.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | // import other component 4 | import Titles from '../../Titles/Titles' 5 | import FormInput from '../../Forms/FormInput/FormInput' 6 | 7 | // import other pkg 8 | import { Form, Button } from 'react-bootstrap' 9 | import { useFormik } from 'formik' 10 | import { string, object, ref } from 'yup' 11 | 12 | const UserChangePassword = ({ password, onChangeInfo }) => { 13 | const [submit, setSubmit] = useState(false) 14 | 15 | const formik = useFormik({ 16 | initialValues: { 17 | currentPassword: '', 18 | newPassword: '', 19 | confirmNewPassword: '', 20 | }, 21 | validationSchema: object({ 22 | currentPassword: string().required('please enter your current password') 23 | .min(8, 'your current password must be 8 characters or more') 24 | .matches(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/, 'invalid password'), 25 | 26 | newPassword: string().required('please enter your new password') 27 | .min(8, 'your new password must be 8 characters or more') 28 | .matches(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/, 'invalid password'), 29 | 30 | confirmNewPassword: string().required('please enter your confirm new password') 31 | .matches(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/, 'invalid password') 32 | .oneOf([ref('newPassword')], 'your confirm new password must match') 33 | }), 34 | onSubmit: (values, { setFieldError }) => { 35 | if (values.currentPassword === password) 36 | onChangeInfo(['password'], [values.newPassword]) 37 | else 38 | setFieldError('currentPassword', "your current password isn't true") 39 | } 40 | }) 41 | 42 | return ( 43 | <> 44 | 45 | 46 |
    47 | 61 | 75 | 89 | 97 | 98 | 99 | ) 100 | } 101 | 102 | export default UserChangePassword -------------------------------------------------------------------------------- /src/Components/Forms/LoginForm/LoginForm.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | // import styles of this component 4 | import styles from '../Forms.module.css' 5 | 6 | // import other component 7 | import FormInput from '../FormInput/FormInput' 8 | 9 | // import other pkgs 10 | import { Container, Form, Button } from 'react-bootstrap' 11 | import { useFormik } from 'formik' 12 | import { object, string } from 'yup' 13 | import PropTypes from 'prop-types' 14 | 15 | // import utils 16 | import { getStorage, setUserId, updateStorage } from '../../../utils/storage' 17 | 18 | const LoginForm = ({ onRegister, onLogin }) => { 19 | const [submit, setSubmit] = useState(false) 20 | 21 | const formik = useFormik({ 22 | initialValues: { 23 | username: '', 24 | email: '', 25 | password: '', 26 | }, 27 | validationSchema: object({ 28 | username: string().required('please enter your username') 29 | .max(15, 'your username must be 15 characters or less') 30 | .min(4, 'your username must be 4 characters or more'), 31 | email: string().email('invalid email').required('please enter your email'), 32 | password: string().required('please enter your password') 33 | .min(8, 'your password must be 8 characters or more') 34 | .matches(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/, 'invalid password'), 35 | }), 36 | onSubmit: ({ username, email, password }, { setFieldError }) => { 37 | const users = getStorage('users') 38 | const myVerifyUser = users && users.find(user => user.username === username) 39 | 40 | if (users && myVerifyUser) { 41 | if (myVerifyUser.email === email && myVerifyUser.password === password) 42 | login(myVerifyUser) 43 | else if (myVerifyUser.email !== email) 44 | setFieldError('email', `your email isn't true`) 45 | else 46 | setFieldError('password', `your password isn't correct`) 47 | } else 48 | setFieldError('username', 'your username not found') 49 | } 50 | }) 51 | 52 | const login = (myVerifyUser) => { 53 | const users = getStorage('users') 54 | updateStorage(users, myVerifyUser, true) 55 | setUserId(myVerifyUser.id) 56 | onLogin() 57 | } 58 | 59 | return ( 60 | 61 |
    62 |

    Login

    63 | 64 | 76 | 77 | 89 | 90 | 103 | 104 | 111 | 112 | 120 | 121 |
    122 | ) 123 | } 124 | 125 | // validate the component 126 | LoginForm.propTypes = { 127 | onRegister: PropTypes.func.isRequired, 128 | onLogin: PropTypes.func.isRequired, 129 | } 130 | 131 | export default LoginForm -------------------------------------------------------------------------------- /src/Components/Panel/Panel.jsx: -------------------------------------------------------------------------------- 1 | import { PureComponent } from 'react' 2 | import { createPortal } from 'react-dom'; 3 | 4 | // import styles of this component 5 | import styles from './Panel.module.css' 6 | 7 | // import other component 8 | import UserCard from './UserCard/UserCard' 9 | import UserInformation from './UserInformation/UserInformation' 10 | import UserChangePassword from './UserChangePassword/UserChangePassword' 11 | 12 | // import other pkgs 13 | import { UserEdit, Lock, ProfileCircle, Code1 } from "iconsax-react"; 14 | import { Row, Col, Button } from 'react-bootstrap' 15 | import PropTypes from 'prop-types' 16 | 17 | // import utils 18 | import { getStorage, updateStorage } from './../../utils/storage'; 19 | 20 | class Panel extends PureComponent { 21 | constructor(props) { 22 | super(props) 23 | this.myVerifyUser = this.getUserFromStorage() 24 | 25 | this.state = { 26 | user: {...this.initState(this.myVerifyUser)}, 27 | toggle: 'information', 28 | } 29 | 30 | this.sidebarLinks = [ 31 | { 32 | id: 1, 33 | border: true, 34 | text: 'Information', 35 | icon: , 36 | active: true, 37 | }, 38 | { 39 | id: 2, 40 | border: true, 41 | text: 'Password', 42 | icon: , 43 | active: false, 44 | }, 45 | { 46 | id: 3, 47 | border: true, 48 | text: 'Profile', 49 | icon: , 50 | active: false, 51 | }, 52 | { 53 | id: 4, 54 | border: false, 55 | href: 'https://github.com/Banana021s/React-User-Panel', 56 | text: 'Github repo', 57 | icon: , 58 | } 59 | ] 60 | 61 | this.logOut = this.logOut.bind(this) 62 | this.changeToggle = this.changeToggle.bind(this) 63 | this.changeUserInformation = this.changeUserInformation.bind(this) 64 | } 65 | 66 | getUserFromStorage() { 67 | const users = getStorage('users') 68 | const userId = getStorage('id') 69 | const myVerifyUser = users.find(user => user.id === userId) 70 | 71 | return myVerifyUser 72 | } 73 | 74 | initState({ id, username, email, birthday, password, isLogin, firstName, lastName }) { 75 | return ({ id, username, email, birthday, password, firstName, lastName, isLogin, }) 76 | } 77 | 78 | logOut() { 79 | this.changeUserInformation(['isLogin'], [false]) 80 | } 81 | 82 | componentDidUpdate() { 83 | updateStorage(getStorage('users'), this.state.user) 84 | !this.state.user.isLogin && this.props.onLogOut() 85 | } 86 | 87 | changeToggle(toggle) { 88 | this.setState({ toggle }) 89 | } 90 | 91 | changeUserInformation(keyInfos, valInfos) { 92 | let newInfo = {} 93 | 94 | keyInfos.forEach((keyInfo, idx) => ( 95 | newInfo[keyInfo] = valInfos[idx] 96 | )) 97 | 98 | this.setState(prev => { 99 | return { 100 | user: { 101 | ...prev.user, 102 | ...newInfo, 103 | } 104 | } 105 | }) 106 | } 107 | 108 | render() { 109 | return ( 110 |
    111 | {/*
    TODO */} 112 |
    113 | 114 | 115 | 122 | 123 | 124 | 125 | {this.state.toggle === 'information' && ( 126 | 134 | )} 135 | {this.state.toggle === 'password' && ( 136 | 140 | )} 141 | 142 | 143 |
    144 | {createPortal(( 145 | 151 | ), document.getElementById("root"))} 152 |
    153 | ) 154 | } 155 | } 156 | 157 | // validate component 158 | Panel.propTypes = { 159 | onLogOut: PropTypes.func.isRequired 160 | } 161 | 162 | export default Panel -------------------------------------------------------------------------------- /src/Components/Forms/RegisterForm/RegisterForm.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | // import styles of this component 4 | import styles from '../Forms.module.css' 5 | 6 | // import other component to use 7 | import FormInput from '../FormInput/FormInput'; 8 | 9 | // import other pkg to use 10 | import { useFormik } from 'formik'; 11 | import { object, string, date, ref } from 'yup' 12 | import PropTypes from 'prop-types'; 13 | import { v4 as uniqid } from 'uuid'; 14 | import { Container, Button, Form } from 'react-bootstrap'; 15 | 16 | // import utils 17 | import { getStorage, setUserId, setUserInStorage } from '../../../utils/storage'; 18 | 19 | const RegisterForm = ({ onRegister, onLogin }) => { 20 | const [submit, setSubmit] = useState(false) 21 | 22 | const formik = useFormik({ 23 | initialValues: { 24 | username: '', 25 | email: '', 26 | birthday: '', 27 | password: '', 28 | confirmPassword: '', 29 | }, 30 | validationSchema: object({ 31 | username: string().required('please enter your username') 32 | .max(15, 'your username must be 15 characters or less') 33 | .min(4, 'your username must be 4 characters or more'), 34 | email: string().email('invalid email').required('Please enter your email'), 35 | birthday: date().required('please enter your birthday date') 36 | .min('1922-01-01', 'your birthday date must be 1922-01-01 or more') 37 | .max('2022-05-22', 'invalid birthday date'), 38 | password: string().required('please enter your password') 39 | .min(8, 'your password must be 8 characters or more') 40 | .matches(/^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/, 'invalid password'), 41 | confirmPassword: string().required('please enter your confirm password') 42 | .oneOf([ref('password')], 'your confirm password must match'), 43 | }), 44 | onSubmit: (values, { setFieldError }) => { 45 | if (getStorage('users')) { 46 | const [isIterateUsername, isIterateEmail] = checkUser(values.username, values.email) 47 | 48 | if (isIterateUsername) 49 | setFieldError('username', 'please change your username') 50 | else if (isIterateEmail) 51 | setFieldError('email', 'please change your email') 52 | else { 53 | const userId = uniqid() 54 | const users = getStorage('users') 55 | const user = { id: userId, ...values, isLogin: true, } 56 | users.push(user) 57 | 58 | setUserId(userId) 59 | setUserInStorage('users', users) 60 | onRegister() 61 | } 62 | } else { 63 | const userId = uniqid() 64 | const users = [{ id: userId, ...values, isLogin: true, }] 65 | 66 | setUserId(userId) 67 | setUserInStorage('users', users) 68 | onRegister() 69 | } 70 | } 71 | }) 72 | 73 | const checkUser = (username, email) => { 74 | const users = getStorage('users') 75 | const isIterateUsername = users.some(user => user.username === username) 76 | const isIterateEmail = users.some(user => user.email === email) 77 | 78 | return [isIterateUsername , isIterateEmail] 79 | } 80 | 81 | return ( 82 | 83 |
    84 |

    Register

    85 | 86 | 98 | 99 | 111 | 112 | 125 | 126 | 139 | 140 | 153 | 154 | 161 | 162 | 170 | 171 |
    172 | ) 173 | } 174 | 175 | // validate component 176 | RegisterForm.propTypes = { 177 | onRegister: PropTypes.func.isRequired, 178 | onLogin: PropTypes.func.isRequired, 179 | } 180 | 181 | export default RegisterForm -------------------------------------------------------------------------------- /src/Components/Panel/UserInformation/UserInformation.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | // import other component 4 | import FormInput from '../../Forms/FormInput/FormInput'; 5 | import Titles from '../../Titles/Titles'; 6 | 7 | // import other pkg 8 | import { Form, Row, Col, Button } from 'react-bootstrap'; 9 | import { useFormik } from 'formik'; 10 | import { object, string, date } from 'yup' 11 | import PropTypes from 'prop-types'; 12 | 13 | // import utils 14 | import { getStorage } from '../../../utils/storage'; 15 | 16 | 17 | const UserInformation = ({ username , firstName, lastName, email, birthday, onChangeInfo }) => { 18 | const [submit, setSubmit] = useState(false) 19 | 20 | const isNotIterateEmail = (username, email) => { 21 | const users = getStorage('users') 22 | 23 | const [isNotIterateUsername, isNotIterateEmail] = users.map(user => ( 24 | user.username !== username && user.email === email 25 | )) 26 | 27 | return ((!isNotIterateUsername && !isNotIterateEmail) || (isNotIterateUsername && isNotIterateEmail)) ? true : false 28 | } 29 | 30 | const formik = useFormik({ 31 | initialValues: { 32 | firstName: firstName ? firstName : '', 33 | lastName: lastName ? lastName : '', 34 | email, 35 | birthday, 36 | }, 37 | validationSchema: object({ 38 | firstName: string().max(10, 'your fisrt name must be 10 or less'), 39 | lastName: string().max(10, 'your fisrt name must be 10 or less'), 40 | email: string().required('please enter your email').email('invalid email'), 41 | birthday: date().required('please enter your birthday date') 42 | .min('1922-01-01', 'your birthday date must be 1922-01-01 or more') 43 | .max('2022-05-22', 'invalid birthday date'), 44 | }), 45 | onSubmit: ({ firstName, lastName, email, birthday }, { setFieldError }) => { 46 | const boolIsIterateEmail = isNotIterateEmail(username, email) 47 | 48 | if (boolIsIterateEmail) { 49 | if (!firstName && !lastName) { 50 | onChangeInfo( 51 | ['firstName', 'lastName', 'email', 'birthday'], 52 | ['', '', email, birthday] 53 | ) 54 | } else if (!firstName) { 55 | onChangeInfo( 56 | ['firstName' , 'lastName', 'email', 'birthday'], 57 | ['', lastName, email, birthday] 58 | ) 59 | } else if (!lastName) { 60 | onChangeInfo( 61 | ['firstName', 'lastName','email', 'birthday'], 62 | [firstName, '' , email, birthday] 63 | ) 64 | } else { 65 | onChangeInfo( 66 | ['firstName', 'lastName', 'email', 'birthday'], 67 | [firstName, lastName, email, birthday] 68 | ) 69 | } 70 | } else 71 | setFieldError('email', "you can't choose this email") 72 | } 73 | }) 74 | 75 | return ( 76 | <> 77 | 78 | 79 |
    80 | 81 | 102 | 123 | 124 | 125 | 126 | 143 | 161 | 162 | 169 |
    170 | 171 | ) 172 | } 173 | 174 | // validate the component 175 | UserInformation.propTypes = { 176 | username: PropTypes.string.isRequired, 177 | firstName: PropTypes.string.isRequired, 178 | lastName: PropTypes.string.isRequired, 179 | email: PropTypes.string.isRequired, 180 | birthday: PropTypes.string.isRequired, 181 | onChangeInfo: PropTypes.func.isRequired, 182 | } 183 | 184 | export default UserInformation --------------------------------------------------------------------------------