diff --git a/CHANGES.rst b/CHANGES.rst index 218ea523..b41891fd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,6 +11,13 @@ Version 0.8.0 (UNRELEASED) - Adds filtering by status and search by name in workflow list page. - Adds import aliases. +Version 0.7.2 (2021-02-04) +-------------------------- + +- Adds option to require user email confirmation after sign-up. +- Adds option to display CERN Privacy Notice for CERN installations. +- Changes notification system to improve sign-in and sign-up messages. + Version 0.7.1 (2020-11-24) --------------------------- diff --git a/reana-ui/src/actions.js b/reana-ui/src/actions.js index 09ec1cf9..fac646bb 100644 --- a/reana-ui/src/actions.js +++ b/reana-ui/src/actions.js @@ -19,6 +19,7 @@ import { stringifyQueryParams, } from "~/util"; import { + getConfig, getWorkflow, getWorkflowLogs, getWorkflowSpecification, @@ -45,6 +46,9 @@ export const USER_SIGNEDOUT = "User signed out"; export const USER_REQUEST_TOKEN = "Request user token"; export const USER_TOKEN_REQUESTED = "User token requested"; export const USER_TOKEN_ERROR = "User token error"; +export const USER_EMAIL_CONFIRMATION = "User request email confirmation"; +export const USER_EMAIL_CONFIRMED = "User email confirmed"; +export const USER_EMAIL_CONFIRMATION_ERROR = "User email confirmation error"; export const QUOTA_FETCH = "Fetch user quota info"; export const QUOTA_RECEIVED = "User quota info received"; @@ -72,6 +76,7 @@ const USER_SIGNUP_URL = `${api}/api/register`; const USER_SIGNIN_URL = `${api}/api/login`; const USER_SIGNOUT_URL = `${api}/api/logout`; const USER_REQUEST_TOKEN_URL = `${api}/api/token`; +const USER_CONFIRM_EMAIL_URL = `${api}/api/confirm-email`; const WORKFLOWS_URL = (params) => `${api}/api/workflows?verbose=true&${stringifyQueryParams(params)}`; const WORKFLOW_LOGS_URL = (id) => `${api}/api/workflows/${id}/logs`; @@ -149,7 +154,10 @@ export function loadUser() { } function userSignFactory(initAction, succeedAction, actionURL, body) { - return async (dispatch) => { + return async (dispatch, getStore) => { + const state = getStore(); + const { userConfirmation } = getConfig(state); + dispatch({ type: initAction }); return await axios .post(actionURL, body, { @@ -157,12 +165,30 @@ function userSignFactory(initAction, succeedAction, actionURL, body) { headers: { "Content-Type": "application/json" }, }) .then((resp) => { + dispatch(clearNotification); dispatch({ type: succeedAction }); dispatch(loadUser()); + if (initAction === USER_SIGNUP) { + dispatch( + triggerNotification( + "Success!", + `User registered. ${ + userConfirmation + ? "Please confirm your email by clicking on the link we sent you." + : "" + }` + ) + ); + } return resp; }) .catch((err) => { - dispatch({ type: USER_SIGN_ERROR, ...err.response.data }); + // validation errors + if (err.response.data.errors) { + dispatch({ type: USER_SIGN_ERROR, ...err.response.data }); + } else { + dispatch(errorActionCreator(err, USER_SIGN_ERROR)); + } return err; }); }; @@ -203,6 +229,32 @@ export function requestToken() { }; } +export function confirmUserEmail(token) { + return async (dispatch) => { + dispatch({ type: USER_EMAIL_CONFIRMATION }); + return await axios + .post( + USER_CONFIRM_EMAIL_URL, + { token: token }, + { + withCredentials: true, + headers: { "Content-Type": "application/json" }, + } + ) + .then((resp) => { + dispatch({ type: USER_EMAIL_CONFIRMED }); + dispatch(triggerNotification("Success!", resp.data?.message)); + }) + .catch((err) => { + // adapt error format coming from invenio-accounts (remove array) + if (Array.isArray(err.response?.data?.message)) { + err.response.data.message = err.response?.data?.message[0]; + } + dispatch(errorActionCreator(err, USER_EMAIL_CONFIRMATION_ERROR)); + }); + }; +} + export function fetchWorkflows( pagination, search, diff --git a/reana-ui/src/components/App.js b/reana-ui/src/components/App.js index f348fbb7..49f47ac6 100644 --- a/reana-ui/src/components/App.js +++ b/reana-ui/src/components/App.js @@ -14,11 +14,20 @@ import { Redirect, BrowserRouter, Route, Switch } from "react-router-dom"; import { useSelector } from "react-redux"; import { Dimmer, Loader } from "semantic-ui-react"; -import { getUserFetchError, isSignedIn, loadingUser } from "~/selectors"; +import { + getUserFetchError, + isSignedIn, + isSignupHidden, + loadingUser, + loadingConfig, +} from "~/selectors"; +import Confirm from "~/pages/signin/Confirm"; import Signin from "~/pages/signin/Signin"; +import Signup from "~/pages/signin/Signup"; import WorkflowList from "~/pages/workflowList/WorkflowList"; import WorkflowDetails from "~/pages/workflowDetails/WorkflowDetails"; import Profile from "~/pages/profile/Profile"; +import PrivacyNotice from "~/pages/privacyNotice/PrivacyNotice"; import Error from "./Error"; import "./App.module.scss"; @@ -42,13 +51,15 @@ function ProtectedRoute(props) { } export default function App() { - const loading = useSelector(loadingUser); + const userLoading = useSelector(loadingUser); + const configLoading = useSelector(loadingConfig); + const loading = userLoading || configLoading; const signedIn = useSelector(isSignedIn); + const signupHidden = useSelector(isSignupHidden); const error = useSelector(getUserFetchError); if (!isEmpty(error)) { return ; } - return ( {loading ? ( @@ -63,8 +74,12 @@ export default function App() { /> (signedIn ? : )} + render={() => + signedIn || signupHidden ? : + } /> + + diff --git a/reana-ui/src/components/Footer.js b/reana-ui/src/components/Footer.js index 1741f742..bcce9dfb 100644 --- a/reana-ui/src/components/Footer.js +++ b/reana-ui/src/components/Footer.js @@ -9,8 +9,9 @@ */ import React from "react"; -import { Icon } from "semantic-ui-react"; import { useSelector } from "react-redux"; +import { Link } from "react-router-dom"; +import { Icon } from "semantic-ui-react"; import { getConfig } from "~/selectors"; @@ -22,6 +23,11 @@ export default function Footer() {