Merge branch 'client-init' of tanner/caremyway-client into master

This commit is contained in:
tanner 2017-09-19 03:05:26 +00:00 committed by Gogs
commit 48cc050c47
55 changed files with 10059 additions and 30 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
REACT_APP_API_ENDPOINT="http://localhost:8000"

49
.gitignore vendored
View File

@ -1,30 +1,21 @@
# ---> Node # See https://help.github.com/ignore-files/ for more about ignoring files.
# Logs
logs # dependencies
*.log /node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log* npm-debug.log*
yarn-debug.log*
# Runtime data yarn-error.log*
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
node_modules

View File

@ -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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

40
public/index.html Normal file
View 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
View 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"
}

View 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
};
}

View 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
};
}

View 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
}

View 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
};
}

View 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
View 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
View 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
View 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
View 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;

View 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);
});

View 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);

View 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);

View 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);

View 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);

View 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);

View 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
View 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);

View 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;

View 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);

View 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;

View 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;

View 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;

View 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;

View 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;

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View 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;

View 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;

View 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";

View 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
View 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
View 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
View 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
View 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;

View 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
View 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
View 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
View 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
View 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;
}

6657
yarn.lock Normal file

File diff suppressed because it is too large Load Diff