mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat(requests): add request quotas (#1277)
* feat(quotas): rebased * feat: add getQuota() method to User entity * feat(ui): add default quota setting options * feat: user quota settings * feat: quota display in request modals * fix: only show user quotas on own profile or with manage users permission * feat: add request progress circles to profile page * feat: add migration * fix: add missing restricted field to api schema * fix: dont show auto approve message for movie request when restricted * fix(lang): change enable checkbox langauge to "enable override" Co-authored-by: Jakob Ankarhem <jakob.ankarhem@outlook.com> Co-authored-by: TheCatLady <52870424+TheCatLady@users.noreply.github.com>
This commit is contained in:
@@ -1,20 +1,22 @@
|
||||
import axios from 'axios';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
import useSWR from 'swr';
|
||||
import { UserSettingsGeneralResponse } from '../../../../../server/interfaces/api/userSettingsInterfaces';
|
||||
import { Language } from '../../../../../server/lib/settings';
|
||||
import useSettings from '../../../../hooks/useSettings';
|
||||
import { UserType, useUser, Permission } from '../../../../hooks/useUser';
|
||||
import { Permission, UserType, useUser } from '../../../../hooks/useUser';
|
||||
import globalMessages from '../../../../i18n/globalMessages';
|
||||
import Error from '../../../../pages/_error';
|
||||
import Badge from '../../../Common/Badge';
|
||||
import Button from '../../../Common/Button';
|
||||
import LoadingSpinner from '../../../Common/LoadingSpinner';
|
||||
import RegionSelector from '../../../RegionSelector';
|
||||
import globalMessages from '../../../../i18n/globalMessages';
|
||||
import PageTitle from '../../../Common/PageTitle';
|
||||
import QuotaSelector from '../../../QuotaSelector';
|
||||
import RegionSelector from '../../../RegionSelector';
|
||||
|
||||
const messages = defineMessages({
|
||||
general: 'General',
|
||||
@@ -37,21 +39,25 @@ const messages = defineMessages({
|
||||
originallanguageTip: 'Filter content by original language',
|
||||
originalLanguageDefault: 'All Languages',
|
||||
languageServerDefault: 'Default ({language})',
|
||||
movierequestlimit: 'Movie Request Limit',
|
||||
seriesrequestlimit: 'Series Request Limit',
|
||||
enableOverride: 'Enable Override',
|
||||
});
|
||||
|
||||
const UserGeneralSettings: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const { addToast } = useToasts();
|
||||
const [movieQuotaEnabled, setMovieQuotaEnabled] = useState(false);
|
||||
const [tvQuotaEnabled, setTvQuotaEnabled] = useState(false);
|
||||
const router = useRouter();
|
||||
const { user, hasPermission, mutate } = useUser({
|
||||
id: Number(router.query.userId),
|
||||
});
|
||||
const { hasPermission: currentHasPermission } = useUser();
|
||||
const { currentSettings } = useSettings();
|
||||
const { data, error, revalidate } = useSWR<{
|
||||
username?: string;
|
||||
region?: string;
|
||||
originalLanguage?: string;
|
||||
}>(user ? `/api/v1/user/${user?.id}/settings/main` : null);
|
||||
const { data, error, revalidate } = useSWR<UserSettingsGeneralResponse>(
|
||||
user ? `/api/v1/user/${user?.id}/settings/main` : null
|
||||
);
|
||||
|
||||
const { data: languages, error: languagesError } = useSWR<Language[]>(
|
||||
'/api/v1/languages'
|
||||
@@ -111,6 +117,10 @@ const UserGeneralSettings: React.FC = () => {
|
||||
displayName: data?.username,
|
||||
region: data?.region,
|
||||
originalLanguage: data?.originalLanguage,
|
||||
movieQuotaLimit: data?.movieQuotaLimit,
|
||||
movieQuotaDays: data?.movieQuotaDays,
|
||||
tvQuotaLimit: data?.tvQuotaLimit,
|
||||
tvQuotaDays: data?.tvQuotaDays,
|
||||
}}
|
||||
enableReinitialize
|
||||
onSubmit={async (values) => {
|
||||
@@ -119,6 +129,12 @@ const UserGeneralSettings: React.FC = () => {
|
||||
username: values.displayName,
|
||||
region: values.region,
|
||||
originalLanguage: values.originalLanguage,
|
||||
movieQuotaLimit: movieQuotaEnabled
|
||||
? values.movieQuotaLimit
|
||||
: null,
|
||||
movieQuotaDays: movieQuotaEnabled ? values.movieQuotaDays : null,
|
||||
tvQuotaLimit: tvQuotaEnabled ? values.tvQuotaLimit : null,
|
||||
tvQuotaDays: tvQuotaEnabled ? values.tvQuotaDays : null,
|
||||
});
|
||||
|
||||
addToast(intl.formatMessage(messages.toastSettingsSuccess), {
|
||||
@@ -252,6 +268,91 @@ const UserGeneralSettings: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{currentHasPermission(Permission.MANAGE_USERS) &&
|
||||
!hasPermission(Permission.MANAGE_USERS) && (
|
||||
<>
|
||||
<div className="form-row">
|
||||
<label htmlFor="movieQuotaLimit" className="text-label">
|
||||
<span>
|
||||
{intl.formatMessage(messages.movierequestlimit)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center mb-4">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={movieQuotaEnabled}
|
||||
onChange={() => setMovieQuotaEnabled((s) => !s)}
|
||||
/>
|
||||
<span className="ml-2 text-gray-300">
|
||||
{intl.formatMessage(messages.enableOverride)}
|
||||
</span>
|
||||
</div>
|
||||
<QuotaSelector
|
||||
isDisabled={!movieQuotaEnabled}
|
||||
dayFieldName="movieQuotaDays"
|
||||
limitFieldName="movieQuotaLimit"
|
||||
mediaType="movie"
|
||||
onChange={setFieldValue}
|
||||
defaultDays={values.movieQuotaDays}
|
||||
defaultLimit={values.movieQuotaLimit}
|
||||
dayOverride={
|
||||
!movieQuotaEnabled
|
||||
? data?.globalMovieQuotaDays
|
||||
: undefined
|
||||
}
|
||||
limitOverride={
|
||||
!movieQuotaEnabled
|
||||
? data?.globalMovieQuotaLimit
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="tvQuotaLimit" className="text-label">
|
||||
<span>
|
||||
{intl.formatMessage(messages.seriesrequestlimit)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center mb-4">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={tvQuotaEnabled}
|
||||
onChange={() => setTvQuotaEnabled((s) => !s)}
|
||||
/>
|
||||
<span className="ml-2 text-gray-300">
|
||||
{intl.formatMessage(messages.enableOverride)}
|
||||
</span>
|
||||
</div>
|
||||
<QuotaSelector
|
||||
isDisabled={!tvQuotaEnabled}
|
||||
dayFieldName="tvQuotaDays"
|
||||
limitFieldName="tvQuotaLimit"
|
||||
mediaType="tv"
|
||||
onChange={setFieldValue}
|
||||
defaultDays={values.tvQuotaDays}
|
||||
defaultLimit={values.tvQuotaLimit}
|
||||
dayOverride={
|
||||
!tvQuotaEnabled
|
||||
? data?.globalTvQuotaDays
|
||||
: undefined
|
||||
}
|
||||
limitOverride={
|
||||
!tvQuotaEnabled
|
||||
? data?.globalTvQuotaLimit
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="actions">
|
||||
<div className="flex justify-end">
|
||||
<span className="inline-flex ml-3 rounded-md shadow-sm">
|
||||
|
Reference in New Issue
Block a user