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",
|
"pug": "^3.0.0",
|
||||||
"react": "17.0.1",
|
"react": "17.0.1",
|
||||||
"react-ace": "^9.2.1",
|
"react-ace": "^9.2.1",
|
||||||
|
"react-animate-height": "^2.0.23",
|
||||||
"react-dom": "17.0.1",
|
"react-dom": "17.0.1",
|
||||||
"react-intersection-observer": "^8.31.0",
|
"react-intersection-observer": "^8.31.0",
|
||||||
"react-intl": "^5.10.16",
|
"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',
|
validationemailrequired: 'Not a valid email address',
|
||||||
validationpasswordrequired: 'Password required',
|
validationpasswordrequired: 'Password required',
|
||||||
loginerror: 'Something went wrong when trying to sign in',
|
loginerror: 'Something went wrong when trying to sign in',
|
||||||
loggingin: 'Logging in...',
|
signingin: 'Signing in…',
|
||||||
login: 'Login',
|
signin: 'Sign in',
|
||||||
goback: 'Go back',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
interface LocalLoginProps {
|
interface LocalLoginProps {
|
||||||
goBack: () => void;
|
|
||||||
revalidate: () => void;
|
revalidate: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LocalLogin: React.FC<LocalLoginProps> = ({ goBack, revalidate }) => {
|
const LocalLogin: React.FC<LocalLoginProps> = ({ revalidate }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [loginError, setLoginError] = useState<string | null>(null);
|
const [loginError, setLoginError] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -107,18 +105,6 @@ const LocalLogin: React.FC<LocalLoginProps> = ({ goBack, revalidate }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="pt-5 mt-8 border-t border-gray-700">
|
<div className="pt-5 mt-8 border-t border-gray-700">
|
||||||
<div className="flex justify-end">
|
<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">
|
<span className="inline-flex ml-3 rounded-md shadow-sm">
|
||||||
<Button
|
<Button
|
||||||
buttonType="primary"
|
buttonType="primary"
|
||||||
@@ -126,8 +112,8 @@ const LocalLogin: React.FC<LocalLoginProps> = ({ goBack, revalidate }) => {
|
|||||||
disabled={isSubmitting || !isValid}
|
disabled={isSubmitting || !isValid}
|
||||||
>
|
>
|
||||||
{isSubmitting
|
{isSubmitting
|
||||||
? intl.formatMessage(messages.loggingin)
|
? intl.formatMessage(messages.signingin)
|
||||||
: intl.formatMessage(messages.login)}
|
: intl.formatMessage(messages.signin)}
|
||||||
</Button>
|
</Button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -7,11 +7,12 @@ import ImageFader from '../Common/ImageFader';
|
|||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
import Transition from '../Transition';
|
import Transition from '../Transition';
|
||||||
import LanguagePicker from '../Layout/LanguagePicker';
|
import LanguagePicker from '../Layout/LanguagePicker';
|
||||||
import Button from '../Common/Button';
|
|
||||||
import LocalLogin from './LocalLogin';
|
import LocalLogin from './LocalLogin';
|
||||||
|
import Accordion from '../Common/Accordion';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
signinplex: 'Sign in to continue',
|
signinheader: 'Sign in to continue',
|
||||||
|
signinwithplex: 'Sign in with Plex',
|
||||||
signinwithoverseerr: 'Sign in with Overseerr',
|
signinwithoverseerr: 'Sign in with Overseerr',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -19,7 +20,6 @@ const Login: React.FC = () => {
|
|||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [isProcessing, setProcessing] = useState(false);
|
const [isProcessing, setProcessing] = useState(false);
|
||||||
const [localLogin, setLocalLogin] = useState(false);
|
|
||||||
const [authToken, setAuthToken] = useState<string | undefined>(undefined);
|
const [authToken, setAuthToken] = useState<string | undefined>(undefined);
|
||||||
const { user, revalidate } = useUser();
|
const { user, revalidate } = useUser();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -56,7 +56,7 @@ const Login: React.FC = () => {
|
|||||||
}, [user, router]);
|
}, [user, router]);
|
||||||
|
|
||||||
return (
|
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
|
<ImageFader
|
||||||
backgroundImages={[
|
backgroundImages={[
|
||||||
'/images/rotate1.jpg',
|
'/images/rotate1.jpg',
|
||||||
@@ -77,75 +77,87 @@ const Login: React.FC = () => {
|
|||||||
alt="Overseerr Logo"
|
alt="Overseerr Logo"
|
||||||
/>
|
/>
|
||||||
<h2 className="mt-2 text-3xl font-extrabold leading-9 text-center text-gray-100">
|
<h2 className="mt-2 text-3xl font-extrabold leading-9 text-center text-gray-100">
|
||||||
<FormattedMessage {...messages.signinplex} />
|
<FormattedMessage {...messages.signinheader} />
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative z-50 mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
<div className="relative z-50 mt-8 sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
<div
|
<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)' }}
|
style={{ backdropFilter: 'blur(5px)' }}
|
||||||
>
|
>
|
||||||
{!localLogin ? (
|
<>
|
||||||
<>
|
<Transition
|
||||||
<Transition
|
show={!!error}
|
||||||
show={!!error}
|
enter="opacity-0 transition duration-300"
|
||||||
enter="opacity-0 transition duration-300"
|
enterFrom="opacity-0"
|
||||||
enterFrom="opacity-0"
|
enterTo="opacity-100"
|
||||||
enterTo="opacity-100"
|
leave="opacity-100 transition duration-300"
|
||||||
leave="opacity-100 transition duration-300"
|
leaveFrom="opacity-100"
|
||||||
leaveFrom="opacity-100"
|
leaveTo="opacity-0"
|
||||||
leaveTo="opacity-0"
|
>
|
||||||
>
|
<div className="p-4 mb-4 bg-red-600 rounded-md">
|
||||||
<div className="p-4 mb-4 bg-red-600 rounded-md">
|
<div className="flex">
|
||||||
<div className="flex">
|
<div className="flex-shrink-0">
|
||||||
<div className="flex-shrink-0">
|
<svg
|
||||||
<svg
|
className="w-5 h-5 text-red-300"
|
||||||
className="w-5 h-5 text-red-300"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
viewBox="0 0 20 20"
|
||||||
viewBox="0 0 20 20"
|
fill="currentColor"
|
||||||
fill="currentColor"
|
aria-hidden="true"
|
||||||
aria-hidden="true"
|
>
|
||||||
>
|
<path
|
||||||
<path
|
fillRule="evenodd"
|
||||||
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"
|
||||||
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"
|
||||||
clipRule="evenodd"
|
/>
|
||||||
/>
|
</svg>
|
||||||
</svg>
|
</div>
|
||||||
</div>
|
<div className="ml-3">
|
||||||
<div className="ml-3">
|
<h3 className="text-sm font-medium text-red-300">
|
||||||
<h3 className="text-sm font-medium text-red-300">
|
{error}
|
||||||
{error}
|
</h3>
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
|
||||||
<div className="pb-4">
|
|
||||||
<PlexLoginButton
|
|
||||||
isProcessing={isProcessing}
|
|
||||||
onAuthToken={(authToken) => setAuthToken(authToken)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="block w-full rounded-md shadow-sm">
|
</Transition>
|
||||||
<Button
|
<Accordion single atLeastOne>
|
||||||
buttonType="primary"
|
{({ openIndexes, handleClick, AccordionContent }) => (
|
||||||
className="w-full"
|
<>
|
||||||
// type="button"
|
<button
|
||||||
onClick={() => {
|
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 ${
|
||||||
setLocalLogin(true);
|
openIndexes.includes(0) && 'text-indigo-500'
|
||||||
}}
|
}`}
|
||||||
>
|
onClick={() => handleClick(0)}
|
||||||
{intl.formatMessage(messages.signinwithoverseerr)}
|
>
|
||||||
</Button>
|
{intl.formatMessage(messages.signinwithplex)}
|
||||||
</span>
|
</button>
|
||||||
</>
|
<AccordionContent isOpen={openIndexes.includes(0)}>
|
||||||
) : (
|
<div className="py-8 px-10">
|
||||||
<LocalLogin
|
<PlexLoginButton
|
||||||
goBack={() => setLocalLogin(false)}
|
isProcessing={isProcessing}
|
||||||
revalidate={revalidate}
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -3,9 +3,9 @@ import PlexOAuth from '../../utils/plex';
|
|||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
loginwithplex: 'Login with Plex',
|
signinwithplex: 'Sign in',
|
||||||
loading: 'Loading...',
|
loading: 'Loading…',
|
||||||
loggingin: 'Logging in...',
|
signingin: 'Signing in…',
|
||||||
});
|
});
|
||||||
|
|
||||||
const plexOAuth = new PlexOAuth();
|
const plexOAuth = new PlexOAuth();
|
||||||
@@ -51,8 +51,8 @@ const PlexLoginButton: React.FC<PlexLoginButtonProps> = ({
|
|||||||
{loading
|
{loading
|
||||||
? intl.formatMessage(messages.loading)
|
? intl.formatMessage(messages.loading)
|
||||||
: isProcessing
|
: isProcessing
|
||||||
? intl.formatMessage(messages.loggingin)
|
? intl.formatMessage(messages.signingin)
|
||||||
: intl.formatMessage(messages.loginwithplex)}
|
: intl.formatMessage(messages.signinwithplex)}
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
@@ -27,13 +27,13 @@
|
|||||||
"components.Layout.UserDropdown.signout": "Sign Out",
|
"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.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.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.loginerror": "Something went wrong when trying to sign in",
|
||||||
"components.Login.password": "Password",
|
"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.signinwithoverseerr": "Sign in with Overseerr",
|
||||||
|
"components.Login.signinwithplex": "Sign in with Plex",
|
||||||
"components.Login.validationemailrequired": "Not a valid email address",
|
"components.Login.validationemailrequired": "Not a valid email address",
|
||||||
"components.Login.validationpasswordrequired": "Password required",
|
"components.Login.validationpasswordrequired": "Password required",
|
||||||
"components.MediaSlider.ShowMoreCard.seemore": "See More",
|
"components.MediaSlider.ShowMoreCard.seemore": "See More",
|
||||||
@@ -83,8 +83,8 @@
|
|||||||
"components.PersonDetails.crewmember": "Crew Member",
|
"components.PersonDetails.crewmember": "Crew Member",
|
||||||
"components.PersonDetails.nobiography": "No biography available.",
|
"components.PersonDetails.nobiography": "No biography available.",
|
||||||
"components.PlexLoginButton.loading": "Loading…",
|
"components.PlexLoginButton.loading": "Loading…",
|
||||||
"components.PlexLoginButton.loggingin": "Logging in…",
|
"components.PlexLoginButton.loggingin": "Signing in…",
|
||||||
"components.PlexLoginButton.loginwithplex": "Login with Plex",
|
"components.PlexLoginButton.loginwithplex": "Sign in",
|
||||||
"components.RequestBlock.profilechanged": "Profile Changed",
|
"components.RequestBlock.profilechanged": "Profile Changed",
|
||||||
"components.RequestBlock.requestoverrides": "Request Overrides",
|
"components.RequestBlock.requestoverrides": "Request Overrides",
|
||||||
"components.RequestBlock.rootfolder": "Root Folder",
|
"components.RequestBlock.rootfolder": "Root Folder",
|
||||||
|
12
yarn.lock
12
yarn.lock
@@ -3869,7 +3869,7 @@ class-utils@^0.3.5:
|
|||||||
isobject "^3.0.0"
|
isobject "^3.0.0"
|
||||||
static-extend "^0.1.1"
|
static-extend "^0.1.1"
|
||||||
|
|
||||||
classnames@2.2.6:
|
classnames@2.2.6, classnames@^2.2.5:
|
||||||
version "2.2.6"
|
version "2.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
||||||
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
||||||
@@ -11177,7 +11177,7 @@ promzard@^0.3.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
read "1"
|
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"
|
version "15.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||||
@@ -11528,6 +11528,14 @@ react-ace@^9.2.1:
|
|||||||
lodash.isequal "^4.5.0"
|
lodash.isequal "^4.5.0"
|
||||||
prop-types "^15.7.2"
|
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:
|
react-dom@17.0.1:
|
||||||
version "17.0.1"
|
version "17.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6"
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6"
|
||||||
|
Reference in New Issue
Block a user