* feat(blacklist): add blacktag settings to main settings page * feat(blacklist): create blacktag logic and infrastructure * feat(blacklist): add scheduling for blacktags job * feat(blacklist): create blacktag ui badge for blacklist * docs(blacklist): document blacktags in using-jellyseerr * fix(blacklist): batch blacklist and media db removes to avoid expression tree too large error * feat(blacklist): allow easy import and export of blacktag configuration * fix(settings): don't copy the API key every time you press enter on the main settings * fix(blacklist): move filter inline with page title to match all the other pages * feat(blacklist): allow filtering between manually blacklisted and automatically blacklisted entries * docs(blacklist): reword blacktag documentation a little * refactor(blacklist): remove blacktag settings from public settings interfaces There's no reason for it to be there * refactor(blacklist): remove unused variable from processResults in blacktagsProcessor * refactor(blacklist): change all instances of blacktag to blacklistedTag and update doc to match * docs(blacklist): update general documentation for blacklisted tag settings * fix(blacklist): update setting use of "blacklisted tag" to match between modals * perf(blacklist): remove media type constraint from existing blacklist entry query Doesn't make sense to keep it because tmdbid has a unique constraint on it * fix(blacklist): remove whitespace line causing prettier to fail in CI * refactor(blacklist): swap out some != and == for !s and _s * fix(blacklist): merge back CopyButton changes, disable button when there's nothing to copy * refactor(blacklist): use axios instead of fetch for blacklisted tag queries * style(blacklist): use templated axios types and remove redundant try-catches
150 lines
4.7 KiB
TypeScript
150 lines
4.7 KiB
TypeScript
import BlacklistedTagsBadge from '@app/components/BlacklistedTagsBadge';
|
|
import Badge from '@app/components/Common/Badge';
|
|
import Button from '@app/components/Common/Button';
|
|
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
|
import Tooltip from '@app/components/Common/Tooltip';
|
|
import { useUser } from '@app/hooks/useUser';
|
|
import globalMessages from '@app/i18n/globalMessages';
|
|
import defineMessages from '@app/utils/defineMessages';
|
|
import { CalendarIcon, TrashIcon, UserIcon } from '@heroicons/react/24/solid';
|
|
import type { Blacklist } from '@server/entity/Blacklist';
|
|
import axios from 'axios';
|
|
import Link from 'next/link';
|
|
import { useState } from 'react';
|
|
import { useIntl } from 'react-intl';
|
|
import { useToasts } from 'react-toast-notifications';
|
|
import useSWR from 'swr';
|
|
|
|
const messages = defineMessages('component.BlacklistBlock', {
|
|
blacklistedby: 'Blacklisted By',
|
|
blacklistdate: 'Blacklisted date',
|
|
});
|
|
|
|
interface BlacklistBlockProps {
|
|
tmdbId: number;
|
|
onUpdate?: () => void;
|
|
onDelete?: () => void;
|
|
}
|
|
|
|
const BlacklistBlock = ({
|
|
tmdbId,
|
|
onUpdate,
|
|
onDelete,
|
|
}: BlacklistBlockProps) => {
|
|
const { user } = useUser();
|
|
const intl = useIntl();
|
|
const [isUpdating, setIsUpdating] = useState(false);
|
|
const { addToast } = useToasts();
|
|
const { data } = useSWR<Blacklist>(`/api/v1/blacklist/${tmdbId}`);
|
|
|
|
const removeFromBlacklist = async (tmdbId: number, title?: string) => {
|
|
setIsUpdating(true);
|
|
|
|
try {
|
|
await axios.delete('/api/v1/blacklist/' + tmdbId);
|
|
|
|
addToast(
|
|
<span>
|
|
{intl.formatMessage(globalMessages.removeFromBlacklistSuccess, {
|
|
title,
|
|
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
|
|
})}
|
|
</span>,
|
|
{ appearance: 'success', autoDismiss: true }
|
|
);
|
|
} catch {
|
|
addToast(intl.formatMessage(globalMessages.blacklistError), {
|
|
appearance: 'error',
|
|
autoDismiss: true,
|
|
});
|
|
}
|
|
|
|
onUpdate && onUpdate();
|
|
onDelete && onDelete();
|
|
|
|
setIsUpdating(false);
|
|
};
|
|
|
|
if (!data) {
|
|
return (
|
|
<>
|
|
<LoadingSpinner />
|
|
</>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="px-4 py-3 text-gray-300">
|
|
<div className="flex items-center justify-between">
|
|
<div className="mr-6 min-w-0 flex-1 flex-col items-center text-sm leading-5">
|
|
<div className="white mb-1 flex flex-nowrap">
|
|
{data.user ? (
|
|
<>
|
|
<Tooltip content={intl.formatMessage(messages.blacklistedby)}>
|
|
<UserIcon className="mr-1.5 h-5 w-5 min-w-0 flex-shrink-0" />
|
|
</Tooltip>
|
|
<span className="w-40 truncate md:w-auto">
|
|
<Link
|
|
href={
|
|
data.user.id === user?.id
|
|
? '/profile'
|
|
: `/users/${data.user.id}`
|
|
}
|
|
>
|
|
<span className="font-semibold text-gray-100 transition duration-300 hover:text-white hover:underline">
|
|
{data.user.displayName}
|
|
</span>
|
|
</Link>
|
|
</span>
|
|
</>
|
|
) : data.blacklistedTags ? (
|
|
<>
|
|
<span className="w-40 truncate md:w-auto">
|
|
{intl.formatMessage(messages.blacklistedby)}:
|
|
</span>
|
|
<BlacklistedTagsBadge data={data} />
|
|
</>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
<div className="ml-2 flex flex-shrink-0 flex-wrap">
|
|
<Tooltip
|
|
content={intl.formatMessage(globalMessages.removefromBlacklist)}
|
|
>
|
|
<Button
|
|
buttonType="danger"
|
|
onClick={() => removeFromBlacklist(data.tmdbId, data.title)}
|
|
disabled={isUpdating}
|
|
>
|
|
<TrashIcon className="icon-sm" />
|
|
</Button>
|
|
</Tooltip>
|
|
</div>
|
|
</div>
|
|
<div className="mt-2 sm:flex sm:justify-between">
|
|
<div className="sm:flex">
|
|
<div className="mr-6 flex items-center text-sm leading-5">
|
|
<Badge badgeType="danger">
|
|
{intl.formatMessage(globalMessages.blacklisted)}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
<div className="mt-2 flex items-center text-sm leading-5 sm:mt-0">
|
|
<Tooltip content={intl.formatMessage(messages.blacklistdate)}>
|
|
<CalendarIcon className="mr-1.5 h-5 w-5 flex-shrink-0" />
|
|
</Tooltip>
|
|
<span>
|
|
{intl.formatDate(data.createdAt, {
|
|
year: 'numeric',
|
|
month: 'long',
|
|
day: 'numeric',
|
|
})}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default BlacklistBlock;
|