Merge pull request #970 from automatisch/add-signup

feat: add signup page
This commit is contained in:
Ali BARIN
2023-03-03 19:06:43 +01:00
committed by GitHub
10 changed files with 243 additions and 6 deletions

View File

@@ -4,11 +4,12 @@ type Params = {
input: { input: {
email: string; email: string;
password: string; password: string;
fullName: string;
}; };
}; };
const createUser = async (_parent: unknown, params: Params) => { const createUser = async (_parent: unknown, params: Params) => {
const { email, password } = params.input; const { email, password, fullName } = params.input;
const existingUser = await User.query().findOne({ email }); const existingUser = await User.query().findOne({ email });
@@ -19,6 +20,7 @@ const createUser = async (_parent: unknown, params: Params) => {
const user = await User.query().insert({ const user = await User.query().insert({
email, email,
password, password,
fullName,
role: 'user', role: 'user',
}); });

View File

@@ -336,6 +336,7 @@ input DeleteStepInput {
} }
input CreateUserInput { input CreateUserInput {
fullName: String!
email: String! email: String!
password: String! password: String!
} }

View File

@@ -1,7 +1,8 @@
import * as React from 'react'; import * as React from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate, Link as RouterLink } from 'react-router-dom';
import { useMutation } from '@apollo/client'; import { useMutation } from '@apollo/client';
import Paper from '@mui/material/Paper'; import Paper from '@mui/material/Paper';
import Link from '@mui/material/Link';
import Typography from '@mui/material/Typography'; import Typography from '@mui/material/Typography';
import LoadingButton from '@mui/lab/LoadingButton'; import LoadingButton from '@mui/lab/LoadingButton';
@@ -49,6 +50,13 @@ function renderFields(props: { loading: boolean }) {
> >
Login Login
</LoadingButton> </LoadingButton>
<Typography variant="body1" align="center" mt={3}>
Don't have an Automatisch account yet?&nbsp;
<Link component={RouterLink} to={URLS.SIGNUP} underline="none">
Sign up
</Link>
</Typography>
</> </>
); );
}; };

View File

@@ -0,0 +1,177 @@
import * as React from 'react';
import { useNavigate } from 'react-router-dom';
import { useMutation } from '@apollo/client';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import LoadingButton from '@mui/lab/LoadingButton';
import * as yup from 'yup';
import useAuthentication from 'hooks/useAuthentication';
import * as URLS from 'config/urls';
import { CREATE_USER } from 'graphql/mutations/create-user.ee';
import Form from 'components/Form';
import TextField from 'components/TextField';
import { yupResolver } from '@hookform/resolvers/yup';
import { LOGIN } from 'graphql/mutations/login';
import useFormatMessage from 'hooks/useFormatMessage';
const validationSchema = yup.object().shape({
fullName: yup.string().trim().required('signupForm.mandatoryInput'),
email: yup.string().trim().email().required('signupForm.mandatoryInput'),
password: yup.string().required('signupForm.mandatoryInput'),
confirmPassword: yup
.string()
.required('signupForm.mandatoryInput')
.oneOf([yup.ref('password')], 'signupForm.passwordMustMatch'),
});
const initialValues = {
fullName: '',
email: '',
password: '',
confirmPassword: '',
};
function SignUpForm() {
const navigate = useNavigate();
const authentication = useAuthentication();
const formatMessage = useFormatMessage();
const [createUser] = useMutation(CREATE_USER);
const [login, { loading }] = useMutation(LOGIN);
React.useEffect(() => {
if (authentication.isAuthenticated) {
navigate(URLS.DASHBOARD);
}
}, [authentication.isAuthenticated]);
const handleSubmit = async (values: any) => {
const { fullName, email, password } = values;
await createUser({
variables: {
input: { fullName, email, password },
},
});
const { data } = await login({
variables: {
input: { email, password },
},
});
const { token } = data.login;
authentication.updateToken(token);
};
return (
<Paper sx={{ px: 2, py: 4 }}>
<Typography
variant="h3"
align="center"
sx={{
borderBottom: '1px solid',
borderColor: (theme) => theme.palette.text.disabled,
pb: 2,
mb: 2,
}}
gutterBottom
>
{formatMessage('signupForm.title')}
</Typography>
<Form
defaultValues={initialValues}
onSubmit={handleSubmit}
resolver={yupResolver(validationSchema)}
mode="onChange"
render={({ formState: { errors, touchedFields } }) => (
<>
<TextField
label={formatMessage('signupForm.fullNameFieldLabel')}
name="fullName"
fullWidth
margin="dense"
autoComplete="fullName"
data-test="fullName-text-field"
error={touchedFields.fullName && !!errors?.fullName}
helperText={
touchedFields.fullName && errors?.fullName?.message
? formatMessage(errors?.fullName?.message, {
inputName: formatMessage('signupForm.fullNameFieldLabel'),
})
: ''
}
/>
<TextField
label={formatMessage('signupForm.emailFieldLabel')}
name="email"
fullWidth
margin="dense"
autoComplete="email"
data-test="email-text-field"
error={touchedFields.email && !!errors?.email}
helperText={
touchedFields.email && errors?.email?.message
? formatMessage(errors?.email?.message, {
inputName: formatMessage('signupForm.emailFieldLabel'),
})
: ''
}
/>
<TextField
label={formatMessage('signupForm.passwordFieldLabel')}
name="password"
fullWidth
margin="dense"
type="password"
error={touchedFields.password && !!errors?.password}
helperText={
touchedFields.password && errors?.password?.message
? formatMessage(errors?.password?.message, {
inputName: formatMessage('signupForm.passwordFieldLabel'),
})
: ''
}
/>
<TextField
label={formatMessage('signupForm.confirmPasswordFieldLabel')}
name="confirmPassword"
fullWidth
margin="dense"
type="password"
error={touchedFields.confirmPassword && !!errors?.confirmPassword}
helperText={
touchedFields.confirmPassword &&
errors?.confirmPassword?.message
? formatMessage(errors?.confirmPassword?.message, {
inputName: formatMessage(
'signupForm.confirmPasswordFieldLabel'
),
})
: ''
}
/>
<LoadingButton
type="submit"
variant="contained"
color="primary"
sx={{ boxShadow: 2, mt: 3 }}
loading={loading}
fullWidth
data-test="signUp-button"
>
{formatMessage('signupForm.submit')}
</LoadingButton>
</>
)}
/>
</Paper>
);
}
export default SignUpForm;

View File

@@ -5,6 +5,7 @@ export const EXECUTION = (executionId: string): string =>
`/executions/${executionId}`; `/executions/${executionId}`;
export const LOGIN = '/login'; export const LOGIN = '/login';
export const SIGNUP = '/sign-up';
export const APPS = '/apps'; export const APPS = '/apps';
export const NEW_APP_CONNECTION = '/apps/new'; export const NEW_APP_CONNECTION = '/apps/new';

View File

@@ -0,0 +1,10 @@
import { gql } from '@apollo/client';
export const CREATE_USER = gql`
mutation CreateUser($input: CreateUserInput) {
createUser(input: $input) {
email
fullName
}
}
`;

View File

@@ -105,5 +105,13 @@
"webhookUrlInfo.title": "Your webhook URL", "webhookUrlInfo.title": "Your webhook URL",
"webhookUrlInfo.description": "You'll need to configure your application with this webhook URL.", "webhookUrlInfo.description": "You'll need to configure your application with this webhook URL.",
"webhookUrlInfo.helperText": "We've generated a custom webhook URL for you to send requests to. <link>Learn more about webhooks</link>.", "webhookUrlInfo.helperText": "We've generated a custom webhook URL for you to send requests to. <link>Learn more about webhooks</link>.",
"webhookUrlInfo.copy": "Copy" "webhookUrlInfo.copy": "Copy",
"signupForm.title": "Sign up",
"signupForm.fullNameFieldLabel": "Full name",
"signupForm.emailFieldLabel": "Email",
"signupForm.passwordFieldLabel": "Password",
"signupForm.confirmPasswordFieldLabel": "Confirm password",
"signupForm.submit": "Sign up",
"signupForm.passwordMustMatch": "Passwords must match.",
"signupForm.mandatoryInput": "{inputName} is required."
} }

View File

@@ -158,7 +158,9 @@ function ProfileSettings() {
margin="normal" margin="normal"
type="password" type="password"
error={touchedFields.password && !!errors?.password} error={touchedFields.password && !!errors?.password}
helperText={errors?.password?.message || ' '} helperText={
(touchedFields.password && errors?.password?.message) || ''
}
/> />
<TextField <TextField
@@ -170,7 +172,11 @@ function ProfileSettings() {
error={ error={
touchedFields.confirmPassword && !!errors?.confirmPassword touchedFields.confirmPassword && !!errors?.confirmPassword
} }
helperText={errors?.confirmPassword?.message || ' '} helperText={
(touchedFields.confirmPassword &&
errors?.confirmPassword?.message) ||
' '
}
/> />
<Button <Button

View File

@@ -0,0 +1,14 @@
import * as React from 'react';
import Box from '@mui/material/Box';
import Container from 'components/Container';
import SignUpForm from 'components/SignUpForm/index.ee';
export default function Login(): React.ReactElement {
return (
<Box sx={{ display: 'flex', flex: 1, alignItems: 'center' }}>
<Container maxWidth="sm">
<SignUpForm />
</Container>
</Box>
);
}

View File

@@ -8,6 +8,7 @@ import Execution from 'pages/Execution';
import Flows from 'pages/Flows'; import Flows from 'pages/Flows';
import Flow from 'pages/Flow'; import Flow from 'pages/Flow';
import Login from 'pages/Login'; import Login from 'pages/Login';
import SignUp from 'pages/SignUp/index.ee';
import EditorRoutes from 'pages/Editor/routes'; import EditorRoutes from 'pages/Editor/routes';
import * as URLS from 'config/urls'; import * as URLS from 'config/urls';
import settingsRoutes from './settingsRoutes'; import settingsRoutes from './settingsRoutes';
@@ -80,6 +81,15 @@ export default (
} }
/> />
<Route
path={URLS.SIGNUP}
element={
<PublicLayout>
<SignUp />
</PublicLayout>
}
/>
<Route <Route
path={URLS.UPDATES} path={URLS.UPDATES}
element={ element={