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:
palash-signoz 2022-02-09 11:44:08 +05:30 committed by GitHub
parent be8ec756c6
commit 1ee2e302e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 393 additions and 172 deletions

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View 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;

View 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;

View 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;

View File

@ -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 {

View 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;

View File

@ -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>
);
};
interface DispatchProps {
loggedIn: () => void;
}
const mapDispatchToProps = (
dispatch: ThunkDispatch<unknown, unknown, AppActions>,
): DispatchProps => ({
loggedIn: bindActionCreators(UserLoggedIn, dispatch),
});
if (
versionResponse.loading ||
versionResponse.payload === undefined ||
userPrefResponse.loading ||
userPrefResponse.payload === undefined
) {
return <Spinner tip="Loading.." />;
}
type SignupProps = DispatchProps;
const version = versionResponse.payload.version;
export default connect(null, mapDispatchToProps)(Signup);
const userpref = userPrefResponse.payload;
return <SignUpComponent userpref={userpref} version={version} />;
};
export default SignUp;

View File

@ -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;
`;

View File

@ -1,3 +1,2 @@
export * from './toggleDarkMode';
export * from './toggleSettingsTab';
export * from './userLoggedIn';

View File

@ -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',
});
};
};

View File

@ -0,0 +1,6 @@
export interface PayloadProps {
hasOptedUpdates: boolean;
id: number;
isAnonymous: boolean;
uuid: string;
}

View File

@ -0,0 +1,3 @@
export interface PayloadProps {
version: string;
}

View File

@ -0,0 +1,8 @@
export interface Props {
isAnonymous: boolean;
hasOptedUpdates: boolean;
}
export interface PayloadProps {
data: string;
}

View File

@ -1,4 +1,5 @@
export interface Props {
email: string;
name: string;
organizationName: string;
}