Fix login form

This commit is contained in:
Dane Everitt 2020-07-04 14:21:28 -07:00
parent 02f83c58f5
commit 8c20158e58
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
6 changed files with 106 additions and 64 deletions

View file

@ -1,29 +1,22 @@
import React from 'react'; import React from 'react';
import MessageBox from '@/components/MessageBox'; import MessageBox from '@/components/MessageBox';
import { State, useStoreState } from 'easy-peasy'; import { useStoreState } from 'easy-peasy';
import { ApplicationStore } from '@/state';
import tw from 'twin.macro'; import tw from 'twin.macro';
type Props = Readonly<{ type Props = Readonly<{
byKey?: string; byKey?: string;
className?: string;
}>; }>;
export default ({ byKey }: Props) => { const FlashMessageRender = ({ byKey, className }: Props) => {
const flashes = useStoreState((state: State<ApplicationStore>) => state.flashes.items); const flashes = useStoreState(state => state.flashes.items.filter(
flash => byKey ? flash.key === byKey : true,
let filtered = flashes; ));
if (byKey) {
filtered = flashes.filter(flash => flash.key === byKey);
}
if (filtered.length === 0) {
return null;
}
return ( return (
<div> <div className={className}>
{ {
filtered.map((flash, index) => ( flashes.map((flash, index) => (
<React.Fragment key={flash.id || flash.type + flash.message}> <React.Fragment key={flash.id || flash.type + flash.message}>
{index > 0 && <div css={tw`mt-2`}></div>} {index > 0 && <div css={tw`mt-2`}></div>}
<MessageBox type={flash.type} title={flash.title}> <MessageBox type={flash.type} title={flash.title}>
@ -35,3 +28,5 @@ export default ({ byKey }: Props) => {
</div> </div>
); );
}; };
export default FlashMessageRender;

View file

@ -1,4 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import tw, { TwStyle } from 'twin.macro';
import styled from 'styled-components/macro';
export type FlashMessageType = 'success' | 'info' | 'warning' | 'error'; export type FlashMessageType = 'success' | 'info' | 'warning' | 'error';
@ -8,11 +10,60 @@ interface Props {
type?: FlashMessageType; type?: FlashMessageType;
} }
export default ({ title, children, type }: Props) => ( const styling = (type?: FlashMessageType): TwStyle | string => {
<div className={`lg:inline-flex alert ${type}`} role={'alert'}> switch (type) {
{title && <span className={'title'}>{title}</span>} case 'error':
<span className={'message'}> return tw`bg-red-600 border-red-800`;
case 'info':
return tw`bg-primary-600 border-primary-800`;
case 'success':
return tw`bg-green-600 border-green-800`;
case 'warning':
return tw`bg-yellow-600 border-yellow-800`;
default:
return '';
}
};
const getBackground = (type?: FlashMessageType): TwStyle | string => {
switch (type) {
case 'error':
return tw`bg-red-500`;
case 'info':
return tw`bg-primary-500`;
case 'success':
return tw`bg-green-500`;
case 'warning':
return tw`bg-yellow-500`;
default:
return '';
}
};
const Container = styled.div<{ $type?: FlashMessageType }>`
${tw`p-2 border items-center leading-normal rounded flex w-full text-sm text-white`};
${props => styling(props.$type)};
`;
Container.displayName = 'MessageBox.Container';
const MessageBox = ({ title, children, type }: Props) => (
<Container css={tw`lg:inline-flex`} $type={type} role={'alert'}>
{title &&
<span
className={'title'}
css={[
tw`flex rounded-full uppercase px-2 py-1 text-xs font-bold mr-3 leading-none`,
getBackground(type),
]}
>
{title}
</span>
}
<span css={tw`mr-2 text-left flex-auto`}>
{children} {children}
</span> </span>
</div> </Container>
); );
MessageBox.displayName = 'MessageBox';
export default MessageBox;

View file

@ -8,6 +8,8 @@ import { ApplicationStore } from '@/state';
import Field from '@/components/elements/Field'; import Field from '@/components/elements/Field';
import { Formik, FormikHelpers } from 'formik'; import { Formik, FormikHelpers } from 'formik';
import { object, string } from 'yup'; import { object, string } from 'yup';
import tw from 'twin.macro';
import Button from '@/components/elements/Button';
interface Values { interface Values {
email: string; email: string;
@ -43,33 +45,30 @@ export default () => {
{({ isSubmitting }) => ( {({ isSubmitting }) => (
<LoginFormContainer <LoginFormContainer
title={'Request Password Reset'} title={'Request Password Reset'}
className={'w-full flex'} css={tw`w-full flex`}
> >
<Field <Field
light={true} light
label={'Email'} label={'Email'}
description={'Enter your account email address to receive instructions on resetting your password.'} description={'Enter your account email address to receive instructions on resetting your password.'}
name={'email'} name={'email'}
type={'email'} type={'email'}
/> />
<div className={'mt-6'}> <div css={tw`mt-6`}>
<button <Button
type={'submit'} type={'submit'}
className={'btn btn-primary btn-jumbo flex justify-center'} size={'xlarge'}
disabled={isSubmitting} disabled={isSubmitting}
isLoading={isSubmitting}
> >
{isSubmitting ? Send Email
<div className={'spinner-circle spinner-sm spinner-white'}></div> </Button>
:
'Send Email'
}
</button>
</div> </div>
<div className={'mt-6 text-center'}> <div css={tw`mt-6 text-center`}>
<Link <Link
type={'button'} type={'button'}
to={'/auth/login'} to={'/auth/login'}
className={'text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700'} css={tw`text-xs text-neutral-500 tracking-wide uppercase no-underline hover:text-neutral-700`}
> >
Return to Login Return to Login
</Link> </Link>

View file

@ -11,6 +11,8 @@ import { httpErrorToHuman } from '@/api/http';
import { FlashMessage } from '@/state/flashes'; import { FlashMessage } from '@/state/flashes';
import ReCAPTCHA from 'react-google-recaptcha'; import ReCAPTCHA from 'react-google-recaptcha';
import Spinner from '@/components/elements/Spinner'; import Spinner from '@/components/elements/Spinner';
import tw from 'twin.macro';
import Button from '@/components/elements/Button';
type OwnProps = RouteComponentProps & { type OwnProps = RouteComponentProps & {
clearFlashes: ActionCreator<void>; clearFlashes: ActionCreator<void>;
@ -36,36 +38,29 @@ const LoginContainer = ({ isSubmitting, setFieldValue, values, submitForm, handl
{ref.current && ref.current.render()} {ref.current && ref.current.render()}
<LoginFormContainer <LoginFormContainer
title={'Login to Continue'} title={'Login to Continue'}
className={'w-full flex'} css={tw`w-full flex`}
onSubmit={submit} onSubmit={submit}
> >
<label htmlFor={'username'}>Username or Email</label>
<Field <Field
type={'text'} type={'text'}
label={'Username or Email'}
id={'username'} id={'username'}
name={'username'} name={'username'}
className={'input'} light
/> />
<div className={'mt-6'}> <div css={tw`mt-6`}>
<label htmlFor={'password'}>Password</label>
<Field <Field
type={'password'} type={'password'}
label={'Password'}
id={'password'} id={'password'}
name={'password'} name={'password'}
className={'input'} light
/> />
</div> </div>
<div className={'mt-6'}> <div css={tw`mt-6`}>
<button <Button type={'submit'} size={'xlarge'} isLoading={isSubmitting}>
type={'submit'} Login
className={'btn btn-primary btn-jumbo'} </Button>
>
{isSubmitting ?
<Spinner size={'small'} className={'mx-auto'}/>
:
'Login'
}
</button>
</div> </div>
{recaptchaEnabled && {recaptchaEnabled &&
<ReCAPTCHA <ReCAPTCHA
@ -80,10 +75,10 @@ const LoginContainer = ({ isSubmitting, setFieldValue, values, submitForm, handl
onExpired={() => setFieldValue('recaptchaData', null)} onExpired={() => setFieldValue('recaptchaData', null)}
/> />
} }
<div className={'mt-6 text-center'}> <div css={tw`mt-6 text-center`}>
<Link <Link
to={'/auth/password'} to={'/auth/password'}
className={'text-xs text-neutral-500 tracking-wide no-underline uppercase hover:text-neutral-600'} css={tw`text-xs text-neutral-500 tracking-wide no-underline uppercase hover:text-neutral-600`}
> >
Forgot password? Forgot password?
</Link> </Link>

View file

@ -1,7 +1,7 @@
import React, { forwardRef } from 'react'; import React, { forwardRef } from 'react';
import { Form } from 'formik'; import { Form } from 'formik';
import styled from 'styled-components/macro'; import styled from 'styled-components/macro';
import { breakpoint } from 'styled-components-breakpoint'; import { breakpoint } from '@/theme';
import FlashMessageRender from '@/components/FlashMessageRender'; import FlashMessageRender from '@/components/FlashMessageRender';
import tw from 'twin.macro'; import tw from 'twin.macro';
@ -30,27 +30,29 @@ const Container = styled.div`
export default forwardRef<HTMLFormElement, Props>(({ title, ...props }, ref) => ( export default forwardRef<HTMLFormElement, Props>(({ title, ...props }, ref) => (
<Container> <Container>
{title && <h2 className={'text-center text-neutral-100 font-medium py-4'}> {title &&
<h2 css={tw`text-3xl text-center text-neutral-100 font-medium py-4`}>
{title} {title}
</h2>} </h2>
<FlashMessageRender className={'mb-2 px-1'}/> }
<FlashMessageRender css={tw`mb-2 px-1`}/>
<Form {...props} ref={ref}> <Form {...props} ref={ref}>
<div className={'md:flex w-full bg-white shadow-lg rounded-lg p-6 md:pl-0 mx-1'}> <div css={tw`md:flex w-full bg-white shadow-lg rounded-lg p-6 md:pl-0 mx-1`}>
<div className={'flex-none select-none mb-6 md:mb-0 self-center'}> <div css={tw`flex-none select-none mb-6 md:mb-0 self-center`}>
<img src={'/assets/svgs/pterodactyl.svg'} className={'block w-48 md:w-64 mx-auto'}/> <img src={'/assets/svgs/pterodactyl.svg'} css={tw`block w-48 md:w-64 mx-auto`}/>
</div> </div>
<div className={'flex-1'}> <div css={tw`flex-1`}>
{props.children} {props.children}
</div> </div>
</div> </div>
</Form> </Form>
<p className={'text-center text-neutral-500 text-xs mt-4'}> <p css={tw`text-center text-neutral-500 text-xs mt-4`}>
&copy; 2015 - 2020&nbsp; &copy; 2015 - 2020&nbsp;
<a <a
rel={'noopener nofollow'} rel={'noopener nofollow noreferrer'}
href={'https://pterodactyl.io'} href={'https://pterodactyl.io'}
target={'_blank'} target={'_blank'}
className={'no-underline text-neutral-500 hover:text-neutral-300'} css={tw`no-underline text-neutral-500 hover:text-neutral-300`}
> >
Pterodactyl Software Pterodactyl Software
</a> </a>

View file

@ -22,7 +22,6 @@ const inputStyle = css<Props>`
${tw`p-3 border rounded text-sm transition-all duration-150`}; ${tw`p-3 border rounded text-sm transition-all duration-150`};
${tw`bg-neutral-600 border-neutral-500 hover:border-neutral-400 text-neutral-200 shadow-none`}; ${tw`bg-neutral-600 border-neutral-500 hover:border-neutral-400 text-neutral-200 shadow-none`};
${props => props.hasError && tw`text-red-600 border-red-500 hover:border-red-600`};
& + .input-help { & + .input-help {
${tw`mt-1 text-xs`}; ${tw`mt-1 text-xs`};
${props => props.hasError ? tw`text-red-400` : tw`text-neutral-400`}; ${props => props.hasError ? tw`text-red-400` : tw`text-neutral-400`};
@ -41,6 +40,7 @@ const inputStyle = css<Props>`
} }
${props => props.isLight && light}; ${props => props.isLight && light};
${props => props.hasError && tw`text-red-600 border-red-500 hover:border-red-600`};
`; `;
const Input = styled.input<Props>`${inputStyle}`; const Input = styled.input<Props>`${inputStyle}`;