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
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
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
|
||||
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
|
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