You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
overseerr/src/components/UserList/PlexImportModal.tsx

244 lines
9.5 KiB

import { InboxInIcon } from '@heroicons/react/solid';
import axios from 'axios';
import { useState } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr';
import useSettings from '../../hooks/useSettings';
import globalMessages from '../../i18n/globalMessages';
import Alert from '../Common/Alert';
import Modal from '../Common/Modal';
interface PlexImportProps {
onCancel?: () => void;
onComplete?: () => void;
}
const messages = defineMessages({
importfromplex: 'Import Plex Users',
importfromplexerror: 'Something went wrong while importing Plex users.',
importedfromplex:
'<strong>{userCount}</strong> Plex {userCount, plural, one {user} other {users}} imported successfully!',
user: 'User',
nouserstoimport: 'There are no Plex users to import.',
newplexsigninenabled:
'The <strong>Enable New Plex Sign-In</strong> setting is currently enabled. Plex users with library access do not need to be imported in order to sign in.',
});
const PlexImportModal = ({ onCancel, onComplete }: PlexImportProps) => {
const intl = useIntl();
const settings = useSettings();
const { addToast } = useToasts();
const [isImporting, setImporting] = useState(false);
const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
const { data, error } = useSWR<
{
id: string;
title: string;
username: string;
email: string;
thumb: string;
}[]
>(`/api/v1/settings/plex/users`, {
revalidateOnMount: true,
});
const importUsers = async () => {
setImporting(true);
try {
const { data: createdUsers } = await axios.post(
'/api/v1/user/import-from-plex',
{ plexIds: selectedUsers }
);
if (!createdUsers.length) {
throw new Error('No users were imported from Plex.');
}
addToast(
intl.formatMessage(messages.importedfromplex, {
userCount: createdUsers.length,
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
}),
{
autoDismiss: true,
appearance: 'success',
}
);
if (onComplete) {
onComplete();
}
} catch (e) {
addToast(intl.formatMessage(messages.importfromplexerror), {
autoDismiss: true,
appearance: 'error',
});
} finally {
setImporting(false);
}
};
const isSelectedUser = (plexId: string): boolean =>
selectedUsers.includes(plexId);
const isAllUsers = (): boolean => selectedUsers.length === data?.length;
const toggleUser = (plexId: string): void => {
if (selectedUsers.includes(plexId)) {
setSelectedUsers((users) => users.filter((user) => user !== plexId));
} else {
setSelectedUsers((users) => [...users, plexId]);
}
};
const toggleAllUsers = (): void => {
if (data && selectedUsers.length >= 0 && !isAllUsers()) {
setSelectedUsers(data.map((user) => user.id));
} else {
setSelectedUsers([]);
}
};
return (
<Modal
loading={!data && !error}
title={intl.formatMessage(messages.importfromplex)}
iconSvg={<InboxInIcon />}
onOk={() => {
importUsers();
}}
okDisabled={isImporting || !selectedUsers.length}
okText={intl.formatMessage(
isImporting ? globalMessages.importing : globalMessages.import
)}
onCancel={onCancel}
>
{data?.length ? (
<>
{settings.currentSettings.newPlexLogin && (
<Alert
title={intl.formatMessage(messages.newplexsigninenabled, {
strong: (msg: React.ReactNode) => (
<strong className="font-semibold text-white">{msg}</strong>
),
})}
type="info"
/>
)}
<div className="flex flex-col">
<div className="-mx-4 sm:mx-0">
<div className="inline-block min-w-full py-2 align-middle">
<div className="overflow-hidden shadow sm:rounded-lg">
<table className="min-w-full">
<thead>
<tr>
<th className="w-16 bg-gray-500 px-4 py-3">
<span
role="checkbox"
tabIndex={0}
aria-checked={isAllUsers()}
onClick={() => toggleAllUsers()}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === 'Space') {
toggleAllUsers();
}
}}
className="relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer items-center justify-center pt-2 focus:outline-none"
>
<span
aria-hidden="true"
className={`${
isAllUsers() ? 'bg-indigo-500' : 'bg-gray-800'
} absolute mx-auto h-4 w-9 rounded-full transition-colors duration-200 ease-in-out`}
></span>
<span
aria-hidden="true"
className={`${
isAllUsers() ? 'translate-x-5' : 'translate-x-0'
} absolute left-0 inline-block h-5 w-5 transform rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
></span>
</span>
</th>
<th className="bg-gray-500 px-1 py-3 text-left text-xs font-medium uppercase leading-4 tracking-wider text-gray-200 md:px-6">
{intl.formatMessage(messages.user)}
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-700 bg-gray-600">
{data?.map((user) => (
<tr key={`user-${user.id}`}>
<td className="whitespace-nowrap px-4 py-4 text-sm font-medium leading-5 text-gray-100">
<span
role="checkbox"
tabIndex={0}
aria-checked={isSelectedUser(user.id)}
onClick={() => toggleUser(user.id)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === 'Space') {
toggleUser(user.id);
}
}}
className="relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer items-center justify-center pt-2 focus:outline-none"
>
<span
aria-hidden="true"
className={`${
isSelectedUser(user.id)
? 'bg-indigo-500'
: 'bg-gray-800'
} absolute mx-auto h-4 w-9 rounded-full transition-colors duration-200 ease-in-out`}
></span>
<span
aria-hidden="true"
className={`${
isSelectedUser(user.id)
? 'translate-x-5'
: 'translate-x-0'
} absolute left-0 inline-block h-5 w-5 transform rounded-full border border-gray-200 bg-white shadow transition-transform duration-200 ease-in-out group-focus:border-blue-300 group-focus:ring`}
></span>
</span>
</td>
<td className="whitespace-nowrap px-1 py-4 text-sm font-medium leading-5 text-gray-100 md:px-6">
<div className="flex items-center">
<img
className="h-10 w-10 flex-shrink-0 rounded-full"
src={user.thumb}
alt=""
/>
<div className="ml-4">
<div className="text-base font-bold leading-5">
{user.username}
</div>
{user.username &&
user.username.toLowerCase() !==
user.email && (
<div className="text-sm leading-5 text-gray-300">
{user.email}
</div>
)}
</div>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
</div>
</>
) : (
<Alert
title={intl.formatMessage(messages.nouserstoimport)}
type="info"
/>
)}
</Modal>
);
};
export default PlexImportModal;