Skip to content
This repository has been archived by the owner on Apr 4, 2023. It is now read-only.

[#169647021] added registration page 3 #54

Merged
merged 2 commits into from
Nov 14, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.io-onboarding-pa-api.development
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ SAML_KEY_PATH=./certs/key.pem
SPID_AUTOLOGIN=
SPID_TESTENV_URL=http://localhost:8088
IDP_METADATA_URL=https://registry.spid.gov.it/metadata/idp/spid-entities-idps.xml
TOKEN_DURATION_IN_SECONDS=60
TOKEN_DURATION_IN_SECONDS=3600
INDICEPA_ADMINISTRATIONS_URL=https://raw.githubusercontent.com/teamdigitale/io-onboarding-pa-api/master/development-data/administrations.txt

EMAIL_PASSWORD=password
EMAIL_USER=portaleonboarding@gmail.com
Expand Down
12 changes: 12 additions & 0 deletions locales/en/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ translation:
logout: "Logout"
change: "Modifica"
skip: "Salta questo passaggio"
viewDocument: "Visualizza documento"
user:
roles:
delegate: "Delegato Ente"
Expand Down Expand Up @@ -109,6 +110,17 @@ translation:
fcPlaceholder: "Inserisci il CF personale del legale rappresentante"
phoneLabel: "Numero di telefono dell'ufficio del Legale Rappresentante"
phonePlaceholder: "Inserisci il numero di telefono dell'ufficio"
stepThree:
title: "Verifica e invio documenti legali"
description: "Visualizza i documenti creati e, se tutte le informazioni sono corrette, procedi all’invio della documentazione al tuo Legale Rappresentante e alla società PagoPA SpA."
additionalDescription: "Il Legale Rappresentante riceverà i documenti via PEC, dovrà procedere alla firma digitale degli stessi e inviarli nuovamente a PagoPA SpA per verifica, seguendo le istruzioni contenute nella mail."
contract:
title: "Accordo ente/PagoPA SpA"
description: "Questo documento viene generato una sola volta alla creazione di ogni profilo ente. Verificane la correttezza prima di procedere con la creazione del profilo ente."
mandate:
title: "Delega gestione profilo dell’ente su IO"
description: "Questo documento è la tua delega personale alla gestione del profilo digitale dell’ente. Ogni tuo collega che richieda una delega dovrà generare il proprio documento al primo accesso. Verificane la correttezza prima di procedere con la creazione del profilo ente."
checkboxLabel: "Confermo che le informazioni nei documenti sono complete e corrette"
userProfile:
title: "Il tuo profilo"
spidInfo:
Expand Down
12 changes: 12 additions & 0 deletions locales/it/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ translation:
logout: "Logout"
change: "Modifica"
skip: "Salta questo passaggio"
viewDocument: "Visualizza documento"
user:
roles:
delegate: "Delegato Ente"
Expand Down Expand Up @@ -109,6 +110,17 @@ translation:
fcPlaceholder: "Inserisci il CF personale del legale rappresentante"
phoneLabel: "Numero di telefono dell'ufficio del Legale Rappresentante"
phonePlaceholder: "Inserisci il numero di telefono dell'ufficio"
stepThree:
title: "Verifica e invio documenti legali"
description: "Visualizza i documenti creati e, se tutte le informazioni sono corrette, procedi all’invio della documentazione al tuo Legale Rappresentante e alla società PagoPA SpA."
additionalDescription: "Il Legale Rappresentante riceverà i documenti via PEC, dovrà procedere alla firma digitale degli stessi e inviarli nuovamente a PagoPA SpA per verifica, seguendo le istruzioni contenute nella mail."
contract:
title: "Accordo ente/PagoPA SpA"
description: "Questo documento viene generato una sola volta alla creazione di ogni profilo ente. Verificane la correttezza prima di procedere con la creazione del profilo ente."
mandate:
title: "Delega gestione profilo dell’ente su IO"
description: "Questo documento è la tua delega personale alla gestione del profilo digitale dell’ente. Ogni tuo collega che richieda una delega dovrà generare il proprio documento al primo accesso. Verificane la correttezza prima di procedere con la creazione del profilo ente."
checkboxLabel: "Confermo che le informazioni nei documenti sono complete e corrette"
userProfile:
title: "Il tuo profilo"
spidInfo:
Expand Down
19 changes: 6 additions & 13 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,27 @@
import React, { useContext } from "react";
import React from "react";
import { BrowserRouter, Redirect, Route, Switch } from "react-router-dom";
import { DefaultContainer } from "./components/DefaultContainer/DefaultContainer";
import { Home } from "./components/Home/Home";
import { LoadingPage } from "./components/LoadingPage/LoadingPage";
import { ScrollToTop } from "./components/ScrollToTop";

import "../node_modules/bootstrap-italia/dist/css/bootstrap-italia.min.css";
import "./App.scss";
import { LoadingPageContext } from "./context/loading-page-context";

/**
* Entry point for app, with first level routing
*/
export const App = () => {
const redirectToHome = () => <Redirect to="/home" />;

const loadingPageContext = useContext(LoadingPageContext);

return (
<BrowserRouter>
<ScrollToTop>
<div className="App vh-100">
{!loadingPageContext.loadingPage.isVisible ? (
<Switch>
<Route exact={true} path="/" component={redirectToHome} />
<Route path="/home" component={Home} />
<Route path="/*" component={DefaultContainer} />
</Switch>
) : null}
{loadingPageContext.loadingPage.isVisible ? <LoadingPage /> : null}
<Switch>
<Route exact={true} path="/" component={redirectToHome} />
<Route path="/home" component={Home} />
<Route path="/*" component={DefaultContainer} />
</Switch>
</div>
</ScrollToTop>
</BrowserRouter>
Expand Down
95 changes: 3 additions & 92 deletions src/components/Dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,100 +1,11 @@
import { NonEmptyString } from "italia-ts-commons/lib/strings";
import React, { useContext, useEffect } from "react";
import { RouteComponentProps, withRouter } from "react-router";
import React from "react";
import { withRouter } from "react-router";
import { Button, Col, Row } from "reactstrap";
import { EmailAddress } from "../../../generated/definitions/api/EmailAddress";
import { UserProfile } from "../../../generated/definitions/api/UserProfile";
import { TokenContext } from "../../context/token-context";
import { ICustomWindow } from "../../customTypes/CustomWindow";

interface IDashboardProps extends RouteComponentProps {
onGetUserProfile: (userProfile: UserProfile) => void;
spidMail: string;
onWorkMailSet: (newWorkMail: EmailAddress) => void;
toggleAddMailModal: () => void;
}

/**
* Component for delegate dashboard
*/
export const Dashboard = withRouter((props: IDashboardProps) => {
const tokenContext = useContext(TokenContext);

/**
* Create window with custom element _env_ for environment variables
*/
const customWindow = (window as unknown) as ICustomWindow;

/**
* Given a cookie key `cookieName`, returns the value of
* the cookie or empty string, if the key is not found.
*/
function getCookie(cookieName: string): string {
return (
document.cookie
.split(";")
.map(c => c.trim())
.filter(cookie => {
return (
cookie.substring(0, cookieName.length + 1) === `${cookieName}=`
);
})
.map(cookie => {
return decodeURIComponent(cookie.substring(cookieName.length + 1));
})[0] || ""
);
}

/**
* Set token in token context
*/
useEffect(() => {
const token = getCookie("sessionToken");
if (
NonEmptyString.is(token) ||
customWindow._env_.IO_ONBOARDING_PA_IS_MOCK_ENV === "1"
) {
tokenContext.setToken(token);
}
// if getCookie returns an empty string, it means the token has expired and user browser deleted it -> redirect user to login
// TODO: when available, redirect to logout page (tracked in story https://www.pivotaltracker.com/story/show/169033467)
else {
props.history.push("/home");
}
}, []);

useEffect(() => {
// make api call only after onMount -> token to string in any case, no longer undefined
if (tokenContext.token !== undefined) {
const url =
customWindow._env_.IO_ONBOARDING_PA_API_HOST +
":" +
customWindow._env_.IO_ONBOARDING_PA_API_PORT +
"/profile";
fetch(url, {
headers: {
Accept: "application/json",
Authorization: `Bearer ${tokenContext.token}`
// 'Content-Type': 'application/json'
},
method: "GET"
})
.then(response => {
return response.json();
})
.then(responseData => {
props.onGetUserProfile(responseData);
if (!responseData.work_email) {
props.toggleAddMailModal();
}
})
.catch(error => {
// TODO: manage error in promise, tracked with story #169033467
return error;
});
}
}, [tokenContext.token]);

export const Dashboard = withRouter(props => {
/**
* Navigate to signup page
*/
Expand Down
132 changes: 112 additions & 20 deletions src/components/DefaultContainer/DefaultContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import React, { useState } from "react";
import { Route, RouteComponentProps } from "react-router";
import { NonEmptyString } from "italia-ts-commons/lib/strings";
import React, { Fragment, useContext, useEffect, useState } from "react";
import { Route, RouteComponentProps, withRouter } from "react-router";
import { EmailAddress } from "../../../generated/definitions/api/EmailAddress";
import { FiscalCode } from "../../../generated/definitions/api/FiscalCode";
import { UserProfile } from "../../../generated/definitions/api/UserProfile";
import { UserRole } from "../../../generated/definitions/api/UserRole";
import { LoadingPageContext } from "../../context/loading-page-context";
import { TokenContext } from "../../context/token-context";
import { ICustomWindow } from "../../customTypes/CustomWindow";
import { AppAlert } from "../AppAlert/AppAlert";
import { CentralHeader } from "../CentralHeader/CentralHeader";
import { Dashboard } from "../Dashboard/Dashboard";
import { LoadingPage } from "../LoadingPage/LoadingPage";
import { AddMailModal } from "../Modal/AddMailModal";
import { RegistrationContainer } from "../Registration/RegistrationContainer";
import { SlimHeader } from "../SlimHeader/SlimHeader";
Expand All @@ -28,7 +33,16 @@ interface IDefaultContainerUserProfileState {
/**
* Component containing slim header, central header and app body with second level routing
*/
export const DefaultContainer = () => {
export const DefaultContainer = withRouter(props => {
const tokenContext = useContext(TokenContext);

const loadingPageContext = useContext(LoadingPageContext);

/**
* Create window with custom element _env_ for environment variables
*/
const customWindow = (window as unknown) as ICustomWindow;

/**
* Initial state for user profile
*/
Expand Down Expand Up @@ -70,40 +84,117 @@ export const DefaultContainer = () => {
setIsVisibleAddMailModal((prevState: boolean) => !prevState);
};

const navigateToDashboard = (props: RouteComponentProps) => (
<Dashboard
{...props}
onGetUserProfile={handleGetUserProfile}
spidMail={userProfile.email}
onWorkMailSet={handleWorkMailSet}
toggleAddMailModal={toggleAddMailModal}
/**
* Given a cookie key `cookieName`, returns the value of
* the cookie or empty string, if the key is not found.
*/
function getCookie(cookieName: string): string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move this function in a utility package or - better - use some library like https://www.npmjs.com/package/react-cookie

return (
document.cookie
.split(";")
.map(c => c.trim())
.filter(cookie => {
return (
cookie.substring(0, cookieName.length + 1) === `${cookieName}=`
);
})
.map(cookie => {
return decodeURIComponent(cookie.substring(cookieName.length + 1));
})[0] || ""
);
}

/**
* Set token in token context
*/
useEffect(() => {
const token = getCookie("sessionToken");
// if getCookie returns an empty string and the user is not in pre login page (where token still has to be set),
// it means the token has expired and user browser deleted it -> redirect user to login, otherwise set tokenContext.token
if (
NonEmptyString.is(token) ||
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please name conditions ie. const isTokenExpired = NonEmptyString.is(token) || ...

location.pathname === "/spid-login" ||
customWindow._env_.IO_ONBOARDING_PA_IS_MOCK_ENV === "1"
) {
tokenContext.setToken(token);
}
// TODO: when available, redirect to logout page (tracked in story https://www.pivotaltracker.com/story/show/169033467)
else {
props.history.push("/home");
}
}, [location.pathname]);

useEffect(() => {
// make api call only after onMount because token is string in any case, no longer undefined, and only if userProfile is not set and user is not on spid login page
if (
NonEmptyString.is(tokenContext.token) &&
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

!NonEmptyString.is(userProfile.given_name) &&
location.pathname !== "/spid-login"
) {
const url =
customWindow._env_.IO_ONBOARDING_PA_API_HOST +
":" +
customWindow._env_.IO_ONBOARDING_PA_API_PORT +
"/profile";
fetch(url, {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a todo comment linked to pivotal story to refactor these calls with io.utils generated code

headers: {
Accept: "application/json",
Authorization: `Bearer ${tokenContext.token}`
// 'Content-Type': 'application/json'
},
method: "GET"
})
.then(response => {
return response.json();
})
.then(responseData => {
handleGetUserProfile(responseData);
if (!responseData.work_email) {
toggleAddMailModal();
}
})
.catch(error => {
// TODO: manage error in promise, tracked with story #169033467
return error;
});
}
}, [tokenContext.token]);

const navigateToRegistration = (registrationProps: RouteComponentProps) => (
<RegistrationContainer
{...registrationProps}
userFiscalCode={userProfile.fiscal_code}
/>
);

const navigateToUserProfile = (props: RouteComponentProps) => (
const navigateToUserProfile = (userProfileProps: RouteComponentProps) => (
<UserProfileComponent
{...props}
{...userProfileProps}
userProfile={userProfile}
toggleAddMailModal={toggleAddMailModal}
/>
);

return (
<div className="DefaultContainer">
<SlimHeader />
<CentralHeader
userName={`${userProfile.given_name} ${userProfile.family_name}`}
userRole={userProfile.role}
/>
{!loadingPageContext.loadingPage.isVisible ? (
<Fragment>
<SlimHeader />
<CentralHeader
userName={`${userProfile.given_name} ${userProfile.family_name}`}
userRole={userProfile.role}
/>
</Fragment>
) : null}
<div>
<AppAlert />
<Route path="/spid-login" component={SpidLogin} />
<Route
path="/sign-up/:signUpStep"
exact={true}
component={RegistrationContainer}
render={navigateToRegistration}
/>
<Route path="/dashboard" render={navigateToDashboard} />
<Route path="/dashboard" component={Dashboard} />
<Route path="/profile" render={navigateToUserProfile} />
</div>
<AddMailModal
Expand All @@ -113,6 +204,7 @@ export const DefaultContainer = () => {
workMail={userProfile.work_email}
onWorkMailSet={handleWorkMailSet}
/>
{loadingPageContext.loadingPage.isVisible ? <LoadingPage /> : null}
</div>
);
};
});
Loading