Merge branch 'client-init' of tanner/caremyway-client into master
This commit is contained in:
commit
48cc050c47
49
.gitignore
vendored
49
.gitignore
vendored
|
@ -1,30 +1,21 @@
|
||||||
# ---> Node
|
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
# Logs
|
|
||||||
logs
|
# dependencies
|
||||||
*.log
|
/node_modules
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
# Runtime data
|
yarn-error.log*
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
|
|
||||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directory
|
|
||||||
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
|
|
||||||
node_modules
|
|
||||||
|
|
||||||
|
|
38
README.md
38
README.md
|
@ -1,2 +1,38 @@
|
||||||
# caremyway-client
|
# CareMyWay Client
|
||||||
|
|
||||||
|
Generated using [create-react-app](https://github.com/facebookincubator/create-react-app).
|
||||||
|
|
||||||
|
## Quickstart (Development)
|
||||||
|
|
||||||
|
* Ensure the server is up and running locally.
|
||||||
|
* [CareMyWay](https://gogs.tannercollin.com/tanner/caremyway)
|
||||||
|
* Install dependencies with `yarn install`
|
||||||
|
* Run the client with `yarn start`
|
||||||
|
|
||||||
|
Now you can visit `localhost:3000` from your browser.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
The environment variables are embedded during the build time. For more information, please refer to the [docs](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-custom-environment-variables).
|
||||||
|
|
||||||
|
| Environment Variable | Default Value | Description |
|
||||||
|
| ------------------------- |:-------------------------:| -------------------------------:|
|
||||||
|
| `REACT_APP_API_ENDPOINT` | `"http://localhost:8000"` | Server API endpoint |
|
||||||
|
| `REACT_APP_REDUX_LOGGING` | ` ` | Set any value for Redux logging |
|
||||||
|
|
||||||
|
|
||||||
|
## Production
|
||||||
|
|
||||||
|
To build the production instance of this application, run the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export REACT_APP_API_ENDPOINT="http://dev.tannercollin.com:8000/" && yarn build
|
||||||
|
|
||||||
|
# then, to serve the production site locally
|
||||||
|
yarn global add serve
|
||||||
|
serve -s build --port 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
To test the react app, call `yarn test`.
|
||||||
|
|
25
package.json
Normal file
25
package.json
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "caremyway",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.16.2",
|
||||||
|
"localStorage": "^1.0.3",
|
||||||
|
"react": "^15.6.1",
|
||||||
|
"react-dom": "^15.6.1",
|
||||||
|
"react-redux": "^5.0.6",
|
||||||
|
"react-router": "^4.2.0",
|
||||||
|
"react-router-dom": "^4.2.2",
|
||||||
|
"react-scripts": "1.0.12",
|
||||||
|
"redux": "^3.7.2",
|
||||||
|
"redux-logger": "^3.0.6",
|
||||||
|
"redux-saga": "^0.15.6",
|
||||||
|
"semantic-ui-react": "^0.72.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test --env=jsdom",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
}
|
||||||
|
}
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
40
public/index.html
Normal file
40
public/index.html
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is added to the
|
||||||
|
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||||
|
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.13/semantic.min.css"></link>
|
||||||
|
<title>Caremyway</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
You need to enable JavaScript to run this app.
|
||||||
|
</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
13
public/manifest.json
Normal file
13
public/manifest.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"short_name": "Caremyway",
|
||||||
|
"name": "Caremyway",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": "./index.html",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
139
src/actions/auth/reducer.actions.js
Normal file
139
src/actions/auth/reducer.actions.js
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
import {
|
||||||
|
IS_SENDING_AUTH_REQUEST,
|
||||||
|
SET_AUTH_REQUEST_ERROR,
|
||||||
|
CLEAR_AUTH_REQUEST_ERROR,
|
||||||
|
SET_AUTH_REQUEST_SUCCESS,
|
||||||
|
CLEAR_AUTH_REQUEST_SUCCESS,
|
||||||
|
SET_SELF_USER_TOKEN,
|
||||||
|
SET_FORM_EMAIL,
|
||||||
|
SET_FORM_PASSWORD,
|
||||||
|
SET_FORM_PASSWORD_CONFIRMATION,
|
||||||
|
SET_FORM_EMAIL_VERIFICATION,
|
||||||
|
SET_FORM_OLD_PASSWORD
|
||||||
|
} from "../../constants/auth.constants";
|
||||||
|
import { parseError } from "../common.actions";
|
||||||
|
|
||||||
|
export function isSendingAuthRequest(sendingRequest) {
|
||||||
|
return {
|
||||||
|
type: IS_SENDING_AUTH_REQUEST,
|
||||||
|
data: sendingRequest
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setAuthRequestError(exception) {
|
||||||
|
let error = parseError(exception);
|
||||||
|
if (error.email) {
|
||||||
|
error["Email"] = error.email;
|
||||||
|
delete error["email"];
|
||||||
|
}
|
||||||
|
if (error.password) {
|
||||||
|
error["Password"] = error.password;
|
||||||
|
delete error["password"];
|
||||||
|
}
|
||||||
|
if (error.password1) {
|
||||||
|
error["Password"] = error.password1;
|
||||||
|
delete error["password1"];
|
||||||
|
}
|
||||||
|
if (error.password2) {
|
||||||
|
error["Confirm Password"] = error.password2;
|
||||||
|
delete error["password2"];
|
||||||
|
}
|
||||||
|
if (error.non_field_errors) {
|
||||||
|
error["Non Field Errors"] = error.non_field_errors;
|
||||||
|
delete error["non_field_errors"];
|
||||||
|
}
|
||||||
|
if (error.detail) {
|
||||||
|
error["Detail"] = error.detail;
|
||||||
|
delete error["detail"];
|
||||||
|
}
|
||||||
|
if (error.old_password) {
|
||||||
|
error["Old Password"] = error.old_password;
|
||||||
|
delete error["old_password"];
|
||||||
|
}
|
||||||
|
if (error.new_password1) {
|
||||||
|
error["New Password"] = error.new_password1;
|
||||||
|
delete error["new_password1"];
|
||||||
|
}
|
||||||
|
if (error.new_password2) {
|
||||||
|
error["Confirm New Password"] = error.new_password2;
|
||||||
|
delete error["new_password2"];
|
||||||
|
}
|
||||||
|
if (error.token) {
|
||||||
|
error["Token"] = error.token;
|
||||||
|
delete error["token"];
|
||||||
|
}
|
||||||
|
if (error.uid) {
|
||||||
|
error["UID"] = error.uid;
|
||||||
|
delete error["uid"];
|
||||||
|
}
|
||||||
|
if (error.key) {
|
||||||
|
error["Email Verification Key"] = error.key;
|
||||||
|
delete error["key"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: SET_AUTH_REQUEST_ERROR,
|
||||||
|
data: error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearAuthRequestError() {
|
||||||
|
return {
|
||||||
|
type: CLEAR_AUTH_REQUEST_ERROR
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setAuthRequestSuccess(response) {
|
||||||
|
return {
|
||||||
|
type: SET_AUTH_REQUEST_SUCCESS,
|
||||||
|
data: response.detail || response
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearAuthRequestSuccess() {
|
||||||
|
return {
|
||||||
|
type: CLEAR_AUTH_REQUEST_SUCCESS
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setSelfUserToken(selfUser) {
|
||||||
|
return {
|
||||||
|
type: SET_SELF_USER_TOKEN,
|
||||||
|
data: selfUser
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setFormEmail(email) {
|
||||||
|
return {
|
||||||
|
type: SET_FORM_EMAIL,
|
||||||
|
data: email
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setFormPassword(password) {
|
||||||
|
return {
|
||||||
|
type: SET_FORM_PASSWORD,
|
||||||
|
data: password
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setFormPasswordConfirmation(passwordConfirmation) {
|
||||||
|
return {
|
||||||
|
type: SET_FORM_PASSWORD_CONFIRMATION,
|
||||||
|
data: passwordConfirmation
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setFormEmailVerification(emailKey) {
|
||||||
|
return {
|
||||||
|
type: SET_FORM_EMAIL_VERIFICATION,
|
||||||
|
data: emailKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setFormOldPassword(oldPassword) {
|
||||||
|
return {
|
||||||
|
type: SET_FORM_OLD_PASSWORD,
|
||||||
|
data: oldPassword
|
||||||
|
};
|
||||||
|
}
|
57
src/actions/auth/saga.actions.js
Normal file
57
src/actions/auth/saga.actions.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import {
|
||||||
|
SEND_REGISTER_REQUEST,
|
||||||
|
SEND_EMAIL_VERIFICATION_REQUEST,
|
||||||
|
SEND_LOGIN_REQUEST,
|
||||||
|
SEND_LOGOUT_REQUEST,
|
||||||
|
SEND_CHANGE_PASSWORD_REQUEST,
|
||||||
|
SEND_FORGOT_PASSWORD_REQUEST,
|
||||||
|
SEND_RESET_PASSWORD_REQUEST
|
||||||
|
} from "../../constants/auth.constants";
|
||||||
|
|
||||||
|
export function sendRegisterRequest(postBody) {
|
||||||
|
return {
|
||||||
|
type: SEND_REGISTER_REQUEST,
|
||||||
|
data: postBody
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendEmailVerificationRequest(postBody) {
|
||||||
|
return {
|
||||||
|
type: SEND_EMAIL_VERIFICATION_REQUEST,
|
||||||
|
data: postBody
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendLoginRequest(postBody) {
|
||||||
|
return {
|
||||||
|
type: SEND_LOGIN_REQUEST,
|
||||||
|
data: postBody
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendLogoutRequest() {
|
||||||
|
return {
|
||||||
|
type: SEND_LOGOUT_REQUEST
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendChangePasswordRequest(postBody) {
|
||||||
|
return {
|
||||||
|
type: SEND_CHANGE_PASSWORD_REQUEST,
|
||||||
|
data: postBody
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendForgotPasswordRequest(postBody) {
|
||||||
|
return {
|
||||||
|
type: SEND_FORGOT_PASSWORD_REQUEST,
|
||||||
|
data: postBody
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendResetPasswordRequest(postBody) {
|
||||||
|
return {
|
||||||
|
type: SEND_RESET_PASSWORD_REQUEST,
|
||||||
|
data: postBody
|
||||||
|
};
|
||||||
|
}
|
13
src/actions/common.actions.js
Normal file
13
src/actions/common.actions.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Given an exception return the list of errors, the singular error, or generic error
|
||||||
|
* @param {object|string} exception - axios returned exception
|
||||||
|
*/
|
||||||
|
export function parseError(exception) {
|
||||||
|
let response = exception.response || {};
|
||||||
|
let data = response.data || {};
|
||||||
|
let err = "" + exception;
|
||||||
|
if (response.status) {
|
||||||
|
err = `${response.status} ${response.statusText}`;
|
||||||
|
}
|
||||||
|
return data || err
|
||||||
|
}
|
104
src/actions/user/reducer.actions.js
Normal file
104
src/actions/user/reducer.actions.js
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import {
|
||||||
|
IS_SENDING_USER_REQUEST,
|
||||||
|
SET_USER_REQUEST_ERROR,
|
||||||
|
CLEAR_USER_REQUEST_ERROR,
|
||||||
|
SET_USER_REQUEST_SUCCESS,
|
||||||
|
CLEAR_USER_REQUEST_SUCCESS,
|
||||||
|
SET_SELF_USER,
|
||||||
|
SET_COMPLETE_REGISTRATION_STEP,
|
||||||
|
SET_COMPLETE_REGISTRATION_CLIENT_OR_PROVIDER,
|
||||||
|
SET_FORM_PHONE_NUMBER,
|
||||||
|
SET_FORM_BUSINESS_NUMBER,
|
||||||
|
SET_FORM_SOCIAL_INSURANCE_NUMBER,
|
||||||
|
SET_EDIT_PROFILE_TAB_ACTIVE_INDEX
|
||||||
|
} from "../../constants/user.constants";
|
||||||
|
import { parseError } from "../common.actions";
|
||||||
|
|
||||||
|
export function isSendingUserRequest(sendingRequest) {
|
||||||
|
return {
|
||||||
|
type: IS_SENDING_USER_REQUEST,
|
||||||
|
data: sendingRequest
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setUserRequestError(exception) {
|
||||||
|
let error = parseError(exception);
|
||||||
|
|
||||||
|
if (error.phone_number) {
|
||||||
|
error["Phone Number"] = error.phone_number;
|
||||||
|
delete error["phone_number"];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: SET_USER_REQUEST_ERROR,
|
||||||
|
data: error
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearUserRequestError() {
|
||||||
|
return {
|
||||||
|
type: CLEAR_USER_REQUEST_ERROR
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setUserRequestSuccess(response) {
|
||||||
|
return {
|
||||||
|
type: SET_USER_REQUEST_SUCCESS,
|
||||||
|
data: response.detail || response
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearUserRequestSuccess() {
|
||||||
|
return {
|
||||||
|
type: CLEAR_USER_REQUEST_SUCCESS
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setSelfUser(selfUser) {
|
||||||
|
return {
|
||||||
|
type: SET_SELF_USER,
|
||||||
|
data: selfUser
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCompleteRegistrationStep(step) {
|
||||||
|
return {
|
||||||
|
type: SET_COMPLETE_REGISTRATION_STEP,
|
||||||
|
data: step
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCompleteRegistrationClientOrProvider(clientOrProvider) {
|
||||||
|
return {
|
||||||
|
type: SET_COMPLETE_REGISTRATION_CLIENT_OR_PROVIDER,
|
||||||
|
data: clientOrProvider
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setFormPhoneNumber(phoneNumber) {
|
||||||
|
return {
|
||||||
|
type: SET_FORM_PHONE_NUMBER,
|
||||||
|
data: phoneNumber
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setFormBusinessNumber(businessNumber) {
|
||||||
|
return {
|
||||||
|
type: SET_FORM_BUSINESS_NUMBER,
|
||||||
|
data: businessNumber
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setFormSocialInsuranceNumber(socialInsuranceNumber) {
|
||||||
|
return {
|
||||||
|
type: SET_FORM_SOCIAL_INSURANCE_NUMBER,
|
||||||
|
data: socialInsuranceNumber
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setEditProfileTabActiveIndex(indexVal) {
|
||||||
|
return {
|
||||||
|
type: SET_EDIT_PROFILE_TAB_ACTIVE_INDEX,
|
||||||
|
data: indexVal
|
||||||
|
};
|
||||||
|
}
|
57
src/actions/user/saga.actions.js
Normal file
57
src/actions/user/saga.actions.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import {
|
||||||
|
GET_SELF_USER_REQUEST,
|
||||||
|
CREATE_USER_INFO_REQUEST,
|
||||||
|
UPDATE_USER_INFO_REQUEST,
|
||||||
|
CREATE_CLIENT_REQUEST,
|
||||||
|
UPDATE_CLIENT_REQUEST,
|
||||||
|
CREATE_PROVIDER_REQUEST,
|
||||||
|
UPDATE_PROVIDER_REQUEST
|
||||||
|
} from "../../constants/user.constants";
|
||||||
|
|
||||||
|
export function getSelfUserRequest() {
|
||||||
|
return {
|
||||||
|
type: GET_SELF_USER_REQUEST
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createUserInfoRequest(postBody) {
|
||||||
|
return {
|
||||||
|
type: CREATE_USER_INFO_REQUEST,
|
||||||
|
data: postBody
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateUserInfoRequest(payload) {
|
||||||
|
return {
|
||||||
|
type: UPDATE_USER_INFO_REQUEST,
|
||||||
|
data: payload
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createClientRequest(postBody) {
|
||||||
|
return {
|
||||||
|
type: CREATE_CLIENT_REQUEST,
|
||||||
|
data: postBody
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateClientRequest(payload) {
|
||||||
|
return {
|
||||||
|
type: UPDATE_CLIENT_REQUEST,
|
||||||
|
data: payload
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createProviderRequest(postBody) {
|
||||||
|
return {
|
||||||
|
type: CREATE_PROVIDER_REQUEST,
|
||||||
|
data: postBody
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateProviderRequest(payload) {
|
||||||
|
return {
|
||||||
|
type: UPDATE_PROVIDER_REQUEST,
|
||||||
|
data: payload
|
||||||
|
};
|
||||||
|
}
|
83
src/api/auth.api.js
Normal file
83
src/api/auth.api.js
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import { post } from "./baseApi";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function wrapping POST request for user registration
|
||||||
|
* @param {string} email - email of user to register
|
||||||
|
* @param {string} password1 - password of user to register
|
||||||
|
* @param {string} password2 - server side password confirmation
|
||||||
|
*/
|
||||||
|
export function registerUser(email, password1, password2) {
|
||||||
|
return post("/rest-auth/registration/", {
|
||||||
|
email,
|
||||||
|
password1,
|
||||||
|
password2
|
||||||
|
}).then(resp => Promise.resolve(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function wrapping POST request for email validation
|
||||||
|
* @param {string} emailKey - key for email validation
|
||||||
|
*/
|
||||||
|
export function verifyEmail(emailKey) {
|
||||||
|
return post("/rest-auth/registration/verify-email/", {
|
||||||
|
key: emailKey
|
||||||
|
}).then(resp => Promise.resolve(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function wrapping POST request for user login
|
||||||
|
* @param {string} email - email of user to login
|
||||||
|
* @param {string} password - password of user to login
|
||||||
|
*/
|
||||||
|
export function loginUser(email, password) {
|
||||||
|
return post("/rest-auth/login/", { email, password }).then(resp =>
|
||||||
|
Promise.resolve(resp)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function wrapping POST request for user logout
|
||||||
|
*/
|
||||||
|
export function logoutUser() {
|
||||||
|
return post("/rest-auth/logout/").then(resp => Promise.resolve(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function wrapping POST request for change password
|
||||||
|
* @param {string} new_password1 - user new password
|
||||||
|
* @param {string} new_password2 - user new password confirmation
|
||||||
|
* @param {string} old_password - old password of user
|
||||||
|
*/
|
||||||
|
export function changePassword(new_password1, new_password2, old_password) {
|
||||||
|
return post("/rest-auth/password/change/", {
|
||||||
|
new_password1,
|
||||||
|
new_password2,
|
||||||
|
old_password
|
||||||
|
}).then(resp => Promise.resolve(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function wrapping POST request for a forget password email call
|
||||||
|
* @param {string} email - email of user for password reset
|
||||||
|
*/
|
||||||
|
export function forgotPassword(email) {
|
||||||
|
return post("/rest-auth/password/reset/", { email }).then(resp =>
|
||||||
|
Promise.resolve(resp)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function wrapping POST request for resetting a user's password
|
||||||
|
* @param {string} uid - uid supplied by forgot password email
|
||||||
|
* @param {string} token - token supplied by forgot password email
|
||||||
|
* @param {string} new_password1 - user new password
|
||||||
|
* @param {string} new_password2 - user new password confirmation
|
||||||
|
*/
|
||||||
|
export function resetPassword(uid, token, new_password1, new_password2) {
|
||||||
|
return post("/rest-auth/password/reset/confirm/", {
|
||||||
|
uid,
|
||||||
|
token,
|
||||||
|
new_password1,
|
||||||
|
new_password2
|
||||||
|
}).then(resp => Promise.resolve(resp));
|
||||||
|
}
|
54
src/api/baseApi.js
Normal file
54
src/api/baseApi.js
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const API_ENDPOINT = process.env.REACT_APP_API_ENDPOINT;
|
||||||
|
|
||||||
|
// If testing, use localStorage polyfill, else use browser localStorage
|
||||||
|
const localStorage = global.process && process.env.NODE_ENV === "test"
|
||||||
|
? // eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
|
require("localStorage")
|
||||||
|
: global.window.localStorage;
|
||||||
|
|
||||||
|
function headers() {
|
||||||
|
const token = localStorage.getItem("userToken") || "";
|
||||||
|
let header = {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
};
|
||||||
|
if (token) {
|
||||||
|
header["Authorization"] = `Token ${token}`;
|
||||||
|
}
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiInstance = axios.create({
|
||||||
|
baseURL: API_ENDPOINT,
|
||||||
|
timeout: 3000
|
||||||
|
});
|
||||||
|
|
||||||
|
export function get(url, params = {}) {
|
||||||
|
return apiInstance
|
||||||
|
.get(url, { params, headers: headers() })
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => Promise.reject(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function post(url, data) {
|
||||||
|
return apiInstance
|
||||||
|
.post(url, data, { headers: headers() })
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => Promise.reject(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function put(url, data) {
|
||||||
|
return apiInstance
|
||||||
|
.put(url, data, { headers: headers() })
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => Promise.reject(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function del(url) {
|
||||||
|
return apiInstance
|
||||||
|
.delete(url, { headers: headers() })
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => Promise.reject(error));
|
||||||
|
}
|
69
src/api/user.api.js
Normal file
69
src/api/user.api.js
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import { get, post, put } from "./baseApi";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function wrapping GET request for getting user data
|
||||||
|
*/
|
||||||
|
export function getSelfUser() {
|
||||||
|
return get("/user/").then(resp => Promise.resolve(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function wrapping POST request for initializing User Info
|
||||||
|
* @param {string} phone_number - user's phone number
|
||||||
|
*/
|
||||||
|
export function createUserInfo(phone_number) {
|
||||||
|
return post("/userinfo/", { phone_number }).then(resp =>
|
||||||
|
Promise.resolve(resp)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function wrapping PUT request for updating User Info
|
||||||
|
* @param {string} username - username for user
|
||||||
|
* @param {string} phone_number - user's phone number
|
||||||
|
*/
|
||||||
|
export function updateUserInfo(username, phone_number) {
|
||||||
|
return put(`/userinfo/${username}/`, { phone_number }).then(resp =>
|
||||||
|
Promise.resolve(resp)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function wrapping POST request for creating a client
|
||||||
|
* @param {string} business_number - user's client business number
|
||||||
|
*/
|
||||||
|
export function createClient(business_number) {
|
||||||
|
return post("/client/", { business_number }).then(resp =>
|
||||||
|
Promise.resolve(resp)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function wrapping PUT request for updating a client
|
||||||
|
* @param {string} username - username for user
|
||||||
|
* @param {*} business_number - user's client business number
|
||||||
|
*/
|
||||||
|
export function updateClient(username, business_number) {
|
||||||
|
return put(`/client/${username}/`, { business_number }).then(resp =>
|
||||||
|
Promise.resolve(resp)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function wrapping POST request for creating a provider
|
||||||
|
* @param {string} sin - user's provider sin
|
||||||
|
*/
|
||||||
|
export function createProvider(sin) {
|
||||||
|
return post("/provider/", { sin }).then(resp => Promise.resolve(resp));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function wrapping PUT request for updating a provider
|
||||||
|
* @param {string} username - username for user
|
||||||
|
* @param {*} sin - user's provider sin
|
||||||
|
*/
|
||||||
|
export function updateProvider(username, sin) {
|
||||||
|
return put(`/provider/${username}/`, { sin }).then(resp =>
|
||||||
|
Promise.resolve(resp)
|
||||||
|
);
|
||||||
|
}
|
63
src/components/App.jsx
Normal file
63
src/components/App.jsx
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { Route, Switch } from "react-router-dom";
|
||||||
|
|
||||||
|
import ForgotPassword from "./Auth/ForgotPassword";
|
||||||
|
import Login from "./Auth/Login";
|
||||||
|
import Register from "./Auth/Register";
|
||||||
|
import ResetPassword from "./Auth/ResetPassword";
|
||||||
|
import Settings from "./Auth/Settings";
|
||||||
|
import VerifyEmail from "./Auth/VerifyEmail";
|
||||||
|
import CompleteRegistration from "./User/CompleteRegistration";
|
||||||
|
import EditProfile from "./User/EditProfile";
|
||||||
|
import Profile from "./User/Profile";
|
||||||
|
import PrivateRoute from "./Shared/PrivateRoute";
|
||||||
|
import About from "./Static/About";
|
||||||
|
import Footer from "./Static/Footer";
|
||||||
|
import Home from "./Static/Home";
|
||||||
|
import NoMatch from "./Static/NoMatch";
|
||||||
|
import Navbar from "./Navbar";
|
||||||
|
|
||||||
|
class App extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Navbar />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
minHeight: "calc(100vh - 1px)",
|
||||||
|
flexDirection: "column"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ flex: "1" }}>
|
||||||
|
<Switch>
|
||||||
|
<Route exact path="/" component={Home} />
|
||||||
|
<Route path="/about" component={About} />
|
||||||
|
<Route path="/auth/login" component={Login} />
|
||||||
|
<Route path="/auth/register" component={Register} />
|
||||||
|
<Route
|
||||||
|
path="/auth/verify-email/:emailKey"
|
||||||
|
component={VerifyEmail}
|
||||||
|
/>
|
||||||
|
<Route path="/auth/verify-email" component={VerifyEmail} />
|
||||||
|
<PrivateRoute path="/auth/settings" component={Settings} />
|
||||||
|
<Route path="/auth/forgot-password" component={ForgotPassword} />
|
||||||
|
<Route
|
||||||
|
path="/auth/reset-password/:uid/:token"
|
||||||
|
component={ResetPassword}
|
||||||
|
/>
|
||||||
|
<PrivateRoute path="/user/profile/edit" component={EditProfile} />
|
||||||
|
<PrivateRoute path="/user/profile" component={Profile} />
|
||||||
|
<Route path='/user/complete-registration' component={Profile} />
|
||||||
|
<Route component={NoMatch} />
|
||||||
|
</Switch>
|
||||||
|
<Route path='/user/complete-registration' component={CompleteRegistration} />
|
||||||
|
</div>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
8
src/components/App.test.js
Normal file
8
src/components/App.test.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
ReactDOM.render(<App />, div);
|
||||||
|
});
|
98
src/components/Auth/ForgotPassword.jsx
Normal file
98
src/components/Auth/ForgotPassword.jsx
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Link, Redirect } from "react-router-dom";
|
||||||
|
import { Button, Container, Form, Header, Message } from "semantic-ui-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
clearAuthRequestError,
|
||||||
|
clearAuthRequestSuccess,
|
||||||
|
setFormEmail
|
||||||
|
} from "../../actions/auth/reducer.actions";
|
||||||
|
import { sendForgotPasswordRequest } from "../../actions/auth/saga.actions";
|
||||||
|
import Error from "../Shared/Error";
|
||||||
|
|
||||||
|
class ForgotPassword extends Component {
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.dispatch(clearAuthRequestError());
|
||||||
|
this.props.dispatch(clearAuthRequestSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
changeEmail = event => {
|
||||||
|
this.props.dispatch(setFormEmail(event.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmitForgotPassword = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const { dispatch, email } = this.props;
|
||||||
|
dispatch(sendForgotPasswordRequest({ email }));
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isSendingAuthRequest,
|
||||||
|
authRequestError,
|
||||||
|
authRequestSuccess,
|
||||||
|
email,
|
||||||
|
userToken
|
||||||
|
} = this.props;
|
||||||
|
if (userToken) return <Redirect to={"/"} />;
|
||||||
|
return (
|
||||||
|
<ForgotPasswordView
|
||||||
|
isSendingAuthRequest={isSendingAuthRequest}
|
||||||
|
authRequestError={authRequestError}
|
||||||
|
authRequestSuccess={authRequestSuccess}
|
||||||
|
email={email}
|
||||||
|
changeEmail={this.changeEmail}
|
||||||
|
onSubmitForgotPassword={this.onSubmitForgotPassword}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { ...state.auth };
|
||||||
|
}
|
||||||
|
|
||||||
|
const ForgotPasswordView = ({
|
||||||
|
isSendingAuthRequest,
|
||||||
|
authRequestError,
|
||||||
|
authRequestSuccess,
|
||||||
|
email,
|
||||||
|
changeEmail,
|
||||||
|
onSubmitForgotPassword
|
||||||
|
}) => (
|
||||||
|
<Container>
|
||||||
|
<Header>Forgot Password</Header>
|
||||||
|
<p>Enter your email and we will send you a link to reset your password.</p>
|
||||||
|
<Form
|
||||||
|
loading={isSendingAuthRequest}
|
||||||
|
onSubmit={onSubmitForgotPassword}
|
||||||
|
error={!!authRequestError}
|
||||||
|
success={!!authRequestSuccess}
|
||||||
|
>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Email</label>
|
||||||
|
<input
|
||||||
|
placeholder="bob@gmail.com"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={changeEmail}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Error
|
||||||
|
header="Forgot Password Request failed!"
|
||||||
|
error={authRequestError}
|
||||||
|
/>
|
||||||
|
<Message success>
|
||||||
|
<Message.Header>Password Reset Sent!</Message.Header>
|
||||||
|
<p>If the email exists, a password reset link will be sent to it.</p>
|
||||||
|
</Message>
|
||||||
|
<Button.Group>
|
||||||
|
<Button type="submit" primary>Request Password Reset</Button>
|
||||||
|
<Button as={Link} to="/auth/login" secondary>Login</Button>
|
||||||
|
</Button.Group>
|
||||||
|
</Form>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(ForgotPassword);
|
115
src/components/Auth/Login.jsx
Normal file
115
src/components/Auth/Login.jsx
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Link, Redirect } from "react-router-dom";
|
||||||
|
import { Button, Container, Form, Header, Message } from "semantic-ui-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
clearAuthRequestError,
|
||||||
|
clearAuthRequestSuccess,
|
||||||
|
setFormEmail,
|
||||||
|
setFormPassword
|
||||||
|
} from "../../actions/auth/reducer.actions";
|
||||||
|
import { sendLoginRequest } from "../../actions/auth/saga.actions";
|
||||||
|
import Error from "../Shared/Error";
|
||||||
|
|
||||||
|
class Login extends Component {
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.dispatch(clearAuthRequestError());
|
||||||
|
this.props.dispatch(clearAuthRequestSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
changeEmail = event => {
|
||||||
|
this.props.dispatch(setFormEmail(event.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
changePassword = event => {
|
||||||
|
this.props.dispatch(setFormPassword(event.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmitLogin = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const { dispatch, email, password } = this.props;
|
||||||
|
dispatch(sendLoginRequest({ email, password }));
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isSendingAuthRequest,
|
||||||
|
authRequestError,
|
||||||
|
authRequestSuccess,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
userToken
|
||||||
|
} = this.props;
|
||||||
|
if (userToken) return <Redirect to={"/user/profile"} />;
|
||||||
|
return (
|
||||||
|
<LoginView
|
||||||
|
isSendingAuthRequest={isSendingAuthRequest}
|
||||||
|
authRequestError={authRequestError}
|
||||||
|
authRequestSuccess={authRequestSuccess}
|
||||||
|
email={email}
|
||||||
|
password={password}
|
||||||
|
changeEmail={this.changeEmail}
|
||||||
|
changePassword={this.changePassword}
|
||||||
|
onSubmitLogin={this.onSubmitLogin}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { ...state.auth };
|
||||||
|
}
|
||||||
|
|
||||||
|
const LoginView = ({
|
||||||
|
isSendingAuthRequest,
|
||||||
|
authRequestError,
|
||||||
|
authRequestSuccess,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
changeEmail,
|
||||||
|
changePassword,
|
||||||
|
onSubmitLogin
|
||||||
|
}) => (
|
||||||
|
<Container>
|
||||||
|
<Header>Login</Header>
|
||||||
|
<Form
|
||||||
|
loading={isSendingAuthRequest}
|
||||||
|
onSubmit={onSubmitLogin}
|
||||||
|
error={!!authRequestError}
|
||||||
|
success={!!authRequestSuccess}
|
||||||
|
>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Email</label>
|
||||||
|
<input
|
||||||
|
placeholder="bob@gmail.com"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={changeEmail}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Password</label>
|
||||||
|
<input
|
||||||
|
placeholder="••••••••"
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={changePassword}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Error header="Login failed!" error={authRequestError} />
|
||||||
|
<Message success>
|
||||||
|
<Message.Header>Login successful!</Message.Header>
|
||||||
|
<p>Redirecting you now...</p>
|
||||||
|
</Message>
|
||||||
|
<Button.Group>
|
||||||
|
<Button type="submit" primary>Login</Button>
|
||||||
|
<Button as={Link} to="/auth/forgot-password" secondary>
|
||||||
|
Forgot Password
|
||||||
|
</Button>
|
||||||
|
</Button.Group>
|
||||||
|
</Form>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(Login);
|
138
src/components/Auth/Register.jsx
Normal file
138
src/components/Auth/Register.jsx
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Redirect } from "react-router-dom";
|
||||||
|
import { Container, Form, Header, Message } from "semantic-ui-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
clearAuthRequestError,
|
||||||
|
clearAuthRequestSuccess,
|
||||||
|
setFormEmail,
|
||||||
|
setFormPassword,
|
||||||
|
setFormPasswordConfirmation
|
||||||
|
} from "../../actions/auth/reducer.actions";
|
||||||
|
import { sendRegisterRequest } from "../../actions/auth/saga.actions";
|
||||||
|
import Error from "../Shared/Error";
|
||||||
|
|
||||||
|
class Register extends Component {
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.dispatch(clearAuthRequestError());
|
||||||
|
this.props.dispatch(clearAuthRequestSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
changeEmail = event => {
|
||||||
|
this.props.dispatch(setFormEmail(event.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
changePassword = event => {
|
||||||
|
this.props.dispatch(setFormPassword(event.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
changePasswordConfirmation = event => {
|
||||||
|
this.props.dispatch(setFormPasswordConfirmation(event.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmitRegistration = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const { dispatch, email, password, passwordConfirmation } = this.props;
|
||||||
|
dispatch(
|
||||||
|
sendRegisterRequest({
|
||||||
|
email,
|
||||||
|
password1: password,
|
||||||
|
password2: passwordConfirmation
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isSendingAuthRequest,
|
||||||
|
authRequestError,
|
||||||
|
authRequestSuccess,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
passwordConfirmation,
|
||||||
|
userToken
|
||||||
|
} = this.props;
|
||||||
|
if (userToken) return <Redirect to={"/"} />;
|
||||||
|
return (
|
||||||
|
<RegisterView
|
||||||
|
isSendingAuthRequest={isSendingAuthRequest}
|
||||||
|
authRequestError={authRequestError}
|
||||||
|
authRequestSuccess={authRequestSuccess}
|
||||||
|
email={email}
|
||||||
|
password={password}
|
||||||
|
passwordConfirmation={passwordConfirmation}
|
||||||
|
changeEmail={this.changeEmail}
|
||||||
|
changePassword={this.changePassword}
|
||||||
|
changePasswordConfirmation={this.changePasswordConfirmation}
|
||||||
|
onSubmitRegistration={this.onSubmitRegistration}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { ...state.auth };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functional view component for Register logic
|
||||||
|
*/
|
||||||
|
const RegisterView = ({
|
||||||
|
isSendingAuthRequest,
|
||||||
|
authRequestError,
|
||||||
|
authRequestSuccess,
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
passwordConfirmation,
|
||||||
|
changeEmail,
|
||||||
|
changePassword,
|
||||||
|
changePasswordConfirmation,
|
||||||
|
onSubmitRegistration
|
||||||
|
}) => (
|
||||||
|
<Container>
|
||||||
|
<Header>Register</Header>
|
||||||
|
<Form
|
||||||
|
loading={isSendingAuthRequest}
|
||||||
|
onSubmit={onSubmitRegistration}
|
||||||
|
error={!!authRequestError}
|
||||||
|
success={!!authRequestSuccess}
|
||||||
|
>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Email</label>
|
||||||
|
<input
|
||||||
|
placeholder="bob@gmail.com"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={changeEmail}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Password</label>
|
||||||
|
<input
|
||||||
|
placeholder="••••••••"
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={changePassword}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Confirm Password</label>
|
||||||
|
<input
|
||||||
|
placeholder="••••••••"
|
||||||
|
type="password"
|
||||||
|
value={passwordConfirmation}
|
||||||
|
onChange={changePasswordConfirmation}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Error header="Register failed!" error={authRequestError} />
|
||||||
|
<Message success>
|
||||||
|
<Message.Header>Confirmation Email Sent!</Message.Header>
|
||||||
|
<p>Please check your email to confirm your registration.</p>
|
||||||
|
</Message>
|
||||||
|
<Form.Button primary>Register</Form.Button>
|
||||||
|
</Form>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(Register);
|
118
src/components/Auth/ResetPassword.jsx
Normal file
118
src/components/Auth/ResetPassword.jsx
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Link, Redirect } from "react-router-dom";
|
||||||
|
import { Container, Form, Header, Message } from "semantic-ui-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
clearAuthRequestError,
|
||||||
|
clearAuthRequestSuccess,
|
||||||
|
setFormPassword,
|
||||||
|
setFormPasswordConfirmation
|
||||||
|
} from "../../actions/auth/reducer.actions";
|
||||||
|
import { sendResetPasswordRequest } from "../../actions/auth/saga.actions";
|
||||||
|
import Error from "../Shared/Error";
|
||||||
|
|
||||||
|
class ResetPassword extends Component {
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.dispatch(clearAuthRequestError());
|
||||||
|
this.props.dispatch(clearAuthRequestSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
changePassword = event => {
|
||||||
|
this.props.dispatch(setFormPassword(event.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
changePasswordConfirmation = event => {
|
||||||
|
this.props.dispatch(setFormPasswordConfirmation(event.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmitResetPassword = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const { dispatch, password, passwordConfirmation } = this.props;
|
||||||
|
const { uid, token } = this.props.match.params;
|
||||||
|
dispatch(
|
||||||
|
sendResetPasswordRequest({
|
||||||
|
uid,
|
||||||
|
token,
|
||||||
|
new_password1: password,
|
||||||
|
new_password2: passwordConfirmation
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isSendingAuthRequest,
|
||||||
|
authRequestError,
|
||||||
|
authRequestSuccess,
|
||||||
|
password,
|
||||||
|
passwordConfirmation,
|
||||||
|
userToken
|
||||||
|
} = this.props;
|
||||||
|
if (userToken) return <Redirect to={"/"} />;
|
||||||
|
return (
|
||||||
|
<ResetPasswordView
|
||||||
|
isSendingAuthRequest={isSendingAuthRequest}
|
||||||
|
authRequestError={authRequestError}
|
||||||
|
authRequestSuccess={authRequestSuccess}
|
||||||
|
password={password}
|
||||||
|
passwordConfirmation={passwordConfirmation}
|
||||||
|
changePassword={this.changePassword}
|
||||||
|
changePasswordConfirmation={this.changePasswordConfirmation}
|
||||||
|
onSubmitResetPassword={this.onSubmitResetPassword}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { ...state.auth };
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResetPasswordView = ({
|
||||||
|
isSendingAuthRequest,
|
||||||
|
authRequestError,
|
||||||
|
authRequestSuccess,
|
||||||
|
password,
|
||||||
|
passwordConfirmation,
|
||||||
|
changePassword,
|
||||||
|
changePasswordConfirmation,
|
||||||
|
onSubmitResetPassword
|
||||||
|
}) => (
|
||||||
|
<Container>
|
||||||
|
<Header>Reset Password</Header>
|
||||||
|
<Form
|
||||||
|
loading={isSendingAuthRequest}
|
||||||
|
onSubmit={onSubmitResetPassword}
|
||||||
|
error={!!authRequestError}
|
||||||
|
success={!!authRequestSuccess}
|
||||||
|
>
|
||||||
|
<Form.Field>
|
||||||
|
<label>New Password</label>
|
||||||
|
<input
|
||||||
|
placeholder="••••••••"
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={changePassword}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Confirm New Password</label>
|
||||||
|
<input
|
||||||
|
placeholder="••••••••"
|
||||||
|
type="password"
|
||||||
|
value={passwordConfirmation}
|
||||||
|
onChange={changePasswordConfirmation}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Error header="Reset Password failed!" error={authRequestError} />
|
||||||
|
<Message success>
|
||||||
|
<Message.Header>Password Reset!</Message.Header>
|
||||||
|
<p>You may now <Link to="/auth/login">log in</Link> with the new credentials.</p>
|
||||||
|
</Message>
|
||||||
|
<Form.Button primary>Reset Password</Form.Button>{" "}
|
||||||
|
</Form>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(ResetPassword);
|
140
src/components/Auth/Settings.jsx
Normal file
140
src/components/Auth/Settings.jsx
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Container, Form, Header, Message, Segment } from "semantic-ui-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
clearAuthRequestError,
|
||||||
|
clearAuthRequestSuccess,
|
||||||
|
setFormPassword,
|
||||||
|
setFormPasswordConfirmation,
|
||||||
|
setFormOldPassword
|
||||||
|
} from "../../actions/auth/reducer.actions";
|
||||||
|
import { sendChangePasswordRequest } from "../../actions/auth/saga.actions";
|
||||||
|
import Error from "../Shared/Error";
|
||||||
|
|
||||||
|
class Settings extends Component {
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.dispatch(clearAuthRequestError());
|
||||||
|
this.props.dispatch(clearAuthRequestSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
changePassword = event => {
|
||||||
|
this.props.dispatch(setFormPassword(event.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
changePasswordConfirmation = event => {
|
||||||
|
this.props.dispatch(setFormPasswordConfirmation(event.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
changeOldPassword = event => {
|
||||||
|
this.props.dispatch(setFormOldPassword(event.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmitChangePassword = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const {
|
||||||
|
dispatch,
|
||||||
|
password,
|
||||||
|
passwordConfirmation,
|
||||||
|
oldPassword
|
||||||
|
} = this.props;
|
||||||
|
dispatch(
|
||||||
|
sendChangePasswordRequest({
|
||||||
|
new_password1: password,
|
||||||
|
new_password2: passwordConfirmation,
|
||||||
|
old_password: oldPassword
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isSendingAuthRequest,
|
||||||
|
authRequestError,
|
||||||
|
authRequestSuccess,
|
||||||
|
password,
|
||||||
|
passwordConfirmation,
|
||||||
|
oldPassword
|
||||||
|
} = this.props;
|
||||||
|
return (
|
||||||
|
<SettingsView
|
||||||
|
isSendingAuthRequest={isSendingAuthRequest}
|
||||||
|
authRequestError={authRequestError}
|
||||||
|
authRequestSuccess={authRequestSuccess}
|
||||||
|
password={password}
|
||||||
|
passwordConfirmation={passwordConfirmation}
|
||||||
|
oldPassword={oldPassword}
|
||||||
|
changePassword={this.changePassword}
|
||||||
|
changePasswordConfirmation={this.changePasswordConfirmation}
|
||||||
|
changeOldPassword={this.changeOldPassword}
|
||||||
|
onSubmitChangePassword={this.onSubmitChangePassword}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { ...state.auth };
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingsView = ({
|
||||||
|
isSendingAuthRequest,
|
||||||
|
authRequestError,
|
||||||
|
authRequestSuccess,
|
||||||
|
password,
|
||||||
|
passwordConfirmation,
|
||||||
|
oldPassword,
|
||||||
|
changePassword,
|
||||||
|
changePasswordConfirmation,
|
||||||
|
changeOldPassword,
|
||||||
|
onSubmitChangePassword
|
||||||
|
}) => (
|
||||||
|
<Container>
|
||||||
|
<Header>Settings</Header>
|
||||||
|
<Header attached="top"> Change Password </Header>
|
||||||
|
<Segment attached>
|
||||||
|
<Form
|
||||||
|
loading={isSendingAuthRequest}
|
||||||
|
onSubmit={onSubmitChangePassword}
|
||||||
|
error={!!authRequestError}
|
||||||
|
success={!!authRequestSuccess}
|
||||||
|
>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Old Password</label>
|
||||||
|
<input
|
||||||
|
placeholder="••••••••"
|
||||||
|
type="password"
|
||||||
|
value={oldPassword}
|
||||||
|
onChange={changeOldPassword}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field>
|
||||||
|
<label>New Password</label>
|
||||||
|
<input
|
||||||
|
placeholder="••••••••"
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={changePassword}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Confirm New Password</label>
|
||||||
|
<input
|
||||||
|
placeholder="••••••••"
|
||||||
|
type="password"
|
||||||
|
value={passwordConfirmation}
|
||||||
|
onChange={changePasswordConfirmation}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Error header="Change Password failed!" error={authRequestError} />
|
||||||
|
<Message success>
|
||||||
|
<Message.Header>Password Successfully Changed!</Message.Header>
|
||||||
|
<p>New password has been set.</p>
|
||||||
|
</Message>
|
||||||
|
<Form.Button primary>Change Password</Form.Button>
|
||||||
|
</Form>
|
||||||
|
</Segment>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(Settings);
|
98
src/components/Auth/VerifyEmail.jsx
Normal file
98
src/components/Auth/VerifyEmail.jsx
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Link, Redirect } from "react-router-dom";
|
||||||
|
import { Container, Form, Header, Message } from "semantic-ui-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
clearAuthRequestError,
|
||||||
|
clearAuthRequestSuccess,
|
||||||
|
setFormEmailVerification
|
||||||
|
} from "../../actions/auth/reducer.actions";
|
||||||
|
import { sendEmailVerificationRequest } from "../../actions/auth/saga.actions";
|
||||||
|
import Error from "../Shared/Error";
|
||||||
|
|
||||||
|
class VerifyEmail extends Component {
|
||||||
|
componentWillMount() {
|
||||||
|
const emailKey = this.props.match.params.emailKey;
|
||||||
|
this.props.dispatch(clearAuthRequestError());
|
||||||
|
this.props.dispatch(clearAuthRequestSuccess());
|
||||||
|
this.props.dispatch(setFormEmailVerification(emailKey));
|
||||||
|
if (emailKey) {
|
||||||
|
this.props.dispatch(sendEmailVerificationRequest({ emailKey }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeEmailKey = event => {
|
||||||
|
this.props.dispatch(setFormEmailVerification(event.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmitEmailVerification = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const { dispatch, emailVerificationString } = this.props;
|
||||||
|
dispatch(
|
||||||
|
sendEmailVerificationRequest({ emailKey: emailVerificationString })
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isSendingAuthRequest,
|
||||||
|
authRequestError,
|
||||||
|
authRequestSuccess,
|
||||||
|
emailVerificationString,
|
||||||
|
userToken
|
||||||
|
} = this.props;
|
||||||
|
if (userToken) return <Redirect to={"/"} />;
|
||||||
|
return (
|
||||||
|
<VerifyEmailView
|
||||||
|
isSendingAuthRequest={isSendingAuthRequest}
|
||||||
|
authRequestError={authRequestError}
|
||||||
|
authRequestSuccess={authRequestSuccess}
|
||||||
|
emailVerificationString={emailVerificationString}
|
||||||
|
changeEmailKey={this.changeEmailKey}
|
||||||
|
onSubmitEmailVerification={this.onSubmitEmailVerification}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { ...state.auth };
|
||||||
|
}
|
||||||
|
|
||||||
|
const VerifyEmailView = ({
|
||||||
|
isSendingAuthRequest,
|
||||||
|
authRequestError,
|
||||||
|
authRequestSuccess,
|
||||||
|
emailVerificationString,
|
||||||
|
changeEmailKey,
|
||||||
|
onSubmitEmailVerification
|
||||||
|
}) => (
|
||||||
|
<Container>
|
||||||
|
<Header>Verify Email</Header>
|
||||||
|
<Form
|
||||||
|
loading={isSendingAuthRequest}
|
||||||
|
onSubmit={onSubmitEmailVerification}
|
||||||
|
error={!!authRequestError}
|
||||||
|
success={!!authRequestSuccess}
|
||||||
|
>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Email Verification Key</label>
|
||||||
|
<input
|
||||||
|
placeholder=""
|
||||||
|
type="text"
|
||||||
|
value={emailVerificationString}
|
||||||
|
onChange={changeEmailKey}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Error header="Email Verification failed!" error={authRequestError} />
|
||||||
|
<Message success>
|
||||||
|
<Message.Header>Email Verified!</Message.Header>
|
||||||
|
<p>Please proceed to <Link to="/auth/login">log in</Link>.</p>
|
||||||
|
</Message>
|
||||||
|
<Form.Button primary>Verify Email</Form.Button>
|
||||||
|
</Form>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(VerifyEmail);
|
77
src/components/Navbar.jsx
Normal file
77
src/components/Navbar.jsx
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { Dropdown, Menu } from "semantic-ui-react";
|
||||||
|
|
||||||
|
import { getSelfUserRequest } from "../actions/user/saga.actions";
|
||||||
|
import { sendLogoutRequest } from "../actions/auth/saga.actions";
|
||||||
|
|
||||||
|
class Navbar extends Component {
|
||||||
|
componentWillMount() {
|
||||||
|
const { dispatch, userToken, selfUser } = this.props;
|
||||||
|
// If the user token exists and the self user object isn't loaded, dispatch
|
||||||
|
if (userToken && Object.keys(selfUser).length === 0) {
|
||||||
|
dispatch(getSelfUserRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchLogoutRequest = () => {
|
||||||
|
this.props.dispatch(sendLogoutRequest());
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { userToken } = this.props;
|
||||||
|
return (
|
||||||
|
<NavbarView
|
||||||
|
isAuthenticated={!!userToken}
|
||||||
|
dispatchLogoutRequest={this.dispatchLogoutRequest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
userToken: state.auth.userToken,
|
||||||
|
selfUser: state.user.selfUser
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const NavbarView = ({ isAuthenticated, dispatchLogoutRequest }) => (
|
||||||
|
<Menu>
|
||||||
|
<Menu.Item as={Link} to="/">
|
||||||
|
Caremyway
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item as={Link} to="/about">
|
||||||
|
About
|
||||||
|
</Menu.Item>
|
||||||
|
{!isAuthenticated &&
|
||||||
|
<Menu.Menu position="right">
|
||||||
|
<Menu.Item as={Link} to="/auth/login">
|
||||||
|
Login
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item as={Link} to="/auth/register">
|
||||||
|
Register
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.Menu>}
|
||||||
|
{!!isAuthenticated &&
|
||||||
|
<Menu.Menu position="right">
|
||||||
|
<Dropdown item text="Account">
|
||||||
|
<Dropdown.Menu>
|
||||||
|
<Dropdown.Item as={Link} to="/user/profile">
|
||||||
|
My Profile
|
||||||
|
</Dropdown.Item>
|
||||||
|
<Dropdown.Item as={Link} to="/auth/settings">
|
||||||
|
Settings
|
||||||
|
</Dropdown.Item>
|
||||||
|
<Dropdown.Divider />
|
||||||
|
<Dropdown.Item onClick={dispatchLogoutRequest}>
|
||||||
|
Logout
|
||||||
|
</Dropdown.Item>
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
|
</Menu.Menu>}
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(Navbar);
|
41
src/components/Shared/Error.jsx
Normal file
41
src/components/Shared/Error.jsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import React from "react";
|
||||||
|
import { Message } from "semantic-ui-react";
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
error: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
|
header: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultProps = {
|
||||||
|
error: "",
|
||||||
|
header: ""
|
||||||
|
};
|
||||||
|
|
||||||
|
const Error = ({ error, header }) => {
|
||||||
|
if (typeof error === "string") {
|
||||||
|
const hasError = !!error;
|
||||||
|
if (hasError) {
|
||||||
|
return <Message error={hasError} header={header} content={error} />;
|
||||||
|
}
|
||||||
|
} else if (typeof error === "object" && Object.keys(error).length > 0) {
|
||||||
|
const hasError = !!Object.keys(error);
|
||||||
|
if (hasError) {
|
||||||
|
return (
|
||||||
|
<Message
|
||||||
|
error={hasError}
|
||||||
|
header={header}
|
||||||
|
list={Object.keys(error).map(p => (
|
||||||
|
<Message.Item key={p}> {p}: {error[p]}</Message.Item>
|
||||||
|
))}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
Error.propTypes = propTypes;
|
||||||
|
Error.defaultProps = defaultProps;
|
||||||
|
|
||||||
|
export default Error;
|
73
src/components/Shared/PrivateRoute.jsx
Normal file
73
src/components/Shared/PrivateRoute.jsx
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Redirect, Route } from "react-router-dom";
|
||||||
|
import { Loader } from "semantic-ui-react";
|
||||||
|
|
||||||
|
import { getSelfUserRequest } from "../../actions/user/saga.actions";
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
userToken: PropTypes.string.isRequired,
|
||||||
|
selfUser: PropTypes.object.isRequired,
|
||||||
|
isSendingUserRequest: PropTypes.bool.isRequired,
|
||||||
|
path: PropTypes.string.isRequired,
|
||||||
|
component: PropTypes.oneOfType([PropTypes.element, PropTypes.func])
|
||||||
|
.isRequired,
|
||||||
|
dispatch: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class PrivateRoute extends Component {
|
||||||
|
componentWillMount() {
|
||||||
|
const { dispatch, userToken, selfUser } = this.props;
|
||||||
|
// If the user token exists and the self user object isn't loaded, dispatch
|
||||||
|
if (userToken && Object.keys(selfUser).length === 0) {
|
||||||
|
dispatch(getSelfUserRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
userToken,
|
||||||
|
selfUser,
|
||||||
|
isSendingUserRequest,
|
||||||
|
component,
|
||||||
|
...rest
|
||||||
|
} = this.props;
|
||||||
|
// If the user token exists and
|
||||||
|
// * self user object isn't loaded yet or
|
||||||
|
// * we are still sending user request
|
||||||
|
// show loading spinner
|
||||||
|
if (
|
||||||
|
userToken && (Object.keys(selfUser).length === 0 || isSendingUserRequest)
|
||||||
|
) {
|
||||||
|
return <Loader active />;
|
||||||
|
}
|
||||||
|
// If the user exists but they aren't a client or provider yet, confirm-registration
|
||||||
|
if (
|
||||||
|
userToken &&
|
||||||
|
Object.keys(selfUser).length &&
|
||||||
|
(!selfUser.client && !selfUser.provider)
|
||||||
|
) {
|
||||||
|
return <Redirect to="/user/complete-registration" />;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
{...rest}
|
||||||
|
render={props => {
|
||||||
|
if (!!userToken) return React.createElement(component, props);
|
||||||
|
return <Redirect to="/auth/login" />;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrivateRoute.propTypes = propTypes;
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
userToken: state.auth.userToken,
|
||||||
|
selfUser: state.user.selfUser,
|
||||||
|
isSendingUserRequest: state.user.isSendingUserRequest
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(PrivateRoute);
|
11
src/components/Static/About.jsx
Normal file
11
src/components/Static/About.jsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Container } from "semantic-ui-react";
|
||||||
|
|
||||||
|
const About = () => (
|
||||||
|
<Container>
|
||||||
|
<h2>About</h2>
|
||||||
|
<p>About page static content goes here.</p>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default About;
|
14
src/components/Static/Footer.jsx
Normal file
14
src/components/Static/Footer.jsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { List, Segment } from "semantic-ui-react";
|
||||||
|
|
||||||
|
const Footer = () => (
|
||||||
|
<Segment textAlign="center" color="red" style={{ margin: "0" }}>
|
||||||
|
<List bulleted horizontal link>
|
||||||
|
<List.Item as={Link} to="/">Caremyway</List.Item>
|
||||||
|
<List.Item as={Link} to="/about">About</List.Item>
|
||||||
|
</List>
|
||||||
|
</Segment>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Footer;
|
11
src/components/Static/Home.jsx
Normal file
11
src/components/Static/Home.jsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Container } from "semantic-ui-react";
|
||||||
|
|
||||||
|
const Home = () => (
|
||||||
|
<Container>
|
||||||
|
<h2>Home</h2>
|
||||||
|
<p>Home page static content goes here.</p>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Home;
|
13
src/components/Static/NoMatch.jsx
Normal file
13
src/components/Static/NoMatch.jsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { Container } from "semantic-ui-react";
|
||||||
|
|
||||||
|
const NoMatch = ({ location }) => (
|
||||||
|
<Container>
|
||||||
|
<h3>Page not found!</h3>
|
||||||
|
<p>No match found for <code>{location.pathname}</code></p>
|
||||||
|
<p><Link to="/">Go to the home page →</Link></p>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default NoMatch;
|
41
src/components/User/ClientFormView.jsx
Normal file
41
src/components/User/ClientFormView.jsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Container, Form, Header, Message } from "semantic-ui-react";
|
||||||
|
|
||||||
|
import Error from "../Shared/Error";
|
||||||
|
|
||||||
|
const ClientFormView = ({
|
||||||
|
isSendingUserRequest,
|
||||||
|
userRequestError,
|
||||||
|
userRequestSuccess,
|
||||||
|
businessNumber,
|
||||||
|
changeBusinessNumber,
|
||||||
|
onSubmitClient
|
||||||
|
}) => (
|
||||||
|
<Container>
|
||||||
|
<Header>Complete Client Information</Header>
|
||||||
|
<Form
|
||||||
|
loading={isSendingUserRequest}
|
||||||
|
onSubmit={onSubmitClient}
|
||||||
|
error={!!userRequestError}
|
||||||
|
success={!!userRequestSuccess}
|
||||||
|
>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Business Number</label>
|
||||||
|
<input
|
||||||
|
placeholder="12345"
|
||||||
|
type="text"
|
||||||
|
value={businessNumber}
|
||||||
|
onChange={changeBusinessNumber}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Error header="Modify Client failed!" error={userRequestError} />
|
||||||
|
<Message success>
|
||||||
|
<Message.Header>Modify Client successful!</Message.Header>
|
||||||
|
<p>Set client successfully.</p>
|
||||||
|
</Message>
|
||||||
|
<Form.Button>Submit Client</Form.Button>
|
||||||
|
</Form>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ClientFormView;
|
64
src/components/User/ClientOrProviderForm.jsx
Normal file
64
src/components/User/ClientOrProviderForm.jsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Container, Form, Header } from "semantic-ui-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
setCompleteRegistrationClientOrProvider,
|
||||||
|
setCompleteRegistrationStep
|
||||||
|
} from "../../actions/user/reducer.actions";
|
||||||
|
import { CLIENT, PROVIDER, COMPLETE_INFORMATION_STEP } from "../../constants/user.constants";
|
||||||
|
|
||||||
|
class ClientOrProviderForm extends Component {
|
||||||
|
changeClientOrProvider = (event, { value }) => {
|
||||||
|
this.props.dispatch(setCompleteRegistrationClientOrProvider(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelectClientOrProvider = event => {
|
||||||
|
this.props.dispatch(setCompleteRegistrationStep(COMPLETE_INFORMATION_STEP));
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { completeRegistrationClientOrProvider } = this.props;
|
||||||
|
return (
|
||||||
|
<ClientOrProviderFormView
|
||||||
|
clientOrProvider={completeRegistrationClientOrProvider}
|
||||||
|
changeClientOrProvider={this.changeClientOrProvider}
|
||||||
|
onSelectClientOrProvider={this.onSelectClientOrProvider}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { ...state.user };
|
||||||
|
}
|
||||||
|
|
||||||
|
const ClientOrProviderFormView = ({
|
||||||
|
clientOrProvider,
|
||||||
|
changeClientOrProvider,
|
||||||
|
onSelectClientOrProvider
|
||||||
|
}) => (
|
||||||
|
<Container>
|
||||||
|
<Header>Client or Provider</Header>
|
||||||
|
<Form onSubmit={onSelectClientOrProvider}>
|
||||||
|
<Form.Group inline>
|
||||||
|
<label>Client or Provider</label>
|
||||||
|
<Form.Radio
|
||||||
|
label="Client"
|
||||||
|
value={CLIENT}
|
||||||
|
checked={clientOrProvider === CLIENT}
|
||||||
|
onChange={changeClientOrProvider}
|
||||||
|
/>
|
||||||
|
<Form.Radio
|
||||||
|
label="Provider"
|
||||||
|
value={PROVIDER}
|
||||||
|
checked={clientOrProvider === PROVIDER}
|
||||||
|
onChange={changeClientOrProvider}
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Button>Continue</Form.Button>
|
||||||
|
</Form>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(ClientOrProviderForm);
|
118
src/components/User/CompleteRegistration.jsx
Normal file
118
src/components/User/CompleteRegistration.jsx
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Redirect } from "react-router-dom";
|
||||||
|
import { Dimmer, Grid, Loader, Modal, Step } from "semantic-ui-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
setCompleteRegistrationStep
|
||||||
|
} from "../../actions/user/reducer.actions";
|
||||||
|
import {
|
||||||
|
USER_INFO_STEP,
|
||||||
|
CLIENT_OR_PROVIDER_STEP,
|
||||||
|
COMPLETE_INFORMATION_STEP,
|
||||||
|
CLIENT,
|
||||||
|
PROVIDER
|
||||||
|
} from "../../constants/user.constants";
|
||||||
|
import ClientOrProviderForm from "./ClientOrProviderForm";
|
||||||
|
import InitializeClientForm from "./InitializeClientForm";
|
||||||
|
import InitializeProviderForm from "./InitializeProviderForm";
|
||||||
|
import InitializeUserInfoForm from "./InitializeUserInfoForm";
|
||||||
|
|
||||||
|
class CompleteRegistration extends Component {
|
||||||
|
onChangeStep = (event, data) => {
|
||||||
|
event.preventDefault();
|
||||||
|
this.props.dispatch(setCompleteRegistrationStep(data.step));
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
userToken,
|
||||||
|
selfUser,
|
||||||
|
completeRegistrationCurrentStep,
|
||||||
|
completeRegistrationMaxStep,
|
||||||
|
completeRegistrationClientOrProvider
|
||||||
|
} = this.props;
|
||||||
|
if (!userToken) return <Redirect to="/auth/login" />;
|
||||||
|
if (Object.keys(selfUser).length === 0)
|
||||||
|
return <Dimmer active><Loader /></Dimmer>;
|
||||||
|
if (selfUser.client || selfUser.provider)
|
||||||
|
return <Redirect to="/user/profile" />;
|
||||||
|
return (
|
||||||
|
<CompleteRegistrationView
|
||||||
|
currentStep={completeRegistrationCurrentStep}
|
||||||
|
maxStep={completeRegistrationMaxStep}
|
||||||
|
completeRegistrationClientOrProvider={
|
||||||
|
completeRegistrationClientOrProvider
|
||||||
|
}
|
||||||
|
onChangeStep={this.onChangeStep}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return {
|
||||||
|
...state.user,
|
||||||
|
userToken: state.auth.userToken
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const CompleteRegistrationView = ({
|
||||||
|
currentStep,
|
||||||
|
maxStep,
|
||||||
|
completeRegistrationClientOrProvider,
|
||||||
|
onChangeStep
|
||||||
|
}) => (
|
||||||
|
<Modal defaultOpen={true} closeOnDimmerClick={false} size="large">
|
||||||
|
<Modal.Header>Complete Registration</Modal.Header>
|
||||||
|
<Modal.Content>
|
||||||
|
<Grid>
|
||||||
|
<Grid.Row>
|
||||||
|
<Grid.Column stretched={true}>
|
||||||
|
<Step.Group stackable="tablet" ordered>
|
||||||
|
<Step
|
||||||
|
completed={maxStep > USER_INFO_STEP}
|
||||||
|
active={currentStep === USER_INFO_STEP}
|
||||||
|
title="Basic User Info"
|
||||||
|
description="Fill out your user information"
|
||||||
|
onClick={onChangeStep}
|
||||||
|
disabled={maxStep < USER_INFO_STEP}
|
||||||
|
step={USER_INFO_STEP}
|
||||||
|
/>
|
||||||
|
<Step
|
||||||
|
completed={maxStep > CLIENT_OR_PROVIDER_STEP}
|
||||||
|
active={currentStep === CLIENT_OR_PROVIDER_STEP}
|
||||||
|
title="Client or Provider"
|
||||||
|
description="Choose to be a Client or a Provider"
|
||||||
|
onClick={onChangeStep}
|
||||||
|
disabled={maxStep < CLIENT_OR_PROVIDER_STEP}
|
||||||
|
step={CLIENT_OR_PROVIDER_STEP}
|
||||||
|
/>
|
||||||
|
<Step
|
||||||
|
completed={maxStep > COMPLETE_INFORMATION_STEP}
|
||||||
|
active={currentStep === COMPLETE_INFORMATION_STEP}
|
||||||
|
title="Complete Information"
|
||||||
|
description="Fill out the client or provider information"
|
||||||
|
onClick={onChangeStep}
|
||||||
|
disabled={maxStep < COMPLETE_INFORMATION_STEP}
|
||||||
|
step={COMPLETE_INFORMATION_STEP}
|
||||||
|
/>
|
||||||
|
</Step.Group>
|
||||||
|
</Grid.Column>
|
||||||
|
</Grid.Row>
|
||||||
|
<Grid.Row>
|
||||||
|
{currentStep === USER_INFO_STEP && <InitializeUserInfoForm />}
|
||||||
|
{currentStep === CLIENT_OR_PROVIDER_STEP && <ClientOrProviderForm />}
|
||||||
|
{currentStep === COMPLETE_INFORMATION_STEP &&
|
||||||
|
completeRegistrationClientOrProvider === CLIENT &&
|
||||||
|
<InitializeClientForm />}
|
||||||
|
{currentStep === COMPLETE_INFORMATION_STEP &&
|
||||||
|
completeRegistrationClientOrProvider === PROVIDER &&
|
||||||
|
<InitializeProviderForm />}
|
||||||
|
</Grid.Row>
|
||||||
|
</Grid>
|
||||||
|
</Modal.Content>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(CompleteRegistration);
|
59
src/components/User/EditClientForm.jsx
Normal file
59
src/components/User/EditClientForm.jsx
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
|
import {
|
||||||
|
clearUserRequestError,
|
||||||
|
clearUserRequestSuccess,
|
||||||
|
setFormBusinessNumber
|
||||||
|
} from "../../actions/user/reducer.actions";
|
||||||
|
import { updateClientRequest } from "../../actions/user/saga.actions";
|
||||||
|
import ClientFormView from "./ClientFormView";
|
||||||
|
|
||||||
|
class EditClientForm extends Component {
|
||||||
|
changeBusinessNumber = event => {
|
||||||
|
this.props.dispatch(setFormBusinessNumber(event.target.value));
|
||||||
|
this.props.dispatch(clearUserRequestError());
|
||||||
|
this.props.dispatch(clearUserRequestSuccess());
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmitClient = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const { selfUser, businessNumber } = this.props;
|
||||||
|
const businessNumberVal =
|
||||||
|
businessNumber || (selfUser.client || {}).business_number;
|
||||||
|
this.props.dispatch(
|
||||||
|
updateClientRequest({
|
||||||
|
username: selfUser.username,
|
||||||
|
business_number: businessNumberVal
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isSendingUserRequest,
|
||||||
|
userRequestError,
|
||||||
|
userRequestSuccess,
|
||||||
|
businessNumber,
|
||||||
|
selfUser
|
||||||
|
} = this.props;
|
||||||
|
const businessNumberVal =
|
||||||
|
businessNumber || (selfUser.client || {}).business_number;
|
||||||
|
return (
|
||||||
|
<ClientFormView
|
||||||
|
isSendingUserRequest={isSendingUserRequest}
|
||||||
|
userRequestError={userRequestError}
|
||||||
|
userRequestSuccess={userRequestSuccess}
|
||||||
|
businessNumber={businessNumberVal}
|
||||||
|
changeBusinessNumber={this.changeBusinessNumber}
|
||||||
|
onSubmitClient={this.onSubmitClient}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { ...state.user };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(EditClientForm);
|
59
src/components/User/EditProfile.jsx
Normal file
59
src/components/User/EditProfile.jsx
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Container, Header, Tab } from "semantic-ui-react";
|
||||||
|
|
||||||
|
import {
|
||||||
|
setEditProfileTabActiveIndex,
|
||||||
|
clearUserRequestError,
|
||||||
|
clearUserRequestSuccess
|
||||||
|
} from "../../actions/user/reducer.actions";
|
||||||
|
import EditClientForm from "./EditClientForm";
|
||||||
|
import EditProviderForm from "./EditProviderForm";
|
||||||
|
import EditUserInfoForm from "./EditUserInfoForm";
|
||||||
|
|
||||||
|
class EditProfile extends Component {
|
||||||
|
onTabChange = (event, { activeIndex }) => {
|
||||||
|
if (this.props.editProfileActiveIndex !== activeIndex) {
|
||||||
|
this.props.dispatch(clearUserRequestError());
|
||||||
|
this.props.dispatch(clearUserRequestSuccess());
|
||||||
|
}
|
||||||
|
this.props.dispatch(setEditProfileTabActiveIndex(activeIndex));
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { selfUser, editProfileActiveIndex } = this.props;
|
||||||
|
const panes = [
|
||||||
|
{
|
||||||
|
menuItem: "User Information",
|
||||||
|
render: () => <Tab.Pane><EditUserInfoForm /></Tab.Pane>
|
||||||
|
}
|
||||||
|
];
|
||||||
|
if (selfUser.client) {
|
||||||
|
panes.push({
|
||||||
|
menuItem: "Client Information",
|
||||||
|
render: () => <Tab.Pane><EditClientForm /></Tab.Pane>
|
||||||
|
});
|
||||||
|
} else if (selfUser.provider) {
|
||||||
|
panes.push({
|
||||||
|
menuItem: "Provider Information",
|
||||||
|
render: () => <Tab.Pane><EditProviderForm /></Tab.Pane>
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Header>Edit Profile</Header>
|
||||||
|
<Tab
|
||||||
|
panes={panes}
|
||||||
|
onTabChange={this.onTabChange}
|
||||||
|
activeIndex={editProfileActiveIndex}
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { ...state.user };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(EditProfile);
|
56
src/components/User/EditProviderForm.jsx
Normal file
56
src/components/User/EditProviderForm.jsx
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
|
import {
|
||||||
|
clearUserRequestError,
|
||||||
|
clearUserRequestSuccess,
|
||||||
|
setFormSocialInsuranceNumber
|
||||||
|
} from "../../actions/user/reducer.actions";
|
||||||
|
import { updateProviderRequest } from "../../actions/user/saga.actions";
|
||||||
|
import ProviderFormView from "./ProviderFormView";
|
||||||
|
|
||||||
|
class EditProviderForm extends Component {
|
||||||
|
changeSocialInsuranceNumber = event => {
|
||||||
|
this.props.dispatch(setFormSocialInsuranceNumber(event.target.value));
|
||||||
|
this.props.dispatch(clearUserRequestError());
|
||||||
|
this.props.dispatch(clearUserRequestSuccess());
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmitProvider = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const { selfUser, socialInsuranceNumber } = this.props;
|
||||||
|
const socialInsuranceNumberVal =
|
||||||
|
socialInsuranceNumber || (selfUser.provider || {}).sin;
|
||||||
|
this.props.dispatch(
|
||||||
|
updateProviderRequest({ username: selfUser.username, sin: socialInsuranceNumberVal })
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isSendingUserRequest,
|
||||||
|
userRequestError,
|
||||||
|
userRequestSuccess,
|
||||||
|
socialInsuranceNumber,
|
||||||
|
selfUser
|
||||||
|
} = this.props;
|
||||||
|
const socialInsuranceNumberVal =
|
||||||
|
socialInsuranceNumber || (selfUser.provider || {}).sin;
|
||||||
|
return (
|
||||||
|
<ProviderFormView
|
||||||
|
isSendingUserRequest={isSendingUserRequest}
|
||||||
|
userRequestError={userRequestError}
|
||||||
|
userRequestSuccess={userRequestSuccess}
|
||||||
|
socialInsuranceNumber={socialInsuranceNumberVal}
|
||||||
|
changeSocialInsuranceNumber={this.changeSocialInsuranceNumber}
|
||||||
|
onSubmitProvider={this.onSubmitProvider}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { ...state.user };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(EditProviderForm);
|
62
src/components/User/EditUserInfoForm.jsx
Normal file
62
src/components/User/EditUserInfoForm.jsx
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
|
import {
|
||||||
|
clearUserRequestError,
|
||||||
|
clearUserRequestSuccess,
|
||||||
|
setFormPhoneNumber
|
||||||
|
} from "../../actions/user/reducer.actions";
|
||||||
|
import { updateUserInfoRequest } from "../../actions/user/saga.actions";
|
||||||
|
|
||||||
|
import UserInfoFormView from "./UserInfoFormView";
|
||||||
|
|
||||||
|
class EditUserInfoForm extends Component {
|
||||||
|
changePhoneNumber = event => {
|
||||||
|
this.props.dispatch(setFormPhoneNumber(event.target.value));
|
||||||
|
this.props.dispatch(clearUserRequestError());
|
||||||
|
this.props.dispatch(clearUserRequestSuccess());
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmitUserInfo = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const { selfUser, phoneNumber } = this.props;
|
||||||
|
const phoneNumberVal =
|
||||||
|
phoneNumber || (selfUser.userinfo || {}).phone_number;
|
||||||
|
|
||||||
|
this.props.dispatch(
|
||||||
|
updateUserInfoRequest({
|
||||||
|
...selfUser.userinfo,
|
||||||
|
username: selfUser.username,
|
||||||
|
phone_number: phoneNumberVal
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isSendingUserRequest,
|
||||||
|
userRequestError,
|
||||||
|
userRequestSuccess,
|
||||||
|
selfUser,
|
||||||
|
phoneNumber
|
||||||
|
} = this.props;
|
||||||
|
const phoneNumberVal =
|
||||||
|
phoneNumber || (selfUser.userinfo || {}).phone_number;
|
||||||
|
return (
|
||||||
|
<UserInfoFormView
|
||||||
|
isSendingUserRequest={isSendingUserRequest}
|
||||||
|
userRequestError={userRequestError}
|
||||||
|
userRequestSuccess={userRequestSuccess}
|
||||||
|
phoneNumber={phoneNumberVal}
|
||||||
|
changePhoneNumber={this.changePhoneNumber}
|
||||||
|
onSubmitUserInfo={this.onSubmitUserInfo}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { ...state.user };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(EditUserInfoForm);
|
54
src/components/User/InitializeClientForm.jsx
Normal file
54
src/components/User/InitializeClientForm.jsx
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
|
import {
|
||||||
|
clearUserRequestError,
|
||||||
|
clearUserRequestSuccess,
|
||||||
|
setFormBusinessNumber
|
||||||
|
} from "../../actions/user/reducer.actions";
|
||||||
|
import { createClientRequest } from "../../actions/user/saga.actions";
|
||||||
|
import ClientFormView from "./ClientFormView";
|
||||||
|
|
||||||
|
class InitializeClientForm extends Component {
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.dispatch(clearUserRequestError());
|
||||||
|
this.props.dispatch(clearUserRequestSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
changeBusinessNumber = event => {
|
||||||
|
this.props.dispatch(setFormBusinessNumber(event.target.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmitClient = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const { businessNumber } = this.props;
|
||||||
|
this.props.dispatch(
|
||||||
|
createClientRequest({ business_number: businessNumber })
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isSendingUserRequest,
|
||||||
|
userRequestError,
|
||||||
|
userRequestSuccess,
|
||||||
|
businessNumber
|
||||||
|
} = this.props;
|
||||||
|
return (
|
||||||
|
<ClientFormView
|
||||||
|
isSendingUserRequest={isSendingUserRequest}
|
||||||
|
userRequestError={userRequestError}
|
||||||
|
userRequestSuccess={userRequestSuccess}
|
||||||
|
businessNumber={businessNumber}
|
||||||
|
changeBusinessNumber={this.changeBusinessNumber}
|
||||||
|
onSubmitClient={this.onSubmitClient}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { ...state.user };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(InitializeClientForm);
|
54
src/components/User/InitializeProviderForm.jsx
Normal file
54
src/components/User/InitializeProviderForm.jsx
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
|
import {
|
||||||
|
clearUserRequestError,
|
||||||
|
clearUserRequestSuccess,
|
||||||
|
setFormSocialInsuranceNumber
|
||||||
|
} from "../../actions/user/reducer.actions";
|
||||||
|
import { createProviderRequest } from "../../actions/user/saga.actions";
|
||||||
|
import ProviderFormView from "./ProviderFormView";
|
||||||
|
|
||||||
|
class InitializeProviderForm extends Component {
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.dispatch(clearUserRequestError());
|
||||||
|
this.props.dispatch(clearUserRequestSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
changeSocialInsuranceNumber = event => {
|
||||||
|
this.props.dispatch(setFormSocialInsuranceNumber(event.target.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmitProvider = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const { socialInsuranceNumber } = this.props;
|
||||||
|
this.props.dispatch(
|
||||||
|
createProviderRequest({ sin: socialInsuranceNumber })
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isSendingUserRequest,
|
||||||
|
userRequestError,
|
||||||
|
userRequestSuccess,
|
||||||
|
socialInsuranceNumber
|
||||||
|
} = this.props;
|
||||||
|
return (
|
||||||
|
<ProviderFormView
|
||||||
|
isSendingUserRequest={isSendingUserRequest}
|
||||||
|
userRequestError={userRequestError}
|
||||||
|
userRequestSuccess={userRequestSuccess}
|
||||||
|
socialInsuranceNumber={socialInsuranceNumber}
|
||||||
|
changeSocialInsuranceNumber={this.changeSocialInsuranceNumber}
|
||||||
|
onSubmitProvider={this.onSubmitProvider}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { ...state.user };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(InitializeProviderForm);
|
76
src/components/User/InitializeUserInfoForm.jsx
Normal file
76
src/components/User/InitializeUserInfoForm.jsx
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
|
import {
|
||||||
|
clearUserRequestError,
|
||||||
|
clearUserRequestSuccess,
|
||||||
|
setFormPhoneNumber
|
||||||
|
} from "../../actions/user/reducer.actions";
|
||||||
|
import {
|
||||||
|
createUserInfoRequest,
|
||||||
|
updateUserInfoRequest
|
||||||
|
} from "../../actions/user/saga.actions";
|
||||||
|
|
||||||
|
import UserInfoFormView from "./UserInfoFormView";
|
||||||
|
|
||||||
|
class InitializeUserInfoForm extends Component {
|
||||||
|
componentWillMount() {
|
||||||
|
this.props.dispatch(clearUserRequestError());
|
||||||
|
this.props.dispatch(clearUserRequestSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
changePhoneNumber = event => {
|
||||||
|
this.props.dispatch(setFormPhoneNumber(event.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmitUserInfo = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const { selfUser, phoneNumber } = this.props;
|
||||||
|
const phoneNumberVal =
|
||||||
|
phoneNumber || (selfUser.userinfo || {}).phone_number;
|
||||||
|
|
||||||
|
if (selfUser.userinfo) {
|
||||||
|
this.props.dispatch(
|
||||||
|
updateUserInfoRequest({
|
||||||
|
...selfUser.userinfo,
|
||||||
|
username: selfUser.username,
|
||||||
|
phone_number: phoneNumberVal
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.props.dispatch(
|
||||||
|
createUserInfoRequest({
|
||||||
|
phone_number: phoneNumberVal
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
isSendingUserRequest,
|
||||||
|
userRequestError,
|
||||||
|
userRequestSuccess,
|
||||||
|
selfUser,
|
||||||
|
phoneNumber
|
||||||
|
} = this.props;
|
||||||
|
const phoneNumberVal =
|
||||||
|
phoneNumber || (selfUser.userinfo || {}).phone_number;
|
||||||
|
return (
|
||||||
|
<UserInfoFormView
|
||||||
|
isSendingUserRequest={isSendingUserRequest}
|
||||||
|
userRequestError={userRequestError}
|
||||||
|
userRequestSuccess={userRequestSuccess}
|
||||||
|
phoneNumber={phoneNumberVal}
|
||||||
|
changePhoneNumber={this.changePhoneNumber}
|
||||||
|
onSubmitUserInfo={this.onSubmitUserInfo}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { ...state.user };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(InitializeUserInfoForm);
|
73
src/components/User/Profile.jsx
Normal file
73
src/components/User/Profile.jsx
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { Card, Container, Header, Label, List } from "semantic-ui-react";
|
||||||
|
|
||||||
|
class Profile extends Component {
|
||||||
|
render() {
|
||||||
|
const { selfUser } = this.props;
|
||||||
|
return <ProfileView user={selfUser} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state) {
|
||||||
|
return { ...state.user };
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProfileView = ({ user }) => (
|
||||||
|
<Container>
|
||||||
|
<Header>Profile</Header>
|
||||||
|
<Card fluid={true}>
|
||||||
|
<Card.Content>
|
||||||
|
<Card.Header>{user.username || "No username!"}</Card.Header>
|
||||||
|
<Card.Meta>
|
||||||
|
{user.client && <Label color="blue" size="tiny">Client</Label>}
|
||||||
|
{user.provider && <Label color="orange" size="tiny">Provider</Label>}
|
||||||
|
<Label color={user.is_active ? "teal" : "red"} size="tiny">
|
||||||
|
{user.is_active ? "Active" : "Deactivated"}
|
||||||
|
</Label>
|
||||||
|
{" "}{user.email || "No email!"}
|
||||||
|
</Card.Meta>
|
||||||
|
<Card.Description>
|
||||||
|
<span>
|
||||||
|
{!user.client &&
|
||||||
|
!user.provider &&
|
||||||
|
"User Registration Not Completed"}
|
||||||
|
</span>
|
||||||
|
{user.first_name} {user.last_name}
|
||||||
|
<List>
|
||||||
|
{user.userinfo &&
|
||||||
|
Object.keys(user.userinfo).map(function(key) {
|
||||||
|
return (
|
||||||
|
<List.Item key={key}>
|
||||||
|
{key}: {user.userinfo[key]}
|
||||||
|
</List.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{user.client &&
|
||||||
|
Object.keys(user.client).map(function(key) {
|
||||||
|
return (
|
||||||
|
<List.Item key={key}>
|
||||||
|
{key}: {user.client[key]}
|
||||||
|
</List.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{user.provider &&
|
||||||
|
Object.keys(user.provider).map(function(key) {
|
||||||
|
return (
|
||||||
|
<List.Item key={key}>
|
||||||
|
{key}: {user.provider[key]}
|
||||||
|
</List.Item>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
</Card.Description>
|
||||||
|
</Card.Content>
|
||||||
|
<Card.Content extra>
|
||||||
|
<Link to="/user/profile/edit">Edit Profile</Link>
|
||||||
|
</Card.Content>
|
||||||
|
</Card>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(Profile);
|
41
src/components/User/ProviderFormView.jsx
Normal file
41
src/components/User/ProviderFormView.jsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Container, Form, Header, Message } from "semantic-ui-react";
|
||||||
|
|
||||||
|
import Error from "../Shared/Error";
|
||||||
|
|
||||||
|
const ProviderFormView = ({
|
||||||
|
isSendingUserRequest,
|
||||||
|
userRequestError,
|
||||||
|
userRequestSuccess,
|
||||||
|
socialInsuranceNumber,
|
||||||
|
changeSocialInsuranceNumber,
|
||||||
|
onSubmitProvider
|
||||||
|
}) => (
|
||||||
|
<Container>
|
||||||
|
<Header>Complete Provider Information</Header>
|
||||||
|
<Form
|
||||||
|
loading={isSendingUserRequest}
|
||||||
|
onSubmit={onSubmitProvider}
|
||||||
|
error={!!userRequestError}
|
||||||
|
success={!!userRequestSuccess}
|
||||||
|
>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Social Insurance Number</label>
|
||||||
|
<input
|
||||||
|
placeholder="98765"
|
||||||
|
type="text"
|
||||||
|
value={socialInsuranceNumber}
|
||||||
|
onChange={changeSocialInsuranceNumber}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Error header="Modify Provider failed!" error={userRequestError} />
|
||||||
|
<Message success>
|
||||||
|
<Message.Header>Modify Provider successful!</Message.Header>
|
||||||
|
<p>Set provider successfully.</p>
|
||||||
|
</Message>
|
||||||
|
<Form.Button>Submit Provider</Form.Button>
|
||||||
|
</Form>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ProviderFormView;
|
41
src/components/User/UserInfoFormView.jsx
Normal file
41
src/components/User/UserInfoFormView.jsx
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Container, Form, Header, Message } from "semantic-ui-react";
|
||||||
|
|
||||||
|
import Error from "../Shared/Error";
|
||||||
|
|
||||||
|
const UserInfoFormView = ({
|
||||||
|
isSendingUserRequest,
|
||||||
|
userRequestError,
|
||||||
|
userRequestSuccess,
|
||||||
|
phoneNumber,
|
||||||
|
changePhoneNumber,
|
||||||
|
onSubmitUserInfo
|
||||||
|
}) => (
|
||||||
|
<Container>
|
||||||
|
<Header>User Info</Header>
|
||||||
|
<Form
|
||||||
|
loading={isSendingUserRequest}
|
||||||
|
onSubmit={onSubmitUserInfo}
|
||||||
|
error={!!userRequestError}
|
||||||
|
success={!!userRequestSuccess}
|
||||||
|
>
|
||||||
|
<Form.Field>
|
||||||
|
<label>Phone Number</label>
|
||||||
|
<input
|
||||||
|
placeholder="7809999999"
|
||||||
|
type="tel"
|
||||||
|
value={phoneNumber}
|
||||||
|
onChange={changePhoneNumber}
|
||||||
|
/>
|
||||||
|
</Form.Field>
|
||||||
|
<Error header="Modify User Info failed!" error={userRequestError} />
|
||||||
|
<Message success>
|
||||||
|
<Message.Header>Modify User Info successful!</Message.Header>
|
||||||
|
<p>Set user info successfully.</p>
|
||||||
|
</Message>
|
||||||
|
<Form.Button>Submit User Info</Form.Button>
|
||||||
|
</Form>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default UserInfoFormView;
|
22
src/constants/auth.constants.js
Normal file
22
src/constants/auth.constants.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// Reducer Auth Action Constants
|
||||||
|
export const IS_SENDING_AUTH_REQUEST = "IS_SENDING_AUTH_REQUEST";
|
||||||
|
export const SET_AUTH_REQUEST_ERROR = "SET_AUTH_REQUEST_ERROR";
|
||||||
|
export const CLEAR_AUTH_REQUEST_ERROR = "CLEAR_AUTH_REQUEST_ERROR";
|
||||||
|
export const SET_AUTH_REQUEST_SUCCESS = "SET_AUTH_REQUEST_SUCCESS";
|
||||||
|
export const CLEAR_AUTH_REQUEST_SUCCESS = "CLEAR_AUTH_REQUEST_SUCCESS";
|
||||||
|
export const SET_SELF_USER_TOKEN = "SET_SELF_USER_TOKEN";
|
||||||
|
export const SET_FORM_EMAIL = "SET_FORM_EMAIL";
|
||||||
|
export const SET_FORM_PASSWORD = "SET_FORM_PASSWORD";
|
||||||
|
export const SET_FORM_PASSWORD_CONFIRMATION = "SET_FORM_PASSWORD_CONFIRMATION";
|
||||||
|
export const SET_FORM_EMAIL_VERIFICATION = "SET_FORM_EMAIL_VERIFICATION";
|
||||||
|
export const SET_FORM_OLD_PASSWORD = "SET_FORM_OLD_PASSWORD";
|
||||||
|
|
||||||
|
// Saga Auth Action Constants
|
||||||
|
export const SEND_REGISTER_REQUEST = "SEND_REGISTER_REQUEST";
|
||||||
|
export const SEND_EMAIL_VERIFICATION_REQUEST =
|
||||||
|
"SEND_EMAIL_VERIFICATION_REQUEST";
|
||||||
|
export const SEND_LOGIN_REQUEST = "SEND_LOGIN_REQUEST";
|
||||||
|
export const SEND_LOGOUT_REQUEST = "SEND_LOGOUT_REQUEST";
|
||||||
|
export const SEND_CHANGE_PASSWORD_REQUEST = "SEND_CHANGE_PASSWORD_REQUEST";
|
||||||
|
export const SEND_FORGOT_PASSWORD_REQUEST = "SEND_FORGOT_PASSWORD_REQUEST";
|
||||||
|
export const SEND_RESET_PASSWORD_REQUEST = "SEND_RESET_PASSWORD_REQUEST";
|
32
src/constants/user.constants.js
Normal file
32
src/constants/user.constants.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// Reducer User Action Constants
|
||||||
|
export const IS_SENDING_USER_REQUEST = "IS_SENDING_USER_REQUEST";
|
||||||
|
export const SET_USER_REQUEST_ERROR = "SET_USER_REQUEST_ERROR";
|
||||||
|
export const CLEAR_USER_REQUEST_ERROR = "CLEAR_USER_REQUEST_ERROR";
|
||||||
|
export const SET_USER_REQUEST_SUCCESS = "SET_USER_REQUEST_SUCCESS";
|
||||||
|
export const CLEAR_USER_REQUEST_SUCCESS = "CLEAR_USER_REQUEST_SUCCESS";
|
||||||
|
export const SET_SELF_USER = "SET_SELF_USER";
|
||||||
|
export const SET_COMPLETE_REGISTRATION_STEP = "SET_COMPLETE_REGISTRATION_STEP";
|
||||||
|
export const SET_COMPLETE_REGISTRATION_CLIENT_OR_PROVIDER =
|
||||||
|
"SET_COMPLETE_REGISTRATION_CLIENT_OR_PROVIDER";
|
||||||
|
export const SET_FORM_PHONE_NUMBER = "SET_FORM_PHONE_NUMBER";
|
||||||
|
export const SET_FORM_BUSINESS_NUMBER = "SET_FORM_BUSINESS_NUMBER";
|
||||||
|
export const SET_FORM_SOCIAL_INSURANCE_NUMBER =
|
||||||
|
"SET_FORM_SOCIAL_INSURANCE_NUMBER";
|
||||||
|
export const SET_EDIT_PROFILE_TAB_ACTIVE_INDEX =
|
||||||
|
"SET_EDIT_PROFILE_TAB_ACTIVE_INDEX";
|
||||||
|
|
||||||
|
// Saga User Action Constants
|
||||||
|
export const GET_SELF_USER_REQUEST = "GET_SELF_USER_REQUEST";
|
||||||
|
export const CREATE_USER_INFO_REQUEST = "CREATE_USER_INFO_REQUEST";
|
||||||
|
export const UPDATE_USER_INFO_REQUEST = "UPDATE_USER_INFO_REQUEST";
|
||||||
|
export const CREATE_CLIENT_REQUEST = "CREATE_CLIENT_REQUEST";
|
||||||
|
export const UPDATE_CLIENT_REQUEST = "UPDATE_CLIENT_REQUEST";
|
||||||
|
export const CREATE_PROVIDER_REQUEST = "CREATE_PROVIDER_REQUEST";
|
||||||
|
export const UPDATE_PROVIDER_REQUEST = "UPDATE_PROVIDER_REQUEST";
|
||||||
|
|
||||||
|
// Misc. User Constants (int so we can mark prior as completed)
|
||||||
|
export const USER_INFO_STEP = 1;
|
||||||
|
export const CLIENT_OR_PROVIDER_STEP = 2;
|
||||||
|
export const COMPLETE_INFORMATION_STEP = 3;
|
||||||
|
export const CLIENT = "CLIENT";
|
||||||
|
export const PROVIDER = "PROVIDER";
|
40
src/index.js
Normal file
40
src/index.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
import { Provider } from "react-redux";
|
||||||
|
|
||||||
|
import configureStore from "./store";
|
||||||
|
import App from "./components/App";
|
||||||
|
import { unregister } from "./registerServiceWorker";
|
||||||
|
|
||||||
|
const store = configureStore();
|
||||||
|
const supportsHistory = "pushState" in window.history;
|
||||||
|
|
||||||
|
const rootElement = document.getElementById("root");
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<BrowserRouter forceRefresh={!supportsHistory}>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
|
</Provider>,
|
||||||
|
rootElement
|
||||||
|
);
|
||||||
|
|
||||||
|
// hot module reloading
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept("./components/App", () => {
|
||||||
|
const NextApp = require("./components/App").default;
|
||||||
|
ReactDOM.render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<BrowserRouter forceRefresh={!supportsHistory}>
|
||||||
|
<NextApp />
|
||||||
|
</BrowserRouter>
|
||||||
|
</Provider>,
|
||||||
|
rootElement
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// no service worker functionality for now
|
||||||
|
unregister();
|
101
src/reducers/authReducer.js
Normal file
101
src/reducers/authReducer.js
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
import {
|
||||||
|
IS_SENDING_AUTH_REQUEST,
|
||||||
|
SET_AUTH_REQUEST_ERROR,
|
||||||
|
CLEAR_AUTH_REQUEST_ERROR,
|
||||||
|
SET_AUTH_REQUEST_SUCCESS,
|
||||||
|
CLEAR_AUTH_REQUEST_SUCCESS,
|
||||||
|
SET_SELF_USER_TOKEN,
|
||||||
|
SET_FORM_EMAIL,
|
||||||
|
SET_FORM_PASSWORD,
|
||||||
|
SET_FORM_PASSWORD_CONFIRMATION,
|
||||||
|
SET_FORM_EMAIL_VERIFICATION,
|
||||||
|
SET_FORM_OLD_PASSWORD
|
||||||
|
} from "../constants/auth.constants";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the user's auth token, and return the value
|
||||||
|
* @param {string} newToken
|
||||||
|
*/
|
||||||
|
function me(newToken) {
|
||||||
|
if (typeof newToken === "string") {
|
||||||
|
localStorage.setItem("userToken", newToken);
|
||||||
|
}
|
||||||
|
const userToken = localStorage.getItem("userToken");
|
||||||
|
return userToken ? userToken : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
isSendingAuthRequest: false,
|
||||||
|
authRequestError: "",
|
||||||
|
authRequestSuccess: "",
|
||||||
|
userToken: me(null),
|
||||||
|
email: "",
|
||||||
|
password: "",
|
||||||
|
passwordConfirmation: "",
|
||||||
|
emailVerificationString: "",
|
||||||
|
oldPassword: "" // used for change password functionality
|
||||||
|
};
|
||||||
|
|
||||||
|
function authReducer(state = initialState, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case IS_SENDING_AUTH_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isSendingAuthRequest: action.data
|
||||||
|
};
|
||||||
|
case SET_AUTH_REQUEST_ERROR:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
authRequestError: action.data
|
||||||
|
};
|
||||||
|
case CLEAR_AUTH_REQUEST_ERROR:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
authRequestError: ""
|
||||||
|
};
|
||||||
|
case SET_AUTH_REQUEST_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
authRequestSuccess: action.data
|
||||||
|
};
|
||||||
|
case CLEAR_AUTH_REQUEST_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
authRequestSuccess: ""
|
||||||
|
};
|
||||||
|
case SET_SELF_USER_TOKEN:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
userToken: me(action.data)
|
||||||
|
};
|
||||||
|
case SET_FORM_EMAIL:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
email: action.data
|
||||||
|
};
|
||||||
|
case SET_FORM_PASSWORD:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
password: action.data
|
||||||
|
};
|
||||||
|
case SET_FORM_PASSWORD_CONFIRMATION:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
passwordConfirmation: action.data
|
||||||
|
};
|
||||||
|
case SET_FORM_EMAIL_VERIFICATION:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
emailVerificationString: action.data
|
||||||
|
};
|
||||||
|
case SET_FORM_OLD_PASSWORD:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
oldPassword: action.data
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default authReducer;
|
10
src/reducers/index.js
Normal file
10
src/reducers/index.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { combineReducers } from "redux";
|
||||||
|
import authReducer from "./authReducer";
|
||||||
|
import userReducer from "./userReducer";
|
||||||
|
|
||||||
|
const reducer = combineReducers({
|
||||||
|
auth: authReducer,
|
||||||
|
user: userReducer
|
||||||
|
});
|
||||||
|
|
||||||
|
export default reducer;
|
103
src/reducers/userReducer.js
Normal file
103
src/reducers/userReducer.js
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
import {
|
||||||
|
IS_SENDING_USER_REQUEST,
|
||||||
|
SET_USER_REQUEST_ERROR,
|
||||||
|
CLEAR_USER_REQUEST_ERROR,
|
||||||
|
SET_USER_REQUEST_SUCCESS,
|
||||||
|
CLEAR_USER_REQUEST_SUCCESS,
|
||||||
|
SET_SELF_USER,
|
||||||
|
SET_COMPLETE_REGISTRATION_STEP,
|
||||||
|
SET_COMPLETE_REGISTRATION_CLIENT_OR_PROVIDER,
|
||||||
|
SET_FORM_PHONE_NUMBER,
|
||||||
|
SET_FORM_BUSINESS_NUMBER,
|
||||||
|
SET_FORM_SOCIAL_INSURANCE_NUMBER,
|
||||||
|
SET_EDIT_PROFILE_TAB_ACTIVE_INDEX,
|
||||||
|
USER_INFO_STEP,
|
||||||
|
CLIENT
|
||||||
|
} from "../constants/user.constants";
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
isSendingUserRequest: false,
|
||||||
|
userRequestError: "",
|
||||||
|
userRequestSuccess: "",
|
||||||
|
selfUser: {},
|
||||||
|
completeRegistrationCurrentStep: USER_INFO_STEP,
|
||||||
|
completeRegistrationMaxStep: USER_INFO_STEP,
|
||||||
|
completeRegistrationClientOrProvider: CLIENT,
|
||||||
|
phoneNumber: "",
|
||||||
|
businessNumber: "",
|
||||||
|
socialInsuranceNumber: "",
|
||||||
|
editProfileActiveIndex: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
function userReducer(state = initialState, action) {
|
||||||
|
switch (action.type) {
|
||||||
|
case IS_SENDING_USER_REQUEST:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
isSendingUserRequest: action.data
|
||||||
|
};
|
||||||
|
case SET_USER_REQUEST_ERROR:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
userRequestError: action.data
|
||||||
|
};
|
||||||
|
case CLEAR_USER_REQUEST_ERROR:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
userRequestError: ""
|
||||||
|
};
|
||||||
|
case SET_USER_REQUEST_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
userRequestSuccess: action.data
|
||||||
|
};
|
||||||
|
case CLEAR_USER_REQUEST_SUCCESS:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
userRequestSuccess: ""
|
||||||
|
};
|
||||||
|
case SET_SELF_USER:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
selfUser: action.data
|
||||||
|
};
|
||||||
|
case SET_COMPLETE_REGISTRATION_STEP:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
completeRegistrationCurrentStep: action.data,
|
||||||
|
completeRegistrationMaxStep: Math.max(
|
||||||
|
action.data,
|
||||||
|
state.completeRegistrationMaxStep
|
||||||
|
)
|
||||||
|
};
|
||||||
|
case SET_COMPLETE_REGISTRATION_CLIENT_OR_PROVIDER:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
completeRegistrationClientOrProvider: action.data
|
||||||
|
};
|
||||||
|
case SET_FORM_PHONE_NUMBER:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
phoneNumber: action.data
|
||||||
|
};
|
||||||
|
case SET_FORM_BUSINESS_NUMBER:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
businessNumber: action.data
|
||||||
|
};
|
||||||
|
case SET_FORM_SOCIAL_INSURANCE_NUMBER:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
socialInsuranceNumber: action.data
|
||||||
|
};
|
||||||
|
case SET_EDIT_PROFILE_TAB_ACTIVE_INDEX:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
editProfileActiveIndex: action.data
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default userReducer;
|
108
src/registerServiceWorker.js
Normal file
108
src/registerServiceWorker.js
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
// In production, we register a service worker to serve assets from local cache.
|
||||||
|
|
||||||
|
// This lets the app load faster on subsequent visits in production, and gives
|
||||||
|
// it offline capabilities. However, it also means that developers (and users)
|
||||||
|
// will only see deployed updates on the "N+1" visit to a page, since previously
|
||||||
|
// cached resources are updated in the background.
|
||||||
|
|
||||||
|
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
|
||||||
|
// This link also includes instructions on opting out of this behavior.
|
||||||
|
|
||||||
|
const isLocalhost = Boolean(
|
||||||
|
window.location.hostname === 'localhost' ||
|
||||||
|
// [::1] is the IPv6 localhost address.
|
||||||
|
window.location.hostname === '[::1]' ||
|
||||||
|
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||||
|
window.location.hostname.match(
|
||||||
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function register() {
|
||||||
|
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||||
|
// The URL constructor is available in all browsers that support SW.
|
||||||
|
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
|
||||||
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
|
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||||
|
// from what our page is served on. This might happen if a CDN is used to
|
||||||
|
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||||
|
|
||||||
|
if (!isLocalhost) {
|
||||||
|
// Is not local host. Just register service worker
|
||||||
|
registerValidSW(swUrl);
|
||||||
|
} else {
|
||||||
|
// This is running on localhost. Lets check if a service worker still exists or not.
|
||||||
|
checkValidServiceWorker(swUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerValidSW(swUrl) {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register(swUrl)
|
||||||
|
.then(registration => {
|
||||||
|
registration.onupdatefound = () => {
|
||||||
|
const installingWorker = registration.installing;
|
||||||
|
installingWorker.onstatechange = () => {
|
||||||
|
if (installingWorker.state === 'installed') {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
// At this point, the old content will have been purged and
|
||||||
|
// the fresh content will have been added to the cache.
|
||||||
|
// It's the perfect time to display a "New content is
|
||||||
|
// available; please refresh." message in your web app.
|
||||||
|
console.log('New content is available; please refresh.');
|
||||||
|
} else {
|
||||||
|
// At this point, everything has been precached.
|
||||||
|
// It's the perfect time to display a
|
||||||
|
// "Content is cached for offline use." message.
|
||||||
|
console.log('Content is cached for offline use.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error during service worker registration:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkValidServiceWorker(swUrl) {
|
||||||
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
|
fetch(swUrl)
|
||||||
|
.then(response => {
|
||||||
|
// Ensure service worker exists, and that we really are getting a JS file.
|
||||||
|
if (
|
||||||
|
response.status === 404 ||
|
||||||
|
response.headers.get('content-type').indexOf('javascript') === -1
|
||||||
|
) {
|
||||||
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister().then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Service worker found. Proceed as normal.
|
||||||
|
registerValidSW(swUrl);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log(
|
||||||
|
'No internet connection found. App is running in offline mode.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregister() {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
196
src/sagas/auth.sagas.js
Normal file
196
src/sagas/auth.sagas.js
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
import { effects } from "redux-saga";
|
||||||
|
import {
|
||||||
|
isSendingAuthRequest,
|
||||||
|
setAuthRequestError,
|
||||||
|
setAuthRequestSuccess,
|
||||||
|
clearAuthRequestError,
|
||||||
|
clearAuthRequestSuccess,
|
||||||
|
setSelfUserToken,
|
||||||
|
setFormEmail,
|
||||||
|
setFormPassword,
|
||||||
|
setFormPasswordConfirmation,
|
||||||
|
setFormOldPassword
|
||||||
|
} from "../actions/auth/reducer.actions";
|
||||||
|
import { setSelfUser } from "../actions/user/reducer.actions";
|
||||||
|
import {
|
||||||
|
registerUser,
|
||||||
|
verifyEmail,
|
||||||
|
loginUser,
|
||||||
|
logoutUser,
|
||||||
|
changePassword,
|
||||||
|
forgotPassword,
|
||||||
|
resetPassword
|
||||||
|
} from "../api/auth.api";
|
||||||
|
|
||||||
|
function* registerUserCall(postBody) {
|
||||||
|
yield effects.put(isSendingAuthRequest(true));
|
||||||
|
const { email, password1, password2 } = postBody;
|
||||||
|
try {
|
||||||
|
return yield effects.call(registerUser, email, password1, password2);
|
||||||
|
} catch (exception) {
|
||||||
|
yield effects.put(setAuthRequestError(exception));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
yield effects.put(isSendingAuthRequest(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* verifyEmailCall(postBody) {
|
||||||
|
yield effects.put(isSendingAuthRequest(true));
|
||||||
|
const { emailKey } = postBody;
|
||||||
|
try {
|
||||||
|
return yield effects.call(verifyEmail, emailKey);
|
||||||
|
} catch (exception) {
|
||||||
|
yield effects.put(setAuthRequestError(exception));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
yield effects.put(isSendingAuthRequest(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* loginUserCall(postBody) {
|
||||||
|
yield effects.put(isSendingAuthRequest(true));
|
||||||
|
const { email, password } = postBody;
|
||||||
|
try {
|
||||||
|
return yield effects.call(loginUser, email, password);
|
||||||
|
} catch (exception) {
|
||||||
|
yield effects.put(setAuthRequestError(exception));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
yield effects.put(isSendingAuthRequest(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* logoutUserCall() {
|
||||||
|
yield effects.call(logoutUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
function* changePasswordCall(postBody) {
|
||||||
|
yield effects.put(isSendingAuthRequest(true));
|
||||||
|
const { new_password1, new_password2, old_password } = postBody;
|
||||||
|
try {
|
||||||
|
return yield effects.call(
|
||||||
|
changePassword,
|
||||||
|
new_password1,
|
||||||
|
new_password2,
|
||||||
|
old_password
|
||||||
|
);
|
||||||
|
} catch (exception) {
|
||||||
|
yield effects.put(setAuthRequestError(exception));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
yield effects.put(isSendingAuthRequest(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* forgotPasswordCall(postBody) {
|
||||||
|
yield effects.put(isSendingAuthRequest(true));
|
||||||
|
const { email } = postBody;
|
||||||
|
try {
|
||||||
|
return yield effects.call(forgotPassword, email);
|
||||||
|
} catch (exception) {
|
||||||
|
yield effects.put(setAuthRequestError(exception));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
yield effects.put(isSendingAuthRequest(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* resetPasswordCall(postBody) {
|
||||||
|
yield effects.put(isSendingAuthRequest(true));
|
||||||
|
const { uid, token, new_password1, new_password2 } = postBody;
|
||||||
|
try {
|
||||||
|
return yield effects.call(
|
||||||
|
resetPassword,
|
||||||
|
uid,
|
||||||
|
token,
|
||||||
|
new_password1,
|
||||||
|
new_password2
|
||||||
|
);
|
||||||
|
} catch (exception) {
|
||||||
|
yield effects.put(setAuthRequestError(exception));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
yield effects.put(isSendingAuthRequest(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* registerUserFlow(request) {
|
||||||
|
yield effects.put(clearAuthRequestSuccess());
|
||||||
|
yield effects.put(clearAuthRequestError());
|
||||||
|
const wasSuccessful = yield effects.call(registerUserCall, request.data);
|
||||||
|
if (wasSuccessful) {
|
||||||
|
yield effects.put(setAuthRequestSuccess(wasSuccessful));
|
||||||
|
yield effects.put(clearAuthRequestError());
|
||||||
|
yield effects.put(setFormEmail(""));
|
||||||
|
yield effects.put(setFormPassword(""));
|
||||||
|
yield effects.put(setFormPasswordConfirmation(""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* verifyEmailFlow(request) {
|
||||||
|
yield effects.put(clearAuthRequestSuccess());
|
||||||
|
yield effects.put(clearAuthRequestError());
|
||||||
|
const wasSuccessful = yield effects.call(verifyEmailCall, request.data);
|
||||||
|
if (wasSuccessful) {
|
||||||
|
yield effects.put(setAuthRequestSuccess(wasSuccessful));
|
||||||
|
yield effects.put(clearAuthRequestError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* loginUserFlow(request) {
|
||||||
|
yield effects.put(clearAuthRequestSuccess());
|
||||||
|
yield effects.put(clearAuthRequestError());
|
||||||
|
const wasSuccessful = yield effects.call(loginUserCall, request.data);
|
||||||
|
if (wasSuccessful) {
|
||||||
|
yield effects.put(setSelfUserToken(wasSuccessful.key));
|
||||||
|
yield effects.put(setAuthRequestSuccess(wasSuccessful));
|
||||||
|
yield effects.put(clearAuthRequestError());
|
||||||
|
yield effects.put(setFormEmail(""));
|
||||||
|
yield effects.put(setFormPassword(""));
|
||||||
|
yield effects.put(setFormPasswordConfirmation(""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* logoutUserFlow(request) {
|
||||||
|
yield effects.put(clearAuthRequestSuccess());
|
||||||
|
yield effects.put(clearAuthRequestError());
|
||||||
|
yield effects.call(logoutUserCall);
|
||||||
|
yield effects.put(setSelfUserToken(""));
|
||||||
|
yield effects.put(setSelfUser({}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* changePasswordFlow(request) {
|
||||||
|
yield effects.put(clearAuthRequestSuccess());
|
||||||
|
yield effects.put(clearAuthRequestError());
|
||||||
|
const wasSuccessful = yield effects.call(changePasswordCall, request.data);
|
||||||
|
if (wasSuccessful) {
|
||||||
|
yield effects.put(setAuthRequestSuccess(wasSuccessful));
|
||||||
|
yield effects.put(clearAuthRequestError());
|
||||||
|
yield effects.put(setFormOldPassword(""));
|
||||||
|
yield effects.put(setFormPassword(""));
|
||||||
|
yield effects.put(setFormPasswordConfirmation(""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* forgotPasswordFlow(request) {
|
||||||
|
yield effects.put(clearAuthRequestSuccess());
|
||||||
|
yield effects.put(clearAuthRequestError());
|
||||||
|
const wasSuccessful = yield effects.call(forgotPasswordCall, request.data);
|
||||||
|
if (wasSuccessful) {
|
||||||
|
yield effects.put(setAuthRequestSuccess(wasSuccessful));
|
||||||
|
yield effects.put(clearAuthRequestError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* resetPasswordFlow(request) {
|
||||||
|
yield effects.put(clearAuthRequestSuccess());
|
||||||
|
yield effects.put(clearAuthRequestError());
|
||||||
|
const wasSuccessful = yield effects.call(resetPasswordCall, request.data);
|
||||||
|
if (wasSuccessful) {
|
||||||
|
yield effects.put(setAuthRequestSuccess(wasSuccessful));
|
||||||
|
yield effects.put(clearAuthRequestError());
|
||||||
|
yield effects.put(setFormPassword(""));
|
||||||
|
yield effects.put(setFormPasswordConfirmation(""));
|
||||||
|
}
|
||||||
|
}
|
54
src/sagas/index.js
Normal file
54
src/sagas/index.js
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import { takeLatest } from "redux-saga/effects";
|
||||||
|
import {
|
||||||
|
SEND_REGISTER_REQUEST,
|
||||||
|
SEND_EMAIL_VERIFICATION_REQUEST,
|
||||||
|
SEND_LOGIN_REQUEST,
|
||||||
|
SEND_LOGOUT_REQUEST,
|
||||||
|
SEND_CHANGE_PASSWORD_REQUEST,
|
||||||
|
SEND_FORGOT_PASSWORD_REQUEST,
|
||||||
|
SEND_RESET_PASSWORD_REQUEST
|
||||||
|
} from "../constants/auth.constants";
|
||||||
|
import {
|
||||||
|
registerUserFlow,
|
||||||
|
verifyEmailFlow,
|
||||||
|
loginUserFlow,
|
||||||
|
logoutUserFlow,
|
||||||
|
changePasswordFlow,
|
||||||
|
forgotPasswordFlow,
|
||||||
|
resetPasswordFlow,
|
||||||
|
} from "./auth.sagas";
|
||||||
|
import {
|
||||||
|
GET_SELF_USER_REQUEST,
|
||||||
|
CREATE_USER_INFO_REQUEST,
|
||||||
|
UPDATE_USER_INFO_REQUEST,
|
||||||
|
CREATE_CLIENT_REQUEST,
|
||||||
|
UPDATE_CLIENT_REQUEST,
|
||||||
|
CREATE_PROVIDER_REQUEST,
|
||||||
|
UPDATE_PROVIDER_REQUEST
|
||||||
|
} from "../constants/user.constants";
|
||||||
|
import {
|
||||||
|
getSelfUserFlow,
|
||||||
|
createUserInfoFlow,
|
||||||
|
updateUserInfoFlow,
|
||||||
|
createClientFlow,
|
||||||
|
updateClientFlow,
|
||||||
|
createProviderFlow,
|
||||||
|
updateProviderFlow
|
||||||
|
} from "./user.sagas";
|
||||||
|
|
||||||
|
export default function* rootSaga() {
|
||||||
|
yield takeLatest(SEND_REGISTER_REQUEST, registerUserFlow);
|
||||||
|
yield takeLatest(SEND_EMAIL_VERIFICATION_REQUEST, verifyEmailFlow);
|
||||||
|
yield takeLatest(SEND_LOGIN_REQUEST, loginUserFlow);
|
||||||
|
yield takeLatest(SEND_LOGOUT_REQUEST, logoutUserFlow);
|
||||||
|
yield takeLatest(SEND_CHANGE_PASSWORD_REQUEST, changePasswordFlow);
|
||||||
|
yield takeLatest(SEND_FORGOT_PASSWORD_REQUEST, forgotPasswordFlow);
|
||||||
|
yield takeLatest(SEND_RESET_PASSWORD_REQUEST, resetPasswordFlow);
|
||||||
|
yield takeLatest(GET_SELF_USER_REQUEST, getSelfUserFlow);
|
||||||
|
yield takeLatest(CREATE_USER_INFO_REQUEST, createUserInfoFlow);
|
||||||
|
yield takeLatest(UPDATE_USER_INFO_REQUEST, updateUserInfoFlow);
|
||||||
|
yield takeLatest(CREATE_CLIENT_REQUEST, createClientFlow);
|
||||||
|
yield takeLatest(UPDATE_CLIENT_REQUEST, updateClientFlow);
|
||||||
|
yield takeLatest(CREATE_PROVIDER_REQUEST, createProviderFlow);
|
||||||
|
yield takeLatest(UPDATE_PROVIDER_REQUEST, updateProviderFlow);
|
||||||
|
}
|
186
src/sagas/user.sagas.js
Normal file
186
src/sagas/user.sagas.js
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
import { effects } from "redux-saga";
|
||||||
|
import { setSelfUserToken } from "../actions/auth/reducer.actions";
|
||||||
|
import {
|
||||||
|
isSendingUserRequest,
|
||||||
|
setUserRequestError,
|
||||||
|
setUserRequestSuccess,
|
||||||
|
clearUserRequestError,
|
||||||
|
clearUserRequestSuccess,
|
||||||
|
setSelfUser,
|
||||||
|
setCompleteRegistrationStep
|
||||||
|
} from "../actions/user/reducer.actions";
|
||||||
|
import { getSelfUserRequest } from "../actions/user/saga.actions";
|
||||||
|
import { CLIENT_OR_PROVIDER_STEP } from "../constants/user.constants";
|
||||||
|
import { getSelfUser, createUserInfo, updateUserInfo, createClient, updateClient, createProvider, updateProvider } from "../api/user.api";
|
||||||
|
|
||||||
|
function* getSelfUserCall() {
|
||||||
|
yield effects.put(isSendingUserRequest(true));
|
||||||
|
try {
|
||||||
|
const wasSuccessful = yield effects.call(getSelfUser);
|
||||||
|
yield effects.put(clearUserRequestError());
|
||||||
|
// Check if the user exists, if yes set the user, otherwise force logout
|
||||||
|
if (wasSuccessful.results && wasSuccessful.results.length) {
|
||||||
|
yield effects.put(setSelfUser(wasSuccessful.results[0]));
|
||||||
|
} else {
|
||||||
|
yield effects.put(setSelfUserToken(""));
|
||||||
|
yield effects.put(setSelfUser({}));
|
||||||
|
}
|
||||||
|
return wasSuccessful;
|
||||||
|
} catch (exception) {
|
||||||
|
yield effects.put(setUserRequestError(exception));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
yield effects.put(isSendingUserRequest(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* createUserInfoCall(postBody) {
|
||||||
|
yield effects.put(isSendingUserRequest(true));
|
||||||
|
const { phone_number } = postBody;
|
||||||
|
try {
|
||||||
|
return yield effects.call(createUserInfo, phone_number);
|
||||||
|
} catch (exception) {
|
||||||
|
yield effects.put(setUserRequestError(exception));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
yield effects.put(isSendingUserRequest(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* updateUserInfoCall(payload) {
|
||||||
|
yield effects.put(isSendingUserRequest(true));
|
||||||
|
const { username, phone_number } = payload;
|
||||||
|
try {
|
||||||
|
return yield effects.call(updateUserInfo, username, phone_number);
|
||||||
|
} catch (exception) {
|
||||||
|
yield effects.put(setUserRequestError(exception));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
yield effects.put(isSendingUserRequest(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* createClientCall(postBody) {
|
||||||
|
yield effects.put(isSendingUserRequest(true));
|
||||||
|
const { business_number } = postBody;
|
||||||
|
try {
|
||||||
|
return yield effects.call(createClient, business_number);
|
||||||
|
} catch (exception) {
|
||||||
|
yield effects.put(setUserRequestError(exception));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
yield effects.put(isSendingUserRequest(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* updateClientCall(payload) {
|
||||||
|
yield effects.put(isSendingUserRequest(true));
|
||||||
|
const { username, business_number } = payload;
|
||||||
|
try {
|
||||||
|
return yield effects.call(updateClient, username, business_number);
|
||||||
|
} catch (exception) {
|
||||||
|
yield effects.put(setUserRequestError(exception));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
yield effects.put(isSendingUserRequest(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* createProviderCall(postBody) {
|
||||||
|
yield effects.put(isSendingUserRequest(true));
|
||||||
|
const { sin } = postBody;
|
||||||
|
try {
|
||||||
|
return yield effects.call(createProvider, sin);
|
||||||
|
} catch (exception) {
|
||||||
|
yield effects.put(setUserRequestError(exception));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
yield effects.put(isSendingUserRequest(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function* updateProviderCall(payload) {
|
||||||
|
yield effects.put(isSendingUserRequest(true));
|
||||||
|
const { username, sin } = payload;
|
||||||
|
try {
|
||||||
|
return yield effects.call(updateProvider, username, sin);
|
||||||
|
} catch (exception) {
|
||||||
|
yield effects.put(setUserRequestError(exception));
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
yield effects.put(isSendingUserRequest(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* getSelfUserFlow(request) {
|
||||||
|
const wasSuccessful = yield effects.call(getSelfUserCall);
|
||||||
|
if (!wasSuccessful) {
|
||||||
|
yield effects.put(setSelfUserToken(""));
|
||||||
|
yield effects.put(setSelfUser({}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* createUserInfoFlow(request) {
|
||||||
|
yield effects.put(clearUserRequestSuccess());
|
||||||
|
yield effects.put(clearUserRequestError());
|
||||||
|
const wasSuccessful = yield effects.call(createUserInfoCall, request.data);
|
||||||
|
if (wasSuccessful) {
|
||||||
|
yield effects.put(clearUserRequestError());
|
||||||
|
yield effects.put(setCompleteRegistrationStep(CLIENT_OR_PROVIDER_STEP));
|
||||||
|
yield effects.put(getSelfUserRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* updateUserInfoFlow(request) {
|
||||||
|
yield effects.put(clearUserRequestSuccess());
|
||||||
|
yield effects.put(clearUserRequestError());
|
||||||
|
const wasSuccessful = yield effects.call(updateUserInfoCall, request.data);
|
||||||
|
if (wasSuccessful) {
|
||||||
|
yield effects.put(setUserRequestSuccess(wasSuccessful));
|
||||||
|
yield effects.put(clearUserRequestError());
|
||||||
|
yield effects.put(setCompleteRegistrationStep(CLIENT_OR_PROVIDER_STEP));
|
||||||
|
yield effects.put(getSelfUserRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* createClientFlow(request) {
|
||||||
|
yield effects.put(clearUserRequestSuccess());
|
||||||
|
yield effects.put(clearUserRequestError());
|
||||||
|
const wasSuccessful = yield effects.call(createClientCall, request.data);
|
||||||
|
if (wasSuccessful) {
|
||||||
|
yield effects.put(clearUserRequestError());
|
||||||
|
yield effects.put(getSelfUserRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* updateClientFlow(request) {
|
||||||
|
yield effects.put(clearUserRequestSuccess());
|
||||||
|
yield effects.put(clearUserRequestError());
|
||||||
|
const wasSuccessful = yield effects.call(updateClientCall, request.data);
|
||||||
|
if (wasSuccessful) {
|
||||||
|
yield effects.put(setUserRequestSuccess(wasSuccessful));
|
||||||
|
yield effects.put(clearUserRequestError());
|
||||||
|
yield effects.put(getSelfUserRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* createProviderFlow(request) {
|
||||||
|
yield effects.put(clearUserRequestSuccess());
|
||||||
|
yield effects.put(clearUserRequestError());
|
||||||
|
const wasSuccessful = yield effects.call(createProviderCall, request.data);
|
||||||
|
if (wasSuccessful) {
|
||||||
|
yield effects.put(clearUserRequestError());
|
||||||
|
yield effects.put(getSelfUserRequest());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* updateProviderFlow(request) {
|
||||||
|
yield effects.put(clearUserRequestSuccess());
|
||||||
|
yield effects.put(clearUserRequestError());
|
||||||
|
const wasSuccessful = yield effects.call(updateProviderCall, request.data);
|
||||||
|
if (wasSuccessful) {
|
||||||
|
yield effects.put(setUserRequestSuccess(wasSuccessful));
|
||||||
|
yield effects.put(clearUserRequestError());
|
||||||
|
yield effects.put(getSelfUserRequest());
|
||||||
|
}
|
||||||
|
}
|
22
src/store/index.js
Normal file
22
src/store/index.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { createStore, applyMiddleware, compose } from "redux";
|
||||||
|
import createSagaMiddleware from "redux-saga";
|
||||||
|
import { createLogger } from "redux-logger";
|
||||||
|
import rootReducer from "../reducers";
|
||||||
|
import rootSaga from "../sagas";
|
||||||
|
|
||||||
|
export default function configureStore(initialState = {}) {
|
||||||
|
const sagaMiddleware = createSagaMiddleware();
|
||||||
|
const middlewares = [sagaMiddleware];
|
||||||
|
if (process.env.NODE_ENV === "development" && process.env.REACT_APP_REDUX_LOGGING) {
|
||||||
|
middlewares.push(createLogger());
|
||||||
|
}
|
||||||
|
|
||||||
|
const enhancers = [applyMiddleware(...middlewares)];
|
||||||
|
const store = createStore(rootReducer, initialState, compose(...enhancers));
|
||||||
|
|
||||||
|
// Extensions
|
||||||
|
store.asyncReducers = {}; // Async reducer registry
|
||||||
|
sagaMiddleware.run(rootSaga);
|
||||||
|
|
||||||
|
return store;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user