refactor(login): redesign login page (#709)

pull/701/head^2
Jakob Ankarhem 3 years ago committed by GitHub
parent e1accbcef0
commit f90e43b01a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -41,6 +41,7 @@
"pug": "^3.0.0",
"react": "17.0.1",
"react-ace": "^9.2.1",
"react-animate-height": "^2.0.23",
"react-dom": "17.0.1",
"react-intersection-observer": "^8.31.0",
"react-intl": "^5.10.16",

@ -0,0 +1,67 @@
import * as React from 'react';
import { useState } from 'react';
import AnimateHeight from 'react-animate-height';
export interface AccordionProps {
children: (args: AccordionChildProps) => React.ReactElement<any, any> | null;
/** If true, only one accordion item can be open at any time */
single?: boolean;
/** If true, at least one accordion item will always be open */
atLeastOne?: boolean;
initialOpenIndexes?: number[];
}
export interface AccordionChildProps {
openIndexes: number[];
handleClick(index: number): void;
AccordionContent: any;
}
export const AccordionContent: React.FC<{ isOpen: boolean }> = ({
isOpen,
children,
}) => {
return <AnimateHeight height={isOpen ? 'auto' : 0}>{children}</AnimateHeight>;
};
const Accordion: React.FC<AccordionProps> = ({
single,
atLeastOne,
initialOpenIndexes,
children,
}) => {
const initialState = initialOpenIndexes || (atLeastOne && [0]) || [];
const [openIndexes, setOpenIndexes] = useState<number[]>(initialState);
const close = (index: number) => {
const openCount = openIndexes.length;
const newListOfIndexes =
atLeastOne && openCount === 1 && openIndexes.includes(index)
? openIndexes
: openIndexes.filter((i) => i !== index);
setOpenIndexes(newListOfIndexes);
};
const open = (index: number) => {
const newListOfIndexes = single ? [index] : [...openIndexes, index];
setOpenIndexes(newListOfIndexes);
};
const handleItemClick = (index: number) => {
const action = openIndexes.includes(index) ? 'closing' : 'opening';
if (action === 'closing') {
close(index);
} else {
open(index);
}
};
return children({
openIndexes: openIndexes,
handleClick: handleItemClick,
AccordionContent,
});
};
export default Accordion;

@ -11,17 +11,15 @@ const messages = defineMessages({
validationemailrequired: 'Not a valid email address',
validationpasswordrequired: 'Password required',
loginerror: 'Something went wrong when trying to sign in',
loggingin: 'Logging in...',
login: 'Login',
goback: 'Go back',
signingin: 'Signing in…',
signin: 'Sign in',
});
interface LocalLoginProps {
goBack: () => void;
revalidate: () => void;
}
const LocalLogin: React.FC<LocalLoginProps> = ({ goBack, revalidate }) => {
const LocalLogin: React.FC<LocalLoginProps> = ({ revalidate }) => {
const intl = useIntl();
const [loginError, setLoginError] = useState<string | null>(null);
@ -107,18 +105,6 @@ const LocalLogin: React.FC<LocalLoginProps> = ({ goBack, revalidate }) => {
</div>
<div className="pt-5 mt-8 border-t border-gray-700">
<div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="ghost"
type="reset"
onClick={(e) => {
e.preventDefault();
goBack();
}}
>
{intl.formatMessage(messages.goback)}
</Button>
</span>
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="primary"
@ -126,8 +112,8 @@ const LocalLogin: React.FC<LocalLoginProps> = ({ goBack, revalidate }) => {
disabled={isSubmitting || !isValid}
>
{isSubmitting
? intl.formatMessage(messages.loggingin)
: intl.formatMessage(messages.login)}
? intl.formatMessage(messages.signingin)
: intl.formatMessage(messages.signin)}
</Button>
</span>
</div>

@ -7,11 +7,12 @@ import ImageFader from '../Common/ImageFader';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import Transition from '../Transition';
import LanguagePicker from '../Layout/LanguagePicker';
import Button from '../Common/Button';
import LocalLogin from './LocalLogin';
import Accordion from '../Common/Accordion';
const messages = defineMessages({
signinplex: 'Sign in to continue',
signinheader: 'Sign in to continue',
signinwithplex: 'Sign in with Plex',
signinwithoverseerr: 'Sign in with Overseerr',
});
@ -19,7 +20,6 @@ const Login: React.FC = () => {
const intl = useIntl();
const [error, setError] = useState('');
const [isProcessing, setProcessing] = useState(false);
const [localLogin, setLocalLogin] = useState(false);
const [authToken, setAuthToken] = useState<string | undefined>(undefined);
const { user, revalidate } = useUser();
const router = useRouter();
@ -56,7 +56,7 @@ const Login: React.FC = () => {
}, [user, router]);
return (
<div className="relative flex flex-col justify-center min-h-screen py-12 bg-gray-900">
<div className="relative flex flex-col min-h-screen py-14 bg-gray-900">
<ImageFader
backgroundImages={[
'/images/rotate1.jpg',
@ -77,75 +77,87 @@ const Login: React.FC = () => {
alt="Overseerr Logo"
/>
<h2 className="mt-2 text-3xl font-extrabold leading-9 text-center text-gray-100">
<FormattedMessage {...messages.signinplex} />
<FormattedMessage {...messages.signinheader} />
</h2>
</div>
<div className="relative z-50 mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div
className="px-4 py-8 bg-gray-800 bg-opacity-50 shadow sm:rounded-lg"
className="bg-gray-800 bg-opacity-50 shadow sm:rounded-lg"
style={{ backdropFilter: 'blur(5px)' }}
>
{!localLogin ? (
<>
<Transition
show={!!error}
enter="opacity-0 transition duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="opacity-100 transition duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="p-4 mb-4 bg-red-600 rounded-md">
<div className="flex">
<div className="flex-shrink-0">
<svg
className="w-5 h-5 text-red-300"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
clipRule="evenodd"
/>
</svg>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-300">
{error}
</h3>
</div>
<>
<Transition
show={!!error}
enter="opacity-0 transition duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="opacity-100 transition duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="p-4 mb-4 bg-red-600 rounded-md">
<div className="flex">
<div className="flex-shrink-0">
<svg
className="w-5 h-5 text-red-300"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
clipRule="evenodd"
/>
</svg>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-red-300">
{error}
</h3>
</div>
</div>
</Transition>
<div className="pb-4">
<PlexLoginButton
isProcessing={isProcessing}
onAuthToken={(authToken) => setAuthToken(authToken)}
/>
</div>
<span className="block w-full rounded-md shadow-sm">
<Button
buttonType="primary"
className="w-full"
// type="button"
onClick={() => {
setLocalLogin(true);
}}
>
{intl.formatMessage(messages.signinwithoverseerr)}
</Button>
</span>
</>
) : (
<LocalLogin
goBack={() => setLocalLogin(false)}
revalidate={revalidate}
/>
)}
</Transition>
<Accordion single atLeastOne>
{({ openIndexes, handleClick, AccordionContent }) => (
<>
<button
className={`text-sm w-full focus:outline-none transition-colors duration-200 py-2 bg-gray-800 hover:bg-gray-700 bg-opacity-70 hover:bg-opacity-70 rounded-t-xl text-center text-gray-400 ${
openIndexes.includes(0) && 'text-indigo-500'
}`}
onClick={() => handleClick(0)}
>
{intl.formatMessage(messages.signinwithplex)}
</button>
<AccordionContent isOpen={openIndexes.includes(0)}>
<div className="py-8 px-10">
<PlexLoginButton
isProcessing={isProcessing}
onAuthToken={(authToken) => setAuthToken(authToken)}
/>
</div>
</AccordionContent>
<button
className={`text-sm w-full focus:outline-none transition-colors duration-200 py-2 bg-gray-800 hover:bg-gray-700 bg-opacity-70 hover:bg-opacity-70 text-center text-gray-400 ${
openIndexes.includes(1)
? 'text-indigo-500'
: 'rounded-b-xl '
}`}
onClick={() => handleClick(1)}
>
{intl.formatMessage(messages.signinwithoverseerr)}
</button>
<AccordionContent isOpen={openIndexes.includes(1)}>
<div className="py-8 px-10">
<LocalLogin revalidate={revalidate} />
</div>
</AccordionContent>
</>
)}
</Accordion>
</>
</div>
</div>
</div>

@ -3,9 +3,9 @@ import PlexOAuth from '../../utils/plex';
import { defineMessages, useIntl } from 'react-intl';
const messages = defineMessages({
loginwithplex: 'Login with Plex',
loading: 'Loading...',
loggingin: 'Logging in...',
signinwithplex: 'Sign in',
loading: 'Loading',
signingin: 'Signing in…',
});
const plexOAuth = new PlexOAuth();
@ -51,8 +51,8 @@ const PlexLoginButton: React.FC<PlexLoginButtonProps> = ({
{loading
? intl.formatMessage(messages.loading)
: isProcessing
? intl.formatMessage(messages.loggingin)
: intl.formatMessage(messages.loginwithplex)}
? intl.formatMessage(messages.signingin)
: intl.formatMessage(messages.signinwithplex)}
</button>
</span>
);

@ -27,13 +27,13 @@
"components.Layout.UserDropdown.signout": "Sign Out",
"components.Layout.alphawarning": "This is ALPHA software. Almost everything is bound to be nearly broken and/or unstable. Please report issues to the Overseerr GitHub!",
"components.Login.email": "Email Address",
"components.Login.goback": "Go back",
"components.Login.loggingin": "Logging in…",
"components.Login.login": "Login",
"components.Login.loginerror": "Something went wrong when trying to sign in",
"components.Login.password": "Password",
"components.Login.signinplex": "Sign in to continue",
"components.Login.signin": "Sign in",
"components.Login.signingin": "Signing in…",
"components.Login.signinheader": "Sign in to continue",
"components.Login.signinwithoverseerr": "Sign in with Overseerr",
"components.Login.signinwithplex": "Sign in with Plex",
"components.Login.validationemailrequired": "Not a valid email address",
"components.Login.validationpasswordrequired": "Password required",
"components.MediaSlider.ShowMoreCard.seemore": "See More",
@ -83,8 +83,8 @@
"components.PersonDetails.crewmember": "Crew Member",
"components.PersonDetails.nobiography": "No biography available.",
"components.PlexLoginButton.loading": "Loading…",
"components.PlexLoginButton.loggingin": "Logging in…",
"components.PlexLoginButton.loginwithplex": "Login with Plex",
"components.PlexLoginButton.loggingin": "Signing in…",
"components.PlexLoginButton.loginwithplex": "Sign in",
"components.RequestBlock.profilechanged": "Profile Changed",
"components.RequestBlock.requestoverrides": "Request Overrides",
"components.RequestBlock.rootfolder": "Root Folder",

@ -3869,7 +3869,7 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
classnames@2.2.6:
classnames@2.2.6, classnames@^2.2.5:
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
@ -11177,7 +11177,7 @@ promzard@^0.3.0:
dependencies:
read "1"
prop-types@15.7.2, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@15.7.2, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@ -11528,6 +11528,14 @@ react-ace@^9.2.1:
lodash.isequal "^4.5.0"
prop-types "^15.7.2"
react-animate-height@^2.0.23:
version "2.0.23"
resolved "https://registry.yarnpkg.com/react-animate-height/-/react-animate-height-2.0.23.tgz#2e14ac707b20ae67b87766ccfd581e693e0e7ec7"
integrity sha512-DucSC/1QuxWEFzR9IsHMzrf2nrcZ6qAmLIFoENa2kLK7h72XybcMA9o073z7aHccFzdMEW0/fhAdnQG7a4rDow==
dependencies:
classnames "^2.2.5"
prop-types "^15.6.1"
react-dom@17.0.1:
version "17.0.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6"

Loading…
Cancel
Save