Skip to content
This repository has been archived by the owner on Aug 11, 2021. It is now read-only.

Shared signup #349

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions components/link/Info.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Icon from '../icon/Icon';
import { usePopper, Popper, usePopperAnchor } from '../popper';
import useRightToLeft from '../../containers/rightToLeft/useRightToLeft';

/** @type any */
const Info = ({
url,
title,
Expand Down
115 changes: 64 additions & 51 deletions containers/payments/PlansTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ const PlansTable = ({
cycle = DEFAULT_CYCLE,
updateCurrency,
updateCycle,
onSelect
onSelect,
expand = false
}) => {
const planName = getPlanName(subscription) || FREE;
const { hasPaidVpn } = user;
const { state, toggle } = useToggle();
const { state, toggle } = useToggle(expand);
const mySubscription = c('Title').t`My subscription`;

const getPrice = (planName) => {
Expand Down Expand Up @@ -310,54 +311,65 @@ const PlansTable = ({
</td>
</tr>
) : null}
<tr>
<th scope="row" className="pm-simple-table-row-th alignleft bg-global-light">
<span className="mr0-5">ProtonVPN</span>
<Info title={c('Tooltip').t`ProtonVPN keeps your Internet traffic private`} />
</th>
<td className="aligncenter">
<SmallButton className="pm-button--link" onClick={onSelect({ vpnplus: 1 })}>
{hasPaidVpn ? c('Action').t`Edit VPN` : c('Action').t`Add VPN`}
</SmallButton>
</td>
<td className="aligncenter">
<SmallButton className="pm-button--link" onClick={onSelect({ plus: 1, vpnplus: 1 })}>
{hasPaidVpn ? c('Action').t`Edit VPN` : c('Action').t`Add VPN`}
</SmallButton>
</td>
<td className="aligncenter">
<SmallButton className="pm-button--link" onClick={onSelect({ professional: 1, vpnplus: 1 })}>
{hasPaidVpn ? c('Action').t`Edit VPN` : c('Action').t`Add VPN`}
</SmallButton>
</td>
<td className="aligncenter">{c('Plan option').t`Included`}</td>
</tr>
<tr>
<th scope="row" className="pm-simple-table-row-th alignleft bg-global-light">
<SmallButton className="pm-button--link" onClick={toggle}>
{state ? c('Action').t`Hide additional features` : c('Action').t`Compare all features`}
</SmallButton>
</th>
<td className="aligncenter">
<SmallButton className="pm-button--primary" onClick={onSelect()}>{c('Action')
.t`Update`}</SmallButton>
</td>
<td className="aligncenter">
<SmallButton className="pm-button--primary" onClick={onSelect({ plus: 1, vpnplus: 1 })}>{c(
'Action'
).t`Update`}</SmallButton>
</td>
<td className="aligncenter">
<SmallButton
className="pm-button--primary"
onClick={onSelect({ professional: 1, vpnplus: 1 })}
>{c('Action').t`Update`}</SmallButton>
</td>
<td className="aligncenter">
<SmallButton className="pm-button--primary" onClick={onSelect({ visionary: 1 })}>{c('Action')
.t`Update`}</SmallButton>
</td>
</tr>
{onSelect && (
<>
<tr>
<th scope="row" className="pm-simple-table-row-th alignleft bg-global-light">
<span className="mr0-5">ProtonVPN</span>
<Info title={c('Tooltip').t`ProtonVPN keeps your Internet traffic private`} />
</th>
<td className="aligncenter">
<SmallButton className="pm-button--link" onClick={onSelect({ vpnplus: 1 })}>
{hasPaidVpn ? c('Action').t`Edit VPN` : c('Action').t`Add VPN`}
</SmallButton>
</td>
<td className="aligncenter">
<SmallButton className="pm-button--link" onClick={onSelect({ plus: 1, vpnplus: 1 })}>
{hasPaidVpn ? c('Action').t`Edit VPN` : c('Action').t`Add VPN`}
</SmallButton>
</td>
<td className="aligncenter">
<SmallButton
className="pm-button--link"
onClick={onSelect({ professional: 1, vpnplus: 1 })}
>
{hasPaidVpn ? c('Action').t`Edit VPN` : c('Action').t`Add VPN`}
</SmallButton>
</td>
<td className="aligncenter">{c('Plan option').t`Included`}</td>
</tr>
<tr>
<th scope="row" className="pm-simple-table-row-th alignleft bg-global-light">
<SmallButton className="pm-button--link" onClick={toggle}>
{state
? c('Action').t`Hide additional features`
: c('Action').t`Compare all features`}
</SmallButton>
</th>
<td className="aligncenter">
<SmallButton className="pm-button--primary" onClick={onSelect()}>{c('Action')
.t`Update`}</SmallButton>
</td>
<td className="aligncenter">
<SmallButton
className="pm-button--primary"
onClick={onSelect({ plus: 1, vpnplus: 1 })}
>{c('Action').t`Update`}</SmallButton>
</td>
<td className="aligncenter">
<SmallButton
className="pm-button--primary"
onClick={onSelect({ professional: 1, vpnplus: 1 })}
>{c('Action').t`Update`}</SmallButton>
</td>
<td className="aligncenter">
<SmallButton className="pm-button--primary" onClick={onSelect({ visionary: 1 })}>{c(
'Action'
).t`Update`}</SmallButton>
</td>
</tr>
</>
)}
</tbody>
</table>
);
Expand All @@ -371,7 +383,8 @@ PlansTable.propTypes = {
user: PropTypes.object,
updateCurrency: PropTypes.func,
updateCycle: PropTypes.func,
onSelect: PropTypes.func
onSelect: PropTypes.func,
expand: PropTypes.bool
};

export default PlansTable;
156 changes: 156 additions & 0 deletions containers/signup/AccountStep/AccountForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import {
Input,
PasswordInput,
PrimaryButton,
Field,
useApi,
Row,
Label,
EmailInput,
Href,
useLoading,
Info
} from 'react-components';
import { c } from 'ttag';
import { queryCheckUsernameAvailability } from 'proton-shared/lib/api/user';

const AccountForm = ({ model, onSubmit }) => {
const api = useApi();
const [username, setUsername] = useState(model.username);
const [password, setPassword] = useState(model.password);
const [confirmPassword, setConfirmPassword] = useState(model.password);
const [email, setEmail] = useState(model.email);
const [usernameError, setUsernameError] = useState();
const [loading, withLoading] = useLoading();

const handleChangeUsername = ({ target }) => {
if (usernameError) {
setUsernameError(null);
}
setUsername(target.value);
};

const handleChangePassword = ({ target }) => setPassword(target.value);
const handleChangeConfirmPassword = ({ target }) => setConfirmPassword(target.value);
const handleChangeEmail = ({ target }) => setEmail(target.value);

const handleSubmit = async (e) => {
e.preventDefault();

if (password !== confirmPassword) {
return;
}

try {
await api(queryCheckUsernameAvailability(username));
await onSubmit({
username,
password,
email
});
} catch (e) {
setUsernameError(e.data ? e.data.Error : c('Error').t`Can't check username, try again later`);
}
};

const termsOfServiceLink = (
<Href key="terms" url="https://protonvpn.com/terms-and-conditions">{c('Link').t`Terms of Service`}</Href>
);

const privacyPolicyLink = (
<Href key="privacy" url="https://protonvpn.com/privacy-policy">{c('Link').t`Privacy Policy`}</Href>
);

return (
<form className="flex-item-fluid-auto" onSubmit={(e) => withLoading(handleSubmit(e))}>
<Row>
<Label htmlFor="username">
<span className="mr0-5">{c('Label').t`Username`}</span>
<Info
title={c('Tooltip')
.t`Username which is used for all Proton services. This can also be used later to create a secure ProtonMail account.`}
/>
</Label>
<Field className="auto flex-item-fluid">
<Input
required
error={usernameError}
value={username}
onChange={handleChangeUsername}
name="username"
id="username"
autoFocus={true}
placeholder={c('Placeholder').t`Username`}
/>
</Field>
</Row>

<Row>
<Label htmlFor="password">
<span className="mr0-5">{c('Label').t`Password`}</span>
<Info
title={c('Tooltip')
.t`If you forget your password, you will no longer have access to your account or your data. Please save it someplace safe.`}
/>
</Label>
<Field className="auto flex-item-fluid">
<div className="mb1">
<PasswordInput
id="password"
required
value={password}
onChange={handleChangePassword}
name="password"
placeholder={c('Placeholder').t`Password`}
/>
</div>
<PasswordInput
id="passwordConfirmation"
required
value={confirmPassword}
onChange={handleChangeConfirmPassword}
error={password !== confirmPassword ? c('Error').t`Passwords do not match` : undefined}
name="passwordConfirmation"
placeholder={c('Placeholder').t`Confirm password`}
/>
</Field>
</Row>

<Row>
<Label htmlFor="email">
<span className="mr0-5">{c('Label').t`Email address`}</span>
<Info
title={c('Tooltip')
.t`Your email is not shared with third parties and is only used for recovery and account-related questions or communication.`}
/>
</Label>
<Field className="auto flex-item-fluid">
<div className="mb1">
<EmailInput
id="email"
required
value={email}
onChange={handleChangeEmail}
placeholder={c('Placeholder').t`[email protected]`}
/>
</div>
<p>
{c('Info')
.jt`By clicking Create account you agree to abide by our ${termsOfServiceLink} and ${privacyPolicyLink}.`}
</p>

<PrimaryButton loading={loading} type="submit">{c('Action').t`Create account`}</PrimaryButton>
</Field>
</Row>
</form>
);
};

AccountForm.propTypes = {
model: PropTypes.object.isRequired,
onSubmit: PropTypes.func.isRequired
};

export default AccountForm;
42 changes: 42 additions & 0 deletions containers/signup/AccountStep/AccountStep.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import PropTypes from 'prop-types';
import AccountForm from './AccountForm';
import { Row, SubTitle, useModals } from 'react-components';
import { c } from 'ttag';
import { hasProtonDomain } from 'proton-shared/lib/helpers/string';

import LoginPromptModal from './LoginPromptModal';
import LoginPanel from '../LoginPanel';

const AccountStep = ({ onContinue, model, children }) => {
const { createModal } = useModals();

const handleSubmit = ({ email, username, password }) => {
if (hasProtonDomain(email)) {
createModal(<LoginPromptModal email={email} />);
} else {
onContinue({ ...model, email, username, password });
}
};

return (
<div className="pt2 mb2">
<SubTitle>{c('Title').t`Create an account`}</SubTitle>
<Row>
<div>
<AccountForm model={model} onSubmit={handleSubmit} />
<LoginPanel />
</div>
{children}
</Row>
</div>
);
};

AccountStep.propTypes = {
model: PropTypes.object.isRequired,
onContinue: PropTypes.func.isRequired,
children: PropTypes.node.isRequired
};

export default AccountStep;
39 changes: 39 additions & 0 deletions containers/signup/AccountStep/LoginPromptModal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormModal, PrimaryButton, Alert } from 'react-components';
import { c } from 'ttag';
import { Link } from 'react-router-dom';
import useConfig from '../../config/useConfig';
import { CLIENT_TYPES } from 'proton-shared/lib/constants';

const LoginPromptModal = ({ email, ...rest }) => {
const { CLIENT_TYPE } = useConfig();

const title = CLIENT_TYPE === CLIENT_TYPES.VPN ? 'ProtonVPN' : 'ProtonMail';

return (
<FormModal
title={title}
close={c('Action').t`Cancel`}
submit={
<Link to="/login">
<PrimaryButton>{c('Action').t`Go to login`}</PrimaryButton>
</Link>
}
{...rest}
>
<Alert>
{c('Info').t`You already have a Proton account.`}
<br />
{c('Info')
.t`Your existing Proton account can be used to access all Proton services. Please login with ${email}`}
</Alert>
</FormModal>
);
};

LoginPromptModal.propTypes = {
email: PropTypes.string.isRequired
};

export default LoginPromptModal;
Loading