google registration

This commit is contained in:
Gustavo Fuhr 2022-11-30 03:05:07 -03:00
parent e60ed49478
commit 40e9acc5a8
28 changed files with 3471 additions and 2619 deletions

File diff suppressed because it is too large Load Diff

View File

@ -515,5 +515,27 @@
},
"share.message": {
"defaultMessage": "Message"
},
"registration.google.button": {
"defaultMessage": "Sign up with Google"
},
"login.google.button": {
"defaultMessage": "Sign in with Google"
},
"registration.division": {
"defaultMessage": "or"
},
"registration.callback.confirm.title": {
"defaultMessage": "Confirm"
},
"registration.callback.waiting.title": {
"defaultMessage": "Finishing ..."
},
"registration.callback.confirm.description": {
"defaultMessage": "An account with the same email was previously registered. Do you want to link your google account to that WiseMapping account?"
},
"registration.callback.waiting.description": {
"defaultMessage": "Please wait while we validate your identity"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,7 @@ import ReactGA from 'react-ga4';
import AppConfig from './classes/app-config';
import withSessionExpirationHandling from './components/HOCs/withSessionExpirationHandling';
import RegistrationSuccessPage from './components/registration-success-page';
import RegistrationCallbackPage from './components/registration-callback';
const EditorPage = React.lazy(() => import('./components/editor-page'));
const MapsPage = React.lazy(() => import('./components/maps-page'));
@ -63,6 +64,7 @@ const App = (): ReactElement => {
</Route>
<Route path="/c/login" component={LoginPage} />
<Route path="/c/registration" component={RegistationPage} />
<Route path="/c/registration-google" component={RegistrationCallbackPage} />
<Route path="/c/registration-success" component={RegistrationSuccessPage} />
<Route path="/c/forgot-password" component={ForgotPasswordPage} />
<Route path="/c/forgot-password-success" component={ForgotPasswordSuccessPage} />

View File

@ -27,6 +27,7 @@ interface Config {
recaptcha2Enabled: boolean;
recaptcha2SiteKey?: string;
clientType: 'mock' | 'rest';
googleOauth2Url: string;
}
class _AppConfig {
@ -35,6 +36,7 @@ class _AppConfig {
clientType: 'mock',
recaptcha2Enabled: true,
recaptcha2SiteKey: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI',
googleOauth2Url: '/c/registration-google?code=aFakeCode',
};
isDevelopEnv(): boolean {
@ -73,6 +75,11 @@ class _AppConfig {
return config.analyticsAccount;
}
getGoogleOauth2Url(): string | undefined {
const config = this.getInstance();
return config.googleOauth2Url;
}
buildClient(): Client {
const config = this.getInstance();
let result: Client;

View File

@ -7,6 +7,7 @@ import Client, {
MapInfo,
NewUser,
Permission,
Oauth2CallbackResult,
} from '..';
import { LocaleCode } from '../../app-i18n';
@ -17,6 +18,14 @@ class CacheDecoratorClient implements Client {
this.client = client;
}
processGoogleCallback(code: string): Promise<Oauth2CallbackResult> {
return this.client.processGoogleCallback(code);
}
confirmAccountSync(email: string, code: string): Promise<void> {
return this.client.confirmAccountSync(email, code);
}
fetchStarred(id: number): Promise<boolean> {
return this.client.fetchStarred(id);
}

View File

@ -71,6 +71,12 @@ export type Permission = {
role: Role;
};
export type Oauth2CallbackResult = {
email: string;
googleSync: boolean;
syncCode?: string;
};
interface Client {
deleteAccount(): Promise<void>;
importMap(model: ImportMapInfo): Promise<number>;
@ -104,6 +110,8 @@ interface Client {
registerNewUser(user: NewUser): Promise<void>;
resetPassword(email: string): Promise<void>;
processGoogleCallback(code: string): Promise<Oauth2CallbackResult>;
confirmAccountSync(email: string, code: string): Promise<void>;
fetchHistory(id: number): Promise<ChangeHistory[]>;
revertHistory(id: number, cid: number): Promise<void>;

View File

@ -24,6 +24,7 @@ import Client, {
MapInfo,
NewUser,
Permission,
Oauth2CallbackResult,
} from '..';
import { LocaleCode, localeFromStr } from '../../app-i18n';
@ -406,6 +407,38 @@ class MockClient implements Client {
console.log('email:' + email);
return Promise.resolve();
}
wait(ms: number) {
console.log('Start waiting');
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('Done waiting');
resolve(ms);
}, ms);
});
}
processGoogleCallback(code: string): Promise<Oauth2CallbackResult> {
// artificial delay for more realistic mock experience
const handler = (success: (result: Oauth2CallbackResult) => void) => {
setTimeout(() => {
success({
email: 'test@email.com',
// -- use case 1) user must confirm if he wants to link accounts
// googleSync: false,
// syncCode: "834580239598234650234578"
// -- use case 2) user already confirmed
googleSync: true,
syncCode: undefined,
});
}, 3000);
};
return new Promise(handler);
}
confirmAccountSync(email: string, code: string): Promise<void> {
return Promise.resolve();
}
}
export default MockClient;

View File

@ -9,6 +9,7 @@ import Client, {
AccountInfo,
ImportMapInfo,
Permission,
Oauth2CallbackResult,
} from '..';
import { getCsrfToken } from '../../../utils';
import { LocaleCode, localeFromStr } from '../../app-i18n';
@ -628,6 +629,47 @@ export default class RestClient implements Client {
return new Promise(handler);
}
processGoogleCallback(code: string): Promise<Oauth2CallbackResult> {
const handler = (
success: (result: Oauth2CallbackResult) => void,
reject: (error: ErrorInfo) => void,
) => {
this.axios
.post(`${this.baseUrl}/service/users/googleCallback?code=${code}`, {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
success({
email: response.data.email,
googleSync: response.data.googleSync,
syncCode: response.data.syncCode,
});
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
confirmAccountSync(email: string, code: string): Promise<void> {
const handler = (success: () => void, reject: (error: ErrorInfo) => void) => {
this.axios
.put(`${this.baseUrl}/service/users/confirmAccountSync?email=${email}&code=${code}`, {
headers: { 'Content-Type': 'application/json' },
})
.then((response) => {
success();
})
.catch((error) => {
const errorInfo = this.parseResponseOnError(error.response);
reject(errorInfo);
});
};
return new Promise(handler);
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private parseResponseOnError = (response: any): ErrorInfo => {
console.error(`Performing backend action error: ${JSON.stringify(response)}`);

View File

@ -609,6 +609,12 @@
"value": "Melde dich mit deinem Konto an"
}
],
"login.division": [
{
"type": 0,
"value": "oder"
}
],
"login.email": [
{
"type": 0,
@ -627,6 +633,12 @@
"value": "Passwort vergessen?"
}
],
"login.google.button": [
{
"type": 0,
"value": "Anmeldung mit Google"
}
],
"login.hsqldbcofig": [
{
"type": 0,
@ -903,12 +915,42 @@
"value": "Registrierung erfolgreich | WiseMapping"
}
],
"registration.callback.confirm.description": [
{
"type": 0,
"value": "Ein Konto mit derselben E-Mail-Adresse wurde zuvor registriert. Möchten Sie Ihr Google-Konto mit diesem WiseMapping-Konto verknüpfen?"
}
],
"registration.callback.confirm.title": [
{
"type": 0,
"value": "Bestätigen"
}
],
"registration.callback.waiting.description": [
{
"type": 0,
"value": "Bitte warten Sie, während wir Ihre Identität überprüfen"
}
],
"registration.callback.waiting.title": [
{
"type": 0,
"value": "Fertigstellung ..."
}
],
"registration.desc": [
{
"type": 0,
"value": "Nimm dir einen Moment Zeit. Die Anmeldung ist kostenlos."
}
],
"registration.division": [
{
"type": 0,
"value": "oder"
}
],
"registration.email": [
{
"type": 0,
@ -921,6 +963,12 @@
"value": "Vorname"
}
],
"registration.google.button": [
{
"type": 0,
"value": "Melden Sie sich bei Google an"
}
],
"registration.lastname": [
{
"type": 0,

View File

@ -631,6 +631,12 @@
"value": "Forgot Password ?"
}
],
"login.google.button": [
{
"type": 0,
"value": "Sign in with Google"
}
],
"login.hsqldbcofig": [
{
"type": 0,
@ -907,12 +913,42 @@
"value": "Registation Success | WiseMapping"
}
],
"registration.callback.confirm.description": [
{
"type": 0,
"value": "An account with the same email was previously registered. Do you want to link your google account to that WiseMapping account?"
}
],
"registration.callback.confirm.title": [
{
"type": 0,
"value": "Confirm"
}
],
"registration.callback.waiting.description": [
{
"type": 0,
"value": "Please wait while we validate your identity"
}
],
"registration.callback.waiting.title": [
{
"type": 0,
"value": "Finishing ..."
}
],
"registration.desc": [
{
"type": 0,
"value": "Signing up is free and just take a moment"
}
],
"registration.division": [
{
"type": 0,
"value": "or"
}
],
"registration.email": [
{
"type": 0,
@ -925,6 +961,12 @@
"value": "First Name"
}
],
"registration.google.button": [
{
"type": 0,
"value": "Sign up with Google"
}
],
"registration.lastname": [
{
"type": 0,

View File

@ -555,6 +555,12 @@
"value": "Ingrese a su cuenta"
}
],
"login.division": [
{
"type": 0,
"value": "ó"
}
],
"login.email": [
{
"type": 0,
@ -573,6 +579,12 @@
"value": "Has olvidado tu contraseña ?"
}
],
"login.google.button": [
{
"type": 0,
"value": "Ingresar con Google"
}
],
"login.hsqldbcofig": [
{
"type": 0,
@ -756,7 +768,7 @@
"maps.search-action": [
{
"type": 0,
"value": "Búscar ..."
"value": "Buscar ..."
}
],
"maps.tooltip-open": [
@ -849,12 +861,42 @@
"value": "Éxito de registro | WiseMapping"
}
],
"registration.callback.confirm.description": [
{
"type": 0,
"value": "Una cuenta con el mismo email fue registrada previamente. ¿Quieres asociar tu cuenta de Google con esa cuenta de WiseMapping?"
}
],
"registration.callback.confirm.title": [
{
"type": 0,
"value": "Confirmación"
}
],
"registration.callback.waiting.description": [
{
"type": 0,
"value": "Por favor espera mientras validamos tu identidad"
}
],
"registration.callback.waiting.title": [
{
"type": 0,
"value": "Finalizando ..."
}
],
"registration.desc": [
{
"type": 0,
"value": "Registrarse es gratis y solo tómese un momento"
}
],
"registration.division": [
{
"type": 0,
"value": "ó"
}
],
"registration.email": [
{
"type": 0,
@ -867,6 +909,12 @@
"value": "Primer nombre"
}
],
"registration.google.button": [
{
"type": 0,
"value": "Regístrate con Google"
}
],
"registration.lastname": [
{
"type": 0,

View File

@ -601,6 +601,12 @@
"value": "Connectez-vous à votre compte"
}
],
"login.division": [
{
"type": 0,
"value": "ou"
}
],
"login.email": [
{
"type": 0,
@ -619,6 +625,12 @@
"value": "Mot de passe oublié ?"
}
],
"login.google.button": [
{
"type": 0,
"value": "Connectez-vous avec Google"
}
],
"login.hsqldbcofig": [
{
"type": 0,
@ -895,12 +907,42 @@
"value": "Inscription réussie | WiseMapping"
}
],
"registration.callback.confirm.description": [
{
"type": 0,
"value": "Un compte avec le même e-mail a déjà été enregistré. Voulez-vous lier votre compte Google à ce compte WiseMapping ?"
}
],
"registration.callback.confirm.title": [
{
"type": 0,
"value": "Confirmer"
}
],
"registration.callback.waiting.description": [
{
"type": 0,
"value": "Veuillez patienter pendant que nous validons votre identité"
}
],
"registration.callback.waiting.title": [
{
"type": 0,
"value": "Finition ..."
}
],
"registration.desc": [
{
"type": 0,
"value": "L'inscription est gratuite et ne prends qu'un instant"
}
],
"registration.division": [
{
"type": 0,
"value": "ou"
}
],
"registration.email": [
{
"type": 0,
@ -913,6 +955,12 @@
"value": "Prénom"
}
],
"registration.google.button": [
{
"type": 0,
"value": "S'inscrire avec Google"
}
],
"registration.lastname": [
{
"type": 0,

View File

@ -555,6 +555,12 @@
"value": "Войдите в свой аккаунт"
}
],
"login.division": [
{
"type": 0,
"value": "или же"
}
],
"login.email": [
{
"type": 0,
@ -573,6 +579,12 @@
"value": "Забыли пароль?"
}
],
"login.google.button": [
{
"type": 0,
"value": "Войти через Google"
}
],
"login.hsqldbcofig": [
{
"type": 0,
@ -849,12 +861,42 @@
"value": "Успешная регистрация | WiseMapping"
}
],
"registration.callback.confirm.description": [
{
"type": 0,
"value": "Учетная запись c таким же адресом электронной почты была ранее зарегистрирована. Вы хотите связать свою учетную запись Google с этой учетной записью WiseMapping?"
}
],
"registration.callback.confirm.title": [
{
"type": 0,
"value": "Подтверждать"
}
],
"registration.callback.waiting.description": [
{
"type": 0,
"value": "Подождите, пока мы подтвердим вашу личность"
}
],
"registration.callback.waiting.title": [
{
"type": 0,
"value": "Отделка ..."
}
],
"registration.desc": [
{
"type": 0,
"value": "Регистрация бесплатна и займет всего минуту"
}
],
"registration.division": [
{
"type": 0,
"value": "или же"
}
],
"registration.email": [
{
"type": 0,
@ -867,6 +909,12 @@
"value": "Имя"
}
],
"registration.google.button": [
{
"type": 0,
"value": "Зарегистрируйтесь в Google"
}
],
"registration.lastname": [
{
"type": 0,

View File

@ -603,6 +603,12 @@
"value": "登录您的账号"
}
],
"login.division": [
{
"type": 0,
"value": "或者"
}
],
"login.email": [
{
"type": 0,
@ -621,6 +627,12 @@
"value": "忘记密码?"
}
],
"login.google.button": [
{
"type": 0,
"value": "使用 Google 登錄"
}
],
"login.hsqldbcofig": [
{
"type": 0,
@ -897,12 +909,42 @@
"value": "注册成功|WiseMapping"
}
],
"registration.callback.confirm.description": [
{
"type": 0,
"value": "之前注册了一个具有相同电子邮件地址的帐户。你想将你的谷歌账户链接到那个 WiseMapping 账户吗?"
}
],
"registration.callback.confirm.title": [
{
"type": 0,
"value": "确认"
}
],
"registration.callback.waiting.description": [
{
"type": 0,
"value": "我们正在验证您的身份,请稍候"
}
],
"registration.callback.waiting.title": [
{
"type": 0,
"value": "整理 ..."
}
],
"registration.desc": [
{
"type": 0,
"value": "注册免费,分分钟就好"
}
],
"registration.division": [
{
"type": 0,
"value": "或者"
}
],
"registration.email": [
{
"type": 0,
@ -915,6 +957,12 @@
"value": "名字"
}
],
"registration.google.button": [
{
"type": 0,
"value": "註冊谷歌"
}
],
"registration.lastname": [
{
"type": 0,

View File

@ -0,0 +1,23 @@
import { css } from '@emotion/react';
import { Button } from '@mui/material';
import GoogleIcon from '../google-icon';
const googleButtonStyle = css({
color: '#000000',
//fontSize: "15px",
fontWeight: '300',
border: '1px solid black',
'&:hover': {
border: '1px solid black',
},
});
const GoogleButton = ({ text, onClick }) => {
return (
<Button variant="outlined" css={googleButtonStyle} startIcon={GoogleIcon} onClick={onClick}>
{text}
</Button>
);
};
export default GoogleButton;

View File

@ -0,0 +1,30 @@
import { SvgIcon } from '@mui/material';
const GoogleIconSvg = () => {
return (
<SvgIcon>
<g transform="matrix(1, 0, 0, 1, 27.009001, -39.238998)">
<path
fill="#4285F4"
d="M -3.264 51.509 C -3.264 50.719 -3.334 49.969 -3.454 49.239 L -14.754 49.239 L -14.754 53.749 L -8.284 53.749 C -8.574 55.229 -9.424 56.479 -10.684 57.329 L -10.684 60.329 L -6.824 60.329 C -4.564 58.239 -3.264 55.159 -3.264 51.509 Z"
/>
<path
fill="#34A853"
d="M -14.754 63.239 C -11.514 63.239 -8.804 62.159 -6.824 60.329 L -10.684 57.329 C -11.764 58.049 -13.134 58.489 -14.754 58.489 C -17.884 58.489 -20.534 56.379 -21.484 53.529 L -25.464 53.529 L -25.464 56.619 C -23.494 60.539 -19.444 63.239 -14.754 63.239 Z"
/>
<path
fill="#FBBC05"
d="M -21.484 53.529 C -21.734 52.809 -21.864 52.039 -21.864 51.239 C -21.864 50.439 -21.724 49.669 -21.484 48.949 L -21.484 45.859 L -25.464 45.859 C -26.284 47.479 -26.754 49.299 -26.754 51.239 C -26.754 53.179 -26.284 54.999 -25.464 56.619 L -21.484 53.529 Z"
/>
<path
fill="#EA4335"
d="M -14.754 43.989 C -12.984 43.989 -11.404 44.599 -10.154 45.789 L -6.734 42.369 C -8.804 40.429 -11.514 39.239 -14.754 39.239 C -19.444 39.239 -23.494 41.939 -25.464 45.859 L -21.484 48.949 C -20.534 46.099 -17.884 43.989 -14.754 43.989 Z"
/>
</g>
</SvgIcon>
);
};
const GoogleIcon = <GoogleIconSvg />;
export default GoogleIcon;

View File

@ -0,0 +1,77 @@
import { css } from '@emotion/react';
import { useTheme } from '@mui/material/styles';
const Separator = ({ responsive, text, maxWidth = undefined }) => {
const theme = useTheme();
const containerStyle = css([
{
position: 'relative',
width: '100%',
height: '90%',
top: '5%',
display: 'inline-block',
},
responsive && {
[theme.breakpoints.down('md')]: {
paddingTop: '25px',
paddingBottom: '25px',
},
},
!responsive && {
paddingTop: '25px',
paddingBottom: '25px',
},
maxWidth && {
maxWidth: maxWidth,
},
]);
const lineStyle = css([
{
backgroundColor: '#dce2e6',
position: 'absolute',
left: '50%',
height: '1px',
width: '100%',
transform: 'translateX(-50%)',
},
responsive && {
[theme.breakpoints.up('md')]: {
height: '100%',
width: '1px',
transform: 'translateX(0%) translateY(0%)',
},
},
]);
const textStyle = css([
{
backgroundColor: '#DCE2E6',
padding: '5px 10px',
minWidth: '36px',
borderRadius: '18px',
fontSize: '18px',
color: 'white',
textAlign: 'center',
display: 'inline-block',
position: 'absolute',
transform: 'translateX(-50%) translateY(-50%)',
left: '50%',
},
responsive && {
[theme.breakpoints.up('md')]: {
top: '15%',
},
},
]);
return (
<div css={containerStyle}>
<div css={lineStyle}></div>
<div css={textStyle}>{text}</div>
</div>
);
};
export default Separator;

View File

@ -24,7 +24,7 @@ const SubmitButton = (props: SubmitButton): React.ReactElement => {
disableElevation={true}
disabled={disabled}
style={{
width: '350px',
width: '300px',
height: '53px',
padding: '0px 20px',
margin: '7px 0px',

View File

@ -8,6 +8,7 @@ export const StyledFooter = styled.footer`
background-color: #f9a826;
display: grid;
grid-template-columns: 200px 1fr 1fr 1fr 3fr;
overflow: hidden;
& a {
font-size: 14px;

View File

@ -12,6 +12,9 @@ import FormControl from '@mui/material/FormControl';
import Link from '@mui/material/Link';
import ReactGA from 'react-ga4';
import { getCsrfToken, getCsrfTokenParameter } from '../../utils';
import Separator from '../common/separator';
import GoogleButton from '../common/google-button';
import AppConfig from '../../classes/app-config';
const LoginError = () => {
// @Todo: This must be reviewed to be based on navigation state.
@ -32,7 +35,7 @@ const LoginError = () => {
default:
msg = intl.formatMessage({
id: 'login.error',
defaultMessage: 'The email address or password you entered is not valid.',
defaultMessage: 'The email address or password you entered is not valid.',
});
}
}
@ -54,7 +57,7 @@ const LoginPage = (): React.ReactElement => {
<div>
<Header type="only-signup" />
<FormContainer maxWidth="xs">
<FormContainer>
<Typography variant="h4" component="h1">
<FormattedMessage id="login.title" defaultMessage="Welcome" />
</Typography>
@ -102,10 +105,25 @@ const LoginPage = (): React.ReactElement => {
/>
</form>
</FormControl>
<Link component={RouterLink} to="/c/forgot-password">
<FormattedMessage id="login.forgotpwd" defaultMessage="Forgot Password ?" />
</Link>
<Separator
responsive={false}
text={intl.formatMessage({
id: 'login.division',
defaultMessage: 'or',
})}
/>
<GoogleButton
text={intl.formatMessage({
id: 'login.google.button',
defaultMessage: 'Sign in with Google',
})}
onClick={() => {
window.location.href = AppConfig.getGoogleOauth2Url();
}}
/>
</FormContainer>
<Footer />

View File

@ -0,0 +1,132 @@
import React, { useEffect, useState } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import FormContainer from '../layout/form-container';
import Header from '../layout/header';
import Footer from '../layout/footer';
import { Link as RouterLink } from 'react-router-dom';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import ReactGA from 'react-ga4';
import Client from '../../classes/client';
import { useSelector } from 'react-redux';
import { activeInstance } from '../../redux/clientSlice';
import { useHistory } from 'react-router-dom';
import { css } from '@emotion/react';
import { CircularProgress } from '@mui/material';
const RegistrationCallbackPage = (): React.ReactElement => {
const intl = useIntl();
const client: Client = useSelector(activeInstance);
const [email, setEmail] = useState(undefined);
const [syncCode, setSyncCode] = useState(undefined);
const [googleSync, setGoogleSync] = useState(undefined);
const history = useHistory();
useEffect(() => {
document.title = intl.formatMessage({
id: 'registation.success-title',
defaultMessage: 'Registation Success | WiseMapping',
});
ReactGA.send({
hitType: 'pageview',
page: window.location.pathname,
title: 'Registration:Success',
});
});
useEffect(() => {
const googleOauthCode = new URLSearchParams(window.location.search).get('code');
client
.processGoogleCallback(googleOauthCode)
.then((result) => {
if (result.googleSync) {
// if service reports that user already has sync accounts, go to maps page
history.push('/c/maps');
}
setEmail(result.email);
setSyncCode(result.syncCode);
setGoogleSync(result.googleSync);
})
.catch((error) => {
console.log('ERROR', error);
});
}, []);
const buttonsStyle = css({
marginLeft: '10px',
marginRight: '10px',
});
const confirmAccountSynching = () => {
client
.confirmAccountSync(email, syncCode)
.then(() => {
history.push('/c/maps');
})
.catch((error) => {
console.log('ERROR', error);
});
};
// if service reports that user doesnt sync accounts yet, we need to show the options
const needConfirmLinking = email && !googleSync;
const titleKey = needConfirmLinking
? 'registration.callback.confirm.title'
: 'registration.callback.waiting.title';
const titleDefaultValue = needConfirmLinking ? 'Confirm' : 'Finishing...';
const descriptionKey = needConfirmLinking
? 'registration.callback.confirm.description'
: 'registration.callback.waiting.description';
const descriptionDefaultValue = needConfirmLinking
? 'An account with the same email was previously registered. Do you want to link your google account to that WiseMapping account?'
: 'Please wait while we validate your identity';
return (
<div>
<Header type="none" />
<FormContainer>
<Typography variant="h4" component="h1">
<FormattedMessage id={titleKey} defaultMessage={titleDefaultValue} />
</Typography>
<Typography paragraph>
<FormattedMessage id={descriptionKey} defaultMessage={descriptionDefaultValue} />
</Typography>
{!needConfirmLinking && <CircularProgress />}
{needConfirmLinking && (
<>
<Button
color="secondary"
size="medium"
variant="contained"
component={RouterLink}
to="/c/login"
disableElevation={true}
css={buttonsStyle}
>
<FormattedMessage id="registration.callback.back" defaultMessage="Back to login" />
</Button>
<Button
onClick={() => {
confirmAccountSynching();
}}
color="primary"
size="medium"
variant="contained"
disableElevation={true}
css={buttonsStyle}
>
<FormattedMessage id="registration.callback.sync" defaultMessage="Sync account" />
</Button>
</>
)}
</FormContainer>
<Footer />
</div>
);
};
export default RegistrationCallbackPage;

View File

@ -3,7 +3,6 @@ import { FormattedMessage, useIntl } from 'react-intl';
import ReCAPTCHA from 'react-google-recaptcha';
import { useHistory } from 'react-router-dom';
import Client, { ErrorInfo } from '../../classes/client';
import FormContainer from '../layout/form-container';
import Header from '../layout/header';
import Footer from '../layout/footer';
@ -18,6 +17,11 @@ import Typography from '@mui/material/Typography';
import FormControl from '@mui/material/FormControl';
import AppConfig from '../../classes/app-config';
import ReactGA from 'react-ga4';
import Separator from '../common/separator';
import GoogleButton from '../common/google-button';
import { Grid, Link } from '@mui/material';
import { css } from '@emotion/react';
import { Link as RouterLink } from 'react-router-dom';
export type Model = {
email: string;
@ -28,6 +32,7 @@ export type Model = {
};
const defaultModel: Model = { email: '', lastname: '', firstname: '', password: '', recaptcha: '' };
const RegistrationForm = () => {
const [model, setModel] = useState<Model>(defaultModel);
const [error, setError] = useState<ErrorInfo>();
@ -61,99 +66,136 @@ const RegistrationForm = () => {
setModel({ ...model, [name as keyof Model]: value });
};
const recaptchaContainerStyle = css({
paddingTop: '10px',
// override captcha size, without this the component is not shown horizontally centered
'& div div div': {
width: '100% !important',
height: '100% !important',
},
});
const maxFormWidth = 350;
const handleRegisterWithGoogleClick = () => {
window.location.href = AppConfig.getGoogleOauth2Url();
};
return (
<FormContainer>
<Typography variant="h4" component="h1">
<FormattedMessage id="registration.title" defaultMessage="Become a member" />
</Typography>
<Grid container justifyContent="center">
<Grid container spacing={0} justifyContent="center" alignItems="scretch" textAlign="center">
<Grid item md={5} xs={12} justifyContent="center">
<Typography variant="h4" component="h1">
<FormattedMessage id="registration.title" defaultMessage="Become a member" />
</Typography>
<Typography paragraph>
<FormattedMessage
id="registration.desc"
defaultMessage="Signing up is free and just take a moment "
/>
</Typography>
<FormControl>
<form onSubmit={handleOnSubmit}>
<GlobalError error={error} />
<Input
name="email"
type="email"
onChange={handleOnChange}
label={intl.formatMessage({
id: 'registration.email',
defaultMessage: 'Email',
})}
autoComplete="email"
error={error}
/>
<Input
name="firstname"
type="text"
onChange={handleOnChange}
label={intl.formatMessage({
id: 'registration.firstname',
defaultMessage: 'First Name',
})}
autoComplete="given-name"
error={error}
/>
<Input
name="lastname"
type="text"
onChange={handleOnChange}
label={intl.formatMessage({
id: 'registration.lastname',
defaultMessage: 'Last Name',
})}
autoComplete="family-name"
error={error}
/>
<Input
name="password"
type="password"
onChange={handleOnChange}
label={intl.formatMessage({
id: 'registration.password',
defaultMessage: 'Password',
})}
autoComplete="new-password"
error={error}
/>
{AppConfig.isRecaptcha2Enabled() && (
<div style={{ width: '330px', padding: '5px 0px 5px 20px' }}>
<ReCAPTCHA
ref={(el) => setCaptcha(el)}
sitekey={AppConfig.getRecaptcha2SiteKey()}
onChange={(value: string) => {
model.recaptcha = value;
setModel(model);
}}
/>
</div>
)}
<div style={{ fontSize: '12px', padding: '10px 0px' }}>
<Typography paragraph>
<FormattedMessage
id="registration.termandconditions"
defaultMessage="Terms of Client: Please check the WiseMapping Account information you've entered above, and review the Terms of Client here. By clicking on 'Register' below you are agreeing to the Terms of Client above and the Privacy Policy"
id="registration.desc"
defaultMessage="Signing up is free and just take a moment "
/>
</div>
<SubmitButton
value={intl.formatMessage({
id: 'registration.register',
defaultMessage: 'Register',
</Typography>
<GoogleButton
text={intl.formatMessage({
id: 'registration.google.button',
defaultMessage: 'Sign up with Google',
})}
onClick={handleRegisterWithGoogleClick}
/>
</Grid>
<Grid item md={2} xs={12}>
<Separator
responsive={true}
maxWidth={maxFormWidth}
text={intl.formatMessage({
id: 'registration.division',
defaultMessage: 'or',
})}
/>
</form>
</FormControl>
</FormContainer>
</Grid>
<Grid item md={5} xs={12}>
<FormControl css={{ maxWidth: maxFormWidth }}>
<form onSubmit={handleOnSubmit}>
<GlobalError error={error} />
<Input
name="email"
type="email"
onChange={handleOnChange}
label={intl.formatMessage({
id: 'registration.email',
defaultMessage: 'Email',
})}
autoComplete="email"
error={error}
/>
<Input
name="firstname"
type="text"
onChange={handleOnChange}
label={intl.formatMessage({
id: 'registration.firstname',
defaultMessage: 'First Name',
})}
autoComplete="given-name"
error={error}
/>
<Input
name="lastname"
type="text"
onChange={handleOnChange}
label={intl.formatMessage({
id: 'registration.lastname',
defaultMessage: 'Last Name',
})}
autoComplete="family-name"
error={error}
/>
<Input
name="password"
type="password"
onChange={handleOnChange}
label={intl.formatMessage({
id: 'registration.password',
defaultMessage: 'Password',
})}
autoComplete="new-password"
error={error}
/>
{AppConfig.isRecaptcha2Enabled() && (
<div css={recaptchaContainerStyle}>
<ReCAPTCHA
ref={(el) => setCaptcha(el)}
sitekey={AppConfig.getRecaptcha2SiteKey()}
onChange={(value: string) => {
model.recaptcha = value;
setModel(model);
}}
/>
</div>
)}
<div style={{ fontSize: '12px', padding: '10px 0px' }}>
<FormattedMessage
id="registration.termandconditions"
defaultMessage="Terms of Client: Please check the WiseMapping Account information you've entered above, and review the Terms of Client here. By clicking on 'Register' below you are agreeing to the Terms of Client above and the Privacy Policy"
/>
</div>
<SubmitButton
value={intl.formatMessage({
id: 'registration.register',
defaultMessage: 'Register',
})}
/>
</form>
</FormControl>
</Grid>
</Grid>
<Grid item>
<Link component={RouterLink} to="/c/login">
<FormattedMessage id="header.haveaccount" defaultMessage="Already have an account?" />
</Link>
</Grid>
</Grid>
);
};

View File

@ -1,6 +1,5 @@
{
"compilerOptions": {
"jsx": "react",
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": false,
@ -10,7 +9,10 @@
"target": "es6",
"allowJs": true,
"esModuleInterop": true,
"declaration": true
"declaration": true,
"jsx": "react-jsx",
"jsxImportSource": "@emotion/react"
}
}

View File

@ -5,9 +5,11 @@
"noImplicitAny": false,
"module": "es6",
"target": "es6",
"jsx": "react",
"allowJs": true,
"esModuleInterop": true
"esModuleInterop": true,
"jsx": "react-jsx",
"jsxImportSource": "@emotion/react"
},
"exclude": ["node_modules"]
}