├── .gitignore ├── README.md ├── deploy.sh ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── server.js └── src ├── App.js ├── App.test.js ├── actions └── profile.js ├── components ├── Divider │ ├── Divider.js │ ├── index.js │ └── style.css ├── Footer │ ├── Footer.js │ ├── index.js │ └── style.css ├── Header │ ├── Header.js │ ├── index.js │ └── style.css ├── InputRange │ ├── InputRange.js │ ├── index.js │ └── style.css ├── MultiSelect │ ├── MultiSelect.js │ ├── index.js │ └── style.css ├── ProfileImage │ ├── ProfileImage.js │ ├── index.js │ └── style.css ├── UserDetails │ ├── UserDetails.js │ ├── index.js │ └── style.css ├── Welcome │ ├── Welcome.js │ ├── index.js │ └── style.css └── index.js ├── constants └── actionTypes.js ├── context └── auth.js ├── index.js ├── logo.svg ├── mock └── state.json ├── navigation └── index.js ├── reducers └── profile.js ├── screens ├── Confirmation │ ├── Confirmation.js │ ├── components │ │ └── Approval │ │ │ ├── Approval.js │ │ │ ├── index.js │ │ │ └── style.css │ └── index.js ├── Home │ ├── Home.js │ ├── index.js │ └── style.css ├── Login │ ├── Login.js │ ├── index.js │ └── style.css └── Register │ ├── Register.js │ ├── components │ └── RightContent │ │ ├── RightContent.js │ │ ├── index.js │ │ └── style.css │ └── index.js ├── serviceWorker.js ├── setupTests.js ├── store └── index.js ├── styles ├── index.js └── main.css └── utils └── index.js /.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 | package-lock.json 8 | yarn.lock 9 | # testing 10 | /coverage 11 | 12 | # production 13 | /build 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Pure ReactJS form registration 2 | Handling User Registration and Login with Redux, Form validations & Route using Pure ReactJS applications without any external package. 3 | 4 | ## Project setup 5 | 6 | In the project directory, you can run: 7 | 1. ``` npm install ``` 8 | 2. ```npm start``` 9 | 10 | Runs the app in the development mode.
11 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 12 | 13 | The page will reload if you make edits.
14 | You will also see any lint errors in the console. 15 | 16 | ## Features available in the application: 17 | 1. Perfect Folder Structure 18 | 2. User Registration 19 | 3. Login 20 | 3. React-Redux (store implemetation) 21 | 5. Route & Private Route (Authentication) 22 | 6. Form Validations 23 | 7. Individual Components (Input range, Multiselect & Profile Picture, …etc) 24 | 8. Load data from local JSON file (mock data) 25 | 9. No external libraries 26 | 10. Custom Utils functions (Email validation, Format Phone (111) 111–1111), & localstorage, ... etc) 27 | 28 | ## Tutorial is available on [medium article](https://medium.com/@jebasuthan/react-user-registration-and-login-using-redux-81ec739e93d1) 29 | 30 | 31 | ## 🎉 [Demo Link](https://jebasuthan.github.io/React-form-registration/) 🎉 32 | 33 | ## Login Screen 34 | Screenshot 2020-07-19 at 10 38 49 AM 35 | 36 | ## Register User 37 | Screenshot 2020-07-19 at 10 40 15 AM 38 | 39 | ## Home Screen 40 | Screenshot 2020-07-19 at 10 41 02 AM 41 | 42 | ## Compiles and minifies for production 43 | ```npm build``` 44 | 45 | ## Run your tests 46 | ```npm test``` 47 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # abort on errors 3 | set -e 4 | # build 5 | npm run build 6 | # navigate into the build output directory 7 | cd dist 8 | # if you are deploying to a custom domain 9 | # echo 'www.example.com' > CNAME 10 | git init 11 | git add -A 12 | git commit -m 'deploy' 13 | git push -f git@github.com/Jebasuthan/React-form-registration.git master:gh-pages 14 | cd - -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-form-register", 3 | "version": "0.1.0", 4 | "homepage": "https://Jebasuthan.github.io/React-form-registration", 5 | "private": true, 6 | "dependencies": { 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.3.2", 9 | "@testing-library/user-event": "^7.1.2", 10 | "bootstrap": "^4.5.0", 11 | "express": "^4.17.1", 12 | "prop-types": "^15.7.2", 13 | "react": "^16.13.1", 14 | "react-dom": "^16.13.1", 15 | "react-redux": "^7.2.0", 16 | "react-router-dom": "^5.2.0", 17 | "react-scripts": "3.4.1", 18 | "redux": "^4.0.5", 19 | "redux-thunk": "^2.3.0" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject", 26 | "predeploy": "npm run build", 27 | "deploy": "gh-pages -d build" 28 | }, 29 | "eslintConfig": { 30 | "extends": "react-app" 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | }, 44 | "devDependencies": { 45 | "gh-pages": "^4.0.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jebasuthan/React-form-registration/d62f529c48d2e6619268dc6f7ba1313a446dbbfe/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | React simple form registration 20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jebasuthan/React-form-registration/d62f529c48d2e6619268dc6f7ba1313a446dbbfe/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jebasuthan/React-form-registration/d62f529c48d2e6619268dc6f7ba1313a446dbbfe/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | 4 | const app = express(); 5 | const staticFileMiddleware = express.static(path.join(__dirname + '/build')); 6 | 7 | app.use(staticFileMiddleware); 8 | 9 | app.get('/*', function (req, res) { 10 | res.sendFile(path.join(__dirname, '/build', 'index.html')); 11 | }); 12 | 13 | var server = app.listen(process.env.PORT || 8080, function () { 14 | var port = server.address().port; 15 | console.log("App now running on port", port); 16 | }); -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Header, Footer } from './components'; 4 | import Navigation from './navigation'; 5 | import { getStore } from './utils'; 6 | import { ActionCreators } from './actions/profile'; 7 | import './styles'; 8 | 9 | class App extends React.Component { 10 | componentDidMount() { 11 | const user = getStore('user') 12 | if (user) { 13 | this.props.dispatch(ActionCreators.login(user)); 14 | } 15 | } 16 | render() { 17 | return ( 18 |
19 |
20 | 21 |
23 | ) 24 | } 25 | } 26 | 27 | const mapStateToProps = (state) => { 28 | return { 29 | profile: state.user.profile 30 | } 31 | } 32 | 33 | export default connect(mapStateToProps)(App); 34 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/actions/profile.js: -------------------------------------------------------------------------------- 1 | import { Types } from '../constants/actionTypes'; 2 | 3 | export const ActionCreators = { 4 | 5 | addProfile: (user) => ({ type: Types.ADD_USER, payload: { user } }), 6 | 7 | updateProfileImage: (image) => ({ type: Types.UPDATE_PROFILE_PICTURE, payload: { image } }), 8 | 9 | updateProfile: (user) => ({ type: Types.UPDATE_USER, payload: { user } }), 10 | 11 | formSubmittionStatus: (status) => ({ type: Types.FORM_SUBMITION_STATUS, payload: { status }}), 12 | 13 | login: (user) => ({ type: Types.LOGIN, payload: { user } }) 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Divider/Divider.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './style.css'; 3 | 4 | export class Divider extends Component { 5 | render() { 6 | return ( 7 |
8 | ) 9 | } 10 | } 11 | 12 | export default Divider; 13 | -------------------------------------------------------------------------------- /src/components/Divider/index.js: -------------------------------------------------------------------------------- 1 | import Divider from './Divider'; 2 | 3 | export default Divider; 4 | -------------------------------------------------------------------------------- /src/components/Divider/style.css: -------------------------------------------------------------------------------- 1 | .divider { 2 | /* border: 1px solid #ccc; */ 3 | 4 | border: 1px solid #ccc; 5 | height: 100%; 6 | position: absolute; 7 | left: 21%; 8 | } 9 | @media only screen and (max-width: 768px) { 10 | .divider { 11 | border: 0px; 12 | } 13 | } -------------------------------------------------------------------------------- /src/components/Footer/Footer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './style.css'; 3 | 4 | export class Footer extends Component { 5 | render() { 6 | return ( 7 | 8 | ) 9 | } 10 | } 11 | 12 | export default Footer; 13 | -------------------------------------------------------------------------------- /src/components/Footer/index.js: -------------------------------------------------------------------------------- 1 | import Footer from './Footer'; 2 | 3 | export default Footer; 4 | -------------------------------------------------------------------------------- /src/components/Footer/style.css: -------------------------------------------------------------------------------- 1 | footer { 2 | position: fixed; 3 | bottom: 0; 4 | left: 0; 5 | bottom: 0; 6 | width: 100%; 7 | min-height: 8vh; 8 | display: flex !important; 9 | align-items: center; 10 | justify-content: center; 11 | background-color: #daf5ff; 12 | /* background-image: linear-gradient(#daf5ff, transparent, transparent); */ 13 | } -------------------------------------------------------------------------------- /src/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import './style.css'; 3 | 4 | export class Header extends Component { 5 | render() { 6 | return ( 7 |
User - Registration
8 | ) 9 | } 10 | } 11 | 12 | export default Header; 13 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import Header from './Header'; 2 | 3 | export default Header; 4 | -------------------------------------------------------------------------------- /src/components/Header/style.css: -------------------------------------------------------------------------------- 1 | header { 2 | min-height: 8vh; 3 | display: flex !important; 4 | align-items: center; 5 | justify-content: center; 6 | color: #282c34; 7 | /* background-image: linear-gradient(#daf5ff, transparent, transparent); */ 8 | background-color: #daf5ff; 9 | border-bottom: 1px solid #daf5ff; 10 | } -------------------------------------------------------------------------------- /src/components/InputRange/InputRange.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import './style.css'; 5 | 6 | export class InputRange extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | value: 0 11 | } 12 | } 13 | 14 | componentDidMount() { 15 | if(this.props.profile) { 16 | this.setState({ value: this.props.profile.age }); 17 | } 18 | } 19 | 20 | inputChange = (e) => { 21 | this.setState({ value: parseInt(e.target.value) }); 22 | this.props.onChangeInputRange(parseInt(e.target.value)); 23 | } 24 | 25 | render() { 26 | return ( 27 |
28 | {this.props.min} 29 |
30 | {this.inputChange(e)}} step={this.props.step} className="form-control-plaintext rangeSlider" /> 31 | {this.state.value} 32 |
33 | {this.props.max} 34 |
35 | ) 36 | } 37 | } 38 | 39 | InputRange.propTypes = { 40 | className: PropTypes.string, 41 | min: PropTypes.number, 42 | max: PropTypes.number, 43 | step: PropTypes.number, 44 | value: PropTypes.number, 45 | onChangeInputRange: PropTypes.func 46 | } 47 | 48 | const mapStateToProps = (state) => { 49 | return { 50 | profile: state.user.profile 51 | } 52 | } 53 | 54 | export default connect(mapStateToProps)(InputRange); 55 | -------------------------------------------------------------------------------- /src/components/InputRange/index.js: -------------------------------------------------------------------------------- 1 | import InputRange from './InputRange'; 2 | 3 | export default InputRange; 4 | -------------------------------------------------------------------------------- /src/components/InputRange/style.css: -------------------------------------------------------------------------------- 1 | .input-range__label--min { 2 | left: 20px; 3 | } 4 | .input-range__label--max { 5 | right: 0; 6 | } 7 | .input-range__label--min, .input-range__label--max { 8 | bottom: 1.6rem; 9 | position: absolute; 10 | } 11 | .input-range__label-container { 12 | left: -50%; 13 | position: relative; 14 | } 15 | span.rangevalue { 16 | color: #fff; 17 | background: #1b8bec; 18 | border-radius: 50%; 19 | /* position: absolute; */ 20 | width: 25px; 21 | padding: 6px; 22 | } -------------------------------------------------------------------------------- /src/components/MultiSelect/MultiSelect.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import './style.css'; 5 | 6 | export class MultiSelect extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | selected: [], 11 | userInput: '' 12 | } 13 | } 14 | 15 | componentDidMount() { 16 | if (this.props.profile) { 17 | this.setState({ selected: this.props.profile.interests }); 18 | } 19 | } 20 | 21 | handleKeyPress = (event) => { 22 | if(event.key === 'Enter'){ 23 | this.setState({ userInput: event.target.value }); 24 | this.addTag(event.target.value) 25 | } 26 | } 27 | 28 | handleChange = (e) => { 29 | e.preventDefault(); 30 | this.setState({ userInput: e.target.value }); 31 | } 32 | 33 | addTag = (value) => { 34 | let current = this.state.selected; 35 | if (this.state.selected.indexOf(value) === -1) { 36 | current.push(value); 37 | this.setState({ selected: current, userInput: '' }); 38 | } else { 39 | current.splice(this.state.selected.indexOf(value), 1); 40 | this.setState({ selected: current, userInput: '' }); 41 | } 42 | this.props.onSelect(this.state.selected); 43 | } 44 | 45 | removeTag = (index) => { 46 | let current = this.state.selected; 47 | current.splice(index, 1); 48 | this.setState({ selected: current }); 49 | this.props.onSelect(this.state.selected); 50 | } 51 | 52 | render() { 53 | const listItems = this.state.selected.map((value, index) => { 54 | return ( 55 |
{ value } 56 | { e.preventDefault(); this.removeTag(index); }}> x 57 |
58 | ); 59 | }) 60 | return ( 61 |
62 | { this.handleChange(e) }} placeholder={this.props.searchPlaceholder} className={this.props.className} /> 63 | {listItems} 64 |
65 | ) 66 | } 67 | } 68 | 69 | MultiSelect.propTypes = { 70 | searchPlaceholder: PropTypes.string, 71 | selected: PropTypes.array, 72 | className: PropTypes.string, 73 | onSelect: PropTypes.func.isRequired, 74 | onUserInput: PropTypes.func 75 | } 76 | 77 | const mapStateToProps = (state) => { 78 | return { 79 | profile: state.user.profile 80 | } 81 | } 82 | 83 | export default connect(mapStateToProps)(MultiSelect); 84 | -------------------------------------------------------------------------------- /src/components/MultiSelect/index.js: -------------------------------------------------------------------------------- 1 | import MultiSelect from './MultiSelect'; 2 | 3 | export default MultiSelect; 4 | -------------------------------------------------------------------------------- /src/components/MultiSelect/style.css: -------------------------------------------------------------------------------- 1 | .tags { 2 | color: white; 3 | background: #1b8bec; 4 | margin: 10px 0; 5 | margin-left: 0px; 6 | margin-right: 5px; 7 | padding: 5px; 8 | min-width: 50px; 9 | float: left; 10 | } 11 | .tags span{ 12 | margin-left: 20px; 13 | cursor: pointer; 14 | } 15 | .active span{ 16 | display: block; 17 | } -------------------------------------------------------------------------------- /src/components/ProfileImage/ProfileImage.js: -------------------------------------------------------------------------------- 1 | import React, { Component, createRef } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { NavLink } from 'react-router-dom'; 4 | import { withRouter} from "react-router-dom"; 5 | import { ActionCreators } from '../../actions/profile'; 6 | import { getStore } from '../../utils'; 7 | import './style.css'; 8 | 9 | export class ProfileImage extends Component { 10 | 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | selectedFile: null, 15 | profileImage: null 16 | } 17 | this.profileImgRef = createRef() 18 | this.handleClick = this.handleClick.bind(this); 19 | } 20 | 21 | profileImageSelection = event => { 22 | let file = this.profileImgRef.current.files[0] 23 | if (file) { 24 | let obj = new FileReader() 25 | obj.readAsDataURL(file) 26 | obj.onloadend = function (e) { 27 | this.setState({ 28 | selectedFile: file, 29 | profileImage: obj.result 30 | }); 31 | this.props.dispatch(ActionCreators.updateProfileImage(obj.result)); 32 | }.bind(this) 33 | } 34 | } 35 | 36 | handleClick(e) { 37 | this.profileImgRef.current.click(); 38 | } 39 | 40 | logout = (event) => { 41 | event.preventDefault(); 42 | this.props.history.push('/React-form-registration/login') 43 | } 44 | 45 | render() { 46 | const { profileImage } = this.props.profile; 47 | return ( 48 |
49 |
50 | { profileImage && } 51 | 52 | 53 |
54 |
{ this.props.formSubmitted && this.props.profile && this.props.profile.profileImage.length === 0 && Please Select Image }
55 | { 56 | this.props.location.pathname === '/confirm' && Edit Profile 57 | } 58 | { 59 | getStore('user') && 60 | } 61 |
62 | ) 63 | } 64 | } 65 | 66 | const mapStateToProps = (state) => { 67 | return { 68 | profile: state.user.profile, 69 | formSubmitted: state.user.formSubmitted 70 | } 71 | } 72 | 73 | export default connect(mapStateToProps)(withRouter(ProfileImage)); 74 | -------------------------------------------------------------------------------- /src/components/ProfileImage/index.js: -------------------------------------------------------------------------------- 1 | import ProfileImage from './ProfileImage'; 2 | 3 | export default ProfileImage; 4 | -------------------------------------------------------------------------------- /src/components/ProfileImage/style.css: -------------------------------------------------------------------------------- 1 | .leftPanel { 2 | /* padding: 20px; */ 3 | margin: 10px; 4 | float: left; 5 | width: 20%; 6 | } 7 | .profileImage { 8 | height: 150px; 9 | /* width: 90%; */ 10 | position: absolute; 11 | z-index: 99; 12 | left: 50%; 13 | top: 50%; 14 | transform: translate(-50%, -50%); 15 | cursor: pointer; 16 | } 17 | .container { 18 | position: relative; 19 | height: 30vh; 20 | background-color: #1b8bec; 21 | min-height: 200px; 22 | width: 90% !important; 23 | } 24 | .profileImageSelection { 25 | background-color: #1b8bec; 26 | color: #fff; 27 | position: absolute; 28 | left: 50%; 29 | top: 50%; 30 | transform: translate(-50%, -50%); 31 | border: none; 32 | text-decoration: underline; 33 | height: 100%; 34 | width: 100%; 35 | } 36 | .editProfile { 37 | text-align: center; 38 | display: block; 39 | margin: 10px; 40 | text-decoration: underline; 41 | } 42 | .center { 43 | text-align: center; 44 | } 45 | 46 | @media only screen and (max-width: 768px) { 47 | .leftPanel { 48 | width: 100%; 49 | margin: 10px; 50 | } 51 | } -------------------------------------------------------------------------------- /src/components/UserDetails/UserDetails.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import './style.css'; 4 | 5 | export class UserDetails extends Component { 6 | render() { 7 | const { firstName, lastName, age, email, interests, state, telephone } = this.props.user 8 | return ( 9 |
10 |
I am {firstName} {lastName} and I am above {age} years and you can send your emails to {email}. I live in the state of {state}. I likes to {interests.join(', ')} and please send me the news letters. Please reach out to me on my phone {telephone}.
11 |
12 | ) 13 | } 14 | } 15 | 16 | UserDetails.propTypes = { 17 | user: PropTypes.object 18 | } 19 | 20 | export default UserDetails; 21 | -------------------------------------------------------------------------------- /src/components/UserDetails/index.js: -------------------------------------------------------------------------------- 1 | import UserDetails from './UserDetails'; 2 | 3 | export default UserDetails; 4 | -------------------------------------------------------------------------------- /src/components/UserDetails/style.css: -------------------------------------------------------------------------------- 1 | .bindtext { 2 | color: #1b8bec; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/Welcome/Welcome.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import './style.css'; 4 | 5 | export class Welcome extends Component { 6 | render() { 7 | return ( 8 |

Welcome {this.props.user.firstName} {this.props.user.lastName} !

9 | ) 10 | } 11 | } 12 | 13 | Welcome.propTypes = { 14 | user: PropTypes.object 15 | } 16 | 17 | export default Welcome; 18 | -------------------------------------------------------------------------------- /src/components/Welcome/index.js: -------------------------------------------------------------------------------- 1 | import Welcome from './Welcome'; 2 | 3 | export default Welcome; 4 | -------------------------------------------------------------------------------- /src/components/Welcome/style.css: -------------------------------------------------------------------------------- 1 | .marquee { 2 | color: green; 3 | white-space: nowrap; 4 | overflow: hidden; 5 | box-sizing: border-box; 6 | } 7 | .marquee p { 8 | display: inline-block; 9 | padding-left: 100%; 10 | animation: marquee 15s linear infinite; 11 | } 12 | @keyframes marquee { 13 | 0% { transform: translate(0, 0); } 14 | 100% { transform: translate(-100%, 0); } 15 | } -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Header } from './Header'; 2 | export { default as Footer } from './Footer'; 3 | export { default as ProfileImage } from './ProfileImage'; 4 | export { default as InputRange } from './InputRange'; 5 | export { default as MultiSelect } from './MultiSelect'; 6 | export { default as Divider } from './Divider'; 7 | export { default as UserDetails } from './UserDetails'; 8 | export { default as Welcome } from './Welcome'; 9 | -------------------------------------------------------------------------------- /src/constants/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const Types = { 2 | LOGIN: 'LOGIN', 3 | ADD_USER: 'ADD_USER', 4 | UPDATE_USER: 'UPDATE_USER', 5 | UPDATE_PROFILE_PICTURE: 'UPDATE_PROFILE_PICTURE', 6 | FORM_SUBMITION_STATUS: 'FORM_SUBMITION_STATUS' 7 | } 8 | -------------------------------------------------------------------------------- /src/context/auth.js: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react'; 2 | 3 | export const AuthContext = createContext(); 4 | 5 | export function useAuth() { 6 | return useContext(AuthContext); 7 | } 8 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { BrowserRouter } from 'react-router-dom'; 4 | import { Provider } from 'react-redux'; 5 | import configureStore from './store'; 6 | import App from './App'; 7 | import * as serviceWorker from './serviceWorker'; 8 | import 'bootstrap/dist/css/bootstrap.min.css'; 9 | 10 | const store = configureStore(); 11 | 12 | ReactDOM.render( 13 | 14 | 15 | 16 | 17 | 18 | 19 | , 20 | document.getElementById('root') 21 | ); 22 | 23 | serviceWorker.unregister(); 24 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/mock/state.json: -------------------------------------------------------------------------------- 1 | { 2 | "listStates": [ 3 | { 4 | "id": "1", 5 | "name": "Alaska" 6 | }, 7 | { 8 | "id": "2", 9 | "name": "California" 10 | }, 11 | { 12 | "id": "3", 13 | "name": "Florida" 14 | }, 15 | { 16 | "id": "4", 17 | "name": "Indiana" 18 | }, 19 | { 20 | "id": "5", 21 | "name": "New York" 22 | }, 23 | { 24 | "id": "6", 25 | "name": "Ohio" 26 | }, 27 | { 28 | "id": "7", 29 | "name": "Tennessee" 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /src/navigation/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { BrowserRouter as Router, Route, Redirect, Switch } from 'react-router-dom'; 3 | import Login from '../screens/Login'; 4 | import Home from '../screens/Home'; 5 | import Register from '../screens/Register'; 6 | import Confirmation from '../screens/Confirmation'; 7 | import { AuthContext } from '../context/auth'; 8 | import { getStore } from '../utils'; 9 | 10 | function AuthenticatedRoute ({component: Component, ...rest}) { 11 | return ( 12 | getStore('user') ? 15 | : } 16 | /> 17 | ) 18 | } 19 | 20 | class Navigation extends Component { 21 | render() { 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ) 35 | } 36 | } 37 | 38 | export default Navigation; 39 | -------------------------------------------------------------------------------- /src/reducers/profile.js: -------------------------------------------------------------------------------- 1 | import { Types } from '../constants/actionTypes'; 2 | 3 | const initialState = { 4 | profile: { 5 | firstName: '', 6 | lastName: '', 7 | telephone: '', 8 | age: 28, 9 | email: '', 10 | state: '', 11 | country: '', 12 | address: 'Home', 13 | address1: '', 14 | address2: '', 15 | interests: [], 16 | profileImage: '', 17 | subscribenewsletter: false 18 | }, 19 | formSubmitted: false 20 | } 21 | 22 | const reducer = (state = initialState, action) => { 23 | switch (action.type) { 24 | case Types.LOGIN: 25 | console.log('login', action.payload.user) 26 | return { 27 | ...state, 28 | profile: action.payload.user, 29 | formSubmitted: false // after update user formsubmition reset 30 | } 31 | case Types.ADD_USER: 32 | return { 33 | ...state, 34 | profile: action.payload.user, 35 | formSubmitted: false // after update user formsubmition reset 36 | } 37 | case Types.UPDATE_USER: 38 | return { 39 | ...state, 40 | profile: action.payload.user, 41 | formSubmitted: false // after update user formsubmition reset 42 | } 43 | case Types.UPDATE_PROFILE_PICTURE: 44 | return { 45 | ...state, 46 | profile: { 47 | ...state.profile, 48 | profileImage: action.payload.image 49 | } 50 | } 51 | case Types.FORM_SUBMITION_STATUS: 52 | return { 53 | ...state, 54 | formSubmitted: action.payload.status 55 | } 56 | default: 57 | return state; 58 | } 59 | } 60 | 61 | export default reducer; 62 | -------------------------------------------------------------------------------- /src/screens/Confirmation/Confirmation.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import Approval from './components/Approval'; 4 | import { Divider, ProfileImage } from '../../components'; 5 | 6 | export class Confirmation extends Component { 7 | render() { 8 | return ( 9 |
10 | 11 | 12 | 13 |
14 | ) 15 | } 16 | } 17 | 18 | const mapStateToProps = (state) => { 19 | return { 20 | profile: state.user.profile 21 | } 22 | } 23 | 24 | export default connect(mapStateToProps)(Confirmation); 25 | -------------------------------------------------------------------------------- /src/screens/Confirmation/components/Approval/Approval.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withRouter} from "react-router-dom"; 3 | import PropTypes from 'prop-types'; 4 | import { UserDetails } from '../../../../components'; 5 | import { setStore } from '../../../../utils'; 6 | import './style.css'; 7 | 8 | export class Approval extends Component { 9 | agree = () => { 10 | if (this.props.user.firstName) { 11 | setStore('user', this.props.user); 12 | alert('Congratulation your profile Completed successfully!'); 13 | this.props.history.push('/React-form-registration/home'); 14 | } else { 15 | alert('Invalid User Details!'); 16 | this.props.history.push('/React-form-registration/register'); 17 | } 18 | } 19 | 20 | render() { 21 | return ( 22 |
23 | 24 |
25 |
26 | ) 27 | } 28 | } 29 | 30 | Approval.propTypes = { 31 | user: PropTypes.object 32 | } 33 | 34 | export default withRouter(Approval); 35 | -------------------------------------------------------------------------------- /src/screens/Confirmation/components/Approval/index.js: -------------------------------------------------------------------------------- 1 | import Approval from './Approval'; 2 | 3 | export default Approval; 4 | -------------------------------------------------------------------------------- /src/screens/Confirmation/components/Approval/style.css: -------------------------------------------------------------------------------- 1 | .approvalcontainer { 2 | padding: 20px; 3 | height: 100%; 4 | overflow: auto; 5 | } 6 | .bindtext { 7 | color: #1b8bec; 8 | } 9 | .aggreebtnContainer { 10 | text-align: center; 11 | margin-top: 50px; 12 | } -------------------------------------------------------------------------------- /src/screens/Confirmation/index.js: -------------------------------------------------------------------------------- 1 | import Confirmation from './Confirmation'; 2 | 3 | export default Confirmation; 4 | -------------------------------------------------------------------------------- /src/screens/Home/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { ProfileImage, Divider, UserDetails, Welcome } from '../../components'; 4 | 5 | export class Home extends Component { 6 | render() { 7 | return ( 8 |
9 | 10 | 11 |
12 | 13 | 14 |
15 |
16 | ) 17 | } 18 | } 19 | 20 | const mapStateToProps = (state) => { 21 | return { 22 | profile: state.user.profile 23 | } 24 | } 25 | 26 | export default connect(mapStateToProps)(Home); 27 | -------------------------------------------------------------------------------- /src/screens/Home/index.js: -------------------------------------------------------------------------------- 1 | import Home from './Home'; 2 | 3 | export default Home; 4 | -------------------------------------------------------------------------------- /src/screens/Home/style.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jebasuthan/React-form-registration/d62f529c48d2e6619268dc6f7ba1313a446dbbfe/src/screens/Home/style.css -------------------------------------------------------------------------------- /src/screens/Login/Login.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withRouter, Link } from "react-router-dom"; 3 | import { connect } from 'react-redux'; 4 | import { ActionCreators } from '../../actions/profile'; 5 | import { getStore } from '../../utils'; 6 | import './style.css'; 7 | 8 | export class Login extends Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | username: '', 13 | password: '', 14 | errors: { 15 | username: 'Enter User Name!', 16 | password: 'Enter Password!' 17 | }, 18 | loginStatus: '', 19 | submitted: false 20 | } 21 | } 22 | 23 | inputChange = (event) => { 24 | const { name, value } = event.target; 25 | this.setState({ [name]: value }); 26 | this.validationErrorMessage(event); 27 | } 28 | 29 | validationErrorMessage = (event) => { 30 | const { name, value } = event.target; 31 | let errors = this.state.errors; 32 | switch (name) { 33 | case 'username': 34 | errors.username = value.length < 1 ? 'Enter User Name' : ''; 35 | break; 36 | case 'password': 37 | errors.password = value.length < 1 ? 'Enter Password' : ''; 38 | break; 39 | default: 40 | break; 41 | } 42 | this.setState({ errors }); 43 | } 44 | 45 | validateForm = (errors) => { 46 | let valid = true; 47 | console.log(errors) 48 | Object.entries(errors).forEach(item => { 49 | console.log(item) 50 | item && item[1].length > 0 && (valid = false) 51 | }) 52 | console.log(valid) 53 | return valid; 54 | } 55 | 56 | loginForm = async (event) => { 57 | this.setState({ submitted: true }); 58 | event.preventDefault(); 59 | if (this.validateForm(this.state.errors)) { 60 | console.info('Valid Form') 61 | const user = getStore('user') 62 | if (user) { 63 | this.props.dispatch(ActionCreators.login(user)); 64 | this.props.history.push('/React-form-registration//home') 65 | } else { 66 | this.setState({ loginStatus: 'Login Failed! Invalid Username and Password'}) 67 | } 68 | } else { 69 | console.log('Invalid Form') 70 | } 71 | } 72 | 73 | render() { 74 | const { username, password, errors, submitted, loginStatus } = this.state; 75 | return ( 76 |
77 |
78 |
79 |
80 | 81 |
82 | { this.inputChange(e)} } className="form-control" id="username" placeholder="User Name" /> 83 | { submitted && errors.username.length > 0 && {errors.username}} 84 |
85 |
86 |
87 |
88 |
89 |
90 | 91 |
92 | { this.inputChange(e)} } className="form-control" id="password" placeholder="Password" /> 93 | { submitted && errors.password.length > 0 && {errors.password}} 94 |
95 |
96 |
97 |
98 |
99 | { submitted && loginStatus.length > 0 && {loginStatus}} 100 |
101 |
102 |
103 |
104 | 105 |
106 |
107 |
108 |
109 |
110 | Register 111 |
112 |
113 |
114 |
115 |
116 | ) 117 | } 118 | } 119 | 120 | const mapStateToProps = (state) => { 121 | return { 122 | profile: state.user.profile 123 | } 124 | } 125 | 126 | export default connect(mapStateToProps)(withRouter(Login)); 127 | -------------------------------------------------------------------------------- /src/screens/Login/index.js: -------------------------------------------------------------------------------- 1 | import Login from './Login'; 2 | 3 | export default Login; 4 | -------------------------------------------------------------------------------- /src/screens/Login/style.css: -------------------------------------------------------------------------------- 1 | .loginForm { 2 | width: 100%; 3 | } -------------------------------------------------------------------------------- /src/screens/Register/Register.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Divider, ProfileImage } from '../../components'; 3 | import RightContent from './components/RightContent'; 4 | 5 | export class Register extends Component { 6 | render() { 7 | return ( 8 |
9 | 10 | 11 | 12 |
13 | ) 14 | } 15 | } 16 | 17 | export default Register; 18 | -------------------------------------------------------------------------------- /src/screens/Register/components/RightContent/RightContent.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { withRouter} from "react-router-dom"; 3 | import { connect } from 'react-redux'; 4 | import { ActionCreators } from '../../../../actions/profile'; 5 | import { InputRange, MultiSelect } from '../../../../components'; 6 | import stateList from '../../../../mock/state.json'; 7 | import { formatPhoneNumber, isValidEmail } from '../../../../utils'; 8 | import './style.css'; 9 | 10 | export class RightContent extends Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | user: { 15 | firstName: '', 16 | lastName: '', 17 | telephone: '', 18 | age: 28, 19 | email: '', 20 | state: '', 21 | country: '', 22 | address: 'Home', 23 | address1: '', 24 | address2: '', 25 | interests: [], 26 | subscribenewsletter: false 27 | }, 28 | errors: { 29 | user: { 30 | firstName: 'Enter First Name', 31 | telephone: 'Enter Telephone', 32 | email: 'Email is not valid!', 33 | address1: 'Enter address1', 34 | address2: 'Enter address2', 35 | interests: 'Enter your Interests' 36 | } 37 | }, 38 | validForm: false, 39 | submitted: false 40 | } 41 | } 42 | 43 | componentDidMount() { 44 | if(this.props.profile) { 45 | this.setState({ user: this.props.profile }); 46 | if (this.props.profile.email) { 47 | this.resetErrorMsg(); 48 | } 49 | } 50 | } 51 | 52 | validationErrorMessage = (event) => { 53 | const { name, value } = event.target; 54 | let errors = this.state.errors; 55 | 56 | switch (name) { 57 | case 'firstName': 58 | errors.user.firstName = value.length < 1 ? 'Enter First Name' : ''; 59 | break; 60 | case 'email': 61 | errors.user.email = isValidEmail(value) ? '' : 'Email is not valid!'; 62 | break; 63 | case 'telephone': 64 | errors.user.telephone = value.length < 1 && value.length > 10 ? 'Enter valid telephone number' : ''; 65 | break; 66 | case 'address1': 67 | errors.user.address1 = value.length < 1 ? `Enter ${this.state.user.address} address1` : ''; 68 | break; 69 | case 'address2': 70 | errors.user.address2 = value.length < 1 ? `Enter ${this.state.user.address} address2` : ''; 71 | break; 72 | default: 73 | break; 74 | } 75 | 76 | this.setState({ errors }); 77 | } 78 | 79 | inputChange = (event) => { 80 | let telphone = '' 81 | const { name, value } = event.target; 82 | const user = this.state.user; 83 | if (name === 'telephone') { 84 | telphone = formatPhoneNumber(value); 85 | user[name] = telphone; 86 | } else { 87 | user[name] = value; 88 | } 89 | this.setState({ user }); 90 | this.validationErrorMessage(event); 91 | } 92 | 93 | checkboxChange = (event) => { 94 | const { name, checked } = event.target; 95 | const user = this.state.user; 96 | user[name] = checked; 97 | this.setState({ user }); 98 | } 99 | 100 | onChangeAddress = (event) => { 101 | const user = this.state.user; 102 | user['address'] = event.target.value; 103 | this.setState({ user }); 104 | } 105 | 106 | onChangeInputRange = (value) => { 107 | const user = this.state.user; 108 | user['age'] = value; 109 | this.setState({ user }) 110 | } 111 | 112 | onSelectedInterest = (value) => { 113 | const user = this.state.user; 114 | const errors = this.state.errors; 115 | user['interests'] = value; 116 | errors.user.interests = value.length < 1 ? 'Enter your Interests' : ''; 117 | this.setState({ user, errors }); 118 | } 119 | 120 | validateForm = (errors) => { 121 | let valid = true; 122 | Object.entries(errors.user).forEach(item => { 123 | console.log(item) 124 | item && item[1].length > 0 && (valid = false) 125 | }) 126 | return valid; 127 | } 128 | 129 | submitForm = async (event) => { 130 | this.setState({ submitted: true }); 131 | this.props.dispatch(ActionCreators.formSubmittionStatus(true)); 132 | const user = this.state.user; 133 | if (user && this.props.profile) { 134 | user.profileImage = this.props.profile.profileImage; 135 | } 136 | event.preventDefault(); 137 | if (this.validateForm(this.state.errors) && this.props.profile && this.props.profile.profileImage) { 138 | console.info('Valid Form') 139 | this.props.dispatch(ActionCreators.addProfile(user)); 140 | this.props.history.push('/React-form-registration/confirm') 141 | } else { 142 | console.log('Invalid Form') 143 | } 144 | } 145 | 146 | resetErrorMsg = () => { 147 | let errors = this.state.errors; 148 | errors.user.firstName = '' 149 | errors.user.telephone = '' 150 | errors.user.email = '' 151 | errors.user.address1 = '' 152 | errors.user.address2 = '' 153 | errors.user.interests = '' 154 | this.setState({ errors }); 155 | } 156 | 157 | render() { 158 | const { firstName, lastName, age, email, telephone, state, country, address, address1, address2, interests, subscribenewsletter } = this.state.user; 159 | const { submitted } = this.state; 160 | const listState = stateList.listStates.map((item, key) => 161 | 162 | ); 163 | return ( 164 |
165 |
166 | 167 |
168 | { this.inputChange(e)} } className="form-control" placeholder="First Name" /> 169 | { submitted && this.state.errors.user.firstName.length > 0 && {this.state.errors.user.firstName}} 170 |
171 |
172 | { this.inputChange(e)} } className="form-control" placeholder="Last Name" /> 173 |
174 |
175 |
176 |
177 |
178 | 179 |
180 | 181 |
182 |
183 |
184 |
185 |
186 | 187 |
188 | { this.inputChange(e)} } className="form-control" id="email" placeholder="itjebasuthan@gmail.com" /> 189 | { submitted && this.state.errors.user.email.length > 0 && {this.state.errors.user.email}} 190 |
191 |
192 |
193 |
194 |
195 | 196 |
197 | { this.inputChange(e)} } className="form-control" id="telephone" placeholder="(212)477-1000" /> 198 | { submitted && this.state.errors.user.telephone.length > 0 && {this.state.errors.user.telephone}} 199 |
200 |
201 |
202 |
203 |
204 | 205 |
206 | 209 |
210 |
211 |
212 |
213 |
214 | 215 |
216 | 220 |
221 |
222 |
223 |
224 |
225 | 226 |
227 | 231 |
232 |
233 | 234 | { submitted && this.state.errors.user.address1.length > 0 && {this.state.errors.user.address1}} 235 |
236 |
237 | 238 | { submitted && this.state.errors.user.address2.length > 0 && {this.state.errors.user.address2}} 239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 | 247 |
248 | 249 | { submitted && this.state.errors.user.interests.length > 0 && {this.state.errors.user.interests}} 250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 | 258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 | 267 |
268 |
269 |
270 |
271 | ) 272 | } 273 | } 274 | 275 | const mapStateToProps = (state) => { 276 | return { 277 | profile: state.user.profile 278 | } 279 | } 280 | 281 | export default connect(mapStateToProps)(withRouter(RightContent)); 282 | -------------------------------------------------------------------------------- /src/screens/Register/components/RightContent/index.js: -------------------------------------------------------------------------------- 1 | import RightContent from './RightContent'; 2 | 3 | export default RightContent; 4 | -------------------------------------------------------------------------------- /src/screens/Register/components/RightContent/style.css: -------------------------------------------------------------------------------- 1 | .rightPanel { 2 | padding: 20px; 3 | margin: 10px; 4 | float: right; 5 | width: 75%; 6 | /* height: 90vh; */ 7 | overflow: auto; 8 | margin-bottom: 45px; 9 | } 10 | .col-form-label { 11 | text-align: right; 12 | } 13 | 14 | @media only screen and (max-width: 768px) { 15 | .col-form-label { 16 | text-align: left; 17 | } 18 | .rightPanel { 19 | width: 100%; 20 | margin-bottom: 50px; 21 | } 22 | } -------------------------------------------------------------------------------- /src/screens/Register/index.js: -------------------------------------------------------------------------------- 1 | import Register from './Register'; 2 | 3 | export default Register; 4 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers, compose, applyMiddleware } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | 4 | import profile from '../reducers/profile'; 5 | 6 | const rootReducer = combineReducers({ 7 | user: profile 8 | }); 9 | 10 | const configureStore = () => { 11 | return createStore( 12 | rootReducer, 13 | compose(applyMiddleware(thunk)) 14 | ); 15 | }; 16 | 17 | export default configureStore; 18 | -------------------------------------------------------------------------------- /src/styles/index.js: -------------------------------------------------------------------------------- 1 | import './main.css'; 2 | -------------------------------------------------------------------------------- /src/styles/main.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100% !important; 3 | margin: 0; 4 | } 5 | .form-control, .col-form-label, .custom-select, label, header, footer { 6 | font-size: 0.8rem !important; 7 | } 8 | .button { 9 | padding: .6rem 2rem; 10 | line-height: 1.5; 11 | border-radius: .4rem; 12 | color: #fff; 13 | background-color: #1b8bec; 14 | border-color: #6c757d; 15 | text-decoration: none; 16 | border: 1px solid rgb(24, 24, 80); 17 | } 18 | .pagecenter { 19 | position: absolute; 20 | left: 50%; 21 | top: 50%; 22 | transform: translate(-50%, -50%); 23 | } 24 | .error { 25 | color: red; 26 | font-size: 0.8rem; 27 | } 28 | .right { 29 | text-align: right; 30 | } 31 | .link-button { 32 | background-color: transparent; 33 | border: none; 34 | cursor: pointer; 35 | text-decoration: underline; 36 | display: block; 37 | text-align: center; 38 | margin: 10px; 39 | width: 100%; 40 | color: #1b8bec; 41 | } 42 | .link-button:hover, 43 | .link-button:focus { 44 | text-decoration: none; 45 | } -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set localStorage 3 | */ 4 | export const setStore = (name, content) => { 5 | if (!name) return 6 | if (typeof content !== 'string') { 7 | content = JSON.stringify(content) 8 | } 9 | return window.localStorage.setItem(name, content) 10 | } 11 | 12 | /** 13 | * Get localStorage 14 | */ 15 | export const getStore = (name) => { 16 | if (!name) return 17 | return JSON.parse(window.localStorage.getItem(name)) 18 | } 19 | 20 | /** 21 | * Clear localStorage 22 | */ 23 | export const removeItem = (name) => { 24 | if (!name) return 25 | return window.localStorage.removeItem(name) 26 | } 27 | 28 | /** 29 | * Validate Email address 30 | */ 31 | export const isValidEmail = (value) => { 32 | return !(value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,64}$/i.test(value)) 33 | } 34 | 35 | /** 36 | * Format Phone Number 37 | */ 38 | export const formatPhoneNumber = (value) => { 39 | if (!value) return 40 | const currentValue = value.replace(/[^\d]/g, ''); 41 | const mobileNoLength = currentValue.length; 42 | if (mobileNoLength >=7) { 43 | if (mobileNoLength < 4) return currentValue; 44 | if (mobileNoLength < 7) return `(${currentValue.slice(0, 3)}) ${currentValue.slice(3)}`; 45 | return `(${currentValue.slice(0, 3)}) ${currentValue.slice(3, 6)}-${currentValue.slice(6, 10)}`; 46 | } else{ 47 | return currentValue; 48 | } 49 | } 50 | --------------------------------------------------------------------------------