├── 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 | 
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 | 
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 | 
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 | 
33 |
34 |
35 | ## Change Password Page
36 | 
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 |
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 |
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 |
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 |
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
--------------------------------------------------------------------------------