mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-11 02:39:02 +08:00
Feature(FE): signup page (#642)
* chore: icon is updated * feat: signup page design is updated * chore: set get user pref is added * chore: svg is added * feat: signup page is updated * feat: signup page is updated
This commit is contained in:
parent
be8ec756c6
commit
1ee2e302e2
9
frontend/public/signoz-signup.svg
Normal file
9
frontend/public/signoz-signup.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 10 KiB |
24
frontend/src/api/user/getPreference.ts
Normal file
24
frontend/src/api/user/getPreference.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/user/getUserPreference';
|
||||
|
||||
const getPreference = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get(`/userPreferences`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getPreference;
|
24
frontend/src/api/user/getVersion.ts
Normal file
24
frontend/src/api/user/getVersion.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/user/getVersion';
|
||||
|
||||
const getVersion = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get(`/version`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getVersion;
|
26
frontend/src/api/user/setPreference.ts
Normal file
26
frontend/src/api/user/setPreference.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/setUserPreference';
|
||||
|
||||
const setPreference = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post(`/userPreferences`, {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default setPreference;
|
@ -9,8 +9,7 @@ const signup = async (
|
||||
): Promise<SuccessResponse<undefined> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post(`/user`, {
|
||||
email: props.email,
|
||||
name: props.name,
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
|
219
frontend/src/pages/SignUp/SignUp.tsx
Normal file
219
frontend/src/pages/SignUp/SignUp.tsx
Normal file
@ -0,0 +1,219 @@
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
notification,
|
||||
Typography,
|
||||
Switch,
|
||||
Space,
|
||||
Card,
|
||||
} from 'antd';
|
||||
import signup from 'api/user/signup';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||
|
||||
import AppActions from 'types/actions';
|
||||
const { Title } = Typography;
|
||||
import { PayloadProps } from 'types/api/user/getUserPreference';
|
||||
|
||||
import {
|
||||
ButtonContainer,
|
||||
Container,
|
||||
FormWrapper,
|
||||
Label,
|
||||
LeftContainer,
|
||||
Logo,
|
||||
MarginTop,
|
||||
} from './styles';
|
||||
import { IS_LOGGED_IN } from 'constants/auth';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import setPreference from 'api/user/setPreference';
|
||||
|
||||
const Signup = ({ version, userpref }: SignupProps): JSX.Element => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [firstName, setFirstName] = useState<string>('');
|
||||
const [email, setEmail] = useState<string>('');
|
||||
const [organizationName, setOrganisationName] = useState<string>('');
|
||||
const [hasOptedUpdates, setHasOptedUpdates] = useState<boolean>(
|
||||
userpref.hasOptedUpdates,
|
||||
);
|
||||
const [isAnonymous, setisAnonymous] = useState<boolean>(userpref.isAnonymous);
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
useEffect(() => {
|
||||
setisAnonymous(userpref.isAnonymous);
|
||||
setHasOptedUpdates(userpref.hasOptedUpdates);
|
||||
}, []);
|
||||
|
||||
const setState = (
|
||||
value: string,
|
||||
setFunction: React.Dispatch<React.SetStateAction<string>>,
|
||||
) => {
|
||||
setFunction(value);
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
|
||||
(async (): Promise<void> => {
|
||||
try {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
const userPrefernceResponse = await setPreference({
|
||||
isAnonymous,
|
||||
hasOptedUpdates,
|
||||
});
|
||||
|
||||
if (userPrefernceResponse.statusCode === 200) {
|
||||
const response = await signup({
|
||||
email: email,
|
||||
name: firstName,
|
||||
organizationName,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
setLocalStorageKey(IS_LOGGED_IN, 'yes');
|
||||
dispatch({
|
||||
type: 'LOGGED_IN',
|
||||
});
|
||||
|
||||
history.push(ROUTES.APPLICATION);
|
||||
} else {
|
||||
notification.error({
|
||||
message: 'Something went wrong',
|
||||
});
|
||||
}
|
||||
} else {
|
||||
notification.error({
|
||||
message: 'Something went wrong',
|
||||
});
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
notification.error({
|
||||
message: 'Something went wrong',
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
console.log(userpref);
|
||||
|
||||
const onSwitchHandler = (
|
||||
value: boolean,
|
||||
setFunction: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
) => {
|
||||
setFunction(value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<LeftContainer direction="vertical">
|
||||
<Space align="center">
|
||||
<Logo src={'signoz-signup.svg'} alt="logo" />
|
||||
<Title style={{ fontSize: '46px', margin: 0 }}>SigNoz</Title>
|
||||
</Space>
|
||||
<Typography>
|
||||
Monitor your applications. Find what is causing issues.
|
||||
</Typography>
|
||||
<Card
|
||||
style={{ width: 'max-content' }}
|
||||
bodyStyle={{ padding: '1px 8px', width: '100%' }}
|
||||
>
|
||||
SigNoz {version}
|
||||
</Card>
|
||||
</LeftContainer>
|
||||
|
||||
<FormWrapper>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Title level={4}>Create your account</Title>
|
||||
<div>
|
||||
<Label htmlFor="signupEmail">Email</Label>
|
||||
<Input
|
||||
placeholder="mike@netflix.com"
|
||||
type="email"
|
||||
autoFocus
|
||||
value={email}
|
||||
onChange={(e): void => {
|
||||
setState(e.target.value, setEmail);
|
||||
}}
|
||||
required
|
||||
id="signupEmail"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Label htmlFor="signupFirstName">First Name</Label>
|
||||
<Input
|
||||
placeholder="Mike"
|
||||
value={firstName}
|
||||
onChange={(e): void => {
|
||||
setState(e.target.value, setFirstName);
|
||||
}}
|
||||
required
|
||||
id="signupFirstName"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="organizationName">Organization Name</Label>
|
||||
<Input
|
||||
placeholder="Netflix"
|
||||
value={organizationName}
|
||||
onChange={(e): void => {
|
||||
setState(e.target.value, setOrganisationName);
|
||||
}}
|
||||
required
|
||||
id="organizationName"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<MarginTop marginTop={'2.4375rem'}>
|
||||
<Space>
|
||||
<Switch
|
||||
onChange={(value) => onSwitchHandler(value, setHasOptedUpdates)}
|
||||
checked={hasOptedUpdates}
|
||||
/>
|
||||
<Typography>Keep me updated on new SigNoz features</Typography>
|
||||
</Space>
|
||||
</MarginTop>
|
||||
|
||||
<MarginTop marginTop={'0.5rem'}>
|
||||
<Space>
|
||||
<Switch
|
||||
onChange={(value) => onSwitchHandler(value, setisAnonymous)}
|
||||
checked={isAnonymous}
|
||||
/>
|
||||
<Typography>
|
||||
Anonymise my usage date. We collect data to measure product usage
|
||||
</Typography>
|
||||
</Space>
|
||||
</MarginTop>
|
||||
|
||||
<ButtonContainer>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
data-attr="signup"
|
||||
loading={loading}
|
||||
disabled={loading || !email || !organizationName || !firstName}
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</form>
|
||||
</FormWrapper>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
interface SignupProps {
|
||||
version: string;
|
||||
userpref: PayloadProps;
|
||||
}
|
||||
|
||||
export default Signup;
|
@ -1,148 +1,43 @@
|
||||
import { Button, Input, notification, Typography } from 'antd';
|
||||
import signup from 'api/user/signup';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import React, { useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { ThunkDispatch } from 'redux-thunk';
|
||||
import { UserLoggedIn } from 'store/actions';
|
||||
import AppActions from 'types/actions';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import React from 'react';
|
||||
import SignUpComponent from './SignUp';
|
||||
import getVersion from 'api/user/getVersion';
|
||||
import { PayloadProps as VersionPayload } from 'types/api/user/getVersion';
|
||||
import { PayloadProps as UserPrefPayload } from 'types/api/user/getUserPreference';
|
||||
|
||||
import {
|
||||
ButtonContainer,
|
||||
Container,
|
||||
FormWrapper,
|
||||
LogoImageContainer,
|
||||
Title,
|
||||
} from './styles';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { Typography } from 'antd';
|
||||
import getPreference from 'api/user/getPreference';
|
||||
|
||||
const Signup = ({ loggedIn }: SignupProps): JSX.Element => {
|
||||
const [notificationsInstance, Element] = notification.useNotification();
|
||||
const SignUp = () => {
|
||||
const versionResponse = useFetch<VersionPayload, undefined>(getVersion);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [formState, setFormState] = useState({
|
||||
firstName: { value: '' },
|
||||
email: { value: '' },
|
||||
});
|
||||
|
||||
const updateForm = (
|
||||
name: string,
|
||||
target: EventTarget & HTMLInputElement,
|
||||
): void => {
|
||||
if (name === 'firstName') {
|
||||
setFormState({
|
||||
...formState,
|
||||
firstName: { ...formState.firstName, value: target.value },
|
||||
});
|
||||
} else if (name === 'email') {
|
||||
setFormState({
|
||||
...formState,
|
||||
email: { ...formState.email, value: target.value },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
|
||||
(async (): Promise<void> => {
|
||||
try {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
const payload = {
|
||||
first_name: formState.firstName,
|
||||
email: formState.email,
|
||||
};
|
||||
|
||||
const response = await signup({
|
||||
email: payload.email.value,
|
||||
name: payload.first_name.value,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
loggedIn();
|
||||
history.push(ROUTES.APPLICATION);
|
||||
} else {
|
||||
notificationsInstance.error({
|
||||
message: 'Something went wrong',
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
notificationsInstance.error({
|
||||
message: 'Something went wrong',
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
})();
|
||||
};
|
||||
const userPrefResponse = useFetch<UserPrefPayload, undefined>(getPreference);
|
||||
|
||||
if (versionResponse.error || userPrefResponse.error) {
|
||||
return (
|
||||
<div>
|
||||
{Element}
|
||||
|
||||
<Container direction="vertical">
|
||||
<Title>Create your account</Title>
|
||||
<Typography>
|
||||
Monitor your applications. Find what is causing issues.
|
||||
{versionResponse.errorMessage ||
|
||||
userPrefResponse.errorMessage ||
|
||||
'Somehthing went wrong'}
|
||||
</Typography>
|
||||
</Container>
|
||||
|
||||
<FormWrapper>
|
||||
<LogoImageContainer src={'signoz.svg'} alt="logo" />
|
||||
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div>
|
||||
<label htmlFor="signupEmail">Email</label>
|
||||
<Input
|
||||
placeholder="mike@netflix.com"
|
||||
type="email"
|
||||
autoFocus
|
||||
value={formState.email.value}
|
||||
onChange={(e): void => updateForm('email', e.target)}
|
||||
required
|
||||
id="signupEmail"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label htmlFor="signupFirstName">First Name</label>
|
||||
<Input
|
||||
placeholder="Mike"
|
||||
value={formState.firstName.value}
|
||||
onChange={(e): void => updateForm('firstName', e.target)}
|
||||
required
|
||||
id="signupFirstName"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ButtonContainer>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
data-attr="signup"
|
||||
loading={loading}
|
||||
disabled={loading || !formState.email.value}
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</form>
|
||||
</FormWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
versionResponse.loading ||
|
||||
versionResponse.payload === undefined ||
|
||||
userPrefResponse.loading ||
|
||||
userPrefResponse.payload === undefined
|
||||
) {
|
||||
return <Spinner tip="Loading.." />;
|
||||
}
|
||||
|
||||
const version = versionResponse.payload.version;
|
||||
|
||||
const userpref = userPrefResponse.payload;
|
||||
|
||||
return <SignUpComponent userpref={userpref} version={version} />;
|
||||
};
|
||||
|
||||
interface DispatchProps {
|
||||
loggedIn: () => void;
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (
|
||||
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
|
||||
): DispatchProps => ({
|
||||
loggedIn: bindActionCreators(UserLoggedIn, dispatch),
|
||||
});
|
||||
|
||||
type SignupProps = DispatchProps;
|
||||
|
||||
export default connect(null, mapDispatchToProps)(Signup);
|
||||
export default SignUp;
|
||||
|
@ -1,31 +1,53 @@
|
||||
import { Space, Typography } from 'antd';
|
||||
import { Card, Space } from 'antd';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled(Space)`
|
||||
export const Container = styled.div`
|
||||
&&& {
|
||||
padding-left: 2rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Title = styled(Typography)`
|
||||
&&& {
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
`;
|
||||
|
||||
export const FormWrapper = styled.div`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
|
||||
margin-top: 2rem;
|
||||
max-width: 1024px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
`;
|
||||
|
||||
export const FormWrapper = styled(Card)`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
max-width: 432px;
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const Label = styled.label`
|
||||
margin-bottom: 11px;
|
||||
margin-top: 19px;
|
||||
display: inline-block;
|
||||
font-size: 1rem;
|
||||
line-height: 24px;
|
||||
`;
|
||||
|
||||
export const LeftContainer = styled(Space)`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const ButtonContainer = styled.div`
|
||||
margin-top: 0.5rem;
|
||||
margin-top: 1.8125rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const LogoImageContainer = styled.img`
|
||||
width: 320px;
|
||||
interface Props {
|
||||
marginTop: React.CSSProperties['marginTop'];
|
||||
}
|
||||
|
||||
export const MarginTop = styled.div<Props>`
|
||||
margin-top: ${({ marginTop }) => marginTop};
|
||||
`;
|
||||
|
||||
export const Logo = styled.img`
|
||||
width: 60px;
|
||||
`;
|
||||
|
@ -1,3 +1,2 @@
|
||||
export * from './toggleDarkMode';
|
||||
export * from './toggleSettingsTab';
|
||||
export * from './userLoggedIn';
|
||||
|
@ -1,14 +0,0 @@
|
||||
import { IS_LOGGED_IN } from 'constants/auth';
|
||||
import { Dispatch } from 'redux';
|
||||
import AppActions from 'types/actions';
|
||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||
|
||||
export const UserLoggedIn = (): ((dispatch: Dispatch<AppActions>) => void) => {
|
||||
return (dispatch: Dispatch<AppActions>): void => {
|
||||
setLocalStorageKey(IS_LOGGED_IN, 'yes');
|
||||
|
||||
dispatch({
|
||||
type: 'LOGGED_IN',
|
||||
});
|
||||
};
|
||||
};
|
6
frontend/src/types/api/user/getUserPreference.ts
Normal file
6
frontend/src/types/api/user/getUserPreference.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export interface PayloadProps {
|
||||
hasOptedUpdates: boolean;
|
||||
id: number;
|
||||
isAnonymous: boolean;
|
||||
uuid: string;
|
||||
}
|
3
frontend/src/types/api/user/getVersion.ts
Normal file
3
frontend/src/types/api/user/getVersion.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export interface PayloadProps {
|
||||
version: string;
|
||||
}
|
8
frontend/src/types/api/user/setUserPreference.ts
Normal file
8
frontend/src/types/api/user/setUserPreference.ts
Normal file
@ -0,0 +1,8 @@
|
||||
export interface Props {
|
||||
isAnonymous: boolean;
|
||||
hasOptedUpdates: boolean;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
data: string;
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
export interface Props {
|
||||
email: string;
|
||||
name: string;
|
||||
organizationName: string;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user