summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/actions/profile.js14
-rw-r--r--src/actions/users.js18
-rw-r--r--src/api/sagas/index.js4
-rw-r--r--src/api/sagas/profile.js12
-rw-r--r--src/api/sagas/users.js2
-rw-r--r--src/auth/index.js2
-rw-r--r--src/components/modals/ConfirmationModal.js35
-rw-r--r--src/components/modals/Modal.js14
-rw-r--r--src/containers/profile/DeleteProfileModal.js36
-rw-r--r--src/containers/simulations/NewSimulationModal.js2
-rw-r--r--src/pages/Profile.js37
-rw-r--r--src/pages/Profile.sass2
-rw-r--r--src/reducers/index.js5
-rw-r--r--src/reducers/modals.js30
-rw-r--r--src/reducers/simulations.js13
-rw-r--r--src/routes/index.js12
16 files changed, 211 insertions, 27 deletions
diff --git a/src/actions/profile.js b/src/actions/profile.js
new file mode 100644
index 00000000..421e7602
--- /dev/null
+++ b/src/actions/profile.js
@@ -0,0 +1,14 @@
+export const OPEN_DELETE_PROFILE_MODAL = "OPEN_DELETE_PROFILE_MODAL";
+export const CLOSE_DELETE_PROFILE_MODAL = "CLOSE_DELETE_PROFILE_MODAL";
+
+export function openDeleteProfileModal() {
+ return {
+ type: OPEN_DELETE_PROFILE_MODAL
+ };
+}
+
+export function closeDeleteProfileModal() {
+ return {
+ type: CLOSE_DELETE_PROFILE_MODAL
+ };
+}
diff --git a/src/actions/users.js b/src/actions/users.js
index 093adddd..04a5b95a 100644
--- a/src/actions/users.js
+++ b/src/actions/users.js
@@ -1,5 +1,7 @@
export const FETCH_AUTHORIZATIONS_OF_CURRENT_USER = "FETCH_AUTHORIZATIONS_OF_CURRENT_USER";
export const FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED = "FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED";
+export const DELETE_CURRENT_USER = "DELETE_CURRENT_USER";
+export const DELETE_CURRENT_USER_SUCCEEDED = "DELETE_CURRENT_USER_SUCCEEDED";
export function fetchAuthorizationsOfCurrentUser() {
return (dispatch, getState) => {
@@ -17,3 +19,19 @@ export function fetchAuthorizationsOfCurrentUserSucceeded(authorizationsOfCurren
authorizationsOfCurrentUser
};
}
+
+export function deleteCurrentUser() {
+ return (dispatch, getState) => {
+ const {auth} = getState();
+ dispatch({
+ type: DELETE_CURRENT_USER,
+ userId: auth.userId
+ });
+ };
+}
+
+export function deleteCurrentUserSucceeded() {
+ return {
+ type: DELETE_CURRENT_USER_SUCCEEDED
+ };
+}
diff --git a/src/api/sagas/index.js b/src/api/sagas/index.js
index 426b344a..7fe57453 100644
--- a/src/api/sagas/index.js
+++ b/src/api/sagas/index.js
@@ -1,7 +1,8 @@
import {takeEvery} from "redux-saga/effects";
import {LOG_IN} from "../../actions/auth";
import {ADD_SIMULATION, DELETE_SIMULATION} from "../../actions/simulations";
-import {FETCH_AUTHORIZATIONS_OF_CURRENT_USER} from "../../actions/users";
+import {DELETE_CURRENT_USER, FETCH_AUTHORIZATIONS_OF_CURRENT_USER} from "../../actions/users";
+import {onDeleteCurrentUser} from "./profile";
import {onSimulationAdd, onSimulationDelete} from "./simulations";
import {onFetchAuthorizationsOfCurrentUser, onFetchLoggedInUser} from "./users";
@@ -10,4 +11,5 @@ export default function* rootSaga() {
yield takeEvery(FETCH_AUTHORIZATIONS_OF_CURRENT_USER, onFetchAuthorizationsOfCurrentUser);
yield takeEvery(ADD_SIMULATION, onSimulationAdd);
yield takeEvery(DELETE_SIMULATION, onSimulationDelete);
+ yield takeEvery(DELETE_CURRENT_USER, onDeleteCurrentUser);
}
diff --git a/src/api/sagas/profile.js b/src/api/sagas/profile.js
new file mode 100644
index 00000000..3c4e1825
--- /dev/null
+++ b/src/api/sagas/profile.js
@@ -0,0 +1,12 @@
+import {call, put} from "redux-saga/effects";
+import {deleteCurrentUserSucceeded} from "../../actions/users";
+import {deleteUser} from "../routes/users";
+
+export function* onDeleteCurrentUser(action) {
+ try {
+ yield call(deleteUser, action.userId);
+ yield put(deleteCurrentUserSucceeded());
+ } catch (error) {
+ console.log(error);
+ }
+}
diff --git a/src/api/sagas/users.js b/src/api/sagas/users.js
index d3ef32a6..c1daab30 100644
--- a/src/api/sagas/users.js
+++ b/src/api/sagas/users.js
@@ -2,6 +2,7 @@ import {call, put} from "redux-saga/effects";
import {logInSucceeded} from "../../actions/auth";
import {addToAuthorizationStore} from "../../actions/objects";
import {fetchAuthorizationsOfCurrentUserSucceeded} from "../../actions/users";
+import {saveAuthLocalStorage} from "../../auth/index";
import {performTokenSignIn} from "../routes/auth";
import {addUser, getAuthorizationsByUser} from "../routes/users";
import {fetchAndStoreSimulation, fetchAndStoreUser} from "./objects";
@@ -12,6 +13,7 @@ export function* onFetchLoggedInUser(action) {
let userId = tokenResponse.userId;
if (tokenResponse.isNewUser) {
+ saveAuthLocalStorage({authToken: action.payload.authToken});
const newUser = yield call(addUser, action.payload);
userId = newUser.id;
}
diff --git a/src/auth/index.js b/src/auth/index.js
index 8950c529..fb2a7038 100644
--- a/src/auth/index.js
+++ b/src/auth/index.js
@@ -1,4 +1,5 @@
import {LOG_IN_SUCCEEDED, LOG_OUT} from "../actions/auth";
+import {DELETE_CURRENT_USER_SUCCEEDED} from "../actions/users";
const getAuthObject = () => {
const authItem = localStorage.getItem("auth");
@@ -43,6 +44,7 @@ export const authRedirectMiddleware = store => next => action => {
window.location.href = "/simulations";
break;
case LOG_OUT:
+ case DELETE_CURRENT_USER_SUCCEEDED:
clearAuthLocalStorage();
window.location.href = "/";
break;
diff --git a/src/components/modals/ConfirmationModal.js b/src/components/modals/ConfirmationModal.js
new file mode 100644
index 00000000..4543cfd4
--- /dev/null
+++ b/src/components/modals/ConfirmationModal.js
@@ -0,0 +1,35 @@
+import PropTypes from "prop-types";
+import React from "react";
+import Modal from "./Modal";
+
+class ConfirmationModal extends React.Component {
+ static propTypes = {
+ title: PropTypes.string.isRequired,
+ message: PropTypes.string.isRequired,
+ show: PropTypes.bool.isRequired,
+ callback: PropTypes.func.isRequired,
+ };
+
+ onConfirm() {
+ this.props.callback(true);
+ }
+
+ onCancel() {
+ this.props.callback(false);
+ }
+
+ render() {
+ return (
+ <Modal title={this.props.title}
+ show={this.props.show}
+ onSubmit={this.onConfirm.bind(this)}
+ onCancel={this.onCancel.bind(this)}
+ submitButtonType="danger"
+ submitButtonText="Confirm">
+ {this.props.message}
+ </Modal>
+ );
+ }
+}
+
+export default ConfirmationModal;
diff --git a/src/components/modals/Modal.js b/src/components/modals/Modal.js
index 0b3301af..06273a46 100644
--- a/src/components/modals/Modal.js
+++ b/src/components/modals/Modal.js
@@ -1,3 +1,4 @@
+import classNames from "classnames";
import PropTypes from "prop-types";
import React from "react";
@@ -7,6 +8,12 @@ class Modal extends React.Component {
show: PropTypes.bool.isRequired,
onSubmit: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
+ submitButtonType: PropTypes.string,
+ submitButtonText: PropTypes.string,
+ };
+ static defaultProps = {
+ submitButtonType: "primary",
+ submitButtonText: "Save",
};
static idCounter = 0;
@@ -83,7 +90,7 @@ class Modal extends React.Component {
<h5 className="modal-title">{this.props.title}</h5>
<button type="button" className="close" onClick={this.onCancel.bind(this)}
aria-label="Close">
- <span aria-hidden="true">&times;</span>
+ <span>&times;</span>
</button>
</div>
<div className="modal-body">
@@ -93,8 +100,9 @@ class Modal extends React.Component {
<button type="button" className="btn btn-secondary" onClick={this.onCancel.bind(this)}>
Close
</button>
- <button type="button" className="btn btn-primary" onClick={this.onSubmit.bind(this)}>
- Save
+ <button type="button" className={classNames("btn", "btn-" + this.props.submitButtonType)}
+ onClick={this.onSubmit.bind(this)}>
+ {this.props.submitButtonText}
</button>
</div>
</div>
diff --git a/src/containers/profile/DeleteProfileModal.js b/src/containers/profile/DeleteProfileModal.js
new file mode 100644
index 00000000..b59db055
--- /dev/null
+++ b/src/containers/profile/DeleteProfileModal.js
@@ -0,0 +1,36 @@
+import React from "react";
+import {connect} from "react-redux";
+import {closeDeleteProfileModal} from "../../actions/profile";
+import {deleteCurrentUser} from "../../actions/users";
+import ConfirmationModal from "../../components/modals/ConfirmationModal";
+
+const NewSimulationModalComponent = ({visible, callback}) => (
+ <ConfirmationModal title="Delete my account"
+ message="Are you sure you want do delete your OpenDC account?"
+ show={visible}
+ callback={callback}/>
+);
+
+const mapStateToProps = state => {
+ return {
+ visible: state.modals.deleteProfileModalVisible
+ };
+};
+
+const mapDispatchToProps = dispatch => {
+ return {
+ callback: (isConfirmed) => {
+ if (isConfirmed) {
+ dispatch(deleteCurrentUser());
+ }
+ dispatch(closeDeleteProfileModal());
+ }
+ };
+};
+
+const NewSimulationModal = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(NewSimulationModalComponent);
+
+export default NewSimulationModal;
diff --git a/src/containers/simulations/NewSimulationModal.js b/src/containers/simulations/NewSimulationModal.js
index a4a3d2a8..cb858fed 100644
--- a/src/containers/simulations/NewSimulationModal.js
+++ b/src/containers/simulations/NewSimulationModal.js
@@ -11,7 +11,7 @@ const NewSimulationModalComponent = ({visible, callback}) => (
const mapStateToProps = state => {
return {
- visible: state.newSimulationModalVisible
+ visible: state.modals.newSimulationModalVisible
};
};
diff --git a/src/pages/Profile.js b/src/pages/Profile.js
new file mode 100644
index 00000000..3c3b0899
--- /dev/null
+++ b/src/pages/Profile.js
@@ -0,0 +1,37 @@
+import React from 'react';
+import {connect} from "react-redux";
+import {openDeleteProfileModal} from "../actions/profile";
+import Navbar from "../components/navigation/Navbar";
+import Login from "../containers/auth/Login";
+import DeleteProfileModal from "../containers/profile/DeleteProfileModal";
+import "./Profile.css";
+
+const ProfileContainer = ({onDelete}) => (
+ <div className="full-height">
+ <Navbar/>
+ <div className="container profile-page-container full-height">
+ <h2>Profile Settings</h2>
+ <button className="btn btn-danger" onClick={onDelete}>Delete my account on OpenDC</button>
+ <p>
+ This does not delete your Google account, it simply disconnects it from the OpenDC app and deletes any
+ simulation info that is associated with you (simulations you own, and any authorizations you may
+ have on other projects).
+ </p>
+ </div>
+ <DeleteProfileModal/>
+ <Login visible={false}/>
+ </div>
+);
+
+const mapDispatchToProps = dispatch => {
+ return {
+ onDelete: () => dispatch(openDeleteProfileModal()),
+ };
+};
+
+const Profile = connect(
+ undefined,
+ mapDispatchToProps
+)(ProfileContainer);
+
+export default Profile;
diff --git a/src/pages/Profile.sass b/src/pages/Profile.sass
new file mode 100644
index 00000000..9bb0a7e4
--- /dev/null
+++ b/src/pages/Profile.sass
@@ -0,0 +1,2 @@
+.profile-page-container
+ padding-top: 2rem
diff --git a/src/reducers/index.js b/src/reducers/index.js
index bfafaedd..40a51a04 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -1,13 +1,14 @@
import {combineReducers} from "redux";
import {auth} from "./auth";
+import {modals} from "./modals";
import {objects} from "./objects";
-import {authorizationsOfCurrentUser, authVisibilityFilter, newSimulationModalVisible} from "./simulations";
+import {authorizationsOfCurrentUser, authVisibilityFilter} from "./simulations";
const rootReducer = combineReducers({
auth,
objects,
+ modals,
authorizationsOfCurrentUser,
- newSimulationModalVisible,
authVisibilityFilter,
});
diff --git a/src/reducers/modals.js b/src/reducers/modals.js
new file mode 100644
index 00000000..e74b66b9
--- /dev/null
+++ b/src/reducers/modals.js
@@ -0,0 +1,30 @@
+import {combineReducers} from "redux";
+import {CLOSE_DELETE_PROFILE_MODAL, OPEN_DELETE_PROFILE_MODAL} from "../actions/profile";
+import {CLOSE_NEW_SIMULATION_MODAL, OPEN_NEW_SIMULATION_MODAL} from "../actions/simulations";
+
+function newSimulationModalVisible(state = false, action) {
+ switch (action.type) {
+ case OPEN_NEW_SIMULATION_MODAL:
+ return true;
+ case CLOSE_NEW_SIMULATION_MODAL:
+ return false;
+ default:
+ return state;
+ }
+}
+
+function deleteProfileModalVisible(state = false, action) {
+ switch (action.type) {
+ case OPEN_DELETE_PROFILE_MODAL:
+ return true;
+ case CLOSE_DELETE_PROFILE_MODAL:
+ return false;
+ default:
+ return state;
+ }
+}
+
+export const modals = combineReducers({
+ newSimulationModalVisible,
+ deleteProfileModalVisible,
+});
diff --git a/src/reducers/simulations.js b/src/reducers/simulations.js
index 9d830877..7d0b9d66 100644
--- a/src/reducers/simulations.js
+++ b/src/reducers/simulations.js
@@ -1,8 +1,6 @@
import {
ADD_SIMULATION_SUCCEEDED,
- CLOSE_NEW_SIMULATION_MODAL,
DELETE_SIMULATION_SUCCEEDED,
- OPEN_NEW_SIMULATION_MODAL,
SET_AUTH_VISIBILITY_FILTER
} from "../actions/simulations";
import {FETCH_AUTHORIZATIONS_OF_CURRENT_USER_SUCCEEDED} from "../actions/users";
@@ -23,17 +21,6 @@ export function authorizationsOfCurrentUser(state = [], action) {
}
}
-export function newSimulationModalVisible(state = false, action) {
- switch (action.type) {
- case OPEN_NEW_SIMULATION_MODAL:
- return true;
- case CLOSE_NEW_SIMULATION_MODAL:
- return false;
- default:
- return state;
- }
-}
-
export function authVisibilityFilter(state = "SHOW_ALL", action) {
switch (action.type) {
case SET_AUTH_VISIBILITY_FILTER:
diff --git a/src/routes/index.js b/src/routes/index.js
index 6257017e..af1b70b5 100644
--- a/src/routes/index.js
+++ b/src/routes/index.js
@@ -3,19 +3,17 @@ import {BrowserRouter, Redirect, Route, Switch} from "react-router-dom";
import {userIsLoggedIn} from "../auth/index";
import Home from "../pages/Home";
import NotFound from "../pages/NotFound";
+import Profile from "../pages/Profile";
import Simulations from "../pages/Simulations";
+const ProtectedComponent = (component) => () => userIsLoggedIn() ? component : <Redirect to="/"/>;
+
const Routes = () => (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Home}/>
- <Route exact path="/simulations" render={() => (
- userIsLoggedIn() ? (
- <Simulations/>
- ) : (
- <Redirect to="/"/>
- )
- )}/>
+ <Route exact path="/simulations" render={ProtectedComponent(<Simulations/>)}/>
+ <Route exact path="/profile" render={ProtectedComponent(<Profile/>)}/>
<Route path="/*" component={NotFound}/>
</Switch>
</BrowserRouter>