diff --git a/src/components/Common/Badge/index.tsx b/src/components/Common/Badge/index.tsx index 3868e8a4..bc450d7f 100644 --- a/src/components/Common/Badge/index.tsx +++ b/src/components/Common/Badge/index.tsx @@ -13,7 +13,7 @@ const Badge: React.FC = ({ children, }) => { const badgeStyle = [ - 'px-2 inline-flex text-xs leading-5 font-semibold rounded-full', + 'px-2 inline-flex text-xs leading-5 font-semibold rounded-full whitespace-nowrap', ]; if (url) { diff --git a/src/components/IssueBlock/index.tsx b/src/components/IssueBlock/index.tsx index 7e8067c4..2d3cfb33 100644 --- a/src/components/IssueBlock/index.tsx +++ b/src/components/IssueBlock/index.tsx @@ -8,6 +8,7 @@ import Link from 'next/link'; import React from 'react'; import { useIntl } from 'react-intl'; import type Issue from '../../../server/entity/Issue'; +import globalMessages from '../../i18n/globalMessages'; import Button from '../Common/Button'; import { issueOptions } from '../IssueModal/constants'; @@ -56,7 +57,7 @@ const IssueBlock: React.FC = ({ issue }) => { diff --git a/src/components/IssueDetails/index.tsx b/src/components/IssueDetails/index.tsx index b0c06515..ebdbabd8 100644 --- a/src/components/IssueDetails/index.tsx +++ b/src/components/IssueDetails/index.tsx @@ -44,7 +44,7 @@ const messages = defineMessages({ reopenissueandcomment: 'Reopen with Comment', issuepagetitle: 'Issue', playonplex: 'Play on Plex', - play4konplex: 'Play 4K on Plex', + play4konplex: 'Play in 4K on Plex', openinarr: 'Open in {arr}', openin4karr: 'Open in 4K {arr}', toasteditdescriptionsuccess: 'Issue description edited successfully!', @@ -228,7 +228,7 @@ const IssueDetails: React.FC = () => {
{issueData.status === IssueStatus.OPEN && ( - + {intl.formatMessage(globalMessages.open)} )} @@ -244,15 +244,11 @@ const IssueDetails: React.FC = () => { issueData.media.mediaType === MediaType.MOVIE ? 'movie' : 'tv' }/${data.id}`} > - - {title}{' '} - {releaseYear && ( - - ({releaseYear.slice(0, 4)}) - - )} - - + {title} + {' '} + {releaseYear && ( + ({releaseYear.slice(0, 4)}) + )} {intl.formatMessage(messages.openedby, { diff --git a/src/components/IssueList/IssueItem/index.tsx b/src/components/IssueList/IssueItem/index.tsx index 8a93c2e6..267941fc 100644 --- a/src/components/IssueList/IssueItem/index.tsx +++ b/src/components/IssueList/IssueItem/index.tsx @@ -183,7 +183,7 @@ const IssueItem: React.FC = ({ issue }) => { {intl.formatMessage(messages.issuestatus)} {issue.status === IssueStatus.OPEN ? ( - + {intl.formatMessage(globalMessages.open)} ) : ( diff --git a/src/components/IssueList/index.tsx b/src/components/IssueList/index.tsx index cabf9ad9..a78a762c 100644 --- a/src/components/IssueList/index.tsx +++ b/src/components/IssueList/index.tsx @@ -19,7 +19,7 @@ import IssueItem from './IssueItem'; const messages = defineMessages({ issues: 'Issues', - sortAdded: 'Request Date', + sortAdded: 'Most Recent', sortModified: 'Last Modified', showallissues: 'Show All Issues', }); diff --git a/src/components/IssueModal/CreateIssueModal/index.tsx b/src/components/IssueModal/CreateIssueModal/index.tsx index 2dd4ea8d..c2d75e24 100644 --- a/src/components/IssueModal/CreateIssueModal/index.tsx +++ b/src/components/IssueModal/CreateIssueModal/index.tsx @@ -9,9 +9,12 @@ import { defineMessages, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR from 'swr'; import * as Yup from 'yup'; +import { MediaStatus } from '../../../../server/constants/media'; import type Issue from '../../../../server/entity/Issue'; import { MovieDetails } from '../../../../server/models/Movie'; import { TvDetails } from '../../../../server/models/Tv'; +import useSettings from '../../../hooks/useSettings'; +import { Permission, useUser } from '../../../hooks/useUser'; import globalMessages from '../../../i18n/globalMessages'; import Button from '../../Common/Button'; import Modal from '../../Common/Modal'; @@ -21,7 +24,9 @@ const messages = defineMessages({ validationMessageRequired: 'You must provide a description', issomethingwrong: 'Is there a problem with {title}?', whatswrong: "What's wrong?", - providedetail: 'Provide a detailed explanation of the issue.', + providedetail: + 'Please provide a detailed explanation of the issue you encountered.', + extras: 'Extras', season: 'Season {seasonNumber}', episode: 'Episode {episodeNumber}', allseasons: 'All Seasons', @@ -56,6 +61,8 @@ const CreateIssueModal: React.FC = ({ tmdbId, }) => { const intl = useIntl(); + const settings = useSettings(); + const { hasPermission } = useUser(); const { addToast } = useToasts(); const { data, error } = useSWR( tmdbId ? `/api/v1/${mediaType}/${tmdbId}` : null @@ -65,6 +72,20 @@ const CreateIssueModal: React.FC = ({ return null; } + const availableSeasons = (data?.mediaInfo?.seasons ?? []) + .filter( + (season) => + season.status === MediaStatus.AVAILABLE || + season.status === MediaStatus.PARTIALLY_AVAILABLE || + (settings.currentSettings.series4kEnabled && + hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], { + type: 'or', + }) && + (season.status4k === MediaStatus.AVAILABLE || + season.status4k === MediaStatus.PARTIALLY_AVAILABLE)) + ) + .map((season) => season.seasonNumber); + const CreateIssueModalSchema = Yup.object().shape({ message: Yup.string().required( intl.formatMessage(messages.validationMessageRequired) @@ -76,7 +97,7 @@ const CreateIssueModal: React.FC = ({ initialValues={{ selectedIssue: issueOptions[0], message: '', - problemSeason: 0, + problemSeason: availableSeasons.length === 1 ? availableSeasons[0] : 0, problemEpisode: 0, }} validationSchema={CreateIssueModalSchema} @@ -162,18 +183,23 @@ const CreateIssueModal: React.FC = ({ as="select" id="problemSeason" name="problemSeason" + disabled={availableSeasons.length === 1} > - - {data.seasons.map((season) => ( + {availableSeasons.length > 1 && ( + + )} + {availableSeasons.map((season) => ( ))} diff --git a/src/components/ManageSlideOver/index.tsx b/src/components/ManageSlideOver/index.tsx index 5f865766..9b8898ef 100644 --- a/src/components/ManageSlideOver/index.tsx +++ b/src/components/ManageSlideOver/index.tsx @@ -19,6 +19,7 @@ import RequestBlock from '../RequestBlock'; const messages = defineMessages({ manageModalTitle: 'Manage {mediaType}', + manageModalIssues: 'Open Issues', manageModalRequests: 'Requests', manageModalNoRequests: 'No requests.', manageModalClearMedia: 'Clear Media Data', @@ -77,6 +78,11 @@ const ManageSlideOver: React.FC< revalidate(); }; + const openIssues = + data.mediaInfo?.issues?.filter( + (issue) => issue.status === IssueStatus.OPEN + ) ?? []; + return ( )} - {(data.mediaInfo?.issues ?? []).length > 0 && ( - <> -

Open Issues

-
-
    - {data.mediaInfo?.issues - ?.filter((issue) => issue.status === IssueStatus.OPEN) - .map((issue) => ( + {hasPermission([Permission.MANAGE_ISSUES, Permission.VIEW_ISSUES], { + type: 'or', + }) && + openIssues.length > 0 && ( + <> +

    + {intl.formatMessage(messages.manageModalIssues)} +

    +
    +
      + {openIssues.map((issue) => (
    • ))} -
    -
    - - )} +
+
+ + )}

{intl.formatMessage(messages.manageModalRequests)}

diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index d7b0bbbb..ee0ee872 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -330,7 +330,14 @@ const MovieDetails: React.FC = ({ movie }) => { onUpdate={() => revalidate()} /> {(data.mediaInfo?.status === MediaStatus.AVAILABLE || - data.mediaInfo?.status4k === MediaStatus.AVAILABLE) && + (settings.currentSettings.movie4kEnabled && + hasPermission( + [Permission.REQUEST_4K, Permission.REQUEST_4K_MOVIE], + { + type: 'or', + } + ) && + data.mediaInfo?.status4k === MediaStatus.AVAILABLE)) && hasPermission( [Permission.CREATE_ISSUES, Permission.MANAGE_ISSUES], { @@ -338,7 +345,7 @@ const MovieDetails: React.FC = ({ movie }) => { } ) && ( )}
diff --git a/src/components/NotificationTypeSelector/index.tsx b/src/components/NotificationTypeSelector/index.tsx index 5fd774c4..bef9ed31 100644 --- a/src/components/NotificationTypeSelector/index.tsx +++ b/src/components/NotificationTypeSelector/index.tsx @@ -274,6 +274,11 @@ const NotificationTypeSelector: React.FC = ({ : messages.issuecommentDescription ), value: Notification.ISSUE_COMMENT, + hidden: + user && + !hasPermission([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], { + type: 'or', + }), hasNotifyUser: !user || hasPermission(Permission.MANAGE_ISSUES) ? false : true, }, @@ -286,6 +291,11 @@ const NotificationTypeSelector: React.FC = ({ : messages.issueresolvedDescription ), value: Notification.ISSUE_RESOLVED, + hidden: + user && + !hasPermission([Permission.MANAGE_ISSUES, Permission.CREATE_ISSUES], { + type: 'or', + }), hasNotifyUser: true, }, ]; diff --git a/src/components/RequestList/index.tsx b/src/components/RequestList/index.tsx index c7438228..7c1aa154 100644 --- a/src/components/RequestList/index.tsx +++ b/src/components/RequestList/index.tsx @@ -22,7 +22,7 @@ import RequestItem from './RequestItem'; const messages = defineMessages({ requests: 'Requests', showallrequests: 'Show All Requests', - sortAdded: 'Request Date', + sortAdded: 'Most Recent', sortModified: 'Last Modified', }); diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index 8ff39fb6..7365ba9a 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -331,9 +331,14 @@ const TvDetails: React.FC = ({ tv }) => { is4kShowComplete={is4kComplete} /> {(data.mediaInfo?.status === MediaStatus.AVAILABLE || - data.mediaInfo?.status4k === MediaStatus.AVAILABLE || data.mediaInfo?.status === MediaStatus.PARTIALLY_AVAILABLE || - data?.mediaInfo?.status4k === MediaStatus.PARTIALLY_AVAILABLE) && + (settings.currentSettings.series4kEnabled && + hasPermission([Permission.REQUEST_4K, Permission.REQUEST_4K_TV], { + type: 'or', + }) && + (data.mediaInfo?.status4k === MediaStatus.AVAILABLE || + data?.mediaInfo?.status4k === + MediaStatus.PARTIALLY_AVAILABLE))) && hasPermission( [Permission.CREATE_ISSUES, Permission.MANAGE_ISSUES], { @@ -341,7 +346,7 @@ const TvDetails: React.FC = ({ tv }) => { } ) && ( )}
diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx index 8b7fac7a..a544c8f9 100644 --- a/src/components/UserList/index.tsx +++ b/src/components/UserList/index.tsx @@ -46,8 +46,7 @@ const messages = defineMessages({ totalrequests: 'Requests', accounttype: 'Type', role: 'Role', - created: 'Created', - lastupdated: 'Updated', + created: 'Joined', bulkedit: 'Bulk Edit', owner: 'Owner', admin: 'Admin', @@ -75,8 +74,7 @@ const messages = defineMessages({ autogeneratepassword: 'Automatically Generate Password', autogeneratepasswordTip: 'Email a server-generated password to the user', validationEmail: 'You must provide a valid email address', - sortCreated: 'Creation Date', - sortUpdated: 'Last Updated', + sortCreated: 'Join Date', sortDisplayName: 'Display Name', sortRequests: 'Request Count', localLoginDisabled: @@ -91,7 +89,7 @@ const UserList: React.FC = () => { const settings = useSettings(); const { addToast } = useToasts(); const { user: currentUser, hasPermission: currentHasPermission } = useUser(); - const [currentSort, setCurrentSort] = useState('created'); + const [currentSort, setCurrentSort] = useState('displayname'); const [currentPageSize, setCurrentPageSize] = useState(10); const page = router.query.page ? Number(router.query.page) : 1; @@ -522,9 +520,6 @@ const UserList: React.FC = () => { - @@ -556,7 +551,6 @@ const UserList: React.FC = () => { {intl.formatMessage(messages.accounttype)} {intl.formatMessage(messages.role)} {intl.formatMessage(messages.created)} - {intl.formatMessage(messages.lastupdated)} {(data.results ?? []).length > 1 && (