mirror of
https://github.com/datahub-project/datahub.git
synced 2025-08-12 11:17:01 +00:00
255 lines
11 KiB
TypeScript
255 lines
11 KiB
TypeScript
import React, { useCallback, useEffect, useState } from 'react';
|
|
import { Input, Button, Form, message, Image, Select } from 'antd';
|
|
import { UserOutlined, LockOutlined } from '@ant-design/icons';
|
|
import { useReactiveVar } from '@apollo/client';
|
|
import styled, { useTheme } from 'styled-components/macro';
|
|
import { useHistory } from 'react-router-dom';
|
|
import styles from './login.module.css';
|
|
import { Message } from '../shared/Message';
|
|
import { isLoggedInVar } from './checkAuthStatus';
|
|
import analytics, { EventType } from '../analytics';
|
|
import { useAppConfig } from '../useAppConfig';
|
|
import { PageRoutes } from '../../conf/Global';
|
|
import useGetInviteTokenFromUrlParams from './useGetInviteTokenFromUrlParams';
|
|
import { useAcceptRoleMutation } from '../../graphql/mutations.generated';
|
|
|
|
type FormValues = {
|
|
fullName: string;
|
|
email: string;
|
|
password: string;
|
|
confirmPassword: string;
|
|
title: string;
|
|
};
|
|
|
|
const FormInput = styled(Input)`
|
|
&&& {
|
|
height: 32px;
|
|
font-size: 12px;
|
|
border: 1px solid #555555;
|
|
border-radius: 5px;
|
|
background-color: transparent;
|
|
color: white;
|
|
line-height: 1.5715;
|
|
}
|
|
> .ant-input {
|
|
color: white;
|
|
font-size: 14px;
|
|
background-color: transparent;
|
|
}
|
|
> .ant-input:hover {
|
|
color: white;
|
|
font-size: 14px;
|
|
background-color: transparent;
|
|
}
|
|
`;
|
|
|
|
const TitleSelector = styled(Select)`
|
|
.ant-select-selector {
|
|
color: white;
|
|
border: 1px solid #555555 !important;
|
|
background-color: transparent !important;
|
|
}
|
|
.ant-select-arrow {
|
|
color: white;
|
|
}
|
|
`;
|
|
|
|
const StyledFormItem = styled(Form.Item)`
|
|
.ant-input-affix-wrapper-status-error:not(.ant-input-affix-wrapper-disabled):not(
|
|
.ant-input-affix-wrapper-borderless
|
|
).ant-input-affix-wrapper {
|
|
background-color: transparent;
|
|
}
|
|
`;
|
|
|
|
export type SignUpProps = Record<string, never>;
|
|
|
|
export const SignUp: React.VFC<SignUpProps> = () => {
|
|
const history = useHistory();
|
|
const isLoggedIn = useReactiveVar(isLoggedInVar);
|
|
const inviteToken = useGetInviteTokenFromUrlParams();
|
|
|
|
const themeConfig = useTheme();
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const { refreshContext } = useAppConfig();
|
|
|
|
const [acceptRoleMutation] = useAcceptRoleMutation();
|
|
const acceptRole = () => {
|
|
acceptRoleMutation({
|
|
variables: {
|
|
input: {
|
|
inviteToken,
|
|
},
|
|
},
|
|
})
|
|
.then(({ errors }) => {
|
|
if (!errors) {
|
|
message.success({
|
|
content: `Accepted invite!`,
|
|
duration: 2,
|
|
});
|
|
}
|
|
})
|
|
.catch((e) => {
|
|
message.destroy();
|
|
message.error({
|
|
content: `Failed to accept invite: \n ${e.message || ''}`,
|
|
duration: 3,
|
|
});
|
|
});
|
|
};
|
|
|
|
const handleSignUp = useCallback(
|
|
(values: FormValues) => {
|
|
setLoading(true);
|
|
const requestOptions = {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
fullName: values.fullName,
|
|
email: values.email,
|
|
password: values.password,
|
|
title: values.title,
|
|
inviteToken,
|
|
}),
|
|
};
|
|
fetch('/signUp', requestOptions)
|
|
.then(async (response) => {
|
|
if (!response.ok) {
|
|
const data = await response.json();
|
|
const error = (data && data.message) || response.status;
|
|
return Promise.reject(error);
|
|
}
|
|
isLoggedInVar(true);
|
|
refreshContext();
|
|
analytics.event({ type: EventType.SignUpEvent, title: values.title });
|
|
return Promise.resolve();
|
|
})
|
|
.catch((_) => {
|
|
message.error(`Failed to log in! An unexpected error occurred.`);
|
|
})
|
|
.finally(() => setLoading(false));
|
|
},
|
|
[refreshContext, inviteToken],
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (isLoggedIn && !loading) {
|
|
acceptRole();
|
|
history.push(PageRoutes.ROOT);
|
|
}
|
|
});
|
|
|
|
return (
|
|
<div className={styles.login_page}>
|
|
<div className={styles.login_box}>
|
|
<div className={styles.login_logo_box}>
|
|
<Image wrapperClassName={styles.logo_image} src={themeConfig.assets?.logoUrl} preview={false} />
|
|
</div>
|
|
<div className={styles.login_form_box}>
|
|
{loading && <Message type="loading" content="Signing up..." />}
|
|
<Form onFinish={handleSignUp} layout="vertical">
|
|
<StyledFormItem
|
|
rules={[{ required: true, message: 'Please fill in your email' }]}
|
|
name="email"
|
|
// eslint-disable-next-line jsx-a11y/label-has-associated-control
|
|
label={<label style={{ color: 'white' }}>Email</label>}
|
|
>
|
|
<FormInput prefix={<UserOutlined />} data-testid="email" />
|
|
</StyledFormItem>
|
|
<StyledFormItem
|
|
rules={[{ required: true, message: 'Please fill in your name' }]}
|
|
name="fullName"
|
|
// eslint-disable-next-line jsx-a11y/label-has-associated-control
|
|
label={<label style={{ color: 'white' }}>Full Name</label>}
|
|
>
|
|
<FormInput prefix={<UserOutlined />} data-testid="name" />
|
|
</StyledFormItem>
|
|
<StyledFormItem
|
|
rules={[
|
|
{ required: true, message: 'Please fill in your password' },
|
|
({ getFieldValue }) => ({
|
|
validator() {
|
|
if (getFieldValue('password').length < 8) {
|
|
return Promise.reject(
|
|
new Error('Your password is fewer than 8 characters'),
|
|
);
|
|
}
|
|
return Promise.resolve();
|
|
},
|
|
}),
|
|
]}
|
|
name="password"
|
|
// eslint-disable-next-line jsx-a11y/label-has-associated-control
|
|
label={<label style={{ color: 'white' }}>Password</label>}
|
|
>
|
|
<FormInput prefix={<LockOutlined />} type="password" data-testid="password" />
|
|
</StyledFormItem>
|
|
<StyledFormItem
|
|
rules={[
|
|
{ required: true, message: 'Please confirm your password' },
|
|
({ getFieldValue }) => ({
|
|
validator() {
|
|
if (getFieldValue('confirmPassword') !== getFieldValue('password')) {
|
|
return Promise.reject(new Error('Your passwords do not match'));
|
|
}
|
|
return Promise.resolve();
|
|
},
|
|
}),
|
|
]}
|
|
name="confirmPassword"
|
|
// eslint-disable-next-line jsx-a11y/label-has-associated-control
|
|
label={<label style={{ color: 'white' }}>Confirm Password</label>}
|
|
>
|
|
<FormInput prefix={<LockOutlined />} type="password" data-testid="confirmPassword" />
|
|
</StyledFormItem>
|
|
<StyledFormItem
|
|
rules={[{ required: true, message: 'Please fill in your title!' }]}
|
|
name="title"
|
|
// eslint-disable-next-line jsx-a11y/label-has-associated-control
|
|
label={<label style={{ color: 'white' }}>Title</label>}
|
|
>
|
|
<TitleSelector placeholder="Title">
|
|
<Select.Option value="Data Analyst">Data Analyst</Select.Option>
|
|
<Select.Option value="Data Engineer">Data Engineer</Select.Option>
|
|
<Select.Option value="Data Scientist">Data Scientist</Select.Option>
|
|
<Select.Option value="Software Engineer">Software Engineer</Select.Option>
|
|
<Select.Option value="Manager">Manager</Select.Option>
|
|
<Select.Option value="Product Manager">Product Manager</Select.Option>
|
|
<Select.Option value="Other">Other</Select.Option>
|
|
</TitleSelector>
|
|
</StyledFormItem>
|
|
<StyledFormItem style={{ marginBottom: '0px' }} shouldUpdate>
|
|
{({ getFieldsValue }) => {
|
|
const { fullName, email, password, confirmPassword, title } = getFieldsValue() as {
|
|
fullName: string;
|
|
email: string;
|
|
password: string;
|
|
confirmPassword: string;
|
|
title: string;
|
|
};
|
|
const fieldsAreNotEmpty =
|
|
!!fullName && !!email && !!password && !!confirmPassword && !!title;
|
|
const passwordsMatch = password === confirmPassword;
|
|
const formIsComplete = fieldsAreNotEmpty && passwordsMatch;
|
|
return (
|
|
<Button
|
|
type="primary"
|
|
block
|
|
htmlType="submit"
|
|
className={styles.login_button}
|
|
disabled={!formIsComplete}
|
|
>
|
|
Sign Up!
|
|
</Button>
|
|
);
|
|
}}
|
|
</StyledFormItem>
|
|
</Form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|