├── .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 |
35 |
36 | ## Register User
37 |
38 |
39 | ## Home Screen
40 |
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 | You need to enable JavaScript to run this app.
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 |
22 |
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 | Copyright © 2020 JebaSuthan, Inc. All Rights Reserved
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 |
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 |
{!profileImage ? 'Upload your Photo' : ''}
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') &&
Logout
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 |
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 |
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 |
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 |
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 | {item.name}
162 | );
163 | return (
164 |
165 |
166 |
Name
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 |
Age
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
Email
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 |
Tel
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 |
State
205 |
206 |
207 | {listState}
208 |
209 |
210 |
211 |
212 |
213 |
214 |
Country
215 |
216 |
217 | United States
218 | India
219 |
220 |
221 |
222 |
223 |
224 |
225 |
Address
226 |
227 |
228 | Home
229 | Company
230 |
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 |
Interests
247 |
248 |
249 | { submitted && this.state.errors.user.interests.length > 0 && {this.state.errors.user.interests} }
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 | Subscribe to the news letter
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 | Submit
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 |
--------------------------------------------------------------------------------