mirror of
https://github.com/sct/overseerr.git
synced 2025-12-26 08:25:07 +01:00
feat: add divider between buttons and form on login page
This commit is contained in:
@@ -1,72 +0,0 @@
|
||||
import type * as React from 'react';
|
||||
import { useState } from 'react';
|
||||
import AnimateHeight from 'react-animate-height';
|
||||
|
||||
export interface AccordionProps {
|
||||
children: (args: AccordionChildProps) => JSX.Element;
|
||||
/** 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: typeof AccordionContent;
|
||||
}
|
||||
|
||||
type AccordionContentProps = {
|
||||
isOpen: boolean;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const AccordionContent = ({
|
||||
isOpen,
|
||||
children,
|
||||
}: AccordionContentProps) => {
|
||||
return <AnimateHeight height={isOpen ? 'auto' : 0}>{children}</AnimateHeight>;
|
||||
};
|
||||
|
||||
const Accordion = ({
|
||||
single,
|
||||
atLeastOne,
|
||||
initialOpenIndexes,
|
||||
children,
|
||||
}: AccordionProps) => {
|
||||
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;
|
||||
@@ -76,85 +76,84 @@ const LocalLogin = ({ onError }: LocalLoginProps) => {
|
||||
>
|
||||
{({ errors, touched, isSubmitting, isValid }) => {
|
||||
return (
|
||||
<>
|
||||
<Form>
|
||||
<div>
|
||||
<label htmlFor="email" className="text-label">
|
||||
{intl.formatMessage(messages.email)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
|
||||
<div className="form-input-field">
|
||||
<Field
|
||||
id="email"
|
||||
name="email"
|
||||
type="text"
|
||||
inputMode="email"
|
||||
data-testid="email"
|
||||
/>
|
||||
</div>
|
||||
{errors.email &&
|
||||
touched.email &&
|
||||
typeof errors.email === 'string' && (
|
||||
<div className="error">{errors.email}</div>
|
||||
)}
|
||||
<Form>
|
||||
<div>
|
||||
<label htmlFor="email" className="text-label">
|
||||
{intl.formatMessage(messages.email)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
|
||||
<div className="form-input-field">
|
||||
<Field
|
||||
id="email"
|
||||
name="email"
|
||||
type="text"
|
||||
inputMode="email"
|
||||
data-testid="email"
|
||||
/>
|
||||
</div>
|
||||
<label htmlFor="password" className="text-label">
|
||||
{intl.formatMessage(messages.password)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
|
||||
<div className="form-input-field">
|
||||
<SensitiveInput
|
||||
as="field"
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
data-testid="password"
|
||||
data-1pignore="false"
|
||||
data-lpignore="false"
|
||||
data-bwignore="false"
|
||||
/>
|
||||
</div>
|
||||
{errors.password &&
|
||||
touched.password &&
|
||||
typeof errors.password === 'string' && (
|
||||
<div className="error">{errors.password}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 border-t border-gray-700 pt-5">
|
||||
<div className="flex flex-row-reverse justify-between">
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<Button
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
disabled={isSubmitting || !isValid}
|
||||
data-testid="local-signin-button"
|
||||
>
|
||||
<ArrowLeftOnRectangleIcon />
|
||||
<span>
|
||||
{isSubmitting
|
||||
? intl.formatMessage(messages.signingin)
|
||||
: intl.formatMessage(messages.signin)}
|
||||
</span>
|
||||
</Button>
|
||||
</span>
|
||||
{passwordResetEnabled && (
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<Link href="/resetpassword" passHref>
|
||||
<Button as="a" buttonType="ghost">
|
||||
<LifebuoyIcon />
|
||||
<span>
|
||||
{intl.formatMessage(messages.forgotpassword)}
|
||||
</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</span>
|
||||
{errors.email &&
|
||||
touched.email &&
|
||||
typeof errors.email === 'string' && (
|
||||
<div className="error">{errors.email}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</>
|
||||
<label htmlFor="password" className="text-label">
|
||||
{intl.formatMessage(messages.password)}
|
||||
</label>
|
||||
<div className="mt-1 mb-2 sm:col-span-2 sm:mt-0">
|
||||
<div className="form-input-field">
|
||||
<SensitiveInput
|
||||
as="field"
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
data-testid="password"
|
||||
data-1pignore="false"
|
||||
data-lpignore="false"
|
||||
data-bwignore="false"
|
||||
/>
|
||||
|
||||
</div>
|
||||
{errors.password &&
|
||||
touched.password &&
|
||||
typeof errors.password === 'string' && (
|
||||
<div className="error">{errors.password}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 border-t border-gray-700 pt-5">
|
||||
<div className="flex flex-row-reverse justify-between">
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<Button
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
disabled={isSubmitting || !isValid}
|
||||
data-testid="local-signin-button"
|
||||
>
|
||||
<LoginIcon />
|
||||
<span>
|
||||
{isSubmitting
|
||||
? intl.formatMessage(messages.signingin)
|
||||
: intl.formatMessage(messages.signin)}
|
||||
</span>
|
||||
</Button>
|
||||
</span>
|
||||
{passwordResetEnabled && (
|
||||
<span className="inline-flex rounded-md shadow-sm">
|
||||
<Link href="/resetpassword" passHref>
|
||||
<Button as="a" buttonType="ghost">
|
||||
<SupportIcon />
|
||||
<span>
|
||||
{intl.formatMessage(messages.forgotpassword)}
|
||||
</span>
|
||||
</Button>
|
||||
</Link>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
|
||||
@@ -49,7 +49,7 @@ const Login = () => {
|
||||
</div>
|
||||
<div className="relative z-50 mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||
<div
|
||||
className="flex flex-col space-y-4 bg-gray-800 bg-opacity-50 p-4 shadow sm:rounded-lg "
|
||||
className="flex flex-col bg-gray-800 bg-opacity-50 p-4 shadow sm:rounded-lg "
|
||||
style={{ backdropFilter: 'blur(5px)' }}
|
||||
>
|
||||
<>
|
||||
@@ -79,61 +79,17 @@ const Login = () => {
|
||||
{settings.currentSettings.plexLoginEnabled && (
|
||||
<PlexLogin onError={(msg) => setError(msg)} />
|
||||
)}
|
||||
{settings.currentSettings.plexLoginEnabled &&
|
||||
settings.currentSettings.localLogin && (
|
||||
<div className="relative flex items-center py-4">
|
||||
<div className="flex-grow border-t border-gray-500"></div>
|
||||
<span className="mx-4 flex-shrink text-gray-400">or</span>
|
||||
<div className="flex-grow border-t border-gray-500"></div>
|
||||
</div>
|
||||
)}
|
||||
{settings.currentSettings.localLogin && (
|
||||
<LocalLogin onError={(msg) => setError(msg)} />
|
||||
)}
|
||||
{/* <Accordion single atLeastOne>
|
||||
{({ openIndexes, handleClick, AccordionContent }) => (
|
||||
<>
|
||||
{settings.currentSettings.plexLoginEnabled && (
|
||||
<>
|
||||
<button
|
||||
className={`w-full cursor-default bg-gray-800 bg-opacity-70 py-2 text-center text-sm font-bold text-gray-400 transition-colors duration-200 focus:outline-none sm:rounded-t-lg ${
|
||||
openIndexes.includes(0) && 'text-indigo-500'
|
||||
} ${
|
||||
settings.currentSettings.localLogin &&
|
||||
'hover:cursor-pointer hover:bg-gray-700'
|
||||
}`}
|
||||
onClick={() => handleClick(0)}
|
||||
disabled={!settings.currentSettings.localLogin}
|
||||
>
|
||||
{intl.formatMessage(messages.signinwithplex)}
|
||||
</button>
|
||||
<AccordionContent isOpen={openIndexes.includes(0)}>
|
||||
<div className="px-10 py-8">
|
||||
<PlexLoginButton
|
||||
isProcessing={isProcessing}
|
||||
onAuthToken={(authToken) => setAuthToken(authToken)}
|
||||
/>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</>
|
||||
)}
|
||||
{settings.currentSettings.localLogin && (
|
||||
<div>
|
||||
<button
|
||||
className={`w-full cursor-default bg-gray-800 bg-opacity-70 py-2 text-center text-sm font-bold text-gray-400 transition-colors duration-200 hover:cursor-pointer hover:bg-gray-700 focus:outline-none ${
|
||||
openIndexes.includes(1)
|
||||
? 'text-indigo-500'
|
||||
: 'sm:rounded-b-lg'
|
||||
}`}
|
||||
onClick={() => handleClick(1)}
|
||||
>
|
||||
{intl.formatMessage(messages.signinwithoverseerr, {
|
||||
applicationTitle:
|
||||
settings.currentSettings.applicationTitle,
|
||||
})}
|
||||
</button>
|
||||
<AccordionContent isOpen={openIndexes.includes(1)}>
|
||||
<div className="px-10 py-8">
|
||||
<LocalLogin revalidate={revalidate} />
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Accordion> */}
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
55
yarn.lock
55
yarn.lock
@@ -5254,11 +5254,6 @@ cidr-regex@^3.1.1:
|
||||
dependencies:
|
||||
ip-regex "^4.1.0"
|
||||
|
||||
classnames@^2.2.5:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
|
||||
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
|
||||
|
||||
clean-stack@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
|
||||
@@ -11284,7 +11279,7 @@ promzard@^0.3.0:
|
||||
dependencies:
|
||||
read "1"
|
||||
|
||||
prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
prop-types@^15.0.0, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
|
||||
version "15.8.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
@@ -11543,54 +11538,6 @@ react-ace@10.1.0:
|
||||
lodash.isequal "^4.5.0"
|
||||
prop-types "^15.7.2"
|
||||
|
||||
react-animate-height@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/react-animate-height/-/react-animate-height-2.1.2.tgz#9b450fc64d46f10f5e07da8d0d5e2c47b9f15030"
|
||||
integrity sha512-A9jfz/4CTdsIsE7WCQtO9UkOpMBcBRh8LxyHl2eoZz1ki02jpyUL5xt58gabd0CyeLQ8fRyQ+s2lyV2Ufu8Owg==
|
||||
dependencies:
|
||||
classnames "^2.2.5"
|
||||
prop-types "^15.6.1"
|
||||
|
||||
react-aria@3.23.0:
|
||||
version "3.23.0"
|
||||
resolved "https://registry.yarnpkg.com/react-aria/-/react-aria-3.23.0.tgz#8829ae47373793a7b48e19aa8af1b581021b2a9a"
|
||||
integrity sha512-CMem/+XnL3yuNHU94usRHS2+rdKLuyUzxRmQ/ndVXresflzKdaLYCH7Dtb2Um4rUEjD0BCz33hiNZPsJ7jBVQQ==
|
||||
dependencies:
|
||||
"@react-aria/breadcrumbs" "^3.5.0"
|
||||
"@react-aria/button" "^3.7.0"
|
||||
"@react-aria/calendar" "^3.1.0"
|
||||
"@react-aria/checkbox" "^3.8.0"
|
||||
"@react-aria/combobox" "^3.5.0"
|
||||
"@react-aria/datepicker" "^3.3.0"
|
||||
"@react-aria/dialog" "^3.5.0"
|
||||
"@react-aria/dnd" "^3.1.0"
|
||||
"@react-aria/focus" "^3.11.0"
|
||||
"@react-aria/gridlist" "^3.2.0"
|
||||
"@react-aria/i18n" "^3.7.0"
|
||||
"@react-aria/interactions" "^3.14.0"
|
||||
"@react-aria/label" "^3.5.0"
|
||||
"@react-aria/link" "^3.4.0"
|
||||
"@react-aria/listbox" "^3.8.0"
|
||||
"@react-aria/menu" "^3.8.0"
|
||||
"@react-aria/meter" "^3.4.0"
|
||||
"@react-aria/numberfield" "^3.4.0"
|
||||
"@react-aria/overlays" "^3.13.0"
|
||||
"@react-aria/progress" "^3.4.0"
|
||||
"@react-aria/radio" "^3.5.0"
|
||||
"@react-aria/searchfield" "^3.5.0"
|
||||
"@react-aria/select" "^3.9.0"
|
||||
"@react-aria/selection" "^3.13.0"
|
||||
"@react-aria/separator" "^3.3.0"
|
||||
"@react-aria/slider" "^3.3.0"
|
||||
"@react-aria/ssr" "^3.5.0"
|
||||
"@react-aria/switch" "^3.4.0"
|
||||
"@react-aria/table" "^3.8.0"
|
||||
"@react-aria/tabs" "^3.4.0"
|
||||
"@react-aria/textfield" "^3.9.0"
|
||||
"@react-aria/tooltip" "^3.4.0"
|
||||
"@react-aria/utils" "^3.15.0"
|
||||
"@react-aria/visually-hidden" "^3.7.0"
|
||||
|
||||
react-dom@18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
||||
|
||||
Reference in New Issue
Block a user