mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
refactor(login): redesign login page (#709)
This commit is contained in:
@@ -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",
|
||||
|
67
src/components/Common/Accordion/index.tsx
Normal file
67
src/components/Common/Accordion/index.tsx
Normal file
@@ -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",
|
||||
|
12
yarn.lock
12
yarn.lock
@@ -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"
|
||||
|
Reference in New Issue
Block a user