Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(onboarding-ui): Self-hosted registration #501

Merged
merged 13 commits into from
Jul 22, 2021
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.
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.
4 changes: 3 additions & 1 deletion packages/fuselage/src/components/Callout/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ import { ComponentProps, ForwardRefExoticComponent } from 'react';

import { Box } from '../Box';

type CalloutProps = ComponentProps<typeof Box>;
type CalloutProps = Omit<ComponentProps<typeof Box>, 'type'> & {
type?: 'info' | 'success' | 'warning' | 'danger';
};
export const Callout: ForwardRefExoticComponent<CalloutProps>;
4 changes: 2 additions & 2 deletions packages/fuselage/src/components/Callout/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
padding-inline-start: lengths.padding(16);
padding-inline-end: lengths.padding(32);

color: colors.foreground(default);

border-radius: lengths.border-radius(2);

&--type-info {
Expand Down Expand Up @@ -37,8 +39,6 @@

margin-inline-start: lengths.margin(8);

color: colors.foreground(default);

@include typography.use-font-scale(c1);
}

Expand Down
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.
Binary file not shown.
Binary file not shown.
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
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.
Diff not rendered.
Diff not rendered.
2 changes: 1 addition & 1 deletion packages/onboarding-ui/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module.exports = {
addons: ['@storybook/addon-essentials'],
stories: ['../src/**/*.stories.tsx'],
stories: ['../src/**/*.stories.tsx', '../src/**/stories.tsx'],
features: {
postcss: false,
},
Expand Down
1 change: 1 addition & 0 deletions packages/onboarding-ui/src/common/Form/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const Form: FC<{ onSubmit: () => void }> = ({
padding={40}
width='full'
maxWidth={576}
borderRadius={4}
onSubmit={onSubmit}
>
{children}
Expand Down
104 changes: 104 additions & 0 deletions packages/onboarding-ui/src/flows/SelfHostedRegistration/mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { action } from '@storybook/addon-actions';
import { countries } from 'countries-list';
import type { Validate } from 'react-hook-form';

export const logSubmit =
<T extends (...args: any[]) => any>(onSubmit: T) =>
(...args: Parameters<T>): ReturnType<T> => {
action('submit')(...args);
return onSubmit(...args);
};

const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

const simulateNetworkDelay = () => delay(3000 * Math.random());

const fetchMock =
<T extends (...args: any[]) => any>(endpoint: string, handler: T) =>
async (...args: Parameters<T>): Promise<ReturnType<T>> => {
action(`fetch(${endpoint})`)(...args);
await simulateNetworkDelay();
return handler(...args);
};

export const validateUsername = fetchMock(
'/username/validate',
(username: string) => {
if (username === 'admin') {
return `Username "${username}" is not available`;
}

return true;
}
);

export const validateEmail = fetchMock('/email/validate', (email: string) => {
if (email === 'admin@rocket.chat') {
return `Email "${email}" is already in use`;
}

return true;
});

export const validatePassword: Validate<string> = (password: string) => {
if (password.length < 6) {
return `Password is too short`;
}

return true;
};

export const organizationTypes: [string, string][] = [
['community', 'Community'],
['enterprise', 'Enterprise'],
['government', 'Government'],
['nonprofit', 'Nonprofit'],
];

export const organizationIndustryOptions: [string, string][] = [
['aerospaceDefense', 'Aerospace and Defense'],
['blockchain', 'Blockchain'],
['consulting', 'Consulting'],
['consumerGoods', 'Consumer Packaged Goods'],
['contactCenter', 'Contact Center'],
['education', 'Education'],
['entertainment', 'Entertainment'],
['financialServices', 'Financial Services'],
['gaming', 'Gaming'],
['healthcare', 'Healthcare'],
['hospitalityBusinness', 'Hospitality Businness'],
['insurance', 'Insurance'],
['itSecurity', 'IT Security'],
['logistics', 'Logistics'],
['manufacturing', 'Manufacturing'],
['media', 'Media'],
['pharmaceutical', 'Pharmaceutical'],
['realEstate', 'Real Estate'],
['religious', 'Religious'],
['retail', 'Retail'],
['socialNetwork', 'Social Network'],
['technologyProvider', 'Technology Provider'],
['technologyServices', 'Technology Services'],
['telecom', 'Telecom'],
['utilities', 'Utilities'],
['other', 'Other'],
];

export const organizationSizeOptions: [string, string][] = [
['0', '1-10 people'],
['1', '11-50 people'],
['2', '51-100 people'],
['3', '101-250 people'],
['4', '251-500 people'],
['5', '501-1000 people'],
['6', '1001-4000 people'],
['7', '4000 or more people'],
];

export const countryOptions: [string, string][] = [
...Object.entries(countries).map<[string, string]>(([code, { name }]) => [
code,
name,
]),
['worldwide', 'Worldwide'],
];
219 changes: 219 additions & 0 deletions packages/onboarding-ui/src/flows/SelfHostedRegistration/stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import { Box, Callout } from '@rocket.chat/fuselage';
import type { Meta, Story } from '@storybook/react';
import { useState } from 'react';

import type { AdminInfoPayload } from '../../forms/AdminInfoForm/AdminInfoForm';
import type { CloudAccountEmailPayload } from '../../forms/CloudAccountEmailForm/CloudAccountEmailForm';
import type { OrganizationInfoPayload } from '../../forms/OrganizationInfoForm/OrganizationInfoForm';
import type { RegisterServerPayload } from '../../forms/RegisterServerForm/RegisterServerForm';
import AdminInfoPage from '../../pages/AdminInfoPage';
import AwaitingConfirmationPage from '../../pages/AwaitingConfirmationPage';
import CloudAccountEmailPage from '../../pages/CloudAccountEmailPage';
import ConfirmationProcessPage from '../../pages/ConfirmationProcessPage';
import EmailConfirmedPage from '../../pages/EmailConfirmedPage';
import OrganizationInfoPage from '../../pages/OrganizationInfoPage';
import RegisterServerPage from '../../pages/RegisterServerPage';
import {
countryOptions,
logSubmit,
organizationIndustryOptions,
organizationSizeOptions,
organizationTypes,
validateEmail,
validatePassword,
validateUsername,
} from './mocks';

export default {
title: 'flows/Self-Hosted Registration',
parameters: {
layout: 'fullscreen',
actions: { argTypesRegex: '^on.*' },
loki: { skip: true },
},
} as Meta;

export const SelfHostedRegistration: Story = () => {
const [path, navigateTo] =
useState<`/${
| 'admin-info'
| 'org-info'
| 'register-server'
| 'cloud-email'
| 'awaiting'
| 'home'
| 'email'
| 'confirmation-progress'
| 'email-confirmed'}`>('/admin-info');

const [adminInfo, setAdminInfo] =
useState<Omit<AdminInfoPayload, 'password'>>();

const [organizationInfo, setOrganizationInfo] =
useState<OrganizationInfoPayload>();

const [serverRegistration, setServerRegistration] = useState<{
updates?: boolean;
agreement?: boolean;
cloudAccountEmail?: string;
securityCode?: string;
}>();

const handleAdminInfoSubmit = logSubmit((data: AdminInfoPayload) => {
setAdminInfo(data);
navigateTo('/org-info');
});

const handleOrganizationInfoSubmit = logSubmit(
(data: OrganizationInfoPayload) => {
setOrganizationInfo(data);
navigateTo('/register-server');
}
);

const handleRegisterServerSubmit = logSubmit(
(data: RegisterServerPayload) => {
switch (data.registerType) {
case 'standalone': {
navigateTo('/home');
break;
}

case 'registered': {
setServerRegistration((serverRegistration) => ({
...serverRegistration,
updates: data.updates,
agreement: data.agreement,
}));
navigateTo('/cloud-email');
break;
}
}
}
);

const handleCloudAccountEmailSubmit = logSubmit(
(data: CloudAccountEmailPayload) => {
setServerRegistration((serverRegistration) => ({
...serverRegistration,
cloudAccountEmail: data.email,
securityCode: 'Funny Tortoise In The Hat',
}));
navigateTo('/awaiting');
}
);

if (path === '/admin-info') {
return (
<AdminInfoPage
currentStep={1}
stepCount={4}
passwordRulesHint=''
validateUsername={validateUsername}
validateEmail={validateEmail}
validatePassword={validatePassword}
initialValues={adminInfo}
onSubmit={handleAdminInfoSubmit}
/>
);
}

if (path === '/org-info') {
return (
<OrganizationInfoPage
currentStep={2}
stepCount={4}
organizationTypeOptions={organizationTypes}
organizationIndustryOptions={organizationIndustryOptions}
organizationSizeOptions={organizationSizeOptions}
countryOptions={countryOptions}
initialValues={organizationInfo}
onBackButtonClick={() => navigateTo('/admin-info')}
onSubmit={handleOrganizationInfoSubmit}
/>
);
}

if (path === '/register-server') {
return (
<RegisterServerPage
currentStep={3}
stepCount={4}
initialValues={{
...(serverRegistration?.updates && {
updates: serverRegistration?.updates,
}),
...(serverRegistration?.agreement && {
agreement: serverRegistration?.agreement,
}),
}}
onBackButtonClick={() => navigateTo('/org-info')}
onSubmit={handleRegisterServerSubmit}
/>
);
}

if (path === '/cloud-email') {
return (
<CloudAccountEmailPage
currentStep={4}
stepCount={4}
initialValues={{}}
onBackButtonClick={() => navigateTo('/register-server')}
onSubmit={handleCloudAccountEmailSubmit}
/>
);
}

if (path === '/awaiting') {
if (!serverRegistration?.cloudAccountEmail) {
throw new Error('missing cloud account email');
}

if (!serverRegistration?.securityCode) {
throw new Error('missing verification code');
}

setTimeout(() => {
navigateTo('/confirmation-progress');
}, 5000);

return (
<AwaitingConfirmationPage
emailAddress={serverRegistration.cloudAccountEmail}
securityCode={serverRegistration.securityCode}
onChangeEmailRequest={() => navigateTo('/admin-info')}
onResendEmailRequest={() => undefined}
/>
);
}

if (path === '/confirmation-progress') {
setTimeout(() => {
navigateTo('/email-confirmed');
}, 3000);

return <ConfirmationProcessPage />;
}

if (path === '/email-confirmed') {
return <EmailConfirmedPage />;
}

if (path === '/home') {
return (
<Box
width='100vw'
height='100vh'
display='flex'
justifyContent='center'
alignItems='center'
>
<Callout type='success'>This is the home of the workspace.</Callout>
</Box>
);
}

throw new Error('invalid path');
};
SelfHostedRegistration.storyName = 'Self-Hosted Registration';
Loading