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

pull/814/head
TheCatLady 3 years ago committed by GitHub
parent bfe25d9755
commit e34fbf72fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -77,7 +77,7 @@ const Alert: React.FC<AlertProps> = ({ title, children, type }) => {
}
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-shrink-0 ${design.titleColor}`}>{design.svg}</div>
<div className="ml-3">

@ -11,14 +11,14 @@ const Header: React.FC<HeaderProps> = ({
subtext,
}) => {
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}`}>
<h2 className="text-2xl font-bold leading-7 text-gray-100 sm:text-4xl sm:leading-9 truncate sm:overflow-visible">
<span className="bg-clip-text text-transparent bg-gradient-to-br from-indigo-400 to-purple-400">
<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="text-transparent bg-clip-text bg-gradient-to-br from-indigo-400 to-purple-400">
{children}
</span>
</h2>
{subtext && <div className="text-gray-400 mt-2">{subtext}</div>}
{subtext && <div className="mt-2 text-gray-400">{subtext}</div>}
</div>
</div>
);

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

@ -112,7 +112,7 @@ const Modal: React.FC<ModalProps> = ({
)}
<div
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 && (

@ -3,7 +3,7 @@ import { withProperties } from '../../../utils/typeHelpers';
const TBody: React.FC = ({ children }) => {
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>
);
};

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

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

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

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

@ -117,10 +117,10 @@ const LanguagePicker: React.FC = () => {
leaveTo="transform opacity-0 scale-95"
>
<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}
>
<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>
<label
htmlFor="language"
@ -130,7 +130,7 @@ const LanguagePicker: React.FC = () => {
</label>
<select
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) =>
setLocale && setLocale(e.target.value as AvailableLocales)
}

@ -201,7 +201,7 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
}}
role="button"
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(
sidebarLink.activeRegExp
@ -255,7 +255,7 @@ const Sidebar: React.FC<SidebarProps> = ({ open, setClosed }) => {
as={sidebarLink.as}
>
<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(
sidebarLink.activeRegExp

@ -52,10 +52,10 @@ const Layout: React.FC = ({ children }) => {
</div>
<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">
{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-shrink-0">
<svg

@ -57,10 +57,7 @@ const LocalLogin: React.FC<LocalLoginProps> = ({ revalidate }) => {
<>
<Form>
<div className="sm:border-t sm:border-gray-800">
<label
htmlFor="email"
className="block my-1 text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<label htmlFor="email" className="text-label">
{intl.formatMessage(messages.email)}
</label>
<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"
type="text"
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>
{errors.email && touched.email && (
<div className="mt-2 text-red-500">{errors.email}</div>
<div className="error">{errors.email}</div>
)}
</div>
<label
htmlFor="password"
className="block my-1 text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<label htmlFor="password" className="text-label">
{intl.formatMessage(messages.password)}
</label>
<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"
type="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>
{errors.password && touched.password && (
<div className="mt-2 text-red-500">{errors.password}</div>
<div className="error">{errors.password}</div>
)}
</div>
{loginError && (
<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 className="pt-5 mt-8 border-t border-gray-700">
<div className="actions">
<div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button

@ -32,15 +32,17 @@ const MovieCast: React.FC = () => {
return (
<>
<Header
subtext={
<Link href={`/movie/${data.id}`}>
<a className="hover:underline">{data.title}</a>
</Link>
}
>
{intl.formatMessage(messages.fullcast)}
</Header>
<div className="mt-1 mb-5">
<Header
subtext={
<Link href={`/movie/${data.id}`}>
<a className="hover:underline">{data.title}</a>
</Link>
}
>
{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">
{data?.credits.cast.map((person, index) => {
return (

@ -32,15 +32,17 @@ const MovieCrew: React.FC = () => {
return (
<>
<Header
subtext={
<Link href={`/movie/${data.id}`}>
<a className="hover:underline">{data.title}</a>
</Link>
}
>
{intl.formatMessage(messages.fullcrew)}
</Header>
<div className="mt-1 mb-5">
<Header
subtext={
<Link href={`/movie/${data.id}`}>
<a className="hover:underline">{data.title}</a>
</Link>
}
>
{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">
{data?.credits.crew.map((person, index) => {
return (

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

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

@ -178,50 +178,56 @@ const MovieDetails: React.FC<MovieDetailsProps> = ({ movie }) => {
{data?.mediaInfo &&
(data.mediaInfo.status !== 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.status !== MediaStatus.AVAILABLE && (
<Button
onClick={() => markAvailable()}
className="w-full mb-2 sm:mb-0 sm:mr-1 last:mr-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"
<div className="flex flex-col sm:flex-row flex-nowrap mb-2">
<Button
onClick={() => markAvailable()}
className="w-full sm:mb-0"
buttonType="success"
>
<path
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>
<svg
className="w-5 h-5 mr-1"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
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.status4k !== MediaStatus.AVAILABLE && (
<Button
onClick={() => markAvailable(true)}
className="w-full sm:ml-1 first:ml-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"
<div className="flex flex-col sm:flex-row flex-nowrap mb-2">
<Button
onClick={() => markAvailable(true)}
className="w-full sm:mb-0"
buttonType="success"
>
<path
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>
<svg
className="w-5 h-5 mr-1"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
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>
)}

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

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

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

@ -56,7 +56,7 @@ const RequestList: React.FC = () => {
<>
<div className="flex flex-col justify-between md:items-end md:flex-row">
<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">
<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
@ -84,7 +84,7 @@ const RequestList: React.FC = () => {
setCurrentFilter(e.target.value as Filter);
}}
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">
{intl.formatMessage(messages.filterAll)}
@ -120,7 +120,7 @@ const RequestList: React.FC = () => {
setCurrentSort(e.target.value as Sort);
}}
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">
{intl.formatMessage(messages.sortAdded)}
@ -152,10 +152,12 @@ const RequestList: React.FC = () => {
})}
{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>
<div className="flex flex-col items-center justify-center p-4">
<span>{intl.formatMessage(messages.noresults)}</span>
<div className="flex flex-col items-center justify-center p-6">
<span className="text-base">
{intl.formatMessage(messages.noresults)}
</span>
{currentFilter !== 'all' && (
<div className="mt-4">
<Button
@ -171,10 +173,10 @@ const RequestList: React.FC = () => {
</Table.TD>
</tr>
)}
<tr>
<tr className="bg-gray-700">
<Table.TD colSpan={6} noPadding>
<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"
>
<div className="hidden sm:block">

@ -200,15 +200,15 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
<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-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)}
</label>
<select
id="server"
name="server"
value={selectedServer}
onChange={(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"
>
{data.map((server) => (
@ -222,7 +222,7 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
</select>
</div>
<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)}
</label>
<select
@ -255,7 +255,7 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
</select>
</div>
<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)}
</label>
<select

@ -391,7 +391,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
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
aria-hidden="true"
@ -451,7 +451,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
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 ||
(!!seasonRequest &&
!editingSeasons.includes(season.seasonNumber))

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

@ -29,7 +29,7 @@ const CopyButton: React.FC<{ textToCopy: string }> = ({ textToCopy }) => {
e.preventDefault();
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
className="w-5 h-5 text-white"

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

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

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

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

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

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

@ -93,7 +93,9 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
port: Yup.number().required(
intl.formatMessage(messages.validationPortRequired)
),
apiKey: Yup.string().required(intl.formatMessage(messages.apiKey)),
apiKey: Yup.string().required(
intl.formatMessage(messages.validationApiKeyRequired)
),
rootFolder: Yup.string().required(
intl.formatMessage(messages.validationRootFolderRequired)
),
@ -284,47 +286,28 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
}
>
<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">
<label
htmlFor="isDefault"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="isDefault" className="checkbox-label">
{intl.formatMessage(messages.defaultserver)}
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<Field
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 className="form-input">
<Field type="checkbox" id="isDefault" name="isDefault" />
</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="is4k"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="is4k" className="checkbox-label">
{intl.formatMessage(messages.server4k)}
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<Field
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 className="form-input">
<Field type="checkbox" id="is4k" name="is4k" />
</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">
<label
htmlFor="name"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="name" className="text-label">
{intl.formatMessage(messages.servername)}
<span className="text-red-500">*</span>
</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">
<Field
id="name"
@ -337,25 +320,21 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
setIsValidated(false);
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>
{errors.name && touched.name && (
<div className="mt-2 text-red-500">{errors.name}</div>
<div className="error">{errors.name}</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">
<label
htmlFor="hostname"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="hostname" className="text-label">
{intl.formatMessage(messages.hostname)}
<span className="text-red-500">*</span>
</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">
<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://'}
</span>
<Field
@ -367,23 +346,20 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
setIsValidated(false);
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>
{errors.hostname && touched.hostname && (
<div className="mt-2 text-red-500">{errors.hostname}</div>
<div className="error">{errors.hostname}</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"
>
<div className="form-row">
<label htmlFor="port" className="text-label">
{intl.formatMessage(messages.port)}
<span className="text-red-500">*</span>
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="form-input">
<Field
id="port"
name="port"
@ -393,21 +369,17 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
setIsValidated(false);
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 && (
<div className="mt-2 text-red-500">{errors.port}</div>
<div className="error">{errors.port}</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"
>
<div className="form-row">
<label htmlFor="ssl" className="checkbox-label">
{intl.formatMessage(messages.ssl)}
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="form-input">
<Field
type="checkbox"
id="ssl"
@ -416,19 +388,15 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
setIsValidated(false);
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 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">
<label
htmlFor="apiKey"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="apiKey" className="text-label">
{intl.formatMessage(messages.apiKey)}
<span className="text-red-500">*</span>
</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">
<Field
id="apiKey"
@ -441,22 +409,18 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
setIsValidated(false);
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>
{errors.apiKey && touched.apiKey && (
<div className="mt-2 text-red-500">{errors.apiKey}</div>
<div className="error">{errors.apiKey}</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">
<label
htmlFor="baseUrl"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="baseUrl" className="text-label">
{intl.formatMessage(messages.baseUrl)}
</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">
<Field
id="baseUrl"
@ -469,30 +433,25 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
setIsValidated(false);
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>
{errors.baseUrl && touched.baseUrl && (
<div className="mt-2 text-red-500">{errors.baseUrl}</div>
<div className="error">{errors.baseUrl}</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">
<label
htmlFor="activeProfileId"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="activeProfileId" className="text-label">
{intl.formatMessage(messages.qualityprofile)}
<span className="text-red-500">*</span>
</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">
<Field
as="select"
id="activeProfileId"
name="activeProfileId"
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="">
{isTesting
@ -515,28 +474,22 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
</Field>
</div>
{errors.activeProfileId && touched.activeProfileId && (
<div className="mt-2 text-red-500">
{errors.activeProfileId}
</div>
<div className="error">{errors.activeProfileId}</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">
<label
htmlFor="rootFolder"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="rootFolder" className="text-label">
{intl.formatMessage(messages.rootfolder)}
<span className="text-red-500">*</span>
</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">
<Field
as="select"
id="rootFolder"
name="rootFolder"
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="">
{isTesting
@ -557,27 +510,21 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
</Field>
</div>
{errors.rootFolder && touched.rootFolder && (
<div className="mt-2 text-red-500">
{errors.rootFolder}
</div>
<div className="error">{errors.rootFolder}</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">
<label
htmlFor="minimumAvailability"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="minimumAvailability" className="text-label">
{intl.formatMessage(messages.minimumAvailability)}
<span className="text-red-500">*</span>
</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">
<Field
as="select"
id="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="inCinemas">In Cinemas</option>
@ -587,20 +534,17 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
</div>
{errors.minimumAvailability &&
touched.minimumAvailability && (
<div className="mt-2 text-red-500">
<div className="error">
{errors.minimumAvailability}
</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">
<label
htmlFor="externalUrl"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="externalUrl" className="text-label">
{intl.formatMessage(messages.externalUrl)}
</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">
<Field
id="externalUrl"
@ -609,45 +553,34 @@ const RadarrModal: React.FC<RadarrModalProps> = ({
placeholder={intl.formatMessage(
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>
{errors.externalUrl && touched.externalUrl && (
<div className="mt-2 text-red-500">
{errors.externalUrl}
</div>
<div className="error">{errors.externalUrl}</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="syncEnabled"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="syncEnabled" className="checkbox-label">
{intl.formatMessage(messages.syncEnabled)}
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="form-input">
<Field
type="checkbox"
id="syncEnabled"
name="syncEnabled"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</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="preventSearch"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="preventSearch" className="checkbox-label">
{intl.formatMessage(messages.preventSearch)}
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="form-input">
<Field
type="checkbox"
id="preventSearch"
name="preventSearch"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</div>
</div>

@ -100,7 +100,7 @@ const Release: React.FC<ReleaseProps> = ({
</Modal>
</Transition>
<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
value={Math.floor(
(new Date(release.created_at).getTime() - Date.now()) / 1000
@ -109,16 +109,16 @@ const Release: React.FC<ReleaseProps> = ({
numeric="always"
/>
</span>
<span className="text-xl">{release.name}</span>
<span className="text-lg">{release.name}</span>
{isLatest && (
<span className="ml-2">
<span className="ml-2 -mt-1">
<Badge badgeType="primary">
{intl.formatMessage(messages.latestversion)}
</Badge>
</span>
)}
{release.name.includes(currentVersion) && (
<span className="ml-2">
<span className="ml-2 -mt-1">
<Badge badgeType="success">
{intl.formatMessage(messages.currentversion)}
</Badge>
@ -156,38 +156,38 @@ const Releases: React.FC<ReleasesProps> = ({ currentVersion }) => {
return (
<div>
<div className="pb-4 mb-4 text-xl border-b border-gray-800">
{intl.formatMessage(messages.releases)}
<h3 className="heading">{intl.formatMessage(messages.releases)}</h3>
<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>
{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>
);
};

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

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

@ -106,7 +106,6 @@ const SettingsLayout: React.FC = ({ children }) => {
)?.route
}
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) => (
<SettingsLink

@ -29,7 +29,9 @@ const messages = defineMessages({
hideAvailable: 'Hide Available Media',
csrfProtection: 'Enable CSRF Protection',
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',
trustProxyTip:
'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 (
<>
<div>
<h3 className="text-lg font-medium leading-6 text-gray-200">
<div className="mb-6">
<h3 className="heading">
{intl.formatMessage(messages.generalsettings)}
</h3>
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500">
<p className="description">
{intl.formatMessage(messages.generalsettingsDescription)}
</p>
</div>
<div className="mt-6 sm:mt-5">
<div className="section">
<Formik
initialValues={{
applicationUrl: data?.applicationUrl,
@ -110,21 +112,18 @@ const SettingsMain: React.FC = () => {
>
{({ isSubmitting, values, setFieldValue }) => {
return (
<Form>
<Form className="section">
{userHasPermission(Permission.ADMIN) && (
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-800">
<label
htmlFor="username"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="apiKey" className="text-label">
{intl.formatMessage(messages.apikey)}
</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">
<input
type="text"
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}
readOnly
/>
@ -137,7 +136,7 @@ const SettingsMain: React.FC = () => {
e.preventDefault();
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
className="w-5 h-5"
@ -156,40 +155,31 @@ const SettingsMain: React.FC = () => {
</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">
<label
htmlFor="name"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="applicationUrl" className="text-label">
{intl.formatMessage(messages.applicationurl)}
</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">
<Field
id="applicationUrl"
name="applicationUrl"
type="text"
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 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">
<label
htmlFor="trustProxy"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="flex flex-col">
<span className="mr-2">
{intl.formatMessage(messages.trustProxy)}
</span>
<span className="text-gray-500">
{intl.formatMessage(messages.trustProxyTip)}
</span>
</div>
<div className="form-row">
<label htmlFor="trustProxy" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.trustProxy)}
</span>
<span className="label-tip">
{intl.formatMessage(messages.trustProxyTip)}
</span>
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="form-input">
<Field
type="checkbox"
id="trustProxy"
@ -197,41 +187,37 @@ const SettingsMain: React.FC = () => {
onChange={() => {
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 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">
<label
htmlFor="csrfProtection"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="flex flex-col">
<span className="mr-2">
{intl.formatMessage(messages.csrfProtection)}
</span>
<span className="text-gray-500">
{intl.formatMessage(messages.csrfProtectionTip)}
</span>
</div>
<div className="form-row">
<label htmlFor="csrfProtection" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.csrfProtection)}
</span>
<Badge badgeType="danger">
{intl.formatMessage(globalMessages.advanced)}
</Badge>
<span className="label-tip">
{intl.formatMessage(messages.csrfProtectionTip)}
</span>
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="form-input">
<Field
type="checkbox"
id="csrfProtection"
name="csrfProtection"
title={intl.formatMessage(
messages.csrfProtectionHoverTip
)}
onChange={() => {
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 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">
<label
htmlFor="name"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="hideAvailable" className="checkbox-label">
<span className="mr-2">
{intl.formatMessage(messages.hideAvailable)}
</span>
@ -239,7 +225,7 @@ const SettingsMain: React.FC = () => {
{intl.formatMessage(globalMessages.experimental)}
</Badge>
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="form-input">
<Field
type="checkbox"
id="hideAvailable"
@ -247,38 +233,31 @@ const SettingsMain: React.FC = () => {
onChange={() => {
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 className="mt-6">
<div role="group" aria-labelledby="label-permissions">
<div className="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline">
<div>
<div
className="text-base font-medium leading-6 text-gray-400 sm:text-sm sm:leading-5"
id="label-permissions"
>
{intl.formatMessage(messages.defaultPermissions)}
</div>
</div>
<div className="mt-4 sm:mt-0 sm:col-span-2">
<div className="max-w-lg">
<PermissionEdit
currentPermission={values.defaultPermissions}
onUpdate={(newPermissions) =>
setFieldValue(
'defaultPermissions',
newPermissions
)
}
/>
</div>
<div
role="group"
aria-labelledby="group-label"
className="group"
>
<div className="form-row">
<span id="group-label" className="group-label">
{intl.formatMessage(messages.defaultPermissions)}
</span>
<div className="form-input">
<div className="max-w-lg">
<PermissionEdit
currentPermission={values.defaultPermissions}
onUpdate={(newPermissions) =>
setFieldValue('defaultPermissions', newPermissions)
}
/>
</div>
</div>
</div>
</div>
<div className="pt-5 mt-8 border-t border-gray-700">
<div className="actions">
<div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button

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

@ -25,7 +25,7 @@ const messages = defineMessages({
serverLocal: 'local',
serverRemote: 'remote',
serverConnected: 'connected',
serverpresetManualMessage: 'Manually configure',
serverpresetManualMessage: 'Manual configuration',
serverpresetRefreshing: 'Retrieving servers…',
serverpresetLoad: 'Press the button to load available servers',
toastPlexRefresh: 'Retrieving server list from Plex',
@ -259,14 +259,14 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
}
return (
<>
<div>
<h3 className="text-lg font-medium leading-6 text-gray-200">
<div className="mb-6">
<h3 className="heading">
<FormattedMessage {...messages.plexsettings} />
</h3>
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500">
<p className="description">
<FormattedMessage {...messages.plexsettingsDescription} />
</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">
{intl.formatMessage(messages.settingUpPlexDescription, {
RegisterPlexTVLink: function RegisterPlexTVLink(msg) {
@ -346,200 +346,172 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
isSubmitting,
}) => {
return (
<form onSubmit={handleSubmit}>
<div className="mt-6 sm:mt-5">
<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="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="flex flex-col">
<span className="mr-2">
<FormattedMessage {...messages.servername} />
</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>
<form className="section" onSubmit={handleSubmit}>
<div className="form-row">
<label htmlFor="name" className="text-label">
<div className="flex flex-col">
<span className="mr-2">
<FormattedMessage {...messages.servername} />
</span>
<span className="text-gray-500">
<FormattedMessage {...messages.servernameTip} />
</span>
</div>
</label>
<div className="form-input">
<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
/>
</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="preset"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<FormattedMessage {...messages.serverpreset} />
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<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="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)}
</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>
{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"
))}
</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"
>
<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>
<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 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="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>
<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>
{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 && (
<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
title={intl.formatMessage(
messages.toastPlexConnectingFailure
@ -550,7 +522,7 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
</Alert>
</div>
)}
<div className="pt-5 mt-8 border-t border-gray-700">
<div className="actions">
<div className="flex justify-end">
<span className="inline-flex ml-3 rounded-md shadow-sm">
<Button
@ -569,32 +541,32 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
);
}}
</Formik>
<div className="mt-10">
<h3 className="text-lg font-medium leading-6 text-gray-200">
<div className="mt-10 mb-6">
<h3 className="heading">
<FormattedMessage {...messages.plexlibraries} />
</h3>
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500">
<p className="description">
<FormattedMessage {...messages.plexlibrariesDescription} />
</p>
<div className="mt-6">
<Button onClick={() => syncLibraries()} disabled={isSyncing}>
<svg
className={`${isSyncing ? 'animate-spin' : ''} w-5 h-5 mr-1`}
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>
{isSyncing
? intl.formatMessage(messages.syncing)
: intl.formatMessage(messages.sync)}
</Button>
</div>
</div>
<div className="section">
<Button onClick={() => syncLibraries()} disabled={isSyncing}>
<svg
className={`${isSyncing ? 'animate-spin' : ''} w-5 h-5 mr-1`}
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>
{isSyncing
? intl.formatMessage(messages.syncing)
: intl.formatMessage(messages.sync)}
</Button>
<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) => (
<LibraryItem
@ -606,107 +578,107 @@ const SettingsPlex: React.FC<SettingsPlexProps> = ({ onComplete }) => {
))}
</ul>
</div>
<div className="mt-10">
<h3 className="text-lg font-medium leading-6 text-gray-200">
<div className="mt-10 mb-6">
<h3 className="heading">
<FormattedMessage {...messages.manualscan} />
</h3>
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500">
<p className="description">
<FormattedMessage {...messages.manualscanDescription} />
</p>
<div className="mt-6">
<div className="p-4 bg-gray-800 rounded-md">
<div className="relative w-full h-8 mb-6 overflow-hidden bg-gray-600 rounded-full">
{dataSync?.running && (
<div
className="h-8 transition-all duration-200 ease-in-out bg-indigo-600"
style={{
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>
{dataSync?.running
? `${dataSync.progress} of ${dataSync.total}`
: 'Not running'}
</span>
</div>
</div>
<div className="section">
<div className="p-4 bg-gray-800 rounded-md">
<div className="relative w-full h-8 mb-6 overflow-hidden bg-gray-600 rounded-full">
{dataSync?.running && (
<div
className="h-8 transition-all duration-200 ease-in-out bg-indigo-600"
style={{
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>
{dataSync?.running
? `${dataSync.progress} of ${dataSync.total}`
: 'Not running'}
</span>
</div>
<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">
<Badge>
<FormattedMessage
{...messages.currentlibrary}
values={{ name: dataSync.currentLibrary.name }}
/>
</Badge>
</div>
)}
<div className="flex items-center">
<Badge badgeType="warning">
</div>
<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">
<Badge>
<FormattedMessage
{...messages.librariesRemaining}
values={{
count: dataSync.currentLibrary
? dataSync.libraries.slice(
dataSync.libraries.findIndex(
(library) =>
library.id === dataSync.currentLibrary?.id
) + 1
).length
: 0,
}}
{...messages.currentlibrary}
values={{ name: dataSync.currentLibrary.name }}
/>
</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 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>
)}
{dataSync?.running && (
<Button buttonType="danger" onClick={() => cancelScan()}>
<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="M6 18L18 6M6 6l12 12"
/>
</svg>
<FormattedMessage {...messages.cancelscan} />
</Button>
)}
</div>
{dataSync?.running && (
<Button buttonType="danger" onClick={() => cancelScan()}>
<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="M6 18L18 6M6 6l12 12"
/>
</svg>
<FormattedMessage {...messages.cancelscan} />
</Button>
)}
</div>
</div>
</div>

@ -18,10 +18,10 @@ import Alert from '../Common/Alert';
const messages = defineMessages({
radarrsettings: 'Radarr 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 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',
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?',
edit: 'Edit',
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-1 truncate">
<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}
</h3>
{isDefault && (
@ -198,11 +198,11 @@ const SettingsServices: React.FC = () => {
return (
<>
<div>
<h3 className="text-lg font-medium leading-6 text-gray-200">
<div className="mb-6">
<h3 className="heading">
<FormattedMessage {...messages.radarrsettings} />
</h3>
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500">
<p className="description">
<FormattedMessage {...messages.radarrSettingsDescription} />
</p>
</div>
@ -251,7 +251,7 @@ const SettingsServices: React.FC = () => {
<FormattedMessage {...messages.deleteserverconfirm} />
</Modal>
</Transition>
<div className="mt-6 sm:mt-5">
<div className="section">
{!radarrData && !radarrError && <LoadingSpinner />}
{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">
<Button
buttonType="ghost"
className="mt-3 mb-3"
onClick={() =>
setEditRadarrModal({ open: true, radarr: null })
}
@ -311,15 +312,15 @@ const SettingsServices: React.FC = () => {
</>
)}
</div>
<div className="mt-10">
<h3 className="text-lg font-medium leading-6 text-gray-200">
<div className="mt-10 mb-6">
<h3 className="heading">
<FormattedMessage {...messages.sonarrsettings} />
</h3>
<p className="max-w-2xl mt-1 text-sm leading-5 text-gray-500">
<p className="description">
<FormattedMessage {...messages.sonarrSettingsDescription} />
</p>
</div>
<div className="mt-6 sm:mt-5">
<div className="section">
{!sonarrData && !sonarrError && <LoadingSpinner />}
{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">
<Button
buttonType="ghost"

@ -293,47 +293,28 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
}
>
<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">
<label
htmlFor="isDefault"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="isDefault" className="checkbox-label">
{intl.formatMessage(messages.defaultserver)}
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<Field
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 className="form-input">
<Field type="checkbox" id="isDefault" name="isDefault" />
</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="is4k"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="is4k" className="checkbox-label">
{intl.formatMessage(messages.server4k)}
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<Field
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 className="form-input">
<Field type="checkbox" id="is4k" name="is4k" />
</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">
<label
htmlFor="name"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="name" className="text-label">
{intl.formatMessage(messages.servername)}
<span className="text-red-500">*</span>
</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">
<Field
id="name"
@ -346,25 +327,21 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
setIsValidated(false);
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>
{errors.name && touched.name && (
<div className="mt-2 text-red-500">{errors.name}</div>
<div className="error">{errors.name}</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">
<label
htmlFor="hostname"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="hostname" className="text-label">
{intl.formatMessage(messages.hostname)}
<span className="text-red-500">*</span>
</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">
<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://'}
</span>
<Field
@ -376,23 +353,20 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
setIsValidated(false);
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>
{errors.hostname && touched.hostname && (
<div className="mt-2 text-red-500">{errors.hostname}</div>
<div className="error">{errors.hostname}</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"
>
<div className="form-row">
<label htmlFor="port" className="text-label">
{intl.formatMessage(messages.port)}
<span className="text-red-500">*</span>
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="form-input">
<Field
id="port"
name="port"
@ -402,21 +376,17 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
setIsValidated(false);
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 && (
<div className="mt-2 text-red-500">{errors.port}</div>
<div className="error">{errors.port}</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"
>
<div className="form-row">
<label htmlFor="ssl" className="checkbox-label">
{intl.formatMessage(messages.ssl)}
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="form-input">
<Field
type="checkbox"
id="ssl"
@ -425,19 +395,15 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
setIsValidated(false);
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 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">
<label
htmlFor="apiKey"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="apiKey" className="text-label">
{intl.formatMessage(messages.apiKey)}
<span className="text-red-500">*</span>
</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">
<Field
id="apiKey"
@ -450,22 +416,18 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
setIsValidated(false);
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>
{errors.apiKey && touched.apiKey && (
<div className="mt-2 text-red-500">{errors.apiKey}</div>
<div className="error">{errors.apiKey}</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">
<label
htmlFor="baseUrl"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="baseUrl" className="text-label">
{intl.formatMessage(messages.baseUrl)}
</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">
<Field
id="baseUrl"
@ -478,30 +440,25 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
setIsValidated(false);
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>
{errors.baseUrl && touched.baseUrl && (
<div className="mt-2 text-red-500">{errors.baseUrl}</div>
<div className="error">{errors.baseUrl}</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">
<label
htmlFor="activeProfileId"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="activeProfileId" className="text-label">
{intl.formatMessage(messages.qualityprofile)}
<span className="text-red-500">*</span>
</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">
<Field
as="select"
id="activeProfileId"
name="activeProfileId"
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="">
{isTesting
@ -524,28 +481,22 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
</Field>
</div>
{errors.activeProfileId && touched.activeProfileId && (
<div className="mt-2 text-red-500">
{errors.activeProfileId}
</div>
<div className="error">{errors.activeProfileId}</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">
<label
htmlFor="rootFolder"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="rootFolder" className="text-label">
{intl.formatMessage(messages.rootfolder)}
<span className="text-red-500">*</span>
</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">
<Field
as="select"
id="rootFolder"
name="rootFolder"
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="">
{isTesting
@ -566,27 +517,21 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
</Field>
</div>
{errors.rootFolder && touched.rootFolder && (
<div className="mt-2 text-red-500">
{errors.rootFolder}
</div>
<div className="error">{errors.rootFolder}</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">
<label
htmlFor="activeAnimeProfileId"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="activeAnimeProfileId" className="text-label">
{intl.formatMessage(messages.animequalityprofile)}
</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">
<Field
as="select"
id="activeAnimeProfileId"
name="activeAnimeProfileId"
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="">
{isTesting
@ -610,27 +555,23 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
</div>
{errors.activeAnimeProfileId &&
touched.activeAnimeProfileId && (
<div className="mt-2 text-red-500">
<div className="error">
{errors.activeAnimeProfileId}
</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">
<label
htmlFor="activeAnimeRootFolder"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="activeAnimeRootFolder" className="text-label">
{intl.formatMessage(messages.animerootfolder)}
</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">
<Field
as="select"
id="activeAnimeRootFolder"
name="activeAnimeRootFolder"
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="">
{isTesting
@ -652,36 +593,30 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
</div>
{errors.activeAnimeRootFolder &&
touched.activeAnimeRootFolder && (
<div className="mt-2 text-red-500">
{errors.rootFolder}
</div>
<div className="error">{errors.rootFolder}</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
htmlFor="enableSeasonFolders"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
className="checkbox-label"
>
{intl.formatMessage(messages.seasonfolders)}
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="form-input">
<Field
type="checkbox"
id="enableSeasonFolders"
name="enableSeasonFolders"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</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">
<label
htmlFor="externalUrl"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="externalUrl" className="text-label">
{intl.formatMessage(messages.externalUrl)}
</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">
<Field
id="externalUrl"
@ -690,37 +625,27 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
placeholder={intl.formatMessage(
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>
{errors.externalUrl && touched.externalUrl && (
<div className="mt-2 text-red-500">
{errors.externalUrl}
</div>
<div className="error">{errors.externalUrl}</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="syncEnabled"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="syncEnabled" className="checkbox-label">
{intl.formatMessage(messages.syncEnabled)}
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
<div className="form-input">
<Field
type="checkbox"
id="syncEnabled"
name="syncEnabled"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</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="preventSearch"
className="block text-sm font-medium leading-5 text-gray-400 sm:mt-px"
>
<div className="form-row">
<label htmlFor="preventSearch" className="checkbox-label">
{intl.formatMessage(messages.preventSearch)}
</label>
<div className="mt-1 sm:mt-0 sm:col-span-2">
@ -728,7 +653,6 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
type="checkbox"
id="preventSearch"
name="preventSearch"
className="w-6 h-6 text-indigo-600 transition duration-150 ease-in-out rounded-md form-checkbox"
/>
</div>
</div>

@ -44,10 +44,10 @@ const LoginWithPlex: React.FC<LoginWithPlexProps> = ({ onComplete }) => {
return (
<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} />
</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} />
</div>
<div className="flex items-center justify-center">

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

@ -32,21 +32,23 @@ const TvCast: React.FC = () => {
return (
<>
<Header
subtext={
<Link href={`/tv/${data.id}`}>
<a className="hover:underline">{data.name}</a>
</Link>
}
>
{intl.formatMessage(messages.fullseriescast)}
</Header>
<div className="mt-1 mb-5">
<Header
subtext={
<Link href={`/tv/${data.id}`}>
<a className="hover:underline">{data.name}</a>
</Link>
}
>
{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">
{data?.credits.cast.map((person) => {
return (
<li
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
name={person.name}

@ -32,15 +32,17 @@ const TvCrew: React.FC = () => {
return (
<>
<Header
subtext={
<Link href={`/tv/${data.id}`}>
<a className="hover:underline">{data.name}</a>
</Link>
}
>
{intl.formatMessage(messages.fullseriescrew)}
</Header>
<div className="mt-1 mb-5">
<Header
subtext={
<Link href={`/tv/${data.id}`}>
<a className="hover:underline">{data.name}</a>
</Link>
}
>
{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">
{data?.credits.crew.map((person, index) => {
return (

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

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

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

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

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

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

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

@ -411,11 +411,13 @@
"components.Settings.addsonarr": "Add Sonarr Server",
"components.Settings.apikey": "API Key",
"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.command": "Command",
"components.Settings.copied": "Copied API key to clipboard.",
"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.default": "Default",
"components.Settings.default4k": "Default 4K",
@ -453,7 +455,8 @@
"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.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.save": "Save Changes",
"components.Settings.saving": "Saving…",
@ -465,12 +468,12 @@
"components.Settings.servernameTip": "Automatically retrieved from Plex after saving",
"components.Settings.serverpreset": "Server",
"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.serverpresetRefreshing": "Retrieving servers…",
"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.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.ssl": "SSL",
"components.Settings.startscan": "Start Scan",
@ -551,7 +554,7 @@
"components.UserEdit.email": "Email",
"components.UserEdit.permissions": "Permissions",
"components.UserEdit.plexUsername": "Plex Username",
"components.UserEdit.save": "Save",
"components.UserEdit.save": "Save Changes",
"components.UserEdit.saving": "Saving…",
"components.UserEdit.userfail": "Something went wrong while saving the user.",
"components.UserEdit.username": "Display Name",
@ -591,6 +594,7 @@
"components.UserList.usertype": "User Type",
"components.UserList.validationemailrequired": "Must enter a valid email address",
"components.UserList.validationpasswordminchars": "Password is too short; should be a minimum of 8 characters",
"i18n.advanced": "Advanced",
"i18n.approve": "Approve",
"i18n.approved": "Approved",
"i18n.available": "Available",

@ -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;
}
.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 */
.extra-max-height {
max-height: 100rem;

Loading…
Cancel
Save