feat: introduce login page

This commit is contained in:
Ali BARIN
2022-03-07 18:51:57 +01:00
parent bb36748764
commit f5f7a998ca
16 changed files with 235 additions and 36 deletions

View File

@@ -1,4 +1,5 @@
import * as React from 'react';
import type { ContainerProps } from '@mui/material/Container';
import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import MuiAppBar from '@mui/material/AppBar';
@@ -10,6 +11,7 @@ import MenuIcon from '@mui/icons-material/Menu';
import MenuOpenIcon from '@mui/icons-material/MenuOpen';
import SettingsIcon from '@mui/icons-material/Settings';
import Container from 'components/Container';
import HideOnScroll from 'components/HideOnScroll';
import { FormattedMessage } from 'react-intl';
@@ -17,16 +19,24 @@ type AppBarProps = {
drawerOpen: boolean;
onDrawerOpen: () => void;
onDrawerClose: () => void;
maxWidth?: ContainerProps["maxWidth"];
};
export default function AppBar({ drawerOpen, onDrawerOpen, onDrawerClose }: AppBarProps): React.ReactElement {
export default function AppBar(props: AppBarProps): React.ReactElement {
const {
drawerOpen,
onDrawerOpen,
onDrawerClose,
maxWidth = false,
} = props;
const theme = useTheme();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('md'), { noSsr: true });
return (
<Box sx={{ flexGrow: 1 }}>
<HideOnScroll>
<MuiAppBar>
<HideOnScroll>
<MuiAppBar>
<Container maxWidth={maxWidth} disableGutters>
<Toolbar>
<IconButton
size="large"
@@ -57,8 +67,8 @@ export default function AppBar({ drawerOpen, onDrawerOpen, onDrawerClose }: AppB
<SettingsIcon />
</IconButton>
</Toolbar>
</MuiAppBar>
</HideOnScroll>
</Box>
</Container>
</MuiAppBar>
</HideOnScroll>
);
}

View File

@@ -3,15 +3,16 @@ import { FormProvider, useForm, FieldValues, SubmitHandler, UseFormReturn } from
import type { UseFormProps } from 'react-hook-form';
type FormProps = {
children: React.ReactNode;
children?: React.ReactNode;
defaultValues?: UseFormProps['defaultValues'];
onSubmit?: SubmitHandler<FieldValues>;
render?: (props: UseFormReturn) => React.ReactNode;
}
const noop = () => null;
export default function Form(props: FormProps): React.ReactElement {
const { children, onSubmit = noop, defaultValues, ...formProps } = props;
const { children, onSubmit = noop, defaultValues, render, ...formProps } = props;
const methods: UseFormReturn = useForm({
defaultValues,
});
@@ -23,7 +24,7 @@ export default function Form(props: FormProps): React.ReactElement {
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)} {...formProps}>
{children}
{render ? render(methods) : children}
</form>
</FormProvider>
);

View File

@@ -1,5 +1,4 @@
import * as React from 'react';
import { useFormContext } from 'react-hook-form';
import type { IField } from '@automatisch/types';
import PowerInput from 'components/PowerInput';
@@ -20,8 +19,6 @@ export default function InputCreator(props: InputCreatorProps): React.ReactEleme
namePrefix,
} = props;
const { control } = useFormContext();
const {
key: name,
label,
@@ -40,7 +37,6 @@ export default function InputCreator(props: InputCreatorProps): React.ReactEleme
<PowerInput
label={label}
description={description}
control={control}
name={computedName}
required={required}
// onBlur={onBlur}
@@ -62,7 +58,6 @@ export default function InputCreator(props: InputCreatorProps): React.ReactEleme
label={label}
fullWidth
helperText={description}
control={control}
clickToCopy={clickToCopy}
/>
);

View File

@@ -7,11 +7,11 @@ import AppBar from 'components/AppBar';
import Drawer from 'components/Drawer';
import Toolbar from '@mui/material/Toolbar';
type LayoutProps = {
type PublicLayoutProps = {
children: React.ReactNode;
}
export default function Layout({ children }: LayoutProps): React.ReactElement {
export default function PublicLayout({ children }: PublicLayoutProps): React.ReactElement {
const theme = useTheme();
const matchSmallScreens = useMediaQuery(theme.breakpoints.down('lg'), { noSsr: true });
const [isDrawerOpen, setDrawerOpen] = React.useState(!matchSmallScreens);
@@ -30,7 +30,7 @@ export default function Layout({ children }: LayoutProps): React.ReactElement {
onClose={closeDrawer}
/>
<Box sx={{ flex: 1 }}>
<Box sx={{ flex: 1, }}>
<Toolbar />
{children}

View File

@@ -0,0 +1,93 @@
import * as React from 'react';
import { useNavigate } from 'react-router-dom';
import { useMutation } from '@apollo/client';
import { UseFormReturn } from 'react-hook-form';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import LoadingButton from '@mui/lab/LoadingButton';
import * as URLS from 'config/urls';
import { setItem } from 'helpers/storage';
import { LOGIN } from 'graphql/mutations/login';
import Form from 'components/Form';
import TextField from 'components/TextField';
type FormValues = {
email: string;
password: string;
}
function renderFields(props: { loading: boolean }) {
const { loading = false } = props;
return (methods: UseFormReturn) => {
return (
<>
<TextField
label="Email"
name="email"
required
fullWidth
margin="dense"
/>
<TextField
label="Password"
name="password"
type="password"
required
fullWidth
margin="dense"
/>
<LoadingButton
type="submit"
variant="contained"
color="primary"
sx={{ boxShadow: 2, mt: 3 }}
loading={loading}
fullWidth
>
Login
</LoadingButton>
</>
);
}
}
function LoginForm() {
const navigate = useNavigate();
const [login, { loading }] = useMutation(LOGIN);
const handleSubmit = async (values: any) => {
const { data } = await login({
variables: {
input: values
},
});
const { token } = data.login;
setItem('token', token);
navigate(URLS.FLOWS);
};
const render = React.useMemo(() => renderFields({ loading }), [loading]);
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>
Login
</Typography>
<Form onSubmit={handleSubmit} render={render} />
</Paper>
);
};
export default LoginForm;

View File

@@ -2,10 +2,9 @@ import * as React from 'react';
import ClickAwayListener from '@mui/base/ClickAwayListener';
import Chip from '@mui/material/Chip';
import Popper from '@mui/material/Popper';
import TextField from '@mui/material/TextField';
import InputLabel from '@mui/material/InputLabel';
import FormHelperText from '@mui/material/FormHelperText';
import { Controller, Control, FieldValues } from 'react-hook-form';
import { Controller, Control, FieldValues, useFormContext } from 'react-hook-form';
import { Editor, Transforms, Range, createEditor } from 'slate';
import {
Slate,
@@ -13,7 +12,6 @@ import {
useSelected,
useFocused,
} from 'slate-react';
import type { IExecutionStep, IStep } from '@automatisch/types';
import {
serialize,
@@ -29,7 +27,6 @@ import { VariableElement } from './types';
import { processStepWithExecutions } from './data';
type PowerInputProps = {
control?: Control<FieldValues>;
onChange?: (value: string) => void;
onBlur?: (value: string) => void;
defaultValue?: string;
@@ -44,8 +41,8 @@ type PowerInputProps = {
}
const PowerInput = (props: PowerInputProps) => {
const { control } = useFormContext();
const {
control,
defaultValue = '',
onBlur,
name,

View File

@@ -0,0 +1,41 @@
import * as React from 'react';
import Toolbar from '@mui/material/Toolbar';
import MuiAppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Container from 'components/Container';
import { FormattedMessage } from 'react-intl';
type LayoutProps = {
children: React.ReactNode;
}
export default function Layout({ children }: LayoutProps): React.ReactElement {
return (
<>
<MuiAppBar>
<Container maxWidth="lg" disableGutters>
<Toolbar>
<Typography
variant="h6"
noWrap
component="div"
sx={{ flexGrow: 1 }}
>
<FormattedMessage id="brandText" />
</Typography>
</Toolbar>
</Container>
</MuiAppBar>
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
<Toolbar />
{children}
</Box>
</>
);
}

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { Controller, Control, FieldValues } from 'react-hook-form';
import { Controller, useFormContext } from 'react-hook-form';
import MuiTextField, { TextFieldProps as MuiTextFieldProps } from '@mui/material/TextField';
import IconButton from '@mui/material/IconButton';
import InputAdornment from '@mui/material/InputAdornment';
@@ -8,7 +8,6 @@ import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import copyInputValue from 'helpers/copyInputValue';
type TextFieldProps = {
control?: Control<FieldValues>;
shouldUnregister?: boolean;
name: string;
clickToCopy?: boolean;
@@ -17,21 +16,21 @@ type TextFieldProps = {
const createCopyAdornment = (ref: React.RefObject<HTMLInputElement | null>): React.ReactElement => {
return (
<InputAdornment position="end">
<IconButton
onClick={() => copyInputValue(ref.current as HTMLInputElement)}
edge="end"
>
<ContentCopyIcon color="primary" />
</IconButton>
</InputAdornment>
);
<InputAdornment position="end">
<IconButton
onClick={() => copyInputValue(ref.current as HTMLInputElement)}
edge="end"
>
<ContentCopyIcon color="primary" />
</IconButton>
</InputAdornment>
);
}
export default function TextField(props: TextFieldProps): React.ReactElement {
const { control } = useFormContext();
const inputRef = React.useRef<HTMLInputElement | null>(null);
const {
control,
required,
name,
defaultValue,