mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-10-19 00:51:29 +08:00
465 lines
12 KiB
TypeScript
465 lines
12 KiB
TypeScript
import { Button, Form, Input, Space, Switch, Typography } from 'antd';
|
|
import logEvent from 'api/common/logEvent';
|
|
import editOrg from 'api/user/editOrg';
|
|
import getInviteDetails from 'api/user/getInviteDetails';
|
|
import loginApi from 'api/user/login';
|
|
import signUpApi from 'api/user/signup';
|
|
import afterLogin from 'AppRoutes/utils';
|
|
import WelcomeLeftContainer from 'components/WelcomeLeftContainer';
|
|
import { FeatureKeys } from 'constants/features';
|
|
import ROUTES from 'constants/routes';
|
|
import useFeatureFlag from 'hooks/useFeatureFlag';
|
|
import { useNotifications } from 'hooks/useNotifications';
|
|
import history from 'lib/history';
|
|
import { useEffect, useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { useQuery } from 'react-query';
|
|
import { useLocation } from 'react-router-dom';
|
|
import { SuccessResponse } from 'types/api';
|
|
import { PayloadProps } from 'types/api/user/getUser';
|
|
import { PayloadProps as LoginPrecheckPayloadProps } from 'types/api/user/loginPrecheck';
|
|
import { isCloudUser } from 'utils/app';
|
|
|
|
import {
|
|
ButtonContainer,
|
|
FormContainer,
|
|
FormWrapper,
|
|
Label,
|
|
MarginTop,
|
|
} from './styles';
|
|
import { isPasswordNotValidMessage, isPasswordValid } from './utils';
|
|
|
|
const { Title } = Typography;
|
|
|
|
type FormValues = {
|
|
firstName: string;
|
|
email: string;
|
|
organizationName: string;
|
|
password: string;
|
|
confirmPassword: string;
|
|
hasOptedUpdates: boolean;
|
|
isAnonymous: boolean;
|
|
};
|
|
|
|
function SignUp({ version }: SignUpProps): JSX.Element {
|
|
const { t } = useTranslation(['signup']);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const [precheck, setPrecheck] = useState<LoginPrecheckPayloadProps>({
|
|
sso: false,
|
|
isUser: false,
|
|
});
|
|
|
|
const [confirmPasswordError, setConfirmPasswordError] = useState<boolean>(
|
|
false,
|
|
);
|
|
const [isPasswordPolicyError, setIsPasswordPolicyError] = useState<boolean>(
|
|
false,
|
|
);
|
|
const { search } = useLocation();
|
|
const params = new URLSearchParams(search);
|
|
const token = params.get('token');
|
|
const [isDetailsDisable, setIsDetailsDisable] = useState<boolean>(false);
|
|
|
|
const isOnboardingEnabled = useFeatureFlag(FeatureKeys.ONBOARDING)?.active;
|
|
|
|
const getInviteDetailsResponse = useQuery({
|
|
queryFn: () =>
|
|
getInviteDetails({
|
|
inviteId: token || '',
|
|
}),
|
|
queryKey: ['getInviteDetails', token],
|
|
enabled: token !== null,
|
|
});
|
|
|
|
const { notifications } = useNotifications();
|
|
const [form] = Form.useForm<FormValues>();
|
|
|
|
useEffect(() => {
|
|
if (
|
|
getInviteDetailsResponse.status === 'success' &&
|
|
getInviteDetailsResponse.data.payload
|
|
) {
|
|
const responseDetails = getInviteDetailsResponse.data.payload;
|
|
if (responseDetails.precheck) setPrecheck(responseDetails.precheck);
|
|
form.setFieldValue('firstName', responseDetails.name);
|
|
form.setFieldValue('email', responseDetails.email);
|
|
form.setFieldValue('organizationName', responseDetails.organization);
|
|
setIsDetailsDisable(true);
|
|
|
|
logEvent('Account Creation Page Visited', {
|
|
email: responseDetails.email,
|
|
name: responseDetails.name,
|
|
company_name: responseDetails.organization,
|
|
source: 'SigNoz Cloud',
|
|
});
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [
|
|
getInviteDetailsResponse.data?.payload,
|
|
form,
|
|
getInviteDetailsResponse.status,
|
|
]);
|
|
|
|
useEffect(() => {
|
|
if (
|
|
getInviteDetailsResponse.status === 'success' &&
|
|
getInviteDetailsResponse.data?.error
|
|
) {
|
|
const { error } = getInviteDetailsResponse.data;
|
|
notifications.error({
|
|
message: error,
|
|
});
|
|
}
|
|
}, [
|
|
getInviteDetailsResponse.data,
|
|
getInviteDetailsResponse.status,
|
|
notifications,
|
|
]);
|
|
|
|
const isPreferenceVisible = token === null;
|
|
|
|
const commonHandler = async (
|
|
values: FormValues,
|
|
callback: (
|
|
e: SuccessResponse<PayloadProps>,
|
|
values: FormValues,
|
|
) => Promise<void> | VoidFunction,
|
|
): Promise<void> => {
|
|
try {
|
|
const { organizationName, password, firstName, email } = values;
|
|
const response = await signUpApi({
|
|
email,
|
|
name: firstName,
|
|
orgName: organizationName,
|
|
password,
|
|
token: params.get('token') || undefined,
|
|
});
|
|
|
|
if (response.statusCode === 200) {
|
|
const loginResponse = await loginApi({
|
|
email,
|
|
password,
|
|
});
|
|
|
|
if (loginResponse.statusCode === 200) {
|
|
const { payload } = loginResponse;
|
|
const userResponse = await afterLogin(
|
|
payload.userId,
|
|
payload.accessJwt,
|
|
payload.refreshJwt,
|
|
);
|
|
if (userResponse) {
|
|
callback(userResponse, values);
|
|
}
|
|
} else {
|
|
notifications.error({
|
|
message: loginResponse.error || t('unexpected_error'),
|
|
});
|
|
}
|
|
} else {
|
|
notifications.error({
|
|
message: response.error || t('unexpected_error'),
|
|
});
|
|
}
|
|
} catch (error) {
|
|
notifications.error({
|
|
message: t('unexpected_error'),
|
|
});
|
|
}
|
|
};
|
|
|
|
const onAdminAfterLogin = async (
|
|
userResponse: SuccessResponse<PayloadProps>,
|
|
values: FormValues,
|
|
): Promise<void> => {
|
|
const editResponse = await editOrg({
|
|
isAnonymous: values.isAnonymous,
|
|
name: values.organizationName,
|
|
hasOptedUpdates: values.hasOptedUpdates,
|
|
orgId: userResponse.payload.orgId,
|
|
});
|
|
if (editResponse.statusCode === 200) {
|
|
history.push(ROUTES.APPLICATION);
|
|
} else {
|
|
notifications.error({
|
|
message: editResponse.error || t('unexpected_error'),
|
|
});
|
|
}
|
|
};
|
|
const handleSubmitSSO = async (): Promise<void> => {
|
|
if (!params.get('token')) {
|
|
notifications.error({
|
|
message: t('token_required'),
|
|
});
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
|
|
try {
|
|
const values = form.getFieldsValue();
|
|
const response = await signUpApi({
|
|
email: values.email,
|
|
name: values.firstName,
|
|
orgName: values.organizationName,
|
|
password: values.password,
|
|
token: params.get('token') || undefined,
|
|
sourceUrl: encodeURIComponent(window.location.href),
|
|
});
|
|
|
|
if (response.statusCode === 200) {
|
|
if (response.payload?.sso) {
|
|
if (response.payload?.ssoUrl) {
|
|
window.location.href = response.payload?.ssoUrl;
|
|
} else {
|
|
notifications.error({
|
|
message: t('failed_to_initiate_login'),
|
|
});
|
|
// take user to login page as there is nothing to do here
|
|
history.push(ROUTES.LOGIN);
|
|
}
|
|
}
|
|
} else {
|
|
notifications.error({
|
|
message: response.error || t('unexpected_error'),
|
|
});
|
|
}
|
|
} catch (error) {
|
|
notifications.error({
|
|
message: t('unexpected_error'),
|
|
});
|
|
}
|
|
|
|
setLoading(false);
|
|
};
|
|
|
|
const handleSubmit = (): void => {
|
|
(async (): Promise<void> => {
|
|
try {
|
|
const values = form.getFieldsValue();
|
|
setLoading(true);
|
|
|
|
if (!isPasswordValid(values.password)) {
|
|
logEvent('Account Creation Page - Invalid Password', {
|
|
email: values.email,
|
|
name: values.firstName,
|
|
});
|
|
setIsPasswordPolicyError(true);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
if (isPreferenceVisible) {
|
|
await commonHandler(values, onAdminAfterLogin);
|
|
} else {
|
|
logEvent('Account Created Successfully', {
|
|
email: values.email,
|
|
name: values.firstName,
|
|
});
|
|
|
|
await commonHandler(
|
|
values,
|
|
async (): Promise<void> => {
|
|
if (isOnboardingEnabled && isCloudUser()) {
|
|
history.push(ROUTES.GET_STARTED);
|
|
} else {
|
|
history.push(ROUTES.APPLICATION);
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
setLoading(false);
|
|
} catch (error) {
|
|
notifications.error({
|
|
message: t('unexpected_error'),
|
|
});
|
|
setLoading(false);
|
|
}
|
|
})();
|
|
};
|
|
|
|
const getIsNameVisible = (): boolean =>
|
|
!(form.getFieldValue('firstName') === 0 && !isPreferenceVisible);
|
|
|
|
const isNameVisible = getIsNameVisible();
|
|
|
|
const handleValuesChange: (changedValues: Partial<FormValues>) => void = (
|
|
changedValues,
|
|
) => {
|
|
if ('password' in changedValues || 'confirmPassword' in changedValues) {
|
|
const { password, confirmPassword } = form.getFieldsValue();
|
|
|
|
const isInvalidPassword = !isPasswordValid(password) && password.length > 0;
|
|
setIsPasswordPolicyError(isInvalidPassword);
|
|
|
|
const isSamePassword = password === confirmPassword;
|
|
setConfirmPasswordError(!isSamePassword);
|
|
}
|
|
};
|
|
|
|
const isValidForm: () => boolean = () => {
|
|
const values = form.getFieldsValue();
|
|
return (
|
|
loading ||
|
|
!values.email ||
|
|
!values.organizationName ||
|
|
(!precheck.sso && (!values.password || !values.confirmPassword)) ||
|
|
(!isDetailsDisable && !values.firstName) ||
|
|
confirmPasswordError ||
|
|
isPasswordPolicyError
|
|
);
|
|
};
|
|
|
|
return (
|
|
<WelcomeLeftContainer version={version}>
|
|
<FormWrapper>
|
|
<FormContainer
|
|
onFinish={!precheck.sso ? handleSubmit : handleSubmitSSO}
|
|
onValuesChange={handleValuesChange}
|
|
initialValues={{ hasOptedUpdates: true, isAnonymous: false }}
|
|
form={form}
|
|
>
|
|
<Title level={4}>Create your account</Title>
|
|
<div>
|
|
<Label htmlFor="signupEmail">{t('label_email')}</Label>
|
|
<FormContainer.Item noStyle name="email">
|
|
<Input
|
|
placeholder={t('placeholder_email')}
|
|
type="email"
|
|
autoFocus
|
|
required
|
|
id="signupEmail"
|
|
disabled={isDetailsDisable}
|
|
/>
|
|
</FormContainer.Item>
|
|
</div>
|
|
|
|
{isNameVisible && (
|
|
<div>
|
|
<Label htmlFor="signupFirstName">{t('label_firstname')}</Label>{' '}
|
|
<FormContainer.Item noStyle name="firstName">
|
|
<Input
|
|
placeholder={t('placeholder_firstname')}
|
|
required
|
|
id="signupFirstName"
|
|
disabled={isDetailsDisable && form.getFieldValue('firstName')}
|
|
/>
|
|
</FormContainer.Item>
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
<Label htmlFor="organizationName">{t('label_orgname')}</Label>{' '}
|
|
<FormContainer.Item noStyle name="organizationName">
|
|
<Input
|
|
placeholder={t('placeholder_orgname')}
|
|
required
|
|
id="organizationName"
|
|
disabled={isDetailsDisable}
|
|
/>
|
|
</FormContainer.Item>
|
|
</div>
|
|
{!precheck.sso && (
|
|
<div>
|
|
<Label htmlFor="Password">{t('label_password')}</Label>{' '}
|
|
<FormContainer.Item noStyle name="password">
|
|
<Input.Password required id="currentPassword" />
|
|
</FormContainer.Item>
|
|
</div>
|
|
)}
|
|
{!precheck.sso && (
|
|
<div>
|
|
<Label htmlFor="ConfirmPassword">{t('label_confirm_password')}</Label>{' '}
|
|
<FormContainer.Item noStyle name="confirmPassword">
|
|
<Input.Password required id="confirmPassword" />
|
|
</FormContainer.Item>
|
|
{confirmPasswordError && (
|
|
<Typography.Paragraph
|
|
italic
|
|
id="password-confirm-error"
|
|
style={{
|
|
color: '#D89614',
|
|
marginTop: '0.50rem',
|
|
}}
|
|
>
|
|
{t('failed_confirm_password')}
|
|
</Typography.Paragraph>
|
|
)}
|
|
{isPasswordPolicyError && (
|
|
<Typography.Paragraph
|
|
italic
|
|
style={{
|
|
color: '#D89614',
|
|
marginTop: '0.50rem',
|
|
}}
|
|
>
|
|
{isPasswordNotValidMessage}
|
|
</Typography.Paragraph>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{isPreferenceVisible && (
|
|
<>
|
|
<MarginTop marginTop="2.4375rem">
|
|
<Space>
|
|
<FormContainer.Item
|
|
noStyle
|
|
name="hasOptedUpdates"
|
|
valuePropName="checked"
|
|
>
|
|
<Switch />
|
|
</FormContainer.Item>
|
|
|
|
<Typography>{t('prompt_keepme_posted')} </Typography>
|
|
</Space>
|
|
</MarginTop>
|
|
|
|
<MarginTop marginTop="0.5rem">
|
|
<Space>
|
|
<FormContainer.Item noStyle name="isAnonymous" valuePropName="checked">
|
|
<Switch />
|
|
</FormContainer.Item>
|
|
<Typography>{t('prompt_anonymise')}</Typography>
|
|
</Space>
|
|
</MarginTop>
|
|
</>
|
|
)}
|
|
|
|
{isPreferenceVisible && (
|
|
<Typography.Paragraph
|
|
italic
|
|
style={{
|
|
color: '#D89614',
|
|
marginTop: '0.50rem',
|
|
}}
|
|
>
|
|
This will create an admin account. If you are not an admin, please ask
|
|
your admin for an invite link
|
|
</Typography.Paragraph>
|
|
)}
|
|
|
|
<ButtonContainer>
|
|
<Button
|
|
type="primary"
|
|
htmlType="submit"
|
|
data-attr="signup"
|
|
loading={loading}
|
|
disabled={isValidForm()}
|
|
>
|
|
{t('button_get_started')}
|
|
</Button>
|
|
</ButtonContainer>
|
|
</FormContainer>
|
|
</FormWrapper>
|
|
</WelcomeLeftContainer>
|
|
);
|
|
}
|
|
|
|
interface SignUpProps {
|
|
version: string;
|
|
}
|
|
|
|
export default SignUp;
|