fix(ui): uniform-size checkboxes, vertically-aligned form labels, and fixes for other UI imperfections/inconsistencies (#737)

This commit is contained in:
TheCatLady
2021-02-02 02:20:05 -05:00
committed by GitHub
parent bfe25d9755
commit e34fbf72fd
55 changed files with 1230 additions and 1542 deletions

View File

@@ -77,7 +77,7 @@ const Alert: React.FC<AlertProps> = ({ title, children, type }) => {
} }
return ( return (
<div className={`rounded-md p-4 mb-8 ${design.bgColor}`}> <div className={`rounded-md p-4 mb-5 ${design.bgColor}`}>
<div className="flex"> <div className="flex">
<div className={`flex-shrink-0 ${design.titleColor}`}>{design.svg}</div> <div className={`flex-shrink-0 ${design.titleColor}`}>{design.svg}</div>
<div className="ml-3"> <div className="ml-3">

View File

@@ -11,14 +11,14 @@ const Header: React.FC<HeaderProps> = ({
subtext, subtext,
}) => { }) => {
return ( return (
<div className="md:flex md:items-center md:justify-between mt-8 mb-8"> <div className="mt-8 md:flex md:items-center md:justify-between">
<div className={`flex-1 min-w-0 mx-${extraMargin}`}> <div className={`flex-1 min-w-0 mx-${extraMargin}`}>
<h2 className="text-2xl font-bold leading-7 text-gray-100 sm:text-4xl sm:leading-9 truncate sm:overflow-visible"> <h2 className="mb-4 text-2xl font-bold leading-7 text-gray-100 truncate sm:text-4xl sm:leading-9 sm:overflow-visible md:mb-0">
<span className="bg-clip-text text-transparent bg-gradient-to-br from-indigo-400 to-purple-400"> <span className="text-transparent bg-clip-text bg-gradient-to-br from-indigo-400 to-purple-400">
{children} {children}
</span> </span>
</h2> </h2>
{subtext && <div className="text-gray-400 mt-2">{subtext}</div>} {subtext && <div className="mt-2 text-gray-400">{subtext}</div>}
</div> </div>
</div> </div>
); );

View File

@@ -8,8 +8,8 @@ interface ListItemProps {
const ListItem: React.FC<ListItemProps> = ({ title, children }) => { const ListItem: React.FC<ListItemProps> = ({ title, children }) => {
return ( return (
<div className="py-4 sm:grid sm:grid-cols-3 sm:gap-4"> <div className="py-4 sm:grid sm:grid-cols-3 sm:gap-4">
<dt className="text-sm font-medium text-gray-200">{title}</dt> <dt className="block text-sm font-medium text-gray-400">{title}</dt>
<dd className="mt-1 flex text-sm text-gray-400 sm:mt-0 sm:col-span-2"> <dd className="flex text-sm text-white sm:mt-0 sm:col-span-2">
<span className="flex-grow">{children}</span> <span className="flex-grow">{children}</span>
</dd> </dd>
</div> </div>
@@ -25,12 +25,10 @@ const List: React.FC<ListProps> = ({ title, subTitle, children }) => {
return ( return (
<> <>
<div> <div>
<h3 className="text-lg leading-6 font-medium text-gray-100">{title}</h3> <h3 className="heading">{title}</h3>
{subTitle && ( {subTitle && <p className="description">{subTitle}</p>}
<p className="mt-1 max-w-2xl text-sm text-gray-300">{subTitle}</p>
)}
</div> </div>
<div className="mt-5 border-t border-gray-800"> <div className="border-t border-gray-800 section">
<dl className="divide-y divide-gray-800">{children}</dl> <dl className="divide-y divide-gray-800">{children}</dl>
</div> </div>
</> </>

View File

@@ -112,7 +112,7 @@ const Modal: React.FC<ModalProps> = ({
)} )}
<div <div
className={`mt-3 text-center sm:mt-0 sm:text-left ${ className={`mt-3 text-center sm:mt-0 sm:text-left ${
iconSvg ? 'sm:ml-4' : 'mb-6' iconSvg ? 'sm:ml-4' : 'sm:mb-4'
}`} }`}
> >
{title && ( {title && (

View File

@@ -3,7 +3,7 @@ import { withProperties } from '../../../utils/typeHelpers';
const TBody: React.FC = ({ children }) => { const TBody: React.FC = ({ children }) => {
return ( return (
<tbody className="bg-gray-600 divide-y divide-gray-700">{children}</tbody> <tbody className="bg-gray-800 divide-y divide-gray-700">{children}</tbody>
); );
}; };

View File

@@ -68,9 +68,11 @@ const DiscoverMovies: React.FC = () => {
return ( return (
<> <>
<Header> <div className="mt-1 mb-5">
<FormattedMessage {...messages.discovermovies} /> <Header>
</Header> <FormattedMessage {...messages.discovermovies} />
</Header>
</div>
<ListView <ListView
items={titles} items={titles}
isEmpty={isEmpty} isEmpty={isEmpty}

View File

@@ -67,9 +67,11 @@ const DiscoverTv: React.FC = () => {
return ( return (
<> <>
<Header> <div className="mt-1 mb-5">
<FormattedMessage {...messages.discovertv} /> <Header>
</Header> <FormattedMessage {...messages.discovertv} />
</Header>
</div>
<ListView <ListView
items={titles} items={titles}
isEmpty={isEmpty} isEmpty={isEmpty}

View File

@@ -74,9 +74,11 @@ const Trending: React.FC = () => {
return ( return (
<> <>
<Header> <div className="mt-1 mb-5">
<FormattedMessage {...messages.trending} /> <Header>
</Header> <FormattedMessage {...messages.trending} />
</Header>
</div>
<ListView <ListView
items={titles} items={titles}
isEmpty={isEmpty} isEmpty={isEmpty}

View File

@@ -69,9 +69,11 @@ const UpcomingMovies: React.FC = () => {
return ( return (
<> <>
<Header> <div className="mt-1 mb-5">
<FormattedMessage {...messages.upcomingmovies} /> <Header>
</Header> <FormattedMessage {...messages.upcomingmovies} />
</Header>
</div>
<ListView <ListView
items={titles} items={titles}
isEmpty={isEmpty} isEmpty={isEmpty}

View File

@@ -117,10 +117,10 @@ const LanguagePicker: React.FC = () => {
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<div <div
className="absolute right-0 w-48 mt-2 origin-top-right rounded-md shadow-lg" className="absolute right-0 w-56 mt-2 origin-top-right rounded-md shadow-lg"
ref={dropdownRef} ref={dropdownRef}
> >
<div className="px-2 py-2 bg-gray-700 rounded-md ring-1 ring-black ring-opacity-5"> <div className="px-3 py-2 bg-gray-700 rounded-md ring-1 ring-black ring-opacity-5">
<div> <div>
<label <label
htmlFor="language" htmlFor="language"
@@ -130,7 +130,7 @@ const LanguagePicker: React.FC = () => {
</label> </label>
<select <select
id="language" id="language"
className="block w-full py-2 pl-3 pr-10 mt-1 text-base leading-6 text-white bg-gray-700 border-gray-600 form-select focus:outline-none focus:ring-indigo focus:border-blue-800 sm:text-sm sm:leading-5" className="rounded-md"
onChange={(e) => onChange={(e) =>
setLocale && setLocale(e.target.value as AvailableLocales) setLocale && setLocale(e.target.value as AvailableLocales)
} }

View File

@@ -201,7 +201,7 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
}} }}
role="button" role="button"
tabIndex={0} tabIndex={0}
className={`group flex items-center px-2 py-2 text-base leading-6 font-medium rounded-md text-white focus:outline-none focus:bg-gray-700 transition ease-in-out duration-150 className={`flex items-center px-2 py-2 text-base leading-6 font-medium rounded-md text-white focus:outline-none focus:bg-gray-700 transition ease-in-out duration-150
${ ${
router.pathname.match( router.pathname.match(
sidebarLink.activeRegExp sidebarLink.activeRegExp
@@ -255,7 +255,7 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
as={sidebarLink.as} as={sidebarLink.as}
> >
<a <a
className={`group flex items-center px-2 py-2 text-base leading-6 font-medium rounded-md text-white focus:outline-none focus:bg-gray-700 transition ease-in-out duration-150 className={`flex items-center px-2 py-2 text-base leading-6 font-medium rounded-md text-white focus:outline-none focus:bg-gray-700 transition ease-in-out duration-150
${ ${
router.pathname.match( router.pathname.match(
sidebarLink.activeRegExp sidebarLink.activeRegExp

View File

@@ -52,10 +52,10 @@ const Layout: React.FC = ({ children }) => {
</div> </div>
<main className="relative z-0 top-16 focus:outline-none" tabIndex={0}> <main className="relative z-0 top-16 focus:outline-none" tabIndex={0}>
<div className="pt-2 pb-6"> <div className="pt-2 mb-6">
<div className="px-4 mx-auto max-w-8xl"> <div className="px-4 mx-auto max-w-8xl">
{router.pathname === '/' && hasPermission(Permission.ADMIN) && ( {router.pathname === '/' && hasPermission(Permission.ADMIN) && (
<div className="p-4 mt-2 bg-indigo-700 rounded-md"> <div className="p-4 mt-6 bg-indigo-700 rounded-md">
<div className="flex"> <div className="flex">
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<svg <svg

View File

@@ -57,10 +57,7 @@ const LocalLogin: React.FC<LocalLoginProps> = ({ revalidate }) => {
<> <>
<Form> <Form>
<div className="sm:border-t sm:border-gray-800"> <div className="sm:border-t sm:border-gray-800">
<label <label htmlFor="email" className="text-label">
htmlFor="email"
className="block my-1 text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.email)} {intl.formatMessage(messages.email)}
</label> </label>
<div className="mt-1 mb-2 sm:mt-0 sm:col-span-2"> <div className="mt-1 mb-2 sm:mt-0 sm:col-span-2">
@@ -70,17 +67,13 @@ const LocalLogin: React.FC<LocalLoginProps> = ({ revalidate }) => {
name="email" name="email"
type="text" type="text"
placeholder="name@example.com" placeholder="name@example.com"
className="text-white flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.email && touched.email && ( {errors.email && touched.email && (
<div className="mt-2 text-red-500">{errors.email}</div> <div className="error">{errors.email}</div>
)} )}
</div> </div>
<label <label htmlFor="password" className="text-label">
htmlFor="password"
className="block my-1 text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.password)} {intl.formatMessage(messages.password)}
</label> </label>
<div className="mt-1 mb-2 sm:mt-0 sm:col-span-2"> <div className="mt-1 mb-2 sm:mt-0 sm:col-span-2">
@@ -90,20 +83,19 @@ const LocalLogin: React.FC<LocalLoginProps> = ({ revalidate }) => {
name="password" name="password"
type="password" type="password"
placeholder={intl.formatMessage(messages.password)} placeholder={intl.formatMessage(messages.password)}
className="text-white flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.password && touched.password && ( {errors.password && touched.password && (
<div className="mt-2 text-red-500">{errors.password}</div> <div className="error">{errors.password}</div>
)} )}
</div> </div>
{loginError && ( {loginError && (
<div className="mt-1 mb-2 sm:mt-0 sm:col-span-2"> <div className="mt-1 mb-2 sm:mt-0 sm:col-span-2">
<div className="mt-2 text-red-500">{loginError}</div> <div className="error">{loginError}</div>
</div> </div>
)} )}
</div> </div>
<div className="pt-5 mt-8 border-t border-gray-700"> <div className="actions">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button

View File

@@ -32,15 +32,17 @@ const MovieCast: React.FC = () => {
return ( return (
<> <>
<Header <div className="mt-1 mb-5">
subtext={ <Header
<Link href={`/movie/${data.id}`}> subtext={
<a className="hover:underline">{data.title}</a> <Link href={`/movie/${data.id}`}>
</Link> <a className="hover:underline">{data.title}</a>
} </Link>
> }
{intl.formatMessage(messages.fullcast)} >
</Header> {intl.formatMessage(messages.fullcast)}
</Header>
</div>
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8"> <ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8">
{data?.credits.cast.map((person, index) => { {data?.credits.cast.map((person, index) => {
return ( return (

View File

@@ -32,15 +32,17 @@ const MovieCrew: React.FC = () => {
return ( return (
<> <>
<Header <div className="mt-1 mb-5">
subtext={ <Header
<Link href={`/movie/${data.id}`}> subtext={
<a className="hover:underline">{data.title}</a> <Link href={`/movie/${data.id}`}>
</Link> <a className="hover:underline">{data.title}</a>
} </Link>
> }
{intl.formatMessage(messages.fullcrew)} >
</Header> {intl.formatMessage(messages.fullcrew)}
</Header>
</div>
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8"> <ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8">
{data?.credits.crew.map((person, index) => { {data?.credits.crew.map((person, index) => {
return ( return (

View File

@@ -77,17 +77,19 @@ const MovieRecommendations: React.FC = () => {
return ( return (
<> <>
<Header <div className="mt-1 mb-5">
subtext={ <Header
movieData && !movieError subtext={
? intl.formatMessage(messages.recommendationssubtext, { movieData && !movieError
title: movieData.title, ? intl.formatMessage(messages.recommendationssubtext, {
}) title: movieData.title,
: '' })
} : ''
> }
<FormattedMessage {...messages.recommendations} /> >
</Header> <FormattedMessage {...messages.recommendations} />
</Header>
</div>
<ListView <ListView
items={titles} items={titles}
isEmpty={isEmpty} isEmpty={isEmpty}

View File

@@ -77,17 +77,19 @@ const MovieSimilar: React.FC = () => {
return ( return (
<> <>
<Header <div className="mt-1 mb-5">
subtext={ <Header
movieData && !movieError subtext={
? intl.formatMessage(messages.similarsubtext, { movieData && !movieError
title: movieData.title, ? intl.formatMessage(messages.similarsubtext, {
}) title: movieData.title,
: undefined })
} : undefined
> }
<FormattedMessage {...messages.similar} /> >
</Header> <FormattedMessage {...messages.similar} />
</Header>
</div>
<ListView <ListView
items={titles} items={titles}
isEmpty={isEmpty} isEmpty={isEmpty}

View File

@@ -178,50 +178,56 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
{data?.mediaInfo && {data?.mediaInfo &&
(data.mediaInfo.status !== MediaStatus.AVAILABLE || (data.mediaInfo.status !== MediaStatus.AVAILABLE ||
data.mediaInfo.status4k !== MediaStatus.AVAILABLE) && ( data.mediaInfo.status4k !== MediaStatus.AVAILABLE) && (
<div className="flex flex-col mb-6 sm:flex-row flex-nowrap"> <div className="mb-6">
{data?.mediaInfo && {data?.mediaInfo &&
data?.mediaInfo.status !== MediaStatus.AVAILABLE && ( data?.mediaInfo.status !== MediaStatus.AVAILABLE && (
<Button <div className="flex flex-col sm:flex-row flex-nowrap mb-2">
onClick={() => markAvailable()} <Button
className="w-full mb-2 sm:mb-0 sm:mr-1 last:mr-0" onClick={() => markAvailable()}
buttonType="success" className="w-full sm:mb-0"
> buttonType="success"
<svg
className="w-5 h-5 mr-1"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
> >
<path <svg
fillRule="evenodd" className="w-5 h-5 mr-1"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" fill="currentColor"
clipRule="evenodd" viewBox="0 0 20 20"
/> xmlns="http://www.w3.org/2000/svg"
</svg> >
<span>{intl.formatMessage(messages.markavailable)}</span> <path
</Button> fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z"
clipRule="evenodd"
/>
</svg>
<span>{intl.formatMessage(messages.markavailable)}</span>
</Button>
</div>
)} )}
{data?.mediaInfo && {data?.mediaInfo &&
data?.mediaInfo.status4k !== MediaStatus.AVAILABLE && ( data?.mediaInfo.status4k !== MediaStatus.AVAILABLE && (
<Button <div className="flex flex-col sm:flex-row flex-nowrap mb-2">
onClick={() => markAvailable(true)} <Button
className="w-full sm:ml-1 first:ml-0" onClick={() => markAvailable(true)}
buttonType="success" className="w-full sm:mb-0"
> buttonType="success"
<svg
className="w-5 h-5 mr-1"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
> >
<path <svg
fillRule="evenodd" className="w-5 h-5 mr-1"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z" fill="currentColor"
clipRule="evenodd" viewBox="0 0 20 20"
/> xmlns="http://www.w3.org/2000/svg"
</svg> >
<span>{intl.formatMessage(messages.mark4kavailable)}</span> <path
</Button> fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v2H7a1 1 0 100 2h2v2a1 1 0 102 0v-2h2a1 1 0 100-2h-2V7z"
clipRule="evenodd"
/>
</svg>
<span>
{intl.formatMessage(messages.mark4kavailable)}
</span>
</Button>
</div>
)} )}
</div> </div>
)} )}

View File

@@ -23,12 +23,11 @@ const NotificationType: React.FC<NotificationTypeProps> = ({
: '' : ''
}`} }`}
> >
<div className="flex items-center h-5"> <div className="flex items-center h-6">
<input <input
id={option.id} id={option.id}
name="permissions" name="permissions"
type="checkbox" type="checkbox"
className="w-4 h-4 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
disabled={ disabled={
!!parent?.value && hasNotificationType(parent.value, currentTypes) !!parent?.value && hasNotificationType(parent.value, currentTypes)
} }
@@ -46,7 +45,7 @@ const NotificationType: React.FC<NotificationTypeProps> = ({
} }
/> />
</div> </div>
<div className="ml-3 text-sm leading-5"> <div className="ml-3 text-sm leading-6">
<label htmlFor={option.id} className="font-medium"> <label htmlFor={option.id} className="font-medium">
{option.name} {option.name}
</label> </label>

View File

@@ -41,12 +41,11 @@ const PermissionOption: React.FC<PermissionOptionProps> = ({
: '' : ''
}`} }`}
> >
<div className="flex items-center h-5"> <div className="flex items-center h-6">
<input <input
id={option.id} id={option.id}
name="permissions" name="permissions"
type="checkbox" type="checkbox"
className="w-4 h-4 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
disabled={ disabled={
(option.permission !== Permission.ADMIN && (option.permission !== Permission.ADMIN &&
hasPermission(Permission.ADMIN, currentPermission)) || hasPermission(Permission.ADMIN, currentPermission)) ||
@@ -73,15 +72,17 @@ const PermissionOption: React.FC<PermissionOptionProps> = ({
} }
/> />
</div> </div>
<div className="ml-3 text-sm leading-5"> <div className="ml-3 text-sm leading-6">
<label htmlFor={option.id} className="font-medium"> <label htmlFor={option.id} className="block font-medium">
{option.name} <div className="flex flex-col">
<span>{option.name}</span>
<span className="text-gray-500">{option.description}</span>
</div>
</label> </label>
<p className="text-gray-500">{option.description}</p>
</div> </div>
</div> </div>
{(option.children ?? []).map((child) => ( {(option.children ?? []).map((child) => (
<div key={`permission-child-${child.id}`} className="pl-6 mt-4"> <div key={`permission-child-${child.id}`} className="pl-10 mt-4">
<PermissionOption <PermissionOption
option={child} option={child}
currentPermission={currentPermission} currentPermission={currentPermission}

View File

@@ -102,7 +102,7 @@ const RequestItem: React.FC<RequestItemProps> = ({
if (!title && !error) { if (!title && !error) {
return ( return (
<tr className="w-full h-24 bg-gray-800 animate-pulse" ref={ref}> <tr className="w-full h-24 animate-pulse" ref={ref}>
<td colSpan={6}></td> <td colSpan={6}></td>
</tr> </tr>
); );
@@ -110,14 +110,14 @@ const RequestItem: React.FC<RequestItemProps> = ({
if (!title || !requestData) { if (!title || !requestData) {
return ( return (
<tr className="w-full h-24 bg-gray-800 animate-pulse"> <tr className="w-full h-24 animate-pulse">
<td colSpan={6}></td> <td colSpan={6}></td>
</tr> </tr>
); );
} }
return ( return (
<tr className="relative w-full h-24 p-2 text-white bg-gray-800"> <tr className="relative w-full h-24 p-2">
<RequestModal <RequestModal
show={showEditModal} show={showEditModal}
tmdbId={request.media.tmdbId} tmdbId={request.media.tmdbId}
@@ -216,16 +216,18 @@ const RequestItem: React.FC<RequestItemProps> = ({
<div className="flex flex-col"> <div className="flex flex-col">
{requestData.modifiedBy ? ( {requestData.modifiedBy ? (
<span className="text-sm text-gray-300"> <span className="text-sm text-gray-300">
{requestData.modifiedBy.displayName} <span className="mr-1">{requestData.modifiedBy.displayName}</span>
( <span>
<FormattedRelativeTime (
value={Math.floor( <FormattedRelativeTime
(new Date(requestData.updatedAt).getTime() - Date.now()) / value={Math.floor(
1000 (new Date(requestData.updatedAt).getTime() - Date.now()) /
)} 1000
updateIntervalInSeconds={1} )}
/> updateIntervalInSeconds={1}
) />
)
</span>
</span> </span>
) : ( ) : (
<span className="text-sm text-gray-300">N/A</span> <span className="text-sm text-gray-300">N/A</span>

View File

@@ -56,7 +56,7 @@ const RequestList: React.FC = () => {
<> <>
<div className="flex flex-col justify-between md:items-end md:flex-row"> <div className="flex flex-col justify-between md:items-end md:flex-row">
<Header>{intl.formatMessage(messages.requests)}</Header> <Header>{intl.formatMessage(messages.requests)}</Header>
<div className="flex flex-col md:flex-row"> <div className="flex flex-col mt-2 md:flex-row">
<div className="flex mb-2 md:mb-0 md:mr-2"> <div className="flex mb-2 md:mb-0 md:mr-2">
<span className="inline-flex items-center px-3 text-gray-100 bg-gray-800 border border-r-0 border-gray-500 cursor-default rounded-l-md sm:text-sm"> <span className="inline-flex items-center px-3 text-gray-100 bg-gray-800 border border-r-0 border-gray-500 cursor-default rounded-l-md sm:text-sm">
<svg <svg
@@ -84,7 +84,7 @@ const RequestList: React.FC = () => {
setCurrentFilter(e.target.value as Filter); setCurrentFilter(e.target.value as Filter);
}} }}
value={currentFilter} value={currentFilter}
className="flex-1 block w-full py-2 pl-3 pr-10 text-base leading-6 text-white bg-gray-700 border-gray-500 rounded-r-md form-select focus:outline-none focus:ring-blue focus:border-gray-500 sm:text-sm sm:leading-5 disabled:opacity-50" className="rounded-r-only"
> >
<option value="all"> <option value="all">
{intl.formatMessage(messages.filterAll)} {intl.formatMessage(messages.filterAll)}
@@ -120,7 +120,7 @@ const RequestList: React.FC = () => {
setCurrentSort(e.target.value as Sort); setCurrentSort(e.target.value as Sort);
}} }}
value={currentSort} value={currentSort}
className="flex-1 block w-full py-2 pl-3 pr-10 text-base leading-6 text-white bg-gray-700 border-gray-500 rounded-r-md form-select focus:outline-none focus:ring-blue focus:border-gray-500 sm:text-sm sm:leading-5 disabled:opacity-50" className="rounded-r-only"
> >
<option value="added"> <option value="added">
{intl.formatMessage(messages.sortAdded)} {intl.formatMessage(messages.sortAdded)}
@@ -152,10 +152,12 @@ const RequestList: React.FC = () => {
})} })}
{data.results.length === 0 && ( {data.results.length === 0 && (
<tr className="relative w-full h-24 p-2 text-white bg-gray-800"> <tr className="relative w-full h-24 p-2 text-white">
<Table.TD colSpan={6} noPadding> <Table.TD colSpan={6} noPadding>
<div className="flex flex-col items-center justify-center p-4"> <div className="flex flex-col items-center justify-center p-6">
<span>{intl.formatMessage(messages.noresults)}</span> <span className="text-base">
{intl.formatMessage(messages.noresults)}
</span>
{currentFilter !== 'all' && ( {currentFilter !== 'all' && (
<div className="mt-4"> <div className="mt-4">
<Button <Button
@@ -171,10 +173,10 @@ const RequestList: React.FC = () => {
</Table.TD> </Table.TD>
</tr> </tr>
)} )}
<tr> <tr className="bg-gray-700">
<Table.TD colSpan={6} noPadding> <Table.TD colSpan={6} noPadding>
<nav <nav
className="flex items-center justify-between px-4 py-3 text-white bg-gray-700" className="flex items-center justify-between px-6 py-3"
aria-label="Pagination" aria-label="Pagination"
> >
<div className="hidden sm:block"> <div className="hidden sm:block">

View File

@@ -200,15 +200,15 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
<div className="p-4 bg-gray-600 rounded-md shadow"> <div className="p-4 bg-gray-600 rounded-md shadow">
<div className="flex flex-col items-center justify-between md:flex-row"> <div className="flex flex-col items-center justify-between md:flex-row">
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/3 md:pr-4 md:mb-0"> <div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/3 md:pr-4 md:mb-0">
<label htmlFor="server" className="block text-sm font-medium"> <label htmlFor="server" className="text-label">
{intl.formatMessage(messages.destinationserver)} {intl.formatMessage(messages.destinationserver)}
</label> </label>
<select <select
id="server" id="server"
name="server" name="server"
value={selectedServer}
onChange={(e) => setSelectedServer(Number(e.target.value))} onChange={(e) => setSelectedServer(Number(e.target.value))}
onBlur={(e) => setSelectedServer(Number(e.target.value))} onBlur={(e) => setSelectedServer(Number(e.target.value))}
value={selectedServer}
className="block w-full py-2 pl-3 pr-10 mt-1 text-base leading-6 text-white transition duration-150 ease-in-out bg-gray-800 border-gray-700 rounded-md form-select focus:outline-none focus:ring-blue focus:border-blue-300 sm:text-sm sm:leading-5" className="block w-full py-2 pl-3 pr-10 mt-1 text-base leading-6 text-white transition duration-150 ease-in-out bg-gray-800 border-gray-700 rounded-md form-select focus:outline-none focus:ring-blue focus:border-blue-300 sm:text-sm sm:leading-5"
> >
{data.map((server) => ( {data.map((server) => (
@@ -222,7 +222,7 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
</select> </select>
</div> </div>
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/3 md:pr-4 md:mb-0"> <div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/3 md:pr-4 md:mb-0">
<label htmlFor="server" className="block text-sm font-medium"> <label htmlFor="server" className="text-label">
{intl.formatMessage(messages.qualityprofile)} {intl.formatMessage(messages.qualityprofile)}
</label> </label>
<select <select
@@ -255,7 +255,7 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
</select> </select>
</div> </div>
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/3 md:mb-0"> <div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/3 md:mb-0">
<label htmlFor="server" className="block text-sm font-medium"> <label htmlFor="server" className="text-label">
{intl.formatMessage(messages.rootfolder)} {intl.formatMessage(messages.rootfolder)}
</label> </label>
<select <select

View File

@@ -391,7 +391,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
toggleAllSeasons(); toggleAllSeasons();
} }
}} }}
className="relative inline-flex items-center justify-center flex-shrink-0 w-10 h-5 cursor-pointer group focus:outline-none" className="relative inline-flex items-center justify-center flex-shrink-0 w-10 h-5 cursor-pointer pt-2 focus:outline-none"
> >
<span <span
aria-hidden="true" aria-hidden="true"
@@ -451,7 +451,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
toggleSeason(season.seasonNumber); toggleSeason(season.seasonNumber);
} }
}} }}
className={`group relative inline-flex items-center justify-center flex-shrink-0 h-5 w-10 cursor-pointer focus:outline-none ${ className={`pt-2 relative inline-flex items-center justify-center flex-shrink-0 h-5 w-10 cursor-pointer focus:outline-none ${
mediaSeason || mediaSeason ||
(!!seasonRequest && (!!seasonRequest &&
!editingSeasons.includes(season.seasonNumber)) !editingSeasons.includes(season.seasonNumber))

View File

@@ -65,7 +65,9 @@ const Search: React.FC = () => {
return ( return (
<> <>
<Header>{intl.formatMessage(messages.searchresults)}</Header> <div className="mt-1 mb-5">
<Header>{intl.formatMessage(messages.searchresults)}</Header>
</div>
<ListView <ListView
items={titles} items={titles}
isEmpty={isEmpty} isEmpty={isEmpty}

View File

@@ -29,7 +29,7 @@ const CopyButton: React.FC<{ textToCopy: string }> = ({ textToCopy }) => {
e.preventDefault(); e.preventDefault();
setCopied(); setCopied();
}} }}
className="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-500 text-sm leading-5 font-medium text-white bg-indigo-500 hover:bg-indigo-400 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150" className="-ml-px relative inline-flex items-center px-4 py-2 border border-gray-500 text-sm leading-5 font-medium text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150"
> >
<svg <svg
className="w-5 h-5 text-white" className="w-5 h-5 text-white"

View File

@@ -88,31 +88,20 @@ const NotificationsDiscord: React.FC = () => {
}; };
return ( return (
<Form> <Form className="section">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="enabled" className="checkbox-label">
htmlFor="enabled"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.agentenabled)} {intl.formatMessage(messages.agentenabled)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field type="checkbox" id="enabled" name="enabled" />
type="checkbox"
id="enabled"
name="enabled"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="name" className="text-label">
htmlFor="name"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.webhookUrl)} {intl.formatMessage(messages.webhookUrl)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="webhookUrl" id="webhookUrl"
@@ -121,39 +110,29 @@ const NotificationsDiscord: React.FC = () => {
placeholder={intl.formatMessage( placeholder={intl.formatMessage(
messages.webhookUrlPlaceholder messages.webhookUrlPlaceholder
)} )}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.webhookUrl && touched.webhookUrl && ( {errors.webhookUrl && touched.webhookUrl && (
<div className="mt-2 text-red-500">{errors.webhookUrl}</div> <div className="error">{errors.webhookUrl}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6"> <div role="group" aria-labelledby="group-label" className="group">
<div role="group" aria-labelledby="label-permissions"> <div className="form-row">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"> <span id="group-label" className="group-label">
<div> {intl.formatMessage(messages.notificationtypes)}
<div </span>
className="text-base font-medium leading-6 text-gray-400 sm:text-sm sm:leading-5" <div className="form-input">
id="label-types" <div className="max-w-lg">
> <NotificationTypeSelector
{intl.formatMessage(messages.notificationtypes)} currentTypes={values.types}
</div> onUpdate={(newTypes) => setFieldValue('types', newTypes)}
</div> />
<div className="mt-4 sm:mt-0 sm:col-span-2">
<div className="max-w-lg">
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) =>
setFieldValue('types', newTypes)
}
/>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="pt-5 mt-8 border-t border-gray-700"> <div className="actions">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button

View File

@@ -124,113 +124,86 @@ const NotificationsEmail: React.FC = () => {
}; };
return ( return (
<Form> <Form className="section">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="enabled" className="checkbox-label">
htmlFor="enabled"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.agentenabled)} {intl.formatMessage(messages.agentenabled)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field type="checkbox" id="enabled" name="enabled" />
type="checkbox"
id="enabled"
name="enabled"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="emailFrom" className="text-label">
htmlFor="emailFrom"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.emailsender)} {intl.formatMessage(messages.emailsender)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="emailFrom" id="emailFrom"
name="emailFrom" name="emailFrom"
type="text" type="text"
placeholder="no-reply@example.com" placeholder="no-reply@example.com"
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.emailFrom && touched.emailFrom && ( {errors.emailFrom && touched.emailFrom && (
<div className="mt-2 text-red-500">{errors.emailFrom}</div> <div className="error">{errors.emailFrom}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="senderName" className="text-label">
htmlFor="senderName"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.senderName)} {intl.formatMessage(messages.senderName)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="senderName" id="senderName"
name="senderName" name="senderName"
placeholder="Overseerr" placeholder="Overseerr"
type="text" type="text"
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="smtpHost" className="text-label">
htmlFor="smtpHost"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.smtpHost)} {intl.formatMessage(messages.smtpHost)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="smtpHost" id="smtpHost"
name="smtpHost" name="smtpHost"
type="text" type="text"
placeholder="localhost" placeholder="localhost"
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.smtpHost && touched.smtpHost && ( {errors.smtpHost && touched.smtpHost && (
<div className="mt-2 text-red-500">{errors.smtpHost}</div> <div className="error">{errors.smtpHost}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="smtpPort" className="text-label">
htmlFor="smtpPort"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.smtpPort)} {intl.formatMessage(messages.smtpPort)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="smtpPort" id="smtpPort"
name="smtpPort" name="smtpPort"
type="text" type="text"
placeholder="465" placeholder="465"
className="block w-24 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.smtpPort && touched.smtpPort && ( {errors.smtpPort && touched.smtpPort && (
<div className="mt-2 text-red-500">{errors.smtpPort}</div> <div className="error">{errors.smtpPort}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="secure" className="checkbox-label">
htmlFor="secure"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="flex flex-col"> <div className="flex flex-col">
<span>{intl.formatMessage(messages.enableSsl)}</span> <span>{intl.formatMessage(messages.enableSsl)}</span>
<span className="text-gray-500"> <span className="text-gray-500">
@@ -238,93 +211,63 @@ const NotificationsEmail: React.FC = () => {
</span> </span>
</div> </div>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field type="checkbox" id="secure" name="secure" />
type="checkbox"
id="secure"
name="secure"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="allowSelfSigned" className="checkbox-label">
htmlFor="allowSelfSigned"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.allowselfsigned)} {intl.formatMessage(messages.allowselfsigned)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field
type="checkbox" type="checkbox"
id="allowSelfSigned" id="allowSelfSigned"
name="allowSelfSigned" name="allowSelfSigned"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/> />
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="authUser" className="text-label">
htmlFor="authUser"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.authUser)} {intl.formatMessage(messages.authUser)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field id="authUser" name="authUser" type="text" />
id="authUser"
name="authUser"
type="text"
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/>
</div> </div>
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="authPass" className="text-label">
htmlFor="authPass"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.authPass)} {intl.formatMessage(messages.authPass)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="authPass" id="authPass"
name="authPass" name="authPass"
type="password" type="password"
autoComplete="off" autoComplete="off"
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
</div> </div>
</div> </div>
<div className="mt-6"> <div role="group" aria-labelledby="group-label" className="group">
<div role="group" aria-labelledby="label-permissions"> <div className="form-row">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"> <span id="group-label" className="group-label">
<div> {intl.formatMessage(messages.notificationtypes)}
<div </span>
className="text-base font-medium leading-6 text-gray-400 sm:text-sm sm:leading-5" <div className="form-input">
id="label-types" <div className="max-w-lg">
> <NotificationTypeSelector
{intl.formatMessage(messages.notificationtypes)} currentTypes={values.types}
</div> onUpdate={(newTypes) => setFieldValue('types', newTypes)}
</div> />
<div className="mt-4 sm:mt-0 sm:col-span-2">
<div className="max-w-lg">
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) =>
setFieldValue('types', newTypes)
}
/>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="pt-5 mt-8 border-t border-gray-700"> <div className="actions">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button

View File

@@ -133,92 +133,69 @@ const NotificationsPushover: React.FC = () => {
}, },
})} })}
</Alert> </Alert>
<Form> <Form className="section">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="enabled" className="checkbox-label">
htmlFor="enabled"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.agentenabled)} {intl.formatMessage(messages.agentenabled)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field type="checkbox" id="enabled" name="enabled" />
type="checkbox"
id="enabled"
name="enabled"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="accessToken" className="text-label">
htmlFor="accessToken"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.accessToken)} {intl.formatMessage(messages.accessToken)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="accessToken" id="accessToken"
name="accessToken" name="accessToken"
type="text" type="text"
placeholder={intl.formatMessage(messages.accessToken)} placeholder={intl.formatMessage(messages.accessToken)}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.accessToken && touched.accessToken && ( {errors.accessToken && touched.accessToken && (
<div className="mt-2 text-red-500"> <div className="error">{errors.accessToken}</div>
{errors.accessToken}
</div>
)} )}
</div> </div>
<label </div>
htmlFor="userToken" <div className="form-row">
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px" <label htmlFor="userToken" className="text-label">
>
{intl.formatMessage(messages.userToken)} {intl.formatMessage(messages.userToken)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="userToken" id="userToken"
name="userToken" name="userToken"
type="text" type="text"
placeholder={intl.formatMessage(messages.userToken)} placeholder={intl.formatMessage(messages.userToken)}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.userToken && touched.userToken && ( {errors.userToken && touched.userToken && (
<div className="mt-2 text-red-500">{errors.userToken}</div> <div className="error">{errors.userToken}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6"> <div role="group" aria-labelledby="group-label" className="group">
<div role="group" aria-labelledby="label-permissions"> <div className="form-row">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"> <span id="group-label" className="group-label">
<div> {intl.formatMessage(messages.notificationtypes)}
<div </span>
className="text-base font-medium leading-6 text-gray-400 sm:text-sm sm:leading-5" <div className="form-input">
id="label-types" <div className="max-w-lg">
> <NotificationTypeSelector
{intl.formatMessage(messages.notificationtypes)} currentTypes={values.types}
</div> onUpdate={(newTypes) =>
</div> setFieldValue('types', newTypes)
<div className="mt-4 sm:mt-0 sm:col-span-2"> }
<div className="max-w-lg"> />
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) =>
setFieldValue('types', newTypes)
}
/>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="pt-5 mt-8 border-t border-gray-700"> <div className="actions">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button

View File

@@ -116,31 +116,20 @@ const NotificationsSlack: React.FC = () => {
}; };
return ( return (
<Form> <Form className="section">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="isDefault" className="checkbox-label">
htmlFor="isDefault"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.agentenabled)} {intl.formatMessage(messages.agentenabled)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field type="checkbox" id="enabled" name="enabled" />
type="checkbox"
id="enabled"
name="enabled"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="name" className="text-label">
htmlFor="name"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.webhookUrl)} {intl.formatMessage(messages.webhookUrl)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="webhookUrl" id="webhookUrl"
@@ -149,39 +138,31 @@ const NotificationsSlack: React.FC = () => {
placeholder={intl.formatMessage( placeholder={intl.formatMessage(
messages.webhookUrlPlaceholder messages.webhookUrlPlaceholder
)} )}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.webhookUrl && touched.webhookUrl && ( {errors.webhookUrl && touched.webhookUrl && (
<div className="mt-2 text-red-500">{errors.webhookUrl}</div> <div className="error">{errors.webhookUrl}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6"> <div role="group" aria-labelledby="group-label" className="group">
<div role="group" aria-labelledby="label-permissions"> <div className="form-row">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"> <span id="group-label" className="group-label">
<div> {intl.formatMessage(messages.notificationtypes)}
<div </span>
className="text-base font-medium leading-6 text-gray-400 sm:text-sm sm:leading-5" <div className="form-input">
id="label-types" <div className="max-w-lg">
> <NotificationTypeSelector
{intl.formatMessage(messages.notificationtypes)} currentTypes={values.types}
</div> onUpdate={(newTypes) =>
</div> setFieldValue('types', newTypes)
<div className="mt-4 sm:mt-0 sm:col-span-2"> }
<div className="max-w-lg"> />
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) =>
setFieldValue('types', newTypes)
}
/>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="pt-5 mt-8 border-t border-gray-700"> <div className="actions">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button

View File

@@ -133,90 +133,69 @@ const NotificationsTelegram: React.FC = () => {
}, },
})} })}
</Alert> </Alert>
<Form> <Form className="section">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="enabled" className="checkbox-label">
htmlFor="enabled"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.agentenabled)} {intl.formatMessage(messages.agentenabled)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field type="checkbox" id="enabled" name="enabled" />
type="checkbox"
id="enabled"
name="enabled"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="botAPI" className="text-label">
htmlFor="botAPI"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.botAPI)} {intl.formatMessage(messages.botAPI)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="botAPI" id="botAPI"
name="botAPI" name="botAPI"
type="text" type="text"
placeholder={intl.formatMessage(messages.botAPI)} placeholder={intl.formatMessage(messages.botAPI)}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.botAPI && touched.botAPI && ( {errors.botAPI && touched.botAPI && (
<div className="mt-2 text-red-500">{errors.botAPI}</div> <div className="error">{errors.botAPI}</div>
)} )}
</div> </div>
<label </div>
htmlFor="chatId" <div className="form-row">
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px" <label htmlFor="chatId" className="text-label">
>
{intl.formatMessage(messages.chatId)} {intl.formatMessage(messages.chatId)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="chatId" id="chatId"
name="chatId" name="chatId"
type="text" type="text"
placeholder={intl.formatMessage(messages.chatId)} placeholder={intl.formatMessage(messages.chatId)}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.chatId && touched.chatId && ( {errors.chatId && touched.chatId && (
<div className="mt-2 text-red-500">{errors.chatId}</div> <div className="error">{errors.chatId}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6"> <div role="group" aria-labelledby="group-label" className="group">
<div role="group" aria-labelledby="label-permissions"> <div className="form-row">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"> <span id="group-label" className="group-label">
<div> {intl.formatMessage(messages.notificationtypes)}
<div </span>
className="text-base font-medium leading-6 text-gray-400 sm:text-sm sm:leading-5" <div className="form-input">
id="label-types" <div className="max-w-lg">
> <NotificationTypeSelector
{intl.formatMessage(messages.notificationtypes)} currentTypes={values.types}
</div> onUpdate={(newTypes) =>
</div> setFieldValue('types', newTypes)
<div className="mt-4 sm:mt-0 sm:col-span-2"> }
<div className="max-w-lg"> />
<NotificationTypeSelector
currentTypes={values.types}
onUpdate={(newTypes) =>
setFieldValue('types', newTypes)
}
/>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="pt-5 mt-8 border-t border-gray-700"> <div className="actions">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button

View File

@@ -151,31 +151,20 @@ const NotificationsWebhook: React.FC = () => {
}; };
return ( return (
<Form> <Form className="section">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="enabled" className="checkbox-label">
htmlFor="enabled"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.agentenabled)} {intl.formatMessage(messages.agentenabled)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field type="checkbox" id="enabled" name="enabled" />
type="checkbox"
id="enabled"
name="enabled"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="name" className="text-label">
htmlFor="name"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.webhookUrl)} {intl.formatMessage(messages.webhookUrl)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="webhookUrl" id="webhookUrl"
@@ -184,40 +173,28 @@ const NotificationsWebhook: React.FC = () => {
placeholder={intl.formatMessage( placeholder={intl.formatMessage(
messages.webhookUrlPlaceholder messages.webhookUrlPlaceholder
)} )}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.webhookUrl && touched.webhookUrl && ( {errors.webhookUrl && touched.webhookUrl && (
<div className="mt-2 text-red-500">{errors.webhookUrl}</div> <div className="error">{errors.webhookUrl}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="name" className="text-label">
htmlFor="name"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.authheader)} {intl.formatMessage(messages.authheader)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field id="authHeader" name="authHeader" type="text" />
id="authHeader"
name="authHeader"
type="text"
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/>
</div> </div>
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="name" className="text-label">
htmlFor="name"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.customJson)} {intl.formatMessage(messages.customJson)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<JSONEditor <JSONEditor
name="webhook-json-payload" name="webhook-json-payload"
@@ -227,7 +204,7 @@ const NotificationsWebhook: React.FC = () => {
/> />
</div> </div>
{errors.jsonPayload && touched.jsonPayload && ( {errors.jsonPayload && touched.jsonPayload && (
<div className="mt-2 text-red-500">{errors.jsonPayload}</div> <div className="error">{errors.jsonPayload}</div>
)} )}
<div className="mt-2"> <div className="mt-2">
<Button <Button
@@ -275,18 +252,15 @@ const NotificationsWebhook: React.FC = () => {
</div> </div>
</div> </div>
</div> </div>
<div className="mt-6"> <div className="mt-8">
<div role="group" aria-labelledby="label-permissions"> <div role="group" aria-labelledby="group-label" className="group">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"> <div className="sm:grid sm:grid-cols-4 sm:gap-4">
<div> <div>
<div <div id="group-label" className="group-label">
className="text-base font-medium leading-6 text-gray-400 sm:text-sm sm:leading-5"
id="label-types"
>
{intl.formatMessage(messages.notificationtypes)} {intl.formatMessage(messages.notificationtypes)}
</div> </div>
</div> </div>
<div className="mt-4 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="max-w-lg"> <div className="max-w-lg">
<NotificationTypeSelector <NotificationTypeSelector
currentTypes={values.types} currentTypes={values.types}
@@ -299,7 +273,7 @@ const NotificationsWebhook: React.FC = () => {
</div> </div>
</div> </div>
</div> </div>
<div className="pt-5 mt-8 border-t border-gray-700"> <div className="actions">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button

View File

@@ -93,7 +93,9 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
port: Yup.number().required( port: Yup.number().required(
intl.formatMessage(messages.validationPortRequired) intl.formatMessage(messages.validationPortRequired)
), ),
apiKey: Yup.string().required(intl.formatMessage(messages.apiKey)), apiKey: Yup.string().required(
intl.formatMessage(messages.validationApiKeyRequired)
),
rootFolder: Yup.string().required( rootFolder: Yup.string().required(
intl.formatMessage(messages.validationRootFolderRequired) intl.formatMessage(messages.validationRootFolderRequired)
), ),
@@ -284,47 +286,28 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
} }
> >
<div className="mb-6"> <div className="mb-6">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="isDefault" className="checkbox-label">
htmlFor="isDefault"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.defaultserver)} {intl.formatMessage(messages.defaultserver)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field type="checkbox" id="isDefault" name="isDefault" />
type="checkbox"
id="isDefault"
name="isDefault"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="is4k" className="checkbox-label">
htmlFor="is4k"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.server4k)} {intl.formatMessage(messages.server4k)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field type="checkbox" id="is4k" name="is4k" />
type="checkbox"
id="is4k"
name="is4k"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="name" className="text-label">
htmlFor="name"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.servername)} {intl.formatMessage(messages.servername)}
<span className="text-red-500">*</span> <span className="text-red-500">*</span>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="name" id="name"
@@ -337,25 +320,21 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
setIsValidated(false); setIsValidated(false);
setFieldValue('name', e.target.value); setFieldValue('name', e.target.value);
}} }}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.name && touched.name && ( {errors.name && touched.name && (
<div className="mt-2 text-red-500">{errors.name}</div> <div className="error">{errors.name}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="hostname" className="text-label">
htmlFor="hostname"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.hostname)} {intl.formatMessage(messages.hostname)}
<span className="text-red-500">*</span> <span className="text-red-500">*</span>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<span className="inline-flex items-center px-3 text-gray-100 bg-gray-600 border border-r-0 border-gray-500 cursor-default rounded-l-md sm:text-sm"> <span className="protocol">
{values.ssl ? 'https://' : 'http://'} {values.ssl ? 'https://' : 'http://'}
</span> </span>
<Field <Field
@@ -367,23 +346,20 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
setIsValidated(false); setIsValidated(false);
setFieldValue('hostname', e.target.value); setFieldValue('hostname', e.target.value);
}} }}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 form-input rounded-r-md sm:text-sm sm:leading-5" className="rounded-r-only"
/> />
</div> </div>
{errors.hostname && touched.hostname && ( {errors.hostname && touched.hostname && (
<div className="mt-2 text-red-500">{errors.hostname}</div> <div className="error">{errors.hostname}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="port" className="text-label">
htmlFor="port"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.port)} {intl.formatMessage(messages.port)}
<span className="text-red-500">*</span> <span className="text-red-500">*</span>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field
id="port" id="port"
name="port" name="port"
@@ -393,21 +369,17 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
setIsValidated(false); setIsValidated(false);
setFieldValue('port', e.target.value); setFieldValue('port', e.target.value);
}} }}
className="block w-24 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md shadow-sm form-input sm:text-sm sm:leading-5"
/> />
{errors.port && touched.port && ( {errors.port && touched.port && (
<div className="mt-2 text-red-500">{errors.port}</div> <div className="error">{errors.port}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="ssl" className="checkbox-label">
htmlFor="ssl"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.ssl)} {intl.formatMessage(messages.ssl)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field
type="checkbox" type="checkbox"
id="ssl" id="ssl"
@@ -416,19 +388,15 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
setIsValidated(false); setIsValidated(false);
setFieldValue('ssl', !values.ssl); setFieldValue('ssl', !values.ssl);
}} }}
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/> />
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="apiKey" className="text-label">
htmlFor="apiKey"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.apiKey)} {intl.formatMessage(messages.apiKey)}
<span className="text-red-500">*</span> <span className="text-red-500">*</span>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="apiKey" id="apiKey"
@@ -441,22 +409,18 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
setIsValidated(false); setIsValidated(false);
setFieldValue('apiKey', e.target.value); setFieldValue('apiKey', e.target.value);
}} }}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.apiKey && touched.apiKey && ( {errors.apiKey && touched.apiKey && (
<div className="mt-2 text-red-500">{errors.apiKey}</div> <div className="error">{errors.apiKey}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="baseUrl" className="text-label">
htmlFor="baseUrl"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.baseUrl)} {intl.formatMessage(messages.baseUrl)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="baseUrl" id="baseUrl"
@@ -469,30 +433,25 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
setIsValidated(false); setIsValidated(false);
setFieldValue('baseUrl', e.target.value); setFieldValue('baseUrl', e.target.value);
}} }}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.baseUrl && touched.baseUrl && ( {errors.baseUrl && touched.baseUrl && (
<div className="mt-2 text-red-500">{errors.baseUrl}</div> <div className="error">{errors.baseUrl}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="activeProfileId" className="text-label">
htmlFor="activeProfileId"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.qualityprofile)} {intl.formatMessage(messages.qualityprofile)}
<span className="text-red-500">*</span> <span className="text-red-500">*</span>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
as="select" as="select"
id="activeProfileId" id="activeProfileId"
name="activeProfileId" name="activeProfileId"
disabled={!isValidated || isTesting} disabled={!isValidated || isTesting}
className="block w-full py-2 pl-3 pr-10 mt-1 text-base leading-6 bg-gray-700 border-gray-500 rounded-md form-select focus:outline-none focus:ring-blue focus:border-gray-500 sm:text-sm sm:leading-5 disabled:opacity-50"
> >
<option value=""> <option value="">
{isTesting {isTesting
@@ -515,28 +474,22 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
</Field> </Field>
</div> </div>
{errors.activeProfileId && touched.activeProfileId && ( {errors.activeProfileId && touched.activeProfileId && (
<div className="mt-2 text-red-500"> <div className="error">{errors.activeProfileId}</div>
{errors.activeProfileId}
</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-8005"> <div className="form-row">
<label <label htmlFor="rootFolder" className="text-label">
htmlFor="rootFolder"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.rootfolder)} {intl.formatMessage(messages.rootfolder)}
<span className="text-red-500">*</span> <span className="text-red-500">*</span>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
as="select" as="select"
id="rootFolder" id="rootFolder"
name="rootFolder" name="rootFolder"
disabled={!isValidated || isTesting} disabled={!isValidated || isTesting}
className="block w-full py-2 pl-3 pr-10 mt-1 text-base leading-6 bg-gray-700 border-gray-500 rounded-md form-select focus:outline-none focus:ring-blue focus:border-gray-500 sm:text-sm sm:leading-5 disabled:opacity-50"
> >
<option value=""> <option value="">
{isTesting {isTesting
@@ -557,27 +510,21 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
</Field> </Field>
</div> </div>
{errors.rootFolder && touched.rootFolder && ( {errors.rootFolder && touched.rootFolder && (
<div className="mt-2 text-red-500"> <div className="error">{errors.rootFolder}</div>
{errors.rootFolder}
</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="minimumAvailability" className="text-label">
htmlFor="minimumAvailability"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.minimumAvailability)} {intl.formatMessage(messages.minimumAvailability)}
<span className="text-red-500">*</span> <span className="text-red-500">*</span>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
as="select" as="select"
id="minimumAvailability" id="minimumAvailability"
name="minimumAvailability" name="minimumAvailability"
className="block w-full py-2 pl-3 pr-10 mt-1 text-base leading-6 bg-gray-700 border-gray-500 rounded-md form-select focus:outline-none focus:ring-blue focus:border-gray-500 sm:text-sm sm:leading-5"
> >
<option value="announced">Announced</option> <option value="announced">Announced</option>
<option value="inCinemas">In Cinemas</option> <option value="inCinemas">In Cinemas</option>
@@ -587,20 +534,17 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
</div> </div>
{errors.minimumAvailability && {errors.minimumAvailability &&
touched.minimumAvailability && ( touched.minimumAvailability && (
<div className="mt-2 text-red-500"> <div className="error">
{errors.minimumAvailability} {errors.minimumAvailability}
</div> </div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="externalUrl" className="text-label">
htmlFor="externalUrl"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.externalUrl)} {intl.formatMessage(messages.externalUrl)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="externalUrl" id="externalUrl"
@@ -609,45 +553,34 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
placeholder={intl.formatMessage( placeholder={intl.formatMessage(
messages.externalUrlPlaceholder messages.externalUrlPlaceholder
)} )}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.externalUrl && touched.externalUrl && ( {errors.externalUrl && touched.externalUrl && (
<div className="mt-2 text-red-500"> <div className="error">{errors.externalUrl}</div>
{errors.externalUrl}
</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="syncEnabled" className="checkbox-label">
htmlFor="syncEnabled"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.syncEnabled)} {intl.formatMessage(messages.syncEnabled)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field
type="checkbox" type="checkbox"
id="syncEnabled" id="syncEnabled"
name="syncEnabled" name="syncEnabled"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/> />
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="preventSearch" className="checkbox-label">
htmlFor="preventSearch"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.preventSearch)} {intl.formatMessage(messages.preventSearch)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field
type="checkbox" type="checkbox"
id="preventSearch" id="preventSearch"
name="preventSearch" name="preventSearch"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/> />
</div> </div>
</div> </div>

View File

@@ -100,7 +100,7 @@ const Release: React.FC<ReleaseProps> = ({
</Modal> </Modal>
</Transition> </Transition>
<div className="flex items-center justify-center mb-4 sm:mb-0 sm:justify-start"> <div className="flex items-center justify-center mb-4 sm:mb-0 sm:justify-start">
<span className="mr-2 text-sm"> <span className="mt-1 mr-2 text-xs">
<FormattedRelativeTime <FormattedRelativeTime
value={Math.floor( value={Math.floor(
(new Date(release.created_at).getTime() - Date.now()) / 1000 (new Date(release.created_at).getTime() - Date.now()) / 1000
@@ -109,16 +109,16 @@ const Release: React.FC<ReleaseProps> = ({
numeric="always" numeric="always"
/> />
</span> </span>
<span className="text-xl">{release.name}</span> <span className="text-lg">{release.name}</span>
{isLatest && ( {isLatest && (
<span className="ml-2"> <span className="ml-2 -mt-1">
<Badge badgeType="primary"> <Badge badgeType="primary">
{intl.formatMessage(messages.latestversion)} {intl.formatMessage(messages.latestversion)}
</Badge> </Badge>
</span> </span>
)} )}
{release.name.includes(currentVersion) && ( {release.name.includes(currentVersion) && (
<span className="ml-2"> <span className="ml-2 -mt-1">
<Badge badgeType="success"> <Badge badgeType="success">
{intl.formatMessage(messages.currentversion)} {intl.formatMessage(messages.currentversion)}
</Badge> </Badge>
@@ -156,38 +156,38 @@ const Releases: React.FC<ReleasesProps> = ({ currentVersion }) => {
return ( return (
<div> <div>
<div className="pb-4 mb-4 text-xl border-b border-gray-800"> <h3 className="heading">{intl.formatMessage(messages.releases)}</h3>
{intl.formatMessage(messages.releases)} <div className="section">
{currentVersion.startsWith('develop-') && (
<Alert title={intl.formatMessage(messages.runningDevelop)}>
{intl.formatMessage(messages.runningDevelopMessage, {
GithubLink: function GithubLink(msg) {
return (
<a
href="https://github.com/sct/overseerr"
target="_blank"
rel="noreferrer"
className="text-yellow-100 underline transition duration-300 hover:text-white"
>
{msg}
</a>
);
},
})}
</Alert>
)}
{data?.map((release, index) => {
return (
<div key={`release-${release.id}`} className="mb-2">
<Release
release={release}
currentVersion={currentVersion}
isLatest={index === 0}
/>
</div>
);
})}
</div> </div>
{currentVersion.startsWith('develop-') && (
<Alert title={intl.formatMessage(messages.runningDevelop)}>
{intl.formatMessage(messages.runningDevelopMessage, {
GithubLink: function GithubLink(msg) {
return (
<a
href="https://github.com/sct/overseerr"
target="_blank"
rel="noreferrer"
className="text-yellow-100 underline transition duration-300 hover:text-white"
>
{msg}
</a>
);
},
})}
</Alert>
)}
{data?.map((release, index) => {
return (
<div key={`release-${release.id}`} className="mb-2">
<Release
release={release}
currentVersion={currentVersion}
isLatest={index === 0}
/>
</div>
);
})}
</div> </div>
); );
}; };

View File

@@ -37,7 +37,7 @@ const SettingsAbout: React.FC = () => {
return ( return (
<> <>
<div className="mb-8"> <div className="section">
<List title={intl.formatMessage(messages.overseerrinformation)}> <List title={intl.formatMessage(messages.overseerrinformation)}>
<List.Item title={intl.formatMessage(messages.version)}> <List.Item title={intl.formatMessage(messages.version)}>
{data.version} {data.version}
@@ -55,7 +55,7 @@ const SettingsAbout: React.FC = () => {
)} )}
</List> </List>
</div> </div>
<div className="mb-8"> <div className="section">
<List title={intl.formatMessage(messages.gettingsupport)}> <List title={intl.formatMessage(messages.gettingsupport)}>
<List.Item title={intl.formatMessage(messages.documentation)}> <List.Item title={intl.formatMessage(messages.documentation)}>
<a <a
@@ -89,7 +89,7 @@ const SettingsAbout: React.FC = () => {
</List.Item> </List.Item>
</List> </List>
</div> </div>
<div className="mb-8"> <div className="section">
<List title={intl.formatMessage(messages.supportoverseerr)}> <List title={intl.formatMessage(messages.supportoverseerr)}>
<List.Item <List.Item
title={`${intl.formatMessage(messages.helppaycoffee)} ☕️`} title={`${intl.formatMessage(messages.helppaycoffee)} ☕️`}
@@ -105,7 +105,7 @@ const SettingsAbout: React.FC = () => {
</List.Item> </List.Item>
</List> </List>
</div> </div>
<div className="mb-8"> <div className="section">
<Releases currentVersion={data.version} /> <Releases currentVersion={data.version} />
</div> </div>
</> </>

View File

@@ -22,6 +22,8 @@ const messages = defineMessages({
canceljob: 'Cancel Job', canceljob: 'Cancel Job',
jobstarted: '{jobname} started.', jobstarted: '{jobname} started.',
jobcancelled: '{jobname} cancelled.', jobcancelled: '{jobname} cancelled.',
process: 'Process',
command: 'Command',
cache: 'Cache', cache: 'Cache',
cacheDescription: cacheDescription:
'Overseerr caches requests to external API endpoints to optimize performance and avoid making unnecessary API calls.', 'Overseerr caches requests to external API endpoints to optimize performance and avoid making unnecessary API calls.',
@@ -97,100 +99,103 @@ const SettingsJobs: React.FC = () => {
return ( return (
<> <>
<div className="mb-4"> <div className="mb-6">
<h3 className="text-lg font-medium leading-6 text-gray-200"> <h3 className="heading">{intl.formatMessage(messages.jobs)}</h3>
{intl.formatMessage(messages.jobs)} <p className="description">
</h3>
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500">
{intl.formatMessage(messages.jobsDescription)} {intl.formatMessage(messages.jobsDescription)}
</p> </p>
</div> </div>
<Table> <div className="section">
<thead> <Table>
<Table.TH>{intl.formatMessage(messages.jobname)}</Table.TH> <thead>
<Table.TH>{intl.formatMessage(messages.jobtype)}</Table.TH> <Table.TH>{intl.formatMessage(messages.jobname)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.nextexecution)}</Table.TH> <Table.TH>{intl.formatMessage(messages.jobtype)}</Table.TH>
<Table.TH></Table.TH> <Table.TH>{intl.formatMessage(messages.nextexecution)}</Table.TH>
</thead> <Table.TH></Table.TH>
<Table.TBody> </thead>
{data?.map((job) => ( <Table.TBody>
<tr key={`job-list-${job.id}`}> {data?.map((job) => (
<Table.TD> <tr key={`job-list-${job.id}`}>
<div className="flex items-center text-sm leading-5 text-white"> <Table.TD>
{job.running && <Spinner className="w-5 h-5 mr-2" />} <div className="flex items-center text-sm leading-5 text-white">
<span>{job.name}</span> {job.running && <Spinner className="w-5 h-5 mr-2" />}
</div> <span>{job.name}</span>
</Table.TD> </div>
<Table.TD> </Table.TD>
<Badge <Table.TD>
badgeType={job.type === 'process' ? 'primary' : 'warning'} <Badge
className="uppercase" badgeType={job.type === 'process' ? 'primary' : 'warning'}
> className="uppercase"
{job.type} >
</Badge> {job.type === 'process'
</Table.TD> ? intl.formatMessage(messages.process)
<Table.TD> : intl.formatMessage(messages.command)}
<div className="text-sm leading-5 text-white"> </Badge>
<FormattedRelativeTime </Table.TD>
value={Math.floor( <Table.TD>
(new Date(job.nextExecutionTime).getTime() - Date.now()) / <div className="text-sm leading-5 text-white">
1000 <FormattedRelativeTime
)} value={Math.floor(
updateIntervalInSeconds={1} (new Date(job.nextExecutionTime).getTime() -
/> Date.now()) /
</div> 1000
</Table.TD> )}
<Table.TD alignText="right"> updateIntervalInSeconds={1}
{job.running ? ( />
<Button buttonType="danger" onClick={() => cancelJob(job)}> </div>
{intl.formatMessage(messages.canceljob)} </Table.TD>
</Button> <Table.TD alignText="right">
) : ( {job.running ? (
<Button buttonType="primary" onClick={() => runJob(job)}> <Button buttonType="danger" onClick={() => cancelJob(job)}>
{intl.formatMessage(messages.runnow)} {intl.formatMessage(messages.canceljob)}
</Button> </Button>
)} ) : (
</Table.TD> <Button buttonType="primary" onClick={() => runJob(job)}>
</tr> {intl.formatMessage(messages.runnow)}
))} </Button>
</Table.TBody> )}
</Table> </Table.TD>
<div className="my-4"> </tr>
<h3 className="text-lg font-medium leading-6 text-gray-200"> ))}
{intl.formatMessage(messages.cache)} </Table.TBody>
</h3> </Table>
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500"> </div>
<div>
<h3 className="heading">{intl.formatMessage(messages.cache)}</h3>
<p className="description">
{intl.formatMessage(messages.cacheDescription)} {intl.formatMessage(messages.cacheDescription)}
</p> </p>
</div> </div>
<Table> <div className="section">
<thead> <Table>
<Table.TH>{intl.formatMessage(messages.cachename)}</Table.TH> <thead>
<Table.TH>{intl.formatMessage(messages.cachehits)}</Table.TH> <Table.TH>{intl.formatMessage(messages.cachename)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.cachemisses)}</Table.TH> <Table.TH>{intl.formatMessage(messages.cachehits)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.cachekeys)}</Table.TH> <Table.TH>{intl.formatMessage(messages.cachemisses)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.cacheksize)}</Table.TH> <Table.TH>{intl.formatMessage(messages.cachekeys)}</Table.TH>
<Table.TH>{intl.formatMessage(messages.cachevsize)}</Table.TH> <Table.TH>{intl.formatMessage(messages.cacheksize)}</Table.TH>
<Table.TH></Table.TH> <Table.TH>{intl.formatMessage(messages.cachevsize)}</Table.TH>
</thead> <Table.TH></Table.TH>
<Table.TBody> </thead>
{cacheData?.map((cache) => ( <Table.TBody>
<tr key={`cache-list-${cache.id}`}> {cacheData?.map((cache) => (
<Table.TD>{cache.name}</Table.TD> <tr key={`cache-list-${cache.id}`}>
<Table.TD>{intl.formatNumber(cache.stats.hits)}</Table.TD> <Table.TD>{cache.name}</Table.TD>
<Table.TD>{intl.formatNumber(cache.stats.misses)}</Table.TD> <Table.TD>{intl.formatNumber(cache.stats.hits)}</Table.TD>
<Table.TD>{intl.formatNumber(cache.stats.keys)}</Table.TD> <Table.TD>{intl.formatNumber(cache.stats.misses)}</Table.TD>
<Table.TD>{formatBytes(cache.stats.ksize)}</Table.TD> <Table.TD>{intl.formatNumber(cache.stats.keys)}</Table.TD>
<Table.TD>{formatBytes(cache.stats.vsize)}</Table.TD> <Table.TD>{formatBytes(cache.stats.ksize)}</Table.TD>
<Table.TD alignText="right"> <Table.TD>{formatBytes(cache.stats.vsize)}</Table.TD>
<Button buttonType="danger" onClick={() => flushCache(cache)}> <Table.TD alignText="right">
{intl.formatMessage(messages.flushcache)} <Button buttonType="danger" onClick={() => flushCache(cache)}>
</Button> {intl.formatMessage(messages.flushcache)}
</Table.TD> </Button>
</tr> </Table.TD>
))} </tr>
</Table.TBody> ))}
</Table> </Table.TBody>
</Table>
</div>
</> </>
); );
}; };

View File

@@ -106,7 +106,6 @@ const SettingsLayout: React.FC = ({ children }) => {
)?.route )?.route
} }
aria-label="Selected tab" aria-label="Selected tab"
className="block w-full py-2 pl-3 pr-10 mt-1 text-base leading-6 text-white transition duration-150 ease-in-out bg-gray-800 border-gray-700 rounded-md form-select focus:outline-none focus:ring-blue focus:border-blue-300 sm:text-sm sm:leading-5"
> >
{settingsRoutes.map((route, index) => ( {settingsRoutes.map((route, index) => (
<SettingsLink <SettingsLink

View File

@@ -29,7 +29,9 @@ const messages = defineMessages({
hideAvailable: 'Hide Available Media', hideAvailable: 'Hide Available Media',
csrfProtection: 'Enable CSRF Protection', csrfProtection: 'Enable CSRF Protection',
csrfProtectionTip: csrfProtectionTip:
'Sets external API access to read-only (Overseerr must be reloaded for changes to take effect)', 'Sets external API access to read-only (requires HTTPS and Overseerr must be reloaded for changes to take effect)',
csrfProtectionHoverTip:
'Do NOT enable this unless you understand what you are doing!',
trustProxy: 'Enable Proxy Support', trustProxy: 'Enable Proxy Support',
trustProxyTip: trustProxyTip:
'Allows Overseerr to correctly register client IP addresses behind a proxy (Overseerr must be reloaded for changes to take effect)', 'Allows Overseerr to correctly register client IP addresses behind a proxy (Overseerr must be reloaded for changes to take effect)',
@@ -66,15 +68,15 @@ const SettingsMain: React.FC = () => {
return ( return (
<> <>
<div> <div className="mb-6">
<h3 className="text-lg font-medium leading-6 text-gray-200"> <h3 className="heading">
{intl.formatMessage(messages.generalsettings)} {intl.formatMessage(messages.generalsettings)}
</h3> </h3>
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500"> <p className="description">
{intl.formatMessage(messages.generalsettingsDescription)} {intl.formatMessage(messages.generalsettingsDescription)}
</p> </p>
</div> </div>
<div className="mt-6 sm:mt-5"> <div className="section">
<Formik <Formik
initialValues={{ initialValues={{
applicationUrl: data?.applicationUrl, applicationUrl: data?.applicationUrl,
@@ -110,21 +112,18 @@ const SettingsMain: React.FC = () => {
> >
{({ isSubmitting, values, setFieldValue }) => { {({ isSubmitting, values, setFieldValue }) => {
return ( return (
<Form> <Form className="section">
{userHasPermission(Permission.ADMIN) && ( {userHasPermission(Permission.ADMIN) && (
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="apiKey" className="text-label">
htmlFor="username"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.apikey)} {intl.formatMessage(messages.apikey)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<input <input
type="text" type="text"
id="apiKey" id="apiKey"
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-none form-input rounded-l-md sm:text-sm sm:leading-5" className="rounded-l-only"
value={data?.apiKey} value={data?.apiKey}
readOnly readOnly
/> />
@@ -137,7 +136,7 @@ const SettingsMain: React.FC = () => {
e.preventDefault(); e.preventDefault();
regenerate(); regenerate();
}} }}
className="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium leading-5 text-white transition duration-150 ease-in-out bg-indigo-500 border border-gray-500 rounded-r-md hover:bg-indigo-400 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700" className="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium leading-5 text-white transition duration-150 ease-in-out bg-indigo-600 border border-gray-500 rounded-r-md hover:bg-indigo-500 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700"
> >
<svg <svg
className="w-5 h-5" className="w-5 h-5"
@@ -156,40 +155,31 @@ const SettingsMain: React.FC = () => {
</div> </div>
</div> </div>
)} )}
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="applicationUrl" className="text-label">
htmlFor="name"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.applicationurl)} {intl.formatMessage(messages.applicationurl)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="applicationUrl" id="applicationUrl"
name="applicationUrl" name="applicationUrl"
type="text" type="text"
placeholder="https://os.example.com" placeholder="https://os.example.com"
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="trustProxy" className="checkbox-label">
htmlFor="trustProxy" <span className="mr-2">
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px" {intl.formatMessage(messages.trustProxy)}
> </span>
<div className="flex flex-col"> <span className="label-tip">
<span className="mr-2"> {intl.formatMessage(messages.trustProxyTip)}
{intl.formatMessage(messages.trustProxy)} </span>
</span>
<span className="text-gray-500">
{intl.formatMessage(messages.trustProxyTip)}
</span>
</div>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field
type="checkbox" type="checkbox"
id="trustProxy" id="trustProxy"
@@ -197,41 +187,37 @@ const SettingsMain: React.FC = () => {
onChange={() => { onChange={() => {
setFieldValue('trustProxy', !values.trustProxy); setFieldValue('trustProxy', !values.trustProxy);
}} }}
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/> />
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="csrfProtection" className="checkbox-label">
htmlFor="csrfProtection" <span className="mr-2">
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px" {intl.formatMessage(messages.csrfProtection)}
> </span>
<div className="flex flex-col"> <Badge badgeType="danger">
<span className="mr-2"> {intl.formatMessage(globalMessages.advanced)}
{intl.formatMessage(messages.csrfProtection)} </Badge>
</span> <span className="label-tip">
<span className="text-gray-500"> {intl.formatMessage(messages.csrfProtectionTip)}
{intl.formatMessage(messages.csrfProtectionTip)} </span>
</span>
</div>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field
type="checkbox" type="checkbox"
id="csrfProtection" id="csrfProtection"
name="csrfProtection" name="csrfProtection"
title={intl.formatMessage(
messages.csrfProtectionHoverTip
)}
onChange={() => { onChange={() => {
setFieldValue('csrfProtection', !values.csrfProtection); setFieldValue('csrfProtection', !values.csrfProtection);
}} }}
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/> />
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="hideAvailable" className="checkbox-label">
htmlFor="name"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<span className="mr-2"> <span className="mr-2">
{intl.formatMessage(messages.hideAvailable)} {intl.formatMessage(messages.hideAvailable)}
</span> </span>
@@ -239,7 +225,7 @@ const SettingsMain: React.FC = () => {
{intl.formatMessage(globalMessages.experimental)} {intl.formatMessage(globalMessages.experimental)}
</Badge> </Badge>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field
type="checkbox" type="checkbox"
id="hideAvailable" id="hideAvailable"
@@ -247,38 +233,31 @@ const SettingsMain: React.FC = () => {
onChange={() => { onChange={() => {
setFieldValue('hideAvailable', !values.hideAvailable); setFieldValue('hideAvailable', !values.hideAvailable);
}} }}
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/> />
</div> </div>
</div> </div>
<div className="mt-6"> <div
<div role="group" aria-labelledby="label-permissions"> role="group"
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"> aria-labelledby="group-label"
<div> className="group"
<div >
className="text-base font-medium leading-6 text-gray-400 sm:text-sm sm:leading-5" <div className="form-row">
id="label-permissions" <span id="group-label" className="group-label">
> {intl.formatMessage(messages.defaultPermissions)}
{intl.formatMessage(messages.defaultPermissions)} </span>
</div> <div className="form-input">
</div> <div className="max-w-lg">
<div className="mt-4 sm:mt-0 sm:col-span-2"> <PermissionEdit
<div className="max-w-lg"> currentPermission={values.defaultPermissions}
<PermissionEdit onUpdate={(newPermissions) =>
currentPermission={values.defaultPermissions} setFieldValue('defaultPermissions', newPermissions)
onUpdate={(newPermissions) => }
setFieldValue( />
'defaultPermissions',
newPermissions
)
}
/>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="pt-5 mt-8 border-t border-gray-700"> <div className="actions">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button

View File

@@ -27,7 +27,7 @@ const messages = defineMessages({
notificationsettingssaved: 'Notification settings saved!', notificationsettingssaved: 'Notification settings saved!',
notificationsettingsfailed: 'Notification settings failed to save.', notificationsettingsfailed: 'Notification settings failed to save.',
enablenotifications: 'Enable Notifications', enablenotifications: 'Enable Notifications',
autoapprovedrequests: 'Send Notifications for Auto-Approved Requests', autoapprovedrequests: 'Enable Notifications for Auto-Approved Requests',
}); });
interface SettingsRoute { interface SettingsRoute {
@@ -163,14 +163,14 @@ const SettingsNotifications: React.FC = ({ children }) => {
return ( return (
<> <>
<div className="mb-6"> <div className="mb-6">
<h3 className="text-lg font-medium leading-6 text-gray-200"> <h3 className="heading">
{intl.formatMessage(messages.notificationsettings)} {intl.formatMessage(messages.notificationsettings)}
</h3> </h3>
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500"> <p className="description">
{intl.formatMessage(messages.notificationsettingsDescription)} {intl.formatMessage(messages.notificationsettingsDescription)}
</p> </p>
</div> </div>
<div className="mt-6 sm:mt-5"> <div className="section">
<Formik <Formik
initialValues={{ initialValues={{
enabled: data.enabled, enabled: data.enabled,
@@ -202,17 +202,14 @@ const SettingsNotifications: React.FC = ({ children }) => {
> >
{({ isSubmitting, values, setFieldValue }) => { {({ isSubmitting, values, setFieldValue }) => {
return ( return (
<Form> <Form className="section">
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="name" className="checkbox-label">
htmlFor="name"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<span className="mr-2"> <span className="mr-2">
{intl.formatMessage(messages.enablenotifications)} {intl.formatMessage(messages.enablenotifications)}
</span> </span>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field
type="checkbox" type="checkbox"
id="enabled" id="enabled"
@@ -220,20 +217,16 @@ const SettingsNotifications: React.FC = ({ children }) => {
onChange={() => { onChange={() => {
setFieldValue('enabled', !values.enabled); setFieldValue('enabled', !values.enabled);
}} }}
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/> />
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="name" className="checkbox-label">
htmlFor="name"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<span className="mr-2"> <span className="mr-2">
{intl.formatMessage(messages.autoapprovedrequests)} {intl.formatMessage(messages.autoapprovedrequests)}
</span> </span>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field
type="checkbox" type="checkbox"
id="autoapprovalEnabled" id="autoapprovalEnabled"
@@ -244,11 +237,10 @@ const SettingsNotifications: React.FC = ({ children }) => {
!values.autoapprovalEnabled !values.autoapprovalEnabled
); );
}} }}
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/> />
</div> </div>
</div> </div>
<div className="pt-5 mt-8 border-t border-gray-700"> <div className="actions">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
@@ -269,10 +261,10 @@ const SettingsNotifications: React.FC = ({ children }) => {
</Formik> </Formik>
</div> </div>
<div className="mt-10 mb-6"> <div className="mt-10 mb-6">
<h3 className="text-lg font-medium leading-6 text-gray-200"> <h3 className="heading">
{intl.formatMessage(messages.notificationAgentsSettings)} {intl.formatMessage(messages.notificationAgentsSettings)}
</h3> </h3>
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500"> <p className="description">
{intl.formatMessage(messages.notificationAgentSettingsDescription)} {intl.formatMessage(messages.notificationAgentSettingsDescription)}
</p> </p>
</div> </div>
@@ -294,7 +286,6 @@ const SettingsNotifications: React.FC = ({ children }) => {
)?.route )?.route
} }
aria-label="Selected tab" aria-label="Selected tab"
className="block w-full py-2 pl-3 pr-10 mt-1 text-base leading-6 text-white transition duration-150 ease-in-out bg-gray-800 border-gray-700 rounded-md form-select focus:outline-none focus:ring-blue focus:border-blue-300 sm:text-sm sm:leading-5"
> >
{settingsRoutes.map((route, index) => ( {settingsRoutes.map((route, index) => (
<SettingsLink <SettingsLink
@@ -322,7 +313,7 @@ const SettingsNotifications: React.FC = ({ children }) => {
</nav> </nav>
</div> </div>
</div> </div>
<div className="mt-10">{children}</div> <div className="section">{children}</div>
</> </>
); );
}; };

View File

@@ -25,7 +25,7 @@ const messages = defineMessages({
serverLocal: 'local', serverLocal: 'local',
serverRemote: 'remote', serverRemote: 'remote',
serverConnected: 'connected', serverConnected: 'connected',
serverpresetManualMessage: 'Manually configure', serverpresetManualMessage: 'Manual configuration',
serverpresetRefreshing: 'Retrieving servers…', serverpresetRefreshing: 'Retrieving servers…',
serverpresetLoad: 'Press the button to load available servers', serverpresetLoad: 'Press the button to load available servers',
toastPlexRefresh: 'Retrieving server list from Plex', toastPlexRefresh: 'Retrieving server list from Plex',
@@ -259,14 +259,14 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
} }
return ( return (
<> <>
<div> <div className="mb-6">
<h3 className="text-lg font-medium leading-6 text-gray-200"> <h3 className="heading">
<FormattedMessage {...messages.plexsettings} /> <FormattedMessage {...messages.plexsettings} />
</h3> </h3>
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500"> <p className="description">
<FormattedMessage {...messages.plexsettingsDescription} /> <FormattedMessage {...messages.plexsettingsDescription} />
</p> </p>
<div className="mt-6 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="section">
<Alert title={intl.formatMessage(messages.settingUpPlex)} type="info"> <Alert title={intl.formatMessage(messages.settingUpPlex)} type="info">
{intl.formatMessage(messages.settingUpPlexDescription, { {intl.formatMessage(messages.settingUpPlexDescription, {
RegisterPlexTVLink: function RegisterPlexTVLink(msg) { RegisterPlexTVLink: function RegisterPlexTVLink(msg) {
@@ -346,200 +346,172 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
isSubmitting, isSubmitting,
}) => { }) => {
return ( return (
<form onSubmit={handleSubmit}> <form className="section" onSubmit={handleSubmit}>
<div className="mt-6 sm:mt-5"> <div className="form-row">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <label htmlFor="name" className="text-label">
<label <div className="flex flex-col">
htmlFor="name" <span className="mr-2">
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px" <FormattedMessage {...messages.servername} />
> </span>
<div className="flex flex-col"> <span className="text-gray-500">
<span className="mr-2"> <FormattedMessage {...messages.servernameTip} />
<FormattedMessage {...messages.servername} /> </span>
</span>
<span className="text-gray-500">
<FormattedMessage {...messages.servernameTip} />
</span>
</div>
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="flex max-w-lg rounded-md shadow-sm">
<input
type="text"
id="name"
name="name"
placeholder={intl.formatMessage(
messages.servernamePlaceholder
)}
value={data?.name}
readOnly
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/>
</div>
</div> </div>
</div> </label>
<div className="mt-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-input">
<label <div className="flex max-w-lg rounded-md shadow-sm">
htmlFor="preset" <input
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px" type="text"
> id="name"
<FormattedMessage {...messages.serverpreset} /> name="name"
</label> placeholder={intl.formatMessage(
<div className="mt-1 sm:mt-0 sm:col-span-2"> messages.servernamePlaceholder
<div className="flex max-w-lg rounded-md shadow-sm input-group"> )}
<select value={data?.name}
id="preset" readOnly
name="preset" />
placeholder={intl.formatMessage(
messages.serverpresetPlaceholder
)}
value={values.selectedPreset}
disabled={!availableServers || isRefreshingPresets}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-none rounded-l-md form-input sm:text-sm sm:leading-5"
onChange={async (e) => {
const targPreset =
availablePresets[Number(e.target.value)];
if (targPreset) {
setFieldValue('hostname', targPreset.host);
setFieldValue('port', targPreset.port);
setFieldValue('useSsl', targPreset.ssl);
}
setFieldTouched('hostname');
setFieldTouched('port');
setFieldTouched('useSsl');
}}
>
<option value="manual">
{availableServers || isRefreshingPresets
? isRefreshingPresets
? intl.formatMessage(
messages.serverpresetRefreshing
)
: intl.formatMessage(
messages.serverpresetManualMessage
)
: intl.formatMessage(messages.serverpresetLoad)}
</option>
{availablePresets.map((server, index) => (
<option
key={`preset-server-${index}`}
value={index}
disabled={!server.status}
>
{`
${server.name} (${server.address})
[${
server.local
? intl.formatMessage(messages.serverLocal)
: intl.formatMessage(messages.serverRemote)
}]
${server.status ? '' : '(' + server.message + ')'}
`}
</option>
))}
</select>
<button
onClick={(e) => {
e.preventDefault();
refreshPresetServers();
}}
className="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium leading-5 text-white transition duration-150 ease-in-out bg-indigo-500 border border-gray-500 rounded-r-md hover:bg-indigo-400 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700"
>
<svg
className={`w-5 h-5 ${
isRefreshingPresets ? 'animate-spin' : ''
}`}
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
</div>
</div>
<div>
<div>
<div className="mt-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800">
<label
htmlFor="hostname"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<FormattedMessage {...messages.hostname} />
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="flex max-w-lg rounded-md shadow-sm">
<span className="inline-flex items-center px-3 text-gray-100 bg-gray-800 border border-r-0 border-gray-500 cursor-default rounded-l-md sm:text-sm">
{values.useSsl ? 'https://' : 'http://'}
</span>
<Field
type="text"
id="hostname"
name="hostname"
placeholder="127.0.0.1"
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 form-input rounded-r-md sm:text-sm sm:leading-5"
/>
</div>
{errors.hostname && touched.hostname && (
<div className="mt-2 text-red-500">
{errors.hostname}
</div>
)}
</div>
</div>
</div>
<div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200">
<label
htmlFor="port"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<FormattedMessage {...messages.port} />
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="max-w-lg rounded-md shadow-sm sm:max-w-xs">
<Field
type="text"
id="port"
name="port"
placeholder="32400"
className="block w-24 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/>
</div>
{errors.port && touched.port && (
<div className="mt-2 text-red-500">{errors.port}</div>
)}
</div>
</div>
</div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200">
<label
htmlFor="ssl"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.ssl)}
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<Field
type="checkbox"
id="useSsl"
name="useSsl"
onChange={() => {
setFieldValue('useSsl', !values.useSsl);
}}
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</div>
</div> </div>
</div> </div>
</div> </div>
<div className="form-row">
<label htmlFor="preset" className="text-label">
<FormattedMessage {...messages.serverpreset} />
</label>
<div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm input-group">
<select
id="preset"
name="preset"
placeholder={intl.formatMessage(
messages.serverpresetPlaceholder
)}
value={values.selectedPreset}
disabled={!availableServers || isRefreshingPresets}
className="rounded-l-only"
onChange={async (e) => {
const targPreset =
availablePresets[Number(e.target.value)];
if (targPreset) {
setFieldValue('hostname', targPreset.host);
setFieldValue('port', targPreset.port);
setFieldValue('useSsl', targPreset.ssl);
}
setFieldTouched('hostname');
setFieldTouched('port');
setFieldTouched('useSsl');
}}
>
<option value="manual">
{availableServers || isRefreshingPresets
? isRefreshingPresets
? intl.formatMessage(
messages.serverpresetRefreshing
)
: intl.formatMessage(
messages.serverpresetManualMessage
)
: intl.formatMessage(messages.serverpresetLoad)}
</option>
{availablePresets.map((server, index) => (
<option
key={`preset-server-${index}`}
value={index}
disabled={!server.status}
>
{`
${server.name} (${server.address})
[${
server.local
? intl.formatMessage(messages.serverLocal)
: intl.formatMessage(messages.serverRemote)
}]
${server.status ? '' : '(' + server.message + ')'}
`}
</option>
))}
</select>
<button
onClick={(e) => {
e.preventDefault();
refreshPresetServers();
}}
className="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium leading-5 text-white transition duration-150 ease-in-out bg-indigo-600 border border-gray-500 rounded-r-md hover:bg-indigo-500 focus:outline-none focus:ring-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700"
>
<svg
className={`w-5 h-5 ${
isRefreshingPresets ? 'animate-spin' : ''
}`}
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"
clipRule="evenodd"
/>
</svg>
</button>
</div>
</div>
</div>
<div className="form-row">
<label htmlFor="hostname" className="text-label">
<FormattedMessage {...messages.hostname} />
</label>
<div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm">
<span className="inline-flex items-center px-3 text-gray-100 bg-gray-800 border border-r-0 border-gray-500 cursor-default rounded-l-md sm:text-sm">
{values.useSsl ? 'https://' : 'http://'}
</span>
<Field
type="text"
id="hostname"
name="hostname"
placeholder="127.0.0.1"
className="rounded-r-only"
/>
</div>
{errors.hostname && touched.hostname && (
<div className="error">{errors.hostname}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="port" className="text-label">
<FormattedMessage {...messages.port} />
</label>
<div className="form-input">
<div className="max-w-lg rounded-md shadow-sm sm:max-w-xs">
<Field
type="text"
id="port"
name="port"
placeholder="32400"
/>
</div>
{errors.port && touched.port && (
<div className="error">{errors.port}</div>
)}
</div>
</div>
<div className="form-row">
<label htmlFor="ssl" className="checkbox-label">
{intl.formatMessage(messages.ssl)}
</label>
<div className="form-input">
<Field
type="checkbox"
id="useSsl"
name="useSsl"
onChange={() => {
setFieldValue('useSsl', !values.useSsl);
}}
/>
</div>
</div>
{submitError && ( {submitError && (
<div className="mt-6 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="mt-6 sm:gap-4 sm:items-start">
<Alert <Alert
title={intl.formatMessage( title={intl.formatMessage(
messages.toastPlexConnectingFailure messages.toastPlexConnectingFailure
@@ -550,7 +522,7 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
</Alert> </Alert>
</div> </div>
)} )}
<div className="pt-5 mt-8 border-t border-gray-700"> <div className="actions">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
@@ -569,32 +541,32 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
); );
}} }}
</Formik> </Formik>
<div className="mt-10"> <div className="mt-10 mb-6">
<h3 className="text-lg font-medium leading-6 text-gray-200"> <h3 className="heading">
<FormattedMessage {...messages.plexlibraries} /> <FormattedMessage {...messages.plexlibraries} />
</h3> </h3>
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500"> <p className="description">
<FormattedMessage {...messages.plexlibrariesDescription} /> <FormattedMessage {...messages.plexlibrariesDescription} />
</p> </p>
<div className="mt-6"> </div>
<Button onClick={() => syncLibraries()} disabled={isSyncing}> <div className="section">
<svg <Button onClick={() => syncLibraries()} disabled={isSyncing}>
className={`${isSyncing ? 'animate-spin' : ''} w-5 h-5 mr-1`} <svg
fill="currentColor" className={`${isSyncing ? 'animate-spin' : ''} w-5 h-5 mr-1`}
viewBox="0 0 20 20" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
> xmlns="http://www.w3.org/2000/svg"
<path >
fillRule="evenodd" <path
d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" fillRule="evenodd"
clipRule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z"
/> clipRule="evenodd"
</svg> />
{isSyncing </svg>
? intl.formatMessage(messages.syncing) {isSyncing
: intl.formatMessage(messages.sync)} ? intl.formatMessage(messages.syncing)
</Button> : intl.formatMessage(messages.sync)}
</div> </Button>
<ul className="grid grid-cols-1 gap-5 mt-6 sm:gap-6 sm:grid-cols-2 lg:grid-cols-4"> <ul className="grid grid-cols-1 gap-5 mt-6 sm:gap-6 sm:grid-cols-2 lg:grid-cols-4">
{data?.libraries.map((library) => ( {data?.libraries.map((library) => (
<LibraryItem <LibraryItem
@@ -606,107 +578,107 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
))} ))}
</ul> </ul>
</div> </div>
<div className="mt-10"> <div className="mt-10 mb-6">
<h3 className="text-lg font-medium leading-6 text-gray-200"> <h3 className="heading">
<FormattedMessage {...messages.manualscan} /> <FormattedMessage {...messages.manualscan} />
</h3> </h3>
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500"> <p className="description">
<FormattedMessage {...messages.manualscanDescription} /> <FormattedMessage {...messages.manualscanDescription} />
</p> </p>
<div className="mt-6"> </div>
<div className="p-4 bg-gray-800 rounded-md"> <div className="section">
<div className="relative w-full h-8 mb-6 overflow-hidden bg-gray-600 rounded-full"> <div className="p-4 bg-gray-800 rounded-md">
{dataSync?.running && ( <div className="relative w-full h-8 mb-6 overflow-hidden bg-gray-600 rounded-full">
<div {dataSync?.running && (
className="h-8 transition-all duration-200 ease-in-out bg-indigo-600" <div
style={{ className="h-8 transition-all duration-200 ease-in-out bg-indigo-600"
width: `${Math.round( style={{
(dataSync.progress / dataSync.total) * 100 width: `${Math.round(
)}%`, (dataSync.progress / dataSync.total) * 100
}} )}%`,
/> }}
)} />
<div className="absolute inset-0 flex items-center justify-center w-full h-8 text-sm"> )}
<span> <div className="absolute inset-0 flex items-center justify-center w-full h-8 text-sm">
{dataSync?.running <span>
? `${dataSync.progress} of ${dataSync.total}` {dataSync?.running
: 'Not running'} ? `${dataSync.progress} of ${dataSync.total}`
</span> : 'Not running'}
</div> </span>
</div> </div>
<div className="flex flex-col w-full sm:flex-row"> </div>
{dataSync?.running && ( <div className="flex flex-col w-full sm:flex-row">
<> {dataSync?.running && (
{dataSync.currentLibrary && ( <>
<div className="flex items-center mb-2 mr-0 sm:mb-0 sm:mr-2"> {dataSync.currentLibrary && (
<Badge> <div className="flex items-center mb-2 mr-0 sm:mb-0 sm:mr-2">
<FormattedMessage <Badge>
{...messages.currentlibrary}
values={{ name: dataSync.currentLibrary.name }}
/>
</Badge>
</div>
)}
<div className="flex items-center">
<Badge badgeType="warning">
<FormattedMessage <FormattedMessage
{...messages.librariesRemaining} {...messages.currentlibrary}
values={{ values={{ name: dataSync.currentLibrary.name }}
count: dataSync.currentLibrary
? dataSync.libraries.slice(
dataSync.libraries.findIndex(
(library) =>
library.id === dataSync.currentLibrary?.id
) + 1
).length
: 0,
}}
/> />
</Badge> </Badge>
</div> </div>
</> )}
<div className="flex items-center">
<Badge badgeType="warning">
<FormattedMessage
{...messages.librariesRemaining}
values={{
count: dataSync.currentLibrary
? dataSync.libraries.slice(
dataSync.libraries.findIndex(
(library) =>
library.id === dataSync.currentLibrary?.id
) + 1
).length
: 0,
}}
/>
</Badge>
</div>
</>
)}
<div className="flex-1 text-right">
{!dataSync?.running && (
<Button buttonType="warning" onClick={() => startScan()}>
<svg
className="w-5 h-5 mr-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
<FormattedMessage {...messages.startscan} />
</Button>
)} )}
<div className="flex-1 text-right">
{!dataSync?.running && (
<Button buttonType="warning" onClick={() => startScan()}>
<svg
className="w-5 h-5 mr-1"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
<FormattedMessage {...messages.startscan} />
</Button>
)}
{dataSync?.running && ( {dataSync?.running && (
<Button buttonType="danger" onClick={() => cancelScan()}> <Button buttonType="danger" onClick={() => cancelScan()}>
<svg <svg
className="w-5 h-5 mr-1" className="w-5 h-5 mr-1"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
viewBox="0 0 24 24" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
strokeWidth={2} strokeWidth={2}
d="M6 18L18 6M6 6l12 12" d="M6 18L18 6M6 6l12 12"
/> />
</svg> </svg>
<FormattedMessage {...messages.cancelscan} /> <FormattedMessage {...messages.cancelscan} />
</Button> </Button>
)} )}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -18,10 +18,10 @@ import Alert from '../Common/Alert';
const messages = defineMessages({ const messages = defineMessages({
radarrsettings: 'Radarr Settings', radarrsettings: 'Radarr Settings',
radarrSettingsDescription: radarrSettingsDescription:
'Configure your Radarr connection below. You can have multiple Radarr configurations but only two can be active as defaults at any time (one for standard HD and one for 4K). Administrators can override the server will be used when a new request is made.', 'Configure your Radarr connection below. You can have multiple Radarr configurations, but only two can be active as defaults at any time (one for standard HD and one for 4K). Administrators can override the server which is used for new requests.',
sonarrsettings: 'Sonarr Settings', sonarrsettings: 'Sonarr Settings',
sonarrSettingsDescription: sonarrSettingsDescription:
'Configure your Sonarr connection below. You can have multiple Sonarr configurations but only two can be active as defaults at any time (one for standard HD and one for 4K). Administrators can override the server will be used when a new request is made.', 'Configure your Sonarr connection below. You can have multiple Sonarr configurations, but only two can be active as defaults at any time (one for standard HD and one for 4K). Administrators can override the server which is used for new requests.',
deleteserverconfirm: 'Are you sure you want to delete this server?', deleteserverconfirm: 'Are you sure you want to delete this server?',
edit: 'Edit', edit: 'Edit',
delete: 'Delete', delete: 'Delete',
@@ -65,7 +65,7 @@ const ServerInstance: React.FC<ServerInstanceProps> = ({
<div className="flex items-center justify-between w-full p-6 space-x-6"> <div className="flex items-center justify-between w-full p-6 space-x-6">
<div className="flex-1 truncate"> <div className="flex-1 truncate">
<div className="flex items-center mb-2 space-x-3"> <div className="flex items-center mb-2 space-x-3">
<h3 className="text-sm font-medium leading-5 text-white truncate"> <h3 className="font-medium leading-5 text-white truncate">
{name} {name}
</h3> </h3>
{isDefault && ( {isDefault && (
@@ -198,11 +198,11 @@ const SettingsServices: React.FC = () => {
return ( return (
<> <>
<div> <div className="mb-6">
<h3 className="text-lg font-medium leading-6 text-gray-200"> <h3 className="heading">
<FormattedMessage {...messages.radarrsettings} /> <FormattedMessage {...messages.radarrsettings} />
</h3> </h3>
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500"> <p className="description">
<FormattedMessage {...messages.radarrSettingsDescription} /> <FormattedMessage {...messages.radarrSettingsDescription} />
</p> </p>
</div> </div>
@@ -251,7 +251,7 @@ const SettingsServices: React.FC = () => {
<FormattedMessage {...messages.deleteserverconfirm} /> <FormattedMessage {...messages.deleteserverconfirm} />
</Modal> </Modal>
</Transition> </Transition>
<div className="mt-6 sm:mt-5"> <div className="section">
{!radarrData && !radarrError && <LoadingSpinner />} {!radarrData && !radarrError && <LoadingSpinner />}
{radarrData && !radarrError && ( {radarrData && !radarrError && (
<> <>
@@ -283,10 +283,11 @@ const SettingsServices: React.FC = () => {
} }
/> />
))} ))}
<li className="h-32 col-span-1 border-2 border-gray-400 border-dashed rounded-lg shadow sm:h-32"> <li className="h-32 col-span-1 border-2 border-gray-400 border-dashed rounded-lg shadow sm:h-44">
<div className="flex items-center justify-center w-full h-full"> <div className="flex items-center justify-center w-full h-full">
<Button <Button
buttonType="ghost" buttonType="ghost"
className="mt-3 mb-3"
onClick={() => onClick={() =>
setEditRadarrModal({ open: true, radarr: null }) setEditRadarrModal({ open: true, radarr: null })
} }
@@ -311,15 +312,15 @@ const SettingsServices: React.FC = () => {
</> </>
)} )}
</div> </div>
<div className="mt-10"> <div className="mt-10 mb-6">
<h3 className="text-lg font-medium leading-6 text-gray-200"> <h3 className="heading">
<FormattedMessage {...messages.sonarrsettings} /> <FormattedMessage {...messages.sonarrsettings} />
</h3> </h3>
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500"> <p className="description">
<FormattedMessage {...messages.sonarrSettingsDescription} /> <FormattedMessage {...messages.sonarrSettingsDescription} />
</p> </p>
</div> </div>
<div className="mt-6 sm:mt-5"> <div className="section">
{!sonarrData && !sonarrError && <LoadingSpinner />} {!sonarrData && !sonarrError && <LoadingSpinner />}
{sonarrData && !sonarrError && ( {sonarrData && !sonarrError && (
<> <>
@@ -352,7 +353,7 @@ const SettingsServices: React.FC = () => {
} }
/> />
))} ))}
<li className="h-32 col-span-1 border-2 border-gray-400 border-dashed rounded-lg shadow sm:h-32"> <li className="h-32 col-span-1 border-2 border-gray-400 border-dashed rounded-lg shadow sm:h-44">
<div className="flex items-center justify-center w-full h-full"> <div className="flex items-center justify-center w-full h-full">
<Button <Button
buttonType="ghost" buttonType="ghost"

View File

@@ -293,47 +293,28 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
} }
> >
<div className="mb-6"> <div className="mb-6">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="isDefault" className="checkbox-label">
htmlFor="isDefault"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.defaultserver)} {intl.formatMessage(messages.defaultserver)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field type="checkbox" id="isDefault" name="isDefault" />
type="checkbox"
id="isDefault"
name="isDefault"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="is4k" className="checkbox-label">
htmlFor="is4k"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.server4k)} {intl.formatMessage(messages.server4k)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field type="checkbox" id="is4k" name="is4k" />
type="checkbox"
id="is4k"
name="is4k"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="name" className="text-label">
htmlFor="name"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.servername)} {intl.formatMessage(messages.servername)}
<span className="text-red-500">*</span> <span className="text-red-500">*</span>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="name" id="name"
@@ -346,25 +327,21 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
setIsValidated(false); setIsValidated(false);
setFieldValue('name', e.target.value); setFieldValue('name', e.target.value);
}} }}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.name && touched.name && ( {errors.name && touched.name && (
<div className="mt-2 text-red-500">{errors.name}</div> <div className="error">{errors.name}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="hostname" className="text-label">
htmlFor="hostname"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.hostname)} {intl.formatMessage(messages.hostname)}
<span className="text-red-500">*</span> <span className="text-red-500">*</span>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<span className="inline-flex items-center px-3 text-gray-100 bg-gray-600 border border-r-0 border-gray-500 cursor-default rounded-l-md sm:text-sm"> <span className="protocol">
{values.ssl ? 'https://' : 'http://'} {values.ssl ? 'https://' : 'http://'}
</span> </span>
<Field <Field
@@ -376,23 +353,20 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
setIsValidated(false); setIsValidated(false);
setFieldValue('hostname', e.target.value); setFieldValue('hostname', e.target.value);
}} }}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 form-input rounded-r-md sm:text-sm sm:leading-5" className="rounded-r-only"
/> />
</div> </div>
{errors.hostname && touched.hostname && ( {errors.hostname && touched.hostname && (
<div className="mt-2 text-red-500">{errors.hostname}</div> <div className="error">{errors.hostname}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="port" className="text-label">
htmlFor="port"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.port)} {intl.formatMessage(messages.port)}
<span className="text-red-500">*</span> <span className="text-red-500">*</span>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field
id="port" id="port"
name="port" name="port"
@@ -402,21 +376,17 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
setIsValidated(false); setIsValidated(false);
setFieldValue('port', e.target.value); setFieldValue('port', e.target.value);
}} }}
className="block w-24 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md shadow-sm form-input sm:text-sm sm:leading-5"
/> />
{errors.port && touched.port && ( {errors.port && touched.port && (
<div className="mt-2 text-red-500">{errors.port}</div> <div className="error">{errors.port}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="ssl" className="checkbox-label">
htmlFor="ssl"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.ssl)} {intl.formatMessage(messages.ssl)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field
type="checkbox" type="checkbox"
id="ssl" id="ssl"
@@ -425,19 +395,15 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
setIsValidated(false); setIsValidated(false);
setFieldValue('ssl', !values.ssl); setFieldValue('ssl', !values.ssl);
}} }}
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/> />
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="apiKey" className="text-label">
htmlFor="apiKey"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.apiKey)} {intl.formatMessage(messages.apiKey)}
<span className="text-red-500">*</span> <span className="text-red-500">*</span>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="apiKey" id="apiKey"
@@ -450,22 +416,18 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
setIsValidated(false); setIsValidated(false);
setFieldValue('apiKey', e.target.value); setFieldValue('apiKey', e.target.value);
}} }}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.apiKey && touched.apiKey && ( {errors.apiKey && touched.apiKey && (
<div className="mt-2 text-red-500">{errors.apiKey}</div> <div className="error">{errors.apiKey}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="baseUrl" className="text-label">
htmlFor="baseUrl"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.baseUrl)} {intl.formatMessage(messages.baseUrl)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="baseUrl" id="baseUrl"
@@ -478,30 +440,25 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
setIsValidated(false); setIsValidated(false);
setFieldValue('baseUrl', e.target.value); setFieldValue('baseUrl', e.target.value);
}} }}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.baseUrl && touched.baseUrl && ( {errors.baseUrl && touched.baseUrl && (
<div className="mt-2 text-red-500">{errors.baseUrl}</div> <div className="error">{errors.baseUrl}</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="activeProfileId" className="text-label">
htmlFor="activeProfileId"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.qualityprofile)} {intl.formatMessage(messages.qualityprofile)}
<span className="text-red-500">*</span> <span className="text-red-500">*</span>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
as="select" as="select"
id="activeProfileId" id="activeProfileId"
name="activeProfileId" name="activeProfileId"
disabled={!isValidated || isTesting} disabled={!isValidated || isTesting}
className="block w-full py-2 pl-3 pr-10 mt-1 text-base leading-6 bg-gray-700 border-gray-500 rounded-md form-select focus:outline-none focus:ring-blue focus:border-gray-500 sm:text-sm sm:leading-5 disabled:opacity-50"
> >
<option value=""> <option value="">
{isTesting {isTesting
@@ -524,28 +481,22 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
</Field> </Field>
</div> </div>
{errors.activeProfileId && touched.activeProfileId && ( {errors.activeProfileId && touched.activeProfileId && (
<div className="mt-2 text-red-500"> <div className="error">{errors.activeProfileId}</div>
{errors.activeProfileId}
</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="rootFolder" className="text-label">
htmlFor="rootFolder"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.rootfolder)} {intl.formatMessage(messages.rootfolder)}
<span className="text-red-500">*</span> <span className="text-red-500">*</span>
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
as="select" as="select"
id="rootFolder" id="rootFolder"
name="rootFolder" name="rootFolder"
disabled={!isValidated || isTesting} disabled={!isValidated || isTesting}
className="block w-full py-2 pl-3 pr-10 mt-1 text-base leading-6 bg-gray-700 border-gray-500 rounded-md form-select focus:outline-none focus:ring-blue focus:border-gray-500 sm:text-sm sm:leading-5 disabled:opacity-50"
> >
<option value=""> <option value="">
{isTesting {isTesting
@@ -566,27 +517,21 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
</Field> </Field>
</div> </div>
{errors.rootFolder && touched.rootFolder && ( {errors.rootFolder && touched.rootFolder && (
<div className="mt-2 text-red-500"> <div className="error">{errors.rootFolder}</div>
{errors.rootFolder}
</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="activeAnimeProfileId" className="text-label">
htmlFor="activeAnimeProfileId"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.animequalityprofile)} {intl.formatMessage(messages.animequalityprofile)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
as="select" as="select"
id="activeAnimeProfileId" id="activeAnimeProfileId"
name="activeAnimeProfileId" name="activeAnimeProfileId"
disabled={!isValidated || isTesting} disabled={!isValidated || isTesting}
className="block w-full py-2 pl-3 pr-10 mt-1 text-base leading-6 bg-gray-700 border-gray-500 rounded-md form-select focus:outline-none focus:ring-blue focus:border-gray-500 sm:text-sm sm:leading-5 disabled:opacity-50"
> >
<option value=""> <option value="">
{isTesting {isTesting
@@ -610,27 +555,23 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
</div> </div>
{errors.activeAnimeProfileId && {errors.activeAnimeProfileId &&
touched.activeAnimeProfileId && ( touched.activeAnimeProfileId && (
<div className="mt-2 text-red-500"> <div className="error">
{errors.activeAnimeProfileId} {errors.activeAnimeProfileId}
</div> </div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="activeAnimeRootFolder" className="text-label">
htmlFor="activeAnimeRootFolder"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.animerootfolder)} {intl.formatMessage(messages.animerootfolder)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
as="select" as="select"
id="activeAnimeRootFolder" id="activeAnimeRootFolder"
name="activeAnimeRootFolder" name="activeAnimeRootFolder"
disabled={!isValidated || isTesting} disabled={!isValidated || isTesting}
className="block w-full py-2 pl-3 pr-10 mt-1 text-base leading-6 bg-gray-700 border-gray-500 rounded-md form-select focus:outline-none focus:ring-blue focus:border-gray-500 sm:text-sm sm:leading-5 disabled:opacity-50"
> >
<option value=""> <option value="">
{isTesting {isTesting
@@ -652,36 +593,30 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
</div> </div>
{errors.activeAnimeRootFolder && {errors.activeAnimeRootFolder &&
touched.activeAnimeRootFolder && ( touched.activeAnimeRootFolder && (
<div className="mt-2 text-red-500"> <div className="error">{errors.rootFolder}</div>
{errors.rootFolder}
</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label
htmlFor="enableSeasonFolders" htmlFor="enableSeasonFolders"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px" className="checkbox-label"
> >
{intl.formatMessage(messages.seasonfolders)} {intl.formatMessage(messages.seasonfolders)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field
type="checkbox" type="checkbox"
id="enableSeasonFolders" id="enableSeasonFolders"
name="enableSeasonFolders" name="enableSeasonFolders"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/> />
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="externalUrl" className="text-label">
htmlFor="externalUrl"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.externalUrl)} {intl.formatMessage(messages.externalUrl)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="externalUrl" id="externalUrl"
@@ -690,37 +625,27 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
placeholder={intl.formatMessage( placeholder={intl.formatMessage(
messages.externalUrlPlaceholder messages.externalUrlPlaceholder
)} )}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.externalUrl && touched.externalUrl && ( {errors.externalUrl && touched.externalUrl && (
<div className="mt-2 text-red-500"> <div className="error">{errors.externalUrl}</div>
{errors.externalUrl}
</div>
)} )}
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="syncEnabled" className="checkbox-label">
htmlFor="syncEnabled"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.syncEnabled)} {intl.formatMessage(messages.syncEnabled)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field
type="checkbox" type="checkbox"
id="syncEnabled" id="syncEnabled"
name="syncEnabled" name="syncEnabled"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/> />
</div> </div>
</div> </div>
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200"> <div className="form-row">
<label <label htmlFor="preventSearch" className="checkbox-label">
htmlFor="preventSearch"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.preventSearch)} {intl.formatMessage(messages.preventSearch)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="mt-1 sm:mt-0 sm:col-span-2">
@@ -728,7 +653,6 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
type="checkbox" type="checkbox"
id="preventSearch" id="preventSearch"
name="preventSearch" name="preventSearch"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/> />
</div> </div>
</div> </div>

View File

@@ -44,10 +44,10 @@ const LoginWithPlex: React.FC<LoginWithPlexProps> = ({ onComplete }) => {
return ( return (
<form> <form>
<div className="flex justify-center font-bold text-xl mb-2"> <div className="flex justify-center mb-2 text-xl font-bold">
<FormattedMessage {...messages.welcome} /> <FormattedMessage {...messages.welcome} />
</div> </div>
<div className="flex justify-center text-sm pb-6 mb-2"> <div className="flex justify-center pb-6 mb-2 text-sm">
<FormattedMessage {...messages.signinMessage} /> <FormattedMessage {...messages.signinMessage} />
</div> </div>
<div className="flex items-center justify-center"> <div className="flex items-center justify-center">

View File

@@ -101,7 +101,7 @@ const Setup: React.FC = () => {
</span> </span>
{intl.formatMessage(messages.syncingbackground)} {intl.formatMessage(messages.syncingbackground)}
</div> </div>
<div className="pt-5 mt-8 border-t border-gray-700"> <div className="actions">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button
@@ -119,7 +119,7 @@ const Setup: React.FC = () => {
{currentStep === 3 && ( {currentStep === 3 && (
<div> <div>
<SettingsServices /> <SettingsServices />
<div className="pt-5 mt-8 border-t border-gray-700"> <div className="actions">
<div className="flex justify-end"> <div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm"> <span className="inline-flex ml-3 rounded-md shadow-sm">
<Button <Button

View File

@@ -32,21 +32,23 @@ const TvCast: React.FC = () => {
return ( return (
<> <>
<Header <div className="mt-1 mb-5">
subtext={ <Header
<Link href={`/tv/${data.id}`}> subtext={
<a className="hover:underline">{data.name}</a> <Link href={`/tv/${data.id}`}>
</Link> <a className="hover:underline">{data.name}</a>
} </Link>
> }
{intl.formatMessage(messages.fullseriescast)} >
</Header> {intl.formatMessage(messages.fullseriescast)}
</Header>
</div>
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8"> <ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8">
{data?.credits.cast.map((person) => { {data?.credits.cast.map((person) => {
return ( return (
<li <li
key={person.id} key={person.id}
className="col-span-1 flex flex-col text-center items-center" className="flex flex-col items-center col-span-1 text-center"
> >
<PersonCard <PersonCard
name={person.name} name={person.name}

View File

@@ -32,15 +32,17 @@ const TvCrew: React.FC = () => {
return ( return (
<> <>
<Header <div className="mt-1 mb-5">
subtext={ <Header
<Link href={`/tv/${data.id}`}> subtext={
<a className="hover:underline">{data.name}</a> <Link href={`/tv/${data.id}`}>
</Link> <a className="hover:underline">{data.name}</a>
} </Link>
> }
{intl.formatMessage(messages.fullseriescrew)} >
</Header> {intl.formatMessage(messages.fullseriescrew)}
</Header>
</div>
<ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8"> <ul className="grid grid-cols-2 gap-6 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-8">
{data?.credits.crew.map((person, index) => { {data?.credits.crew.map((person, index) => {
return ( return (

View File

@@ -77,17 +77,19 @@ const TvRecommendations: React.FC = () => {
return ( return (
<> <>
<Header <div className="mt-1 mb-5">
subtext={ <Header
tvData && !tvError subtext={
? intl.formatMessage(messages.recommendationssubtext, { tvData && !tvError
title: tvData.name, ? intl.formatMessage(messages.recommendationssubtext, {
}) title: tvData.name,
: '' })
} : ''
> }
<FormattedMessage {...messages.recommendations} /> >
</Header> <FormattedMessage {...messages.recommendations} />
</Header>
</div>
<ListView <ListView
items={titles} items={titles}
isEmpty={isEmpty} isEmpty={isEmpty}

View File

@@ -77,17 +77,19 @@ const TvSimilar: React.FC = () => {
return ( return (
<> <>
<Header <div className="mt-1 mb-5">
subtext={ <Header
tvData && !tvError subtext={
? intl.formatMessage(messages.similarsubtext, { tvData && !tvError
title: tvData.name, ? intl.formatMessage(messages.similarsubtext, {
}) title: tvData.name,
: undefined })
} : undefined
> }
<FormattedMessage {...messages.similar} /> >
</Header> <FormattedMessage {...messages.similar} />
</Header>
</div>
<ListView <ListView
items={titles} items={titles}
isEmpty={isEmpty} isEmpty={isEmpty}

View File

@@ -207,12 +207,12 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
(data.mediaInfo.status !== MediaStatus.AVAILABLE || (data.mediaInfo.status !== MediaStatus.AVAILABLE ||
data.mediaInfo.status4k !== MediaStatus.AVAILABLE) && ( data.mediaInfo.status4k !== MediaStatus.AVAILABLE) && (
<div className="mb-6"> <div className="mb-6">
<div className="flex flex-col sm:flex-row flex-nowrap"> {data?.mediaInfo &&
{data?.mediaInfo && data?.mediaInfo.status !== MediaStatus.AVAILABLE && (
data?.mediaInfo.status !== MediaStatus.AVAILABLE && ( <div className="flex flex-col sm:flex-row flex-nowrap mb-2">
<Button <Button
onClick={() => markAvailable()} onClick={() => markAvailable()}
className="w-full mb-2 sm:mb-0 sm:mr-1 last:mr-0" className="w-full sm:mb-0"
buttonType="success" buttonType="success"
> >
<svg <svg
@@ -229,12 +229,14 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
</svg> </svg>
<span>{intl.formatMessage(messages.markavailable)}</span> <span>{intl.formatMessage(messages.markavailable)}</span>
</Button> </Button>
)} </div>
{data?.mediaInfo && )}
data?.mediaInfo.status4k !== MediaStatus.AVAILABLE && ( {data?.mediaInfo &&
data?.mediaInfo.status4k !== MediaStatus.AVAILABLE && (
<div className="flex flex-col sm:flex-row flex-nowrap mb-2">
<Button <Button
onClick={() => markAvailable(true)} onClick={() => markAvailable(true)}
className="w-full sm:ml-1 first:ml-0" className="w-full sm:mb-0"
buttonType="success" buttonType="success"
> >
<svg <svg
@@ -253,8 +255,8 @@ const TvDetails: React.FC<TvDetailsProps> = ({ tv }) => {
{intl.formatMessage(messages.mark4kavailable)} {intl.formatMessage(messages.mark4kavailable)}
</span> </span>
</Button> </Button>
)} </div>
</div> )}
<div className="mt-3 text-xs text-gray-300"> <div className="mt-3 text-xs text-gray-300">
{intl.formatMessage(messages.allseasonsmarkedavailable)} {intl.formatMessage(messages.allseasonsmarkedavailable)}
</div> </div>

View File

@@ -19,9 +19,9 @@ export const messages = defineMessages({
avatar: 'Avatar', avatar: 'Avatar',
email: 'Email', email: 'Email',
permissions: 'Permissions', permissions: 'Permissions',
save: 'Save', save: 'Save Changes',
saving: 'Saving…', saving: 'Saving…',
usersaved: 'User saved', usersaved: 'User saved!',
userfail: 'Something went wrong while saving the user.', userfail: 'Something went wrong while saving the user.',
}); });
@@ -85,141 +85,98 @@ const UserEdit: React.FC = () => {
> >
{({ isSubmitting, handleSubmit }) => ( {({ isSubmitting, handleSubmit }) => (
<Form> <Form>
<Header> <div>
<FormattedMessage {...messages.edituser} /> <div className="flex flex-col justify-between sm:flex-row">
</Header> <Header>
<div className="space-y-6"> <FormattedMessage {...messages.edituser} />
<div className="flex flex-col space-y-6 text-white lg:flex-row lg:space-y-0 lg:space-x-6"> </Header>
<div className="flex-grow space-y-6"> </div>
{user?.userType === UserType.PLEX && ( {user?.userType === UserType.PLEX && (
<div className="space-y-1"> <div className="form-row">
<label <label htmlFor="plexUsername" className="text-label">
htmlFor="plexUsername" {intl.formatMessage(messages.plexUsername)}
className="block text-sm font-medium leading-5 text-gray-400" </label>
> <div className="form-input">
{intl.formatMessage(messages.plexUsername)} <div className="flex max-w-lg rounded-md shadow-sm">
</label>
<div className="flex rounded-md shadow-sm">
<Field
id="plexUsername"
name="plexUsername"
type="text"
className="flex-grow block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
readOnly
/>
</div>
</div>
)}
<div className="space-y-1">
<label
htmlFor="username"
className="block text-sm font-medium leading-5 text-gray-400"
>
{intl.formatMessage(messages.username)}
</label>
<div className="flex rounded-md shadow-sm">
<Field <Field
id="username" id="plexUsername"
name="username" name="plexUsername"
type="text" type="text"
className="flex-grow block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/>
</div>
</div>
<div className="space-y-1">
<label
htmlFor="email"
className="block text-sm font-medium leading-5 text-gray-400"
>
<FormattedMessage {...messages.email} />
</label>
<div className="flex rounded-md shadow-sm">
<Field
id="email"
name="email"
type="text"
className="flex-grow block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
readOnly readOnly
/> />
</div> </div>
</div> </div>
</div> </div>
)}
<div className="flex-grow space-y-1 lg:flex-grow-0 lg:flex-shrink-0"> <div className="form-row">
<p <label htmlFor="username" className="text-label">
className="block text-sm font-medium leading-5 text-gray-400" {intl.formatMessage(messages.username)}
aria-hidden="true" </label>
> <div className="form-input">
<FormattedMessage {...messages.avatar} /> <div className="flex max-w-lg rounded-md shadow-sm">
</p> <Field id="username" name="username" type="text" />
<div className="lg:hidden">
<div className="flex items-center">
<div
className="flex-shrink-0 inline-block w-12 h-12 overflow-hidden rounded-full"
aria-hidden="true"
>
<img
className="w-full h-full rounded-full"
src={user?.avatar}
alt=""
/>
</div>
</div>
</div> </div>
</div>
<div className="relative hidden overflow-hidden transition duration-150 ease-in-out rounded-full lg:block"> </div>
<div className="form-row">
<label htmlFor="email" className="text-label">
<FormattedMessage {...messages.email} />
</label>
<div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm">
<Field id="email" name="email" type="text" readOnly />
</div>
</div>
</div>
<div className="form-row">
<span className="text-label">
<FormattedMessage {...messages.avatar} />
</span>
<div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm">
<img <img
className="relative w-40 h-40 rounded-full" className="w-40 h-40 rounded-full"
src={user?.avatar} src={user?.avatar}
alt="" alt=""
/> />
</div> </div>
</div> </div>
</div> </div>
<div className="text-white"> </div>
<div className="sm:border-t sm:border-gray-200"> <div role="group" aria-labelledby="group-label" className="group">
<div role="group" aria-labelledby="label-permissions"> <div className="form-row">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"> <span id="group-label" className="group-label">
<div> <FormattedMessage {...messages.permissions} />
<div </span>
className="text-base font-medium leading-6 sm:text-sm sm:leading-5" <div className="form-input">
id="label-permissions" <div className="max-w-lg">
> <PermissionEdit
<FormattedMessage {...messages.permissions} /> user={currentUser}
</div> currentPermission={currentPermission}
</div> onUpdate={(newPermission) =>
<div className="mt-4 sm:mt-0 sm:col-span-2"> setCurrentPermission(newPermission)
<div className="max-w-lg"> }
<PermissionEdit />
user={currentUser}
currentPermission={currentPermission}
onUpdate={(newPermission) =>
setCurrentPermission(newPermission)
}
/>
</div>
</div>
</div>
</div>
</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="primary"
type="submit"
disabled={isSubmitting}
onClick={() => handleSubmit}
>
{isSubmitting
? intl.formatMessage(messages.saving)
: intl.formatMessage(messages.save)}
</Button>
</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div className="actions">
<div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
buttonType="primary"
type="submit"
disabled={isSubmitting}
onClick={() => handleSubmit}
>
{isSubmitting
? intl.formatMessage(messages.saving)
: intl.formatMessage(messages.save)}
</Button>
</span>
</div>
</div>
</Form> </Form>
)} )}
</Formik> </Formik>

View File

@@ -89,22 +89,25 @@ const BulkEditModal: React.FC<BulkEditProps> = ({
okText={intl.formatMessage(userEditMessages.save)} okText={intl.formatMessage(userEditMessages.save)}
onCancel={onCancel} onCancel={onCancel}
> >
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline"> <div className="mt-6 mb-6">
<div> <div role="group" aria-labelledby="group-label">
<div <div className="form-row">
className="text-base font-medium leading-6 sm:text-sm sm:leading-5" <div>
id="label-permissions" <div id="group-label" className="group-label">
> <FormattedMessage {...userEditMessages.permissions} />
<FormattedMessage {...userEditMessages.permissions} /> </div>
</div> </div>
</div> <div className="form-input">
<div className="mt-4 sm:mt-0 sm:col-span-2"> <div className="max-w-lg">
<div className="max-w-lg"> <PermissionEdit
<PermissionEdit user={currentUser}
user={currentUser} currentPermission={currentPermission}
currentPermission={currentPermission} onUpdate={(newPermission) =>
onUpdate={(newPermission) => setCurrentPermission(newPermission)} setCurrentPermission(newPermission)
/> }
/>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -282,50 +282,43 @@ const UserList: React.FC = () => {
<Alert title={intl.formatMessage(messages.passwordinfo)}> <Alert title={intl.formatMessage(messages.passwordinfo)}>
{intl.formatMessage(messages.passwordinfodescription)} {intl.formatMessage(messages.passwordinfodescription)}
</Alert> </Alert>
<Form> <Form className="section">
<div className="mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800"> <div className="form-row">
<label <label htmlFor="email" className="text-label">
htmlFor="email"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
{intl.formatMessage(messages.email)} {intl.formatMessage(messages.email)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="email" id="email"
name="email" name="email"
type="text" type="text"
placeholder="name@example.com" placeholder="name@example.com"
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.email && touched.email && ( {errors.email && touched.email && (
<div className="mt-2 text-red-500">{errors.email}</div> <div className="error">{errors.email}</div>
)} )}
</div> </div>
<label </div>
htmlFor="genpassword" <div className="form-row">
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px" <label htmlFor="genpassword" className="checkbox-label">
>
{intl.formatMessage(messages.autogeneratepassword)} {intl.formatMessage(messages.autogeneratepassword)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<Field <Field
type="checkbox" type="checkbox"
id="genpassword" id="genpassword"
name="genpassword" name="genpassword"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
onClick={() => setFieldValue('password', '')} onClick={() => setFieldValue('password', '')}
/> />
</div> </div>
<label </div>
htmlFor="password" <div className="form-row">
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px" <label htmlFor="password" className="text-label">
>
{intl.formatMessage(messages.password)} {intl.formatMessage(messages.password)}
</label> </label>
<div className="mt-1 sm:mt-0 sm:col-span-2"> <div className="form-input">
<div className="flex max-w-lg rounded-md shadow-sm"> <div className="flex max-w-lg rounded-md shadow-sm">
<Field <Field
id="password" id="password"
@@ -333,13 +326,10 @@ const UserList: React.FC = () => {
type="password" type="password"
disabled={values.genpassword} disabled={values.genpassword}
placeholder={intl.formatMessage(messages.password)} placeholder={intl.formatMessage(messages.password)}
className="flex-1 block w-full min-w-0 transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5"
/> />
</div> </div>
{errors.password && touched.password && ( {errors.password && touched.password && (
<div className="mt-2 text-red-500"> <div className="error">{errors.password}</div>
{errors.password}
</div>
)} )}
</div> </div>
</div> </div>
@@ -370,18 +360,18 @@ const UserList: React.FC = () => {
/> />
</Transition> </Transition>
<div className="flex flex-col justify-between sm:flex-row"> <div className="flex flex-col justify-between md:items-end md:flex-row">
<Header>{intl.formatMessage(messages.userlist)}</Header> <Header>{intl.formatMessage(messages.userlist)}</Header>
<div className="flex"> <div className="flex flex-row justify-between mt-2 sm:flex-row md:mb-0">
<Button <Button
className="mx-4 my-8 outline" className="flex-grow mr-2 outline"
buttonType="primary" buttonType="primary"
onClick={() => setCreateModal({ isOpen: true })} onClick={() => setCreateModal({ isOpen: true })}
> >
{intl.formatMessage(messages.createlocaluser)} {intl.formatMessage(messages.createlocaluser)}
</Button> </Button>
<Button <Button
className="mx-4 my-8" className="flex-grow outline"
buttonType="primary" buttonType="primary"
disabled={isImporting} disabled={isImporting}
onClick={() => importFromPlex()} onClick={() => importFromPlex()}
@@ -390,7 +380,6 @@ const UserList: React.FC = () => {
</Button> </Button>
</div> </div>
</div> </div>
<Table> <Table>
<thead> <thead>
<tr> <tr>
@@ -403,7 +392,6 @@ const UserList: React.FC = () => {
onChange={() => { onChange={() => {
toggleAllUsers(); toggleAllUsers();
}} }}
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/> />
</Table.TH> </Table.TH>
<Table.TH>{intl.formatMessage(messages.username)}</Table.TH> <Table.TH>{intl.formatMessage(messages.username)}</Table.TH>
@@ -414,7 +402,6 @@ const UserList: React.FC = () => {
<Table.TH>{intl.formatMessage(messages.lastupdated)}</Table.TH> <Table.TH>{intl.formatMessage(messages.lastupdated)}</Table.TH>
<Table.TH className="text-right"> <Table.TH className="text-right">
<Button <Button
buttonSize="sm"
buttonType="warning" buttonType="warning"
onClick={() => setShowBulkEditModal(true)} onClick={() => setShowBulkEditModal(true)}
disabled={selectedUsers.length === 0} disabled={selectedUsers.length === 0}
@@ -437,7 +424,6 @@ const UserList: React.FC = () => {
onChange={() => { onChange={() => {
toggleUser(user.id); toggleUser(user.id);
}} }}
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/> />
)} )}
</Table.TD> </Table.TD>

View File

@@ -22,6 +22,7 @@ const globalMessages = defineMessages({
close: 'Close', close: 'Close',
edit: 'Edit', edit: 'Edit',
experimental: 'Experimental', experimental: 'Experimental',
advanced: 'Advanced',
}); });
export default globalMessages; export default globalMessages;

View File

@@ -411,11 +411,13 @@
"components.Settings.addsonarr": "Add Sonarr Server", "components.Settings.addsonarr": "Add Sonarr Server",
"components.Settings.apikey": "API Key", "components.Settings.apikey": "API Key",
"components.Settings.applicationurl": "Application URL", "components.Settings.applicationurl": "Application URL",
"components.Settings.autoapprovedrequests": "Send Notifications for Auto-Approved Requests", "components.Settings.autoapprovedrequests": "Enable Notifications for Auto-Approved Requests",
"components.Settings.cancelscan": "Cancel Scan", "components.Settings.cancelscan": "Cancel Scan",
"components.Settings.command": "Command",
"components.Settings.copied": "Copied API key to clipboard.", "components.Settings.copied": "Copied API key to clipboard.",
"components.Settings.csrfProtection": "Enable CSRF Protection", "components.Settings.csrfProtection": "Enable CSRF Protection",
"components.Settings.csrfProtectionTip": "Sets external API access to read-only (Overseerr must be reloaded for changes to take effect)", "components.Settings.csrfProtectionHoverTip": "Do NOT enable this unless you understand what you are doing!",
"components.Settings.csrfProtectionTip": "Sets external API access to read-only (requires HTTPS and Overseerr must be reloaded for changes to take effect)",
"components.Settings.currentlibrary": "Current Library: {name}", "components.Settings.currentlibrary": "Current Library: {name}",
"components.Settings.default": "Default", "components.Settings.default": "Default",
"components.Settings.default4k": "Default 4K", "components.Settings.default4k": "Default 4K",
@@ -453,7 +455,8 @@
"components.Settings.plexsettings": "Plex Settings", "components.Settings.plexsettings": "Plex Settings",
"components.Settings.plexsettingsDescription": "Configure the settings for your Plex server. Overseerr scans your Plex libraries to see what content is available.", "components.Settings.plexsettingsDescription": "Configure the settings for your Plex server. Overseerr scans your Plex libraries to see what content is available.",
"components.Settings.port": "Port", "components.Settings.port": "Port",
"components.Settings.radarrSettingsDescription": "Set up your Radarr connection below. You can have multiple, but only two active as defaults at any time (one for standard HD, and one for 4K). Administrators can override the server is used for new requests.", "components.Settings.process": "Process",
"components.Settings.radarrSettingsDescription": "Configure your Radarr connection below. You can have multiple Radarr configurations, but only two can be active as defaults at any time (one for standard HD and one for 4K). Administrators can override the server which is used for new requests.",
"components.Settings.radarrsettings": "Radarr Settings", "components.Settings.radarrsettings": "Radarr Settings",
"components.Settings.save": "Save Changes", "components.Settings.save": "Save Changes",
"components.Settings.saving": "Saving…", "components.Settings.saving": "Saving…",
@@ -465,12 +468,12 @@
"components.Settings.servernameTip": "Automatically retrieved from Plex after saving", "components.Settings.servernameTip": "Automatically retrieved from Plex after saving",
"components.Settings.serverpreset": "Server", "components.Settings.serverpreset": "Server",
"components.Settings.serverpresetLoad": "Press the button to load available servers", "components.Settings.serverpresetLoad": "Press the button to load available servers",
"components.Settings.serverpresetManualMessage": "Manually configure", "components.Settings.serverpresetManualMessage": "Manual configuration",
"components.Settings.serverpresetPlaceholder": "Plex Server", "components.Settings.serverpresetPlaceholder": "Plex Server",
"components.Settings.serverpresetRefreshing": "Retrieving servers…", "components.Settings.serverpresetRefreshing": "Retrieving servers…",
"components.Settings.settingUpPlex": "Setting Up Plex", "components.Settings.settingUpPlex": "Setting Up Plex",
"components.Settings.settingUpPlexDescription": "To set up Plex, you can either enter your details manually or select a server retrieved from <RegisterPlexTVLink>plex.tv</RegisterPlexTVLink>. Press the button to the right of the dropdown to check connectivity and retrieve available servers.", "components.Settings.settingUpPlexDescription": "To set up Plex, you can either enter your details manually or select a server retrieved from <RegisterPlexTVLink>plex.tv</RegisterPlexTVLink>. Press the button to the right of the dropdown to check connectivity and retrieve available servers.",
"components.Settings.sonarrSettingsDescription": "Set up your Sonarr connection below. You can have multiple, but only two active as defaults at any time (one for standard HD and one for 4K). Administrators can override the server is used for new requests.", "components.Settings.sonarrSettingsDescription": "Configure your Sonarr connection below. You can have multiple Sonarr configurations, but only two can be active as defaults at any time (one for standard HD and one for 4K). Administrators can override the server which is used for new requests.",
"components.Settings.sonarrsettings": "Sonarr Settings", "components.Settings.sonarrsettings": "Sonarr Settings",
"components.Settings.ssl": "SSL", "components.Settings.ssl": "SSL",
"components.Settings.startscan": "Start Scan", "components.Settings.startscan": "Start Scan",
@@ -551,7 +554,7 @@
"components.UserEdit.email": "Email", "components.UserEdit.email": "Email",
"components.UserEdit.permissions": "Permissions", "components.UserEdit.permissions": "Permissions",
"components.UserEdit.plexUsername": "Plex Username", "components.UserEdit.plexUsername": "Plex Username",
"components.UserEdit.save": "Save", "components.UserEdit.save": "Save Changes",
"components.UserEdit.saving": "Saving…", "components.UserEdit.saving": "Saving…",
"components.UserEdit.userfail": "Something went wrong while saving the user.", "components.UserEdit.userfail": "Something went wrong while saving the user.",
"components.UserEdit.username": "Display Name", "components.UserEdit.username": "Display Name",
@@ -591,6 +594,7 @@
"components.UserList.usertype": "User Type", "components.UserList.usertype": "User Type",
"components.UserList.validationemailrequired": "Must enter a valid email address", "components.UserList.validationemailrequired": "Must enter a valid email address",
"components.UserList.validationpasswordminchars": "Password is too short; should be a minimum of 8 characters", "components.UserList.validationpasswordminchars": "Password is too short; should be a minimum of 8 characters",
"i18n.advanced": "Advanced",
"i18n.approve": "Approve", "i18n.approve": "Approve",
"i18n.approved": "Approved", "i18n.approved": "Approved",
"i18n.available": "Available", "i18n.available": "Available",

View File

@@ -37,6 +37,78 @@ body {
@apply relative top-0 bottom-0 left-0 right-0 flex flex-col items-center justify-center h-screen text-center text-gray-300; @apply relative top-0 bottom-0 left-0 right-0 flex flex-col items-center justify-center h-screen text-center text-gray-300;
} }
.heading {
@apply text-2xl leading-8 text-gray-100;
}
.description {
@apply max-w-2xl mt-1 text-sm leading-5 text-gray-500;
}
.section {
@apply mt-6 mb-10 text-white;
}
.form-row {
@apply mt-6 sm:mt-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start;
}
.form-input {
@apply text-white sm:col-span-2;
}
.label-tip {
@apply block text-gray-500;
}
.actions {
@apply pt-5 mt-8 border-t border-gray-700;
}
input[type='checkbox'] {
@apply w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md;
}
.checkbox-label {
@apply block mb-1 text-sm font-medium leading-5 text-gray-400 sm:mt-1;
}
input[type='text'],
input[type='password'],
select {
@apply flex-1 block w-full min-w-0 text-white transition duration-150 ease-in-out bg-gray-700 border border-gray-500 rounded-md form-input sm:text-sm sm:leading-5;
}
input.rounded-l-only,
select.rounded-l-only {
@apply rounded-r-none;
}
input.rounded-r-only,
select.rounded-r-only {
@apply rounded-l-none;
}
.protocol {
@apply inline-flex items-center px-3 text-gray-100 bg-gray-600 border border-r-0 border-gray-500 cursor-default rounded-l-md sm:text-sm;
}
.text-label {
@apply block mb-1 text-sm font-medium leading-5 text-gray-400 sm:mt-2;
}
.error {
@apply mt-2 text-sm text-red-500;
}
.group {
@apply mt-6 text-white;
}
.group-label {
@apply block mb-1 text-sm font-medium leading-6 text-gray-400;
}
/* Used for animating height */ /* Used for animating height */
.extra-max-height { .extra-max-height {
max-height: 100rem; max-height: 100rem;