Skip to content

Commit

Permalink
feat(onboarding-ui): Create new account page (#575)
Browse files Browse the repository at this point in the history
* feat: add new AccountForm

* feat: add new account page

* chore: update loki screens for tests

* chore: export newAccountPage component

* chore: remove unnecessary id and imports

* chore: loki screenshots

* chore: fix names

* chore: update loki

Co-authored-by: dougfabris <devfabris@gmail.com>
  • Loading branch information
ujorgeleite and dougfabris authored Dec 6, 2021
1 parent ff91fd2 commit f443a11
Show file tree
Hide file tree
Showing 16 changed files with 342 additions and 1 deletion.
31 changes: 30 additions & 1 deletion packages/onboarding-ui/.i18n/en.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"termsAndConditions": "I agree with <1>Terms and Conditions</1> and <3>Privacy Policy</3>"
},
"emailCodeFallback": "Didn’t receive email? <1>Resend</1> or <3>Change email</3>",
"manageWorkspaceFallback": "Already have an account? <1>Manage your workspaces.</1>"
"manageWorkspaceFallback": "Already have an account? <1>Manage your workspaces.</1>",
"createNewAccountPage": "Already registered? <1>Go to login</1>"
},
"page": {
"form": {
Expand Down Expand Up @@ -72,6 +73,9 @@
"createAccount": {
"label": "New here? <1>Create account</1>"
}
},
"newAccountForm": {
"title": "Register new account"
}
},
"form": {
Expand Down Expand Up @@ -230,6 +234,31 @@
"sendLoginLink": "Send login link",
"redirect": "Enter password instead",
"resetPassword": "Forgot your password? <1>Reset password</1>"
},
"newAccountForm": {
"fields": {
"name": {
"label": "Name",
"placeholder": "Name"
},
"email": {
"label": "Email",
"placeholder": "Email"
},
"password": {
"label": "Create Password",
"placeholder": "Enter your password",
"error": "Email and password do not match"
},
"confirmPassword": {
"label": "Confirm Password",
"placeholder": "Enter your password",
"error": "Email and password do not match"
}
},
"button": {
"text": "Log in"
}
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import ReactDOM from 'react-dom';

import NewAccountForm from './NewAccountForm';

it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(
<NewAccountForm
validateEmail={() => undefined}
validatePassword={() => undefined}
validateConfirmationPassword={() => undefined}
onSubmit={() => undefined}
/>,
div
);
ReactDOM.unmountComponentAtNode(div);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Story, Meta } from '@storybook/react';
import type { ComponentProps } from 'react';

import NewAccountForm from './NewAccountForm';

type Args = ComponentProps<typeof NewAccountForm>;

export default {
title: 'forms/NewAccountForm',
component: NewAccountForm,
parameters: {
actions: { argTypesRegex: '^on.*' },
layout: 'centered',
},
argTypes: {
validateUsername: { action: 'validateUsername' },
validateEmail: { action: 'validateEmail' },
validatePassword: { action: 'validatePassword' },
validateConfirmPassword: { action: 'validateConfirmPassword' },
},
args: {},
} as Meta<Args>;

export const _NewAccountForm: Story<Args> = (args) => (
<NewAccountForm {...args} />
);
_NewAccountForm.storyName = 'NewAccountForm';
169 changes: 169 additions & 0 deletions packages/onboarding-ui/src/forms/NewAccountForm/NewAccountForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import {
EmailInput,
FieldGroup,
Field,
ButtonGroup,
Button,
PasswordInput,
TextInput,
Box,
CheckBox,
} from '@rocket.chat/fuselage';
import { ReactElement, useEffect } from 'react';
import { useForm, SubmitHandler, Validate } from 'react-hook-form';
import { Trans, useTranslation } from 'react-i18next';

import Form from '../../common/Form';

export type NewAccountPayload = {
name: string;
email: string;
password: string;
confirmPassword: string;
agreement?: boolean;
};

type NewAccountFormProps = {
initialValues?: Omit<NewAccountPayload, 'password'>;
validateEmail: Validate<string>;
validatePassword: Validate<string>;
validateConfirmationPassword: Validate<string>;
onSubmit: SubmitHandler<NewAccountPayload>;
};

const NewAccountForm = ({
validateEmail,
validatePassword,
validateConfirmationPassword,
onSubmit,
}: NewAccountFormProps): ReactElement => {
const { t } = useTranslation();

const {
register,
handleSubmit,
formState: { isValidating, isSubmitting, isValid, errors },
setFocus,
} = useForm<NewAccountPayload>({ mode: 'onChange' });

useEffect(() => {
setFocus('name');
}, [setFocus]);

return (
<Form onSubmit={handleSubmit(onSubmit)}>
<Form.Container>
<FieldGroup>
<Field>
<Field.Label>
{t('form.newAccountForm.fields.name.label')}
</Field.Label>
<Field.Row>
<TextInput
{...register('name', {
required: String(t('component.form.requiredField')),
})}
/>
</Field.Row>
{errors.name && <Field.Error>{errors.name.message}</Field.Error>}
</Field>
<Field>
<Field.Label>
{t('form.newAccountForm.fields.email.label')}
</Field.Label>
<Field.Row>
<EmailInput
{...register('email', {
validate: validateEmail,
required: true,
})}
/>
</Field.Row>
{errors?.email && <Field.Error>{errors.email.message}</Field.Error>}
</Field>
<Field>
<Field.Label>
{t('form.newAccountForm.fields.password.label')}
</Field.Label>
<Field.Row>
<PasswordInput
{...register('password', {
required: true,
validate: validatePassword,
})}
/>
</Field.Row>
{errors.password && (
<Field.Error>{errors.password.message}</Field.Error>
)}
</Field>
<Field>
<Field.Label>
{t('form.newAccountForm.fields.confirmPassword.label')}
</Field.Label>
<Field.Row>
<PasswordInput
{...register('confirmPassword', {
required: true,
validate: validateConfirmationPassword,
})}
/>
</Field.Row>
{errors.confirmPassword && (
<Field.Error>{errors.confirmPassword.message}</Field.Error>
)}
</Field>
<Field>
<Field.Row justifyContent='flex-start'>
<CheckBox
{...register('agreement', { required: true })}
mie='x8'
/>
<Box
is='label'
htmlFor='agreement'
withRichContent
fontScale='c1'
>
<Trans i18nKey='component.form.termsAndConditions'>
I agree with
<a
href='https://rocket.chat/terms'
target='_blank'
rel='noopener noreferrer'
>
Terms and Conditions
</a>
and
<a
href='https://rocket.chat/policy'
target='_blank'
rel='noopener noreferrer'
>
Privacy Polic
</a>
</Trans>
</Box>
</Field.Row>
{errors.agreement?.type === 'required' && (
<Field.Error>{t('component.form.requiredField')}</Field.Error>
)}
</Field>
</FieldGroup>
</Form.Container>
<Form.Footer>
<ButtonGroup flexGrow={1}>
<Button
type='submit'
primary
disabled={isValidating || isSubmitting || !isValid}
>
{t('component.form.action.next')}
</Button>
</ButtonGroup>
</Form.Footer>
</Form>
);
};

export default NewAccountForm;
1 change: 1 addition & 0 deletions packages/onboarding-ui/src/forms/NewAccountForm/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './NewAccountForm';
1 change: 1 addition & 0 deletions packages/onboarding-ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export { default as RegisterServerPage } from './pages/RegisterServerPage';
export { default as RequestTrialPage } from './pages/RequestTrialPage';
export { default as DarkModeProvider } from './common/DarkModeProvider';
export { default as LoginPage } from './pages/LoginPage';
export { default as CreateNewAccountPage } from './pages/CreateNewAccountPage';
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import ReactDOM from 'react-dom';

import CreateNewAccountPage from './CreateNewAccountPage';

it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(
<CreateNewAccountPage
validateEmail={() => undefined}
validatePassword={() => undefined}
validateConfirmationPassword={() => undefined}
onSubmit={() => undefined}
onLogin={() => undefined}
/>,
div
);
ReactDOM.unmountComponentAtNode(div);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Story, Meta } from '@storybook/react';
import type { ComponentProps } from 'react';

import CreateNewAccountPage from './CreateNewAccountPage';

type Args = ComponentProps<typeof CreateNewAccountPage>;

export default {
title: 'pages/CreateNewAccountPage',
component: CreateNewAccountPage,
parameters: {
actions: { argTypesRegex: '^on.*' },
layout: 'fullscreen',
},
argTypes: {
validateEmail: { action: 'validateEmail' },
validatePassword: { action: 'validatePassword' },
validateConfirmationPassword: { action: 'validateConfirmationPassword' },
},
args: {
control: { type: 'inline-radio' },
},
} as Meta<Args>;

export const _CreateNewAccountPage: Story<Args> = (args) => (
<CreateNewAccountPage {...args} />
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Box } from '@rocket.chat/fuselage';
import type { ReactElement } from 'react';
import type { SubmitHandler, Validate } from 'react-hook-form';
import { Trans, useTranslation } from 'react-i18next';

import ActionLink from '../../common/ActionLink';
import BackgroundLayer from '../../common/BackgroundLayer';
import FormPageLayout from '../../common/FormPageLayout';
import type { FormPageLayoutStyleProps } from '../../common/Types';
import NewAccountForm from '../../forms/NewAccountForm';
import type { NewAccountPayload } from '../../forms/NewAccountForm/NewAccountForm';

type CreateNewAccountPageProps = {
initialValues?: Omit<NewAccountPayload, 'password'>;
validateEmail: Validate<string>;
validatePassword: Validate<string>;
validateConfirmationPassword: Validate<string>;
onSubmit: SubmitHandler<NewAccountPayload>;
onLogin: () => void;
};

const pageLayoutStyleProps: FormPageLayoutStyleProps = {
justifyContent: 'center',
};

const CreateNewAccountPage = ({
onLogin,
...props
}: CreateNewAccountPageProps): ReactElement => {
const { t } = useTranslation();
return (
<BackgroundLayer>
<FormPageLayout
title={t('page.newAccountForm.title')}
styleProps={pageLayoutStyleProps}
>
<NewAccountForm {...props} />
<Box fontScale='h4' pbs='x40'>
<Trans i18nKey='component.createNewAccountPage'>
Already registered?
<ActionLink fontScale='h4' onClick={onLogin}>
Go to login
</ActionLink>
</Trans>
</Box>
</FormPageLayout>
</BackgroundLayer>
);
};

export default CreateNewAccountPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './CreateNewAccountPage';

0 comments on commit f443a11

Please sign in to comment.