fix(frontend): use consistent formatting & strings (#2231)

* fix(frontend): use consistent formatting & strings

* fix(lang): remove duplicated status strings

* fix(frontend): reduce height of items in request & issue lists

* fix(frontend): issue description textarea label should be a label element

* refactor: remove unnecessary reduce

* fix: remove small avatar underneath issue comments

* fix(frontend): don't hide Pushover app token tip
pull/2233/head
TheCatLady 3 years ago committed by GitHub
parent aeb7a48d72
commit 216447121b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -3,6 +3,7 @@ import { ExclamationIcon } from '@heroicons/react/outline';
import { DotsVerticalIcon } from '@heroicons/react/solid'; import { DotsVerticalIcon } from '@heroicons/react/solid';
import axios from 'axios'; import axios from 'axios';
import { Field, Form, Formik } from 'formik'; import { Field, Form, Formik } from 'formik';
import Link from 'next/link';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl'; import { defineMessages, FormattedRelativeTime, useIntl } from 'react-intl';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
@ -14,11 +15,11 @@ import Modal from '../../Common/Modal';
import Transition from '../../Transition'; import Transition from '../../Transition';
const messages = defineMessages({ const messages = defineMessages({
postedby: 'Posted by {username} {relativeTime}', postedby: 'Posted {relativeTime} by {username}',
postedbyedited: 'Posted by {username} {relativeTime} (Edited)', postedbyedited: 'Posted {relativeTime} by {username} (Edited)',
delete: 'Delete Comment', delete: 'Delete Comment',
areyousuredelete: 'Are you sure you want to delete this comment?', areyousuredelete: 'Are you sure you want to delete this comment?',
validationComment: 'You must provide a message', validationComment: 'You must enter a message',
edit: 'Edit Comment', edit: 'Edit Comment',
}); });
@ -86,11 +87,15 @@ const IssueComment: React.FC<IssueCommentProps> = ({
{intl.formatMessage(messages.areyousuredelete)} {intl.formatMessage(messages.areyousuredelete)}
</Modal> </Modal>
</Transition> </Transition>
<img <Link href={isActiveUser ? '/profile' : `/users/${comment.user.id}`}>
src={comment.user.avatar} <a>
alt="" <img
className="w-10 h-10 rounded-full ring-1 ring-gray-500" src={comment.user.avatar}
/> alt=""
className="w-10 h-10 transition duration-300 scale-100 rounded-full ring-1 ring-gray-500 transform-gpu hover:scale-105"
/>
</a>
</Link>
<div className="relative flex-1"> <div className="relative flex-1">
<div className="w-full rounded-md shadow ring-1 ring-gray-500"> <div className="w-full rounded-md shadow ring-1 ring-gray-500">
{(belongsToUser || hasPermission(Permission.MANAGE_ISSUES)) && ( {(belongsToUser || hasPermission(Permission.MANAGE_ISSUES)) && (
@ -221,7 +226,7 @@ const IssueComment: React.FC<IssueCommentProps> = ({
</div> </div>
</div> </div>
<div <div
className={`flex justify-between items-center text-xs pt-2 px-2 ${ className={`flex justify-between items-center text-xs pt-2 ${
isReversed ? 'flex-row-reverse' : 'flex-row' isReversed ? 'flex-row-reverse' : 'flex-row'
}`} }`}
> >
@ -232,14 +237,15 @@ const IssueComment: React.FC<IssueCommentProps> = ({
: messages.postedby, : messages.postedby,
{ {
username: ( username: (
<a <Link
href={ href={
isActiveUser ? '/profile' : `/users/${comment.user.id}` isActiveUser ? '/profile' : `/users/${comment.user.id}`
} }
className="font-semibold text-gray-100 transition duration-300 hover:underline hover:text-white"
> >
{comment.user.displayName} <a className="font-semibold text-gray-100 transition duration-300 hover:text-white hover:underline">
</a> {comment.user.displayName}
</a>
</Link>
), ),
relativeTime: ( relativeTime: (
<FormattedRelativeTime <FormattedRelativeTime

@ -2,7 +2,8 @@ import {
ChatIcon, ChatIcon,
CheckCircleIcon, CheckCircleIcon,
ExclamationIcon, ExclamationIcon,
ExternalLinkIcon, PlayIcon,
ServerIcon,
} from '@heroicons/react/outline'; } from '@heroicons/react/outline';
import { RefreshIcon } from '@heroicons/react/solid'; import { RefreshIcon } from '@heroicons/react/solid';
import axios from 'axios'; import axios from 'axios';
@ -34,8 +35,7 @@ import IssueComment from './IssueComment';
import IssueDescription from './IssueDescription'; import IssueDescription from './IssueDescription';
const messages = defineMessages({ const messages = defineMessages({
openedby: openedby: '#{issueId} opened {relativeTime} by {username}',
'#{issueId} opened {relativeTime} by <UserLink>{username}</UserLink>',
closeissue: 'Close Issue', closeissue: 'Close Issue',
closeissueandcomment: 'Close with Comment', closeissueandcomment: 'Close with Comment',
leavecomment: 'Comment', leavecomment: 'Comment',
@ -43,18 +43,18 @@ const messages = defineMessages({
reopenissue: 'Reopen Issue', reopenissue: 'Reopen Issue',
reopenissueandcomment: 'Reopen with Comment', reopenissueandcomment: 'Reopen with Comment',
issuepagetitle: 'Issue', issuepagetitle: 'Issue',
playonplex: 'Play on Plex',
play4konplex: 'Play 4K on Plex',
openinarr: 'Open in {arr}', openinarr: 'Open in {arr}',
toasteditdescriptionsuccess: 'Edited the issue description successfully!', openin4karr: 'Open in 4K {arr}',
toasteditdescriptionsuccess: 'Issue description edited successfully!',
toasteditdescriptionfailed: toasteditdescriptionfailed:
'Something went wrong while editing the issue description.', 'Something went wrong while editing the issue description.',
toaststatusupdated: 'Updated the issue status successfully!', toaststatusupdated: 'Issue status updated successfully!',
toaststatusupdatefailed: toaststatusupdatefailed:
'Something went wrong while updating the issue status.', 'Something went wrong while updating the issue status.',
issuetype: 'Issue Type', issuetype: 'Type',
mediatype: 'Media Type',
lastupdated: 'Last Updated', lastupdated: 'Last Updated',
statusopen: 'Open',
statusresolved: 'Resolved',
problemseason: 'Affected Season', problemseason: 'Affected Season',
allseasons: 'All Seasons', allseasons: 'All Seasons',
season: 'Season {seasonNumber}', season: 'Season {seasonNumber}',
@ -63,7 +63,7 @@ const messages = defineMessages({
episode: 'Episode {episodeNumber}', episode: 'Episode {episodeNumber}',
deleteissue: 'Delete Issue', deleteissue: 'Delete Issue',
deleteissueconfirm: 'Are you sure you want to delete this issue?', deleteissueconfirm: 'Are you sure you want to delete this issue?',
toastissuedeleted: 'Deleted the issue successfully!', toastissuedeleted: 'Issue deleted successfully!',
toastissuedeletefailed: 'Something went wrong while deleting the issue.', toastissuedeletefailed: 'Something went wrong while deleting the issue.',
nocomments: 'No comments.', nocomments: 'No comments.',
unknownissuetype: 'Unknown', unknownissuetype: 'Unknown',
@ -96,8 +96,6 @@ const IssueDetails: React.FC = () => {
(opt) => opt.issueType === issueData?.issueType (opt) => opt.issueType === issueData?.issueType
); );
const mediaType = issueData?.media.mediaType;
if (!data && !error) { if (!data && !error) {
return <LoadingSpinner />; return <LoadingSpinner />;
} }
@ -212,7 +210,7 @@ const IssueDetails: React.FC = () => {
/> />
</div> </div>
)} )}
<div className="flex flex-col items-center pt-4 lg:items-end lg:flex-row"> <div className="media-header">
<div className="media-poster"> <div className="media-poster">
<CachedImage <CachedImage
src={ src={
@ -231,12 +229,12 @@ const IssueDetails: React.FC = () => {
<div className="media-status"> <div className="media-status">
{issueData.status === IssueStatus.OPEN && ( {issueData.status === IssueStatus.OPEN && (
<Badge badgeType="primary"> <Badge badgeType="primary">
{intl.formatMessage(messages.statusopen)} {intl.formatMessage(globalMessages.open)}
</Badge> </Badge>
)} )}
{issueData.status === IssueStatus.RESOLVED && ( {issueData.status === IssueStatus.RESOLVED && (
<Badge badgeType="success"> <Badge badgeType="success">
{intl.formatMessage(messages.statusresolved)} {intl.formatMessage(globalMessages.resolved)}
</Badge> </Badge>
)} )}
</div> </div>
@ -259,27 +257,26 @@ const IssueDetails: React.FC = () => {
<span className="media-attributes"> <span className="media-attributes">
{intl.formatMessage(messages.openedby, { {intl.formatMessage(messages.openedby, {
issueId: issueData.id, issueId: issueData.id,
username: issueData.createdBy.displayName, username: (
UserLink: function UserLink(msg) { <Link
return ( href={
<div className="inline-flex items-center h-full mx-1"> issueData.createdBy.id === currentUser?.id
<Link href={`/users/${issueData.createdBy.id}`}> ? '/profile'
<a className="flex-shrink-0 w-6 h-6 mr-1"> : `/users/${issueData.createdBy.id}`
<img }
className="w-6 h-6 rounded-full" >
src={issueData.createdBy.avatar} <a className="inline-flex items-center h-full ml-1 xl:ml-1.5 group">
alt="" <img
/> className="w-5 h-5 mr-0.5 transition duration-300 scale-100 rounded-full xl:w-6 xl:h-6 xl:mr-1 transform-gpu group-hover:scale-105"
</a> src={issueData.createdBy.avatar}
</Link> alt=""
<Link href={`/users/${issueData.createdBy.id}`}> />
<a className="font-semibold text-gray-100 transition hover:underline hover:text-white"> <span className="font-semibold text-gray-100 transition duration-300 group-hover:text-white group-hover:underline">
{msg} {issueData.createdBy.displayName}
</a> </span>
</Link> </a>
</div> </Link>
); ),
},
relativeTime: ( relativeTime: (
<FormattedRelativeTime <FormattedRelativeTime
value={Math.floor( value={Math.floor(
@ -306,16 +303,6 @@ const IssueDetails: React.FC = () => {
/> />
<div className="mt-8 lg:hidden"> <div className="mt-8 lg:hidden">
<div className="media-facts"> <div className="media-facts">
<div className="media-fact">
<span>{intl.formatMessage(messages.mediatype)}</span>
<span className="media-fact-value">
{intl.formatMessage(
mediaType === MediaType.MOVIE
? globalMessages.movie
: globalMessages.tvshow
)}
</span>
</div>
<div className="media-fact"> <div className="media-fact">
<span>{intl.formatMessage(messages.issuetype)}</span> <span>{intl.formatMessage(messages.issuetype)}</span>
<span className="media-fact-value"> <span className="media-fact-value">
@ -366,20 +353,66 @@ const IssueDetails: React.FC = () => {
</span> </span>
</div> </div>
</div> </div>
{hasPermission(Permission.MANAGE_ISSUES) && ( <div className="flex flex-col mt-4 mb-6 space-y-2">
<div className="flex flex-col mt-4 mb-6 space-y-2"> {issueData?.media.plexUrl && (
{issueData?.media.serviceUrl && ( <Button
as="a"
href={issueData?.media.plexUrl}
target="_blank"
rel="noreferrer"
className="w-full"
buttonType="ghost"
>
<PlayIcon />
<span>{intl.formatMessage(messages.playonplex)}</span>
</Button>
)}
{issueData?.media.serviceUrl && hasPermission(Permission.ADMIN) && (
<Button
as="a"
href={issueData?.media.serviceUrl}
target="_blank"
rel="noreferrer"
className="w-full"
buttonType="ghost"
>
<ServerIcon />
<span>
{intl.formatMessage(messages.openinarr, {
arr:
issueData.media.mediaType === MediaType.MOVIE
? 'Radarr'
: 'Sonarr',
})}
</span>
</Button>
)}
{issueData?.media.plexUrl4k && (
<Button
as="a"
href={issueData?.media.plexUrl4k}
target="_blank"
rel="noreferrer"
className="w-full"
buttonType="ghost"
>
<PlayIcon />
<span>{intl.formatMessage(messages.play4konplex)}</span>
</Button>
)}
{issueData?.media.serviceUrl4k &&
hasPermission(Permission.ADMIN) && (
<Button <Button
as="a" as="a"
href={issueData?.media.serviceUrl} href={issueData?.media.serviceUrl4k}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
className="w-full" className="w-full"
buttonType="ghost" buttonType="ghost"
> >
<ExternalLinkIcon /> <ServerIcon />
<span> <span>
{intl.formatMessage(messages.openinarr, { {intl.formatMessage(messages.openin4karr, {
arr: arr:
issueData.media.mediaType === MediaType.MOVIE issueData.media.mediaType === MediaType.MOVIE
? 'Radarr' ? 'Radarr'
@ -388,8 +421,7 @@ const IssueDetails: React.FC = () => {
</span> </span>
</Button> </Button>
)} )}
</div> </div>
)}
</div> </div>
<div className="mt-6"> <div className="mt-6">
<div className="font-semibold text-gray-100 lg:text-xl"> <div className="font-semibold text-gray-100 lg:text-xl">
@ -513,16 +545,6 @@ const IssueDetails: React.FC = () => {
)} )}
</span> </span>
</div> </div>
<div className="media-fact">
<span>{intl.formatMessage(messages.mediatype)}</span>
<span className="media-fact-value">
{intl.formatMessage(
mediaType === MediaType.MOVIE
? globalMessages.movie
: globalMessages.tvshow
)}
</span>
</div>
{issueData.media.mediaType === MediaType.TV && ( {issueData.media.mediaType === MediaType.TV && (
<> <>
<div className="media-fact"> <div className="media-fact">
@ -565,30 +587,74 @@ const IssueDetails: React.FC = () => {
</span> </span>
</div> </div>
</div> </div>
{hasPermission(Permission.MANAGE_ISSUES) && ( <div className="flex flex-col mt-4 mb-6 space-y-2">
<div className="flex flex-col mt-4 mb-6 space-y-2"> {issueData?.media.plexUrl && (
{issueData?.media.serviceUrl && ( <Button
<Button as="a"
as="a" href={issueData?.media.plexUrl}
href={issueData?.media.serviceUrl} target="_blank"
target="_blank" rel="noreferrer"
rel="noreferrer" className="w-full"
className="w-full" buttonType="ghost"
buttonType="ghost" >
> <PlayIcon />
<ExternalLinkIcon /> <span>{intl.formatMessage(messages.playonplex)}</span>
<span> </Button>
{intl.formatMessage(messages.openinarr, { )}
arr: {issueData?.media.serviceUrl && hasPermission(Permission.ADMIN) && (
issueData.media.mediaType === MediaType.MOVIE <Button
? 'Radarr' as="a"
: 'Sonarr', href={issueData?.media.serviceUrl}
})} target="_blank"
</span> rel="noreferrer"
</Button> className="w-full"
)} buttonType="ghost"
</div> >
)} <ServerIcon />
<span>
{intl.formatMessage(messages.openinarr, {
arr:
issueData.media.mediaType === MediaType.MOVIE
? 'Radarr'
: 'Sonarr',
})}
</span>
</Button>
)}
{issueData?.media.plexUrl4k && (
<Button
as="a"
href={issueData?.media.plexUrl4k}
target="_blank"
rel="noreferrer"
className="w-full"
buttonType="ghost"
>
<PlayIcon />
<span>{intl.formatMessage(messages.play4konplex)}</span>
</Button>
)}
{issueData?.media.serviceUrl4k && hasPermission(Permission.ADMIN) && (
<Button
as="a"
href={issueData?.media.serviceUrl4k}
target="_blank"
rel="noreferrer"
className="w-full"
buttonType="ghost"
>
<ServerIcon />
<span>
{intl.formatMessage(messages.openin4karr, {
arr:
issueData.media.mediaType === MediaType.MOVIE
? 'Radarr'
: 'Sonarr',
})}
</span>
</Button>
)}
</div>
</div> </div>
</div> </div>
</div> </div>

@ -18,11 +18,9 @@ import { issueOptions } from '../../IssueModal/constants';
const messages = defineMessages({ const messages = defineMessages({
openeduserdate: '{date} by {user}', openeduserdate: '{date} by {user}',
allseasons: 'All Seasons', seasons: '{seasonCount, plural, one {Season} other {Seasons}}',
season: 'Season {seasonNumber}', episodes: '{episodeCount, plural, one {Episode} other {Episodes}}',
problemepisode: 'Affected Episode', problemepisode: 'Affected Episode',
allepisodes: 'All Episodes',
episode: 'Episode {episodeNumber}',
issuetype: 'Type', issuetype: 'Type',
issuestatus: 'Status', issuestatus: 'Status',
opened: 'Opened', opened: 'Opened',
@ -55,7 +53,7 @@ const IssueItem: React.FC<IssueItemProps> = ({ issue }) => {
if (!title && !error) { if (!title && !error) {
return ( return (
<div <div
className="w-full bg-gray-800 h-52 sm:h-40 xl:h-24 rounded-xl animate-pulse" className="w-full h-64 bg-gray-800 rounded-xl xl:h-28 animate-pulse"
ref={ref} ref={ref}
/> />
); );
@ -69,30 +67,48 @@ const IssueItem: React.FC<IssueItemProps> = ({ issue }) => {
(opt) => opt.issueType === issue?.issueType (opt) => opt.issueType === issue?.issueType
); );
const problemSeasonEpisodeLine = []; const problemSeasonEpisodeLine: React.ReactNode[] = [];
if (!isMovie(title) && issue) { if (!isMovie(title) && issue) {
problemSeasonEpisodeLine.push( problemSeasonEpisodeLine.push(
issue.problemSeason > 0 <>
? intl.formatMessage(messages.season, { <span className="card-field-name">
seasonNumber: issue.problemSeason, {intl.formatMessage(messages.seasons, {
}) seasonCount: issue.problemSeason ? 1 : 0,
: intl.formatMessage(messages.allseasons) })}
</span>
<span className="mr-4 uppercase">
<Badge>
{issue.problemSeason > 0
? issue.problemSeason
: intl.formatMessage(globalMessages.all)}
</Badge>
</span>
</>
); );
if (issue.problemSeason > 0) { if (issue.problemSeason > 0) {
problemSeasonEpisodeLine.push( problemSeasonEpisodeLine.push(
issue.problemEpisode > 0 <>
? intl.formatMessage(messages.episode, { <span className="card-field-name">
episodeNumber: issue.problemEpisode, {intl.formatMessage(messages.episodes, {
}) episodeCount: issue.problemEpisode ? 1 : 0,
: intl.formatMessage(messages.allepisodes) })}
</span>
<span className="uppercase">
<Badge>
{issue.problemEpisode > 0
? issue.problemEpisode
: intl.formatMessage(globalMessages.all)}
</Badge>
</span>
</>
); );
} }
} }
return ( return (
<div className="relative flex flex-col justify-between w-full py-2 overflow-hidden text-gray-400 bg-gray-800 shadow-md h-52 sm:h-40 xl:h-24 ring-1 ring-gray-700 rounded-xl xl:flex-row"> <div className="relative flex flex-col justify-between w-full py-4 overflow-hidden text-gray-400 bg-gray-800 shadow-md ring-1 ring-gray-700 rounded-xl xl:h-28 xl:flex-row">
{title.backdropPath && ( {title.backdropPath && (
<div className="absolute inset-0 z-0 w-full bg-center bg-cover xl:w-2/3"> <div className="absolute inset-0 z-0 w-full bg-center bg-cover xl:w-2/3">
<CachedImage <CachedImage
@ -119,7 +135,7 @@ const IssueItem: React.FC<IssueItemProps> = ({ issue }) => {
: `/tv/${issue.media.tmdbId}` : `/tv/${issue.media.tmdbId}`
} }
> >
<a className="relative flex-shrink-0 w-10 h-auto overflow-hidden transition duration-300 scale-100 rounded-md transform-gpu hover:scale-105"> <a className="relative flex-shrink-0 w-12 h-auto overflow-hidden transition duration-300 scale-100 rounded-md transform-gpu hover:scale-105">
<CachedImage <CachedImage
src={ src={
title.posterPath title.posterPath
@ -153,8 +169,10 @@ const IssueItem: React.FC<IssueItemProps> = ({ issue }) => {
</a> </a>
</Link> </Link>
{problemSeasonEpisodeLine.length > 0 && ( {problemSeasonEpisodeLine.length > 0 && (
<div className="text-sm text-gray-200"> <div className="card-field">
{problemSeasonEpisodeLine.join(' | ')} {problemSeasonEpisodeLine.map((t, k) => (
<span key={k}>{t}</span>
))}
</div> </div>
)} )}
</div> </div>
@ -212,7 +230,7 @@ const IssueItem: React.FC<IssueItemProps> = ({ issue }) => {
alt="" alt=""
className="ml-1.5 avatar-sm" className="ml-1.5 avatar-sm"
/> />
<span className="text-sm truncate group-hover:underline"> <span className="text-sm font-semibold truncate group-hover:underline group-hover:text-white">
{issue.createdBy.displayName} {issue.createdBy.displayName}
</span> </span>
</a> </a>

@ -94,7 +94,7 @@ const IssueList: React.FC = () => {
<> <>
<PageTitle title={intl.formatMessage(messages.issues)} /> <PageTitle title={intl.formatMessage(messages.issues)} />
<div className="flex flex-col justify-between mb-4 lg:items-end lg:flex-row"> <div className="flex flex-col justify-between mb-4 lg:items-end lg:flex-row">
<Header>Issues</Header> <Header>{intl.formatMessage(messages.issues)}</Header>
<div className="flex flex-col flex-grow mt-2 sm:flex-row lg:flex-grow-0"> <div className="flex flex-col flex-grow mt-2 sm:flex-row lg:flex-grow-0">
<div className="flex flex-grow mb-2 sm:mb-0 sm:mr-2 lg:flex-grow-0"> <div className="flex flex-grow mb-2 sm:mb-0 sm:mr-2 lg:flex-grow-0">
<span className="inline-flex items-center px-3 text-sm text-gray-100 bg-gray-800 border border-r-0 border-gray-500 cursor-default rounded-l-md"> <span className="inline-flex items-center px-3 text-sm text-gray-100 bg-gray-800 border border-r-0 border-gray-500 cursor-default rounded-l-md">
@ -157,7 +157,7 @@ const IssueList: React.FC = () => {
</div> </div>
{data.results.map((issue) => { {data.results.map((issue) => {
return ( return (
<div className="mb-2" key={`issue-item-${issue.id}`}> <div className="py-2" key={`issue-item-${issue.id}`}>
<IssueItem issue={issue} /> <IssueItem issue={issue} />
</div> </div>
); );

@ -278,10 +278,10 @@ const CreateIssueModal: React.FC<CreateIssueModalProps> = ({
</div> </div>
</RadioGroup> </RadioGroup>
<div className="flex-col mt-4 space-y-2"> <div className="flex-col mt-4 space-y-2">
<span> <label htmlFor="message">
{intl.formatMessage(messages.whatswrong)}{' '} {intl.formatMessage(messages.whatswrong)}
<span className="label-required">*</span> <span className="label-required">*</span>
</span> </label>
<Field <Field
as="textarea" as="textarea"
name="message" name="message"

@ -1,8 +1,5 @@
import { import { ServerIcon } from '@heroicons/react/outline';
CheckCircleIcon, import { CheckCircleIcon, DocumentRemoveIcon } from '@heroicons/react/solid';
DocumentRemoveIcon,
ExternalLinkIcon,
} from '@heroicons/react/solid';
import axios from 'axios'; import axios from 'axios';
import React from 'react'; import React from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
@ -208,7 +205,7 @@ const ManageSlideOver: React.FC<
className="block mb-2 last:mb-0" className="block mb-2 last:mb-0"
> >
<Button buttonType="ghost" className="w-full"> <Button buttonType="ghost" className="w-full">
<ExternalLinkIcon /> <ServerIcon />
<span> <span>
{intl.formatMessage(messages.openarr, { {intl.formatMessage(messages.openarr, {
mediaType: intl.formatMessage( mediaType: intl.formatMessage(
@ -229,7 +226,7 @@ const ManageSlideOver: React.FC<
rel="noreferrer" rel="noreferrer"
> >
<Button buttonType="ghost" className="w-full"> <Button buttonType="ghost" className="w-full">
<ExternalLinkIcon /> <ServerIcon />
<span> <span>
{intl.formatMessage(messages.openarr4k, { {intl.formatMessage(messages.openarr4k, {
mediaType: intl.formatMessage( mediaType: intl.formatMessage(

@ -233,7 +233,7 @@ const RequestCard: React.FC<RequestCardProps> = ({ request, onTitleData }) => {
alt="" alt=""
className="avatar-sm" className="avatar-sm"
/> />
<span className="truncate group-hover:underline"> <span className="font-semibold truncate group-hover:underline group-hover:text-white">
{requestData.requestedBy.displayName} {requestData.requestedBy.displayName}
</span> </span>
</a> </a>

@ -63,7 +63,7 @@ const RequestItemError: React.FC<RequestItemErroProps> = ({
}; };
return ( return (
<div className="flex flex-col items-center justify-center w-full h-64 px-10 bg-gray-800 lg:flex-row ring-1 ring-red-500 rounded-xl xl:h-32"> <div className="flex flex-col items-center justify-center w-full h-64 px-10 bg-gray-800 lg:flex-row ring-1 ring-red-500 rounded-xl xl:h-28">
<span className="text-sm text-center text-gray-300 lg:text-left"> <span className="text-sm text-center text-gray-300 lg:text-left">
{intl.formatMessage(messages.mediaerror)} {intl.formatMessage(messages.mediaerror)}
</span> </span>
@ -149,7 +149,7 @@ const RequestItem: React.FC<RequestItemProps> = ({
if (!title && !error) { if (!title && !error) {
return ( return (
<div <div
className="w-full h-64 bg-gray-800 rounded-xl xl:h-32 animate-pulse" className="w-full h-64 bg-gray-800 rounded-xl xl:h-28 animate-pulse"
ref={ref} ref={ref}
/> />
); );
@ -178,7 +178,7 @@ const RequestItem: React.FC<RequestItemProps> = ({
setShowEditModal(false); setShowEditModal(false);
}} }}
/> />
<div className="relative flex flex-col justify-between w-full py-4 overflow-hidden text-gray-400 bg-gray-800 shadow-md ring-1 ring-gray-700 rounded-xl xl:h-32 xl:flex-row"> <div className="relative flex flex-col justify-between w-full py-4 overflow-hidden text-gray-400 bg-gray-800 shadow-md ring-1 ring-gray-700 rounded-xl xl:h-28 xl:flex-row">
{title.backdropPath && ( {title.backdropPath && (
<div className="absolute inset-0 z-0 w-full bg-center bg-cover xl:w-2/3"> <div className="absolute inset-0 z-0 w-full bg-center bg-cover xl:w-2/3">
<CachedImage <CachedImage
@ -205,7 +205,7 @@ const RequestItem: React.FC<RequestItemProps> = ({
: `/tv/${requestData.media.tmdbId}` : `/tv/${requestData.media.tmdbId}`
} }
> >
<a className="relative flex-shrink-0 w-12 h-auto overflow-hidden transition duration-300 scale-100 rounded-md sm:w-14 transform-gpu hover:scale-105"> <a className="relative flex-shrink-0 w-12 h-auto overflow-hidden transition duration-300 scale-100 rounded-md transform-gpu hover:scale-105">
<CachedImage <CachedImage
src={ src={
title.posterPath title.posterPath
@ -339,7 +339,7 @@ const RequestItem: React.FC<RequestItemProps> = ({
alt="" alt=""
className="ml-1.5 avatar-sm" className="ml-1.5 avatar-sm"
/> />
<span className="text-sm truncate group-hover:underline"> <span className="text-sm font-semibold truncate group-hover:underline group-hover:text-white">
{requestData.requestedBy.displayName} {requestData.requestedBy.displayName}
</span> </span>
</a> </a>
@ -393,7 +393,7 @@ const RequestItem: React.FC<RequestItemProps> = ({
alt="" alt=""
className="ml-1.5 avatar-sm" className="ml-1.5 avatar-sm"
/> />
<span className="text-sm truncate group-hover:underline"> <span className="text-sm font-semibold truncate group-hover:underline group-hover:text-white">
{requestData.modifiedBy.displayName} {requestData.modifiedBy.displayName}
</span> </span>
</a> </a>

@ -7,6 +7,7 @@ import { useToasts } from 'react-toast-notifications';
import useSWR from 'swr'; import useSWR from 'swr';
import * as Yup from 'yup'; import * as Yup from 'yup';
import { UserSettingsNotificationsResponse } from '../../../../../server/interfaces/api/userSettingsInterfaces'; import { UserSettingsNotificationsResponse } from '../../../../../server/interfaces/api/userSettingsInterfaces';
import useSettings from '../../../../hooks/useSettings';
import { useUser } from '../../../../hooks/useUser'; import { useUser } from '../../../../hooks/useUser';
import globalMessages from '../../../../i18n/globalMessages'; import globalMessages from '../../../../i18n/globalMessages';
import Button from '../../../Common/Button'; import Button from '../../../Common/Button';
@ -18,7 +19,7 @@ const messages = defineMessages({
pushoversettingsfailed: 'Pushover notification settings failed to save.', pushoversettingsfailed: 'Pushover notification settings failed to save.',
pushoverApplicationToken: 'Application API Token', pushoverApplicationToken: 'Application API Token',
pushoverApplicationTokenTip: pushoverApplicationTokenTip:
'<ApplicationRegistrationLink>Register an application</ApplicationRegistrationLink> for use with Overseerr', '<ApplicationRegistrationLink>Register an application</ApplicationRegistrationLink> for use with {applicationTitle}',
pushoverUserKey: 'User or Group Key', pushoverUserKey: 'User or Group Key',
pushoverUserKeyTip: pushoverUserKeyTip:
'Your 30-character <UsersGroupsLink>user or group identifier</UsersGroupsLink>', 'Your 30-character <UsersGroupsLink>user or group identifier</UsersGroupsLink>',
@ -29,6 +30,7 @@ const messages = defineMessages({
const UserPushoverSettings: React.FC = () => { const UserPushoverSettings: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const settings = useSettings();
const { addToast } = useToasts(); const { addToast } = useToasts();
const router = useRouter(); const router = useRouter();
const { user } = useUser({ id: Number(router.query.userId) }); const { user } = useUser({ id: Number(router.query.userId) });
@ -121,25 +123,24 @@ const UserPushoverSettings: React.FC = () => {
<label htmlFor="pushoverApplicationToken" className="text-label"> <label htmlFor="pushoverApplicationToken" className="text-label">
{intl.formatMessage(messages.pushoverApplicationToken)} {intl.formatMessage(messages.pushoverApplicationToken)}
<span className="label-required">*</span> <span className="label-required">*</span>
{data?.pushoverApplicationToken && ( <span className="label-tip">
<span className="label-tip"> {intl.formatMessage(messages.pushoverApplicationTokenTip, {
{intl.formatMessage(messages.pushoverApplicationTokenTip, { ApplicationRegistrationLink:
ApplicationRegistrationLink: function ApplicationRegistrationLink(msg) {
function ApplicationRegistrationLink(msg) { return (
return ( <a
<a href="https://pushover.net/api#registration"
href="https://pushover.net/api#registration" className="text-white transition duration-300 hover:underline"
className="text-white transition duration-300 hover:underline" target="_blank"
target="_blank" rel="noreferrer"
rel="noreferrer" >
> {msg}
{msg} </a>
</a> );
); },
}, applicationTitle: settings.currentSettings.applicationTitle,
})} })}
</span> </span>
)}
</label> </label>
<div className="form-input"> <div className="form-input">
<div className="form-input-field"> <div className="form-input-field">

@ -35,9 +35,9 @@
"components.IssueDetails.IssueComment.areyousuredelete": "Are you sure you want to delete this comment?", "components.IssueDetails.IssueComment.areyousuredelete": "Are you sure you want to delete this comment?",
"components.IssueDetails.IssueComment.delete": "Delete Comment", "components.IssueDetails.IssueComment.delete": "Delete Comment",
"components.IssueDetails.IssueComment.edit": "Edit Comment", "components.IssueDetails.IssueComment.edit": "Edit Comment",
"components.IssueDetails.IssueComment.postedby": "Posted by {username} {relativeTime}", "components.IssueDetails.IssueComment.postedby": "Posted {relativeTime} by {username}",
"components.IssueDetails.IssueComment.postedbyedited": "Posted by {username} {relativeTime} (Edited)", "components.IssueDetails.IssueComment.postedbyedited": "Posted {relativeTime} by {username} (Edited)",
"components.IssueDetails.IssueComment.validationComment": "You must provide a message", "components.IssueDetails.IssueComment.validationComment": "You must enter a message",
"components.IssueDetails.IssueDescription.deleteissue": "Delete Issue", "components.IssueDetails.IssueDescription.deleteissue": "Delete Issue",
"components.IssueDetails.IssueDescription.description": "Description", "components.IssueDetails.IssueDescription.description": "Description",
"components.IssueDetails.IssueDescription.edit": "Edit Description", "components.IssueDetails.IssueDescription.edit": "Edit Description",
@ -50,36 +50,34 @@
"components.IssueDetails.deleteissueconfirm": "Are you sure you want to delete this issue?", "components.IssueDetails.deleteissueconfirm": "Are you sure you want to delete this issue?",
"components.IssueDetails.episode": "Episode {episodeNumber}", "components.IssueDetails.episode": "Episode {episodeNumber}",
"components.IssueDetails.issuepagetitle": "Issue", "components.IssueDetails.issuepagetitle": "Issue",
"components.IssueDetails.issuetype": "Issue Type", "components.IssueDetails.issuetype": "Type",
"components.IssueDetails.lastupdated": "Last Updated", "components.IssueDetails.lastupdated": "Last Updated",
"components.IssueDetails.leavecomment": "Comment", "components.IssueDetails.leavecomment": "Comment",
"components.IssueDetails.mediatype": "Media Type",
"components.IssueDetails.nocomments": "No comments.", "components.IssueDetails.nocomments": "No comments.",
"components.IssueDetails.openedby": "#{issueId} opened {relativeTime} by <UserLink>{username}</UserLink>", "components.IssueDetails.openedby": "#{issueId} opened {relativeTime} by {username}",
"components.IssueDetails.openin4karr": "Open in 4K {arr}",
"components.IssueDetails.openinarr": "Open in {arr}", "components.IssueDetails.openinarr": "Open in {arr}",
"components.IssueDetails.play4konplex": "Play 4K on Plex",
"components.IssueDetails.playonplex": "Play on Plex",
"components.IssueDetails.problemepisode": "Affected Episode", "components.IssueDetails.problemepisode": "Affected Episode",
"components.IssueDetails.problemseason": "Affected Season", "components.IssueDetails.problemseason": "Affected Season",
"components.IssueDetails.reopenissue": "Reopen Issue", "components.IssueDetails.reopenissue": "Reopen Issue",
"components.IssueDetails.reopenissueandcomment": "Reopen with Comment", "components.IssueDetails.reopenissueandcomment": "Reopen with Comment",
"components.IssueDetails.season": "Season {seasonNumber}", "components.IssueDetails.season": "Season {seasonNumber}",
"components.IssueDetails.statusopen": "Open",
"components.IssueDetails.statusresolved": "Resolved",
"components.IssueDetails.toasteditdescriptionfailed": "Something went wrong while editing the issue description.", "components.IssueDetails.toasteditdescriptionfailed": "Something went wrong while editing the issue description.",
"components.IssueDetails.toasteditdescriptionsuccess": "Edited the issue description successfully!", "components.IssueDetails.toasteditdescriptionsuccess": "Issue description edited successfully!",
"components.IssueDetails.toastissuedeleted": "Deleted the issue successfully!", "components.IssueDetails.toastissuedeleted": "Issue deleted successfully!",
"components.IssueDetails.toastissuedeletefailed": "Something went wrong while deleting the issue.", "components.IssueDetails.toastissuedeletefailed": "Something went wrong while deleting the issue.",
"components.IssueDetails.toaststatusupdated": "Updated the issue status successfully!", "components.IssueDetails.toaststatusupdated": "Issue status updated successfully!",
"components.IssueDetails.toaststatusupdatefailed": "Something went wrong while updating the issue status.", "components.IssueDetails.toaststatusupdatefailed": "Something went wrong while updating the issue status.",
"components.IssueDetails.unknownissuetype": "Unknown", "components.IssueDetails.unknownissuetype": "Unknown",
"components.IssueList.IssueItem.allepisodes": "All Episodes", "components.IssueList.IssueItem.episodes": "{episodeCount, plural, one {Episode} other {Episodes}}",
"components.IssueList.IssueItem.allseasons": "All Seasons",
"components.IssueList.IssueItem.episode": "Episode {episodeNumber}",
"components.IssueList.IssueItem.issuestatus": "Status", "components.IssueList.IssueItem.issuestatus": "Status",
"components.IssueList.IssueItem.issuetype": "Type", "components.IssueList.IssueItem.issuetype": "Type",
"components.IssueList.IssueItem.opened": "Opened", "components.IssueList.IssueItem.opened": "Opened",
"components.IssueList.IssueItem.openeduserdate": "{date} by {user}", "components.IssueList.IssueItem.openeduserdate": "{date} by {user}",
"components.IssueList.IssueItem.problemepisode": "Affected Episode", "components.IssueList.IssueItem.problemepisode": "Affected Episode",
"components.IssueList.IssueItem.season": "Season {seasonNumber}", "components.IssueList.IssueItem.seasons": "{seasonCount, plural, one {Season} other {Seasons}}",
"components.IssueList.IssueItem.unknownissuetype": "Unknown", "components.IssueList.IssueItem.unknownissuetype": "Unknown",
"components.IssueList.IssueItem.viewissue": "View Issue", "components.IssueList.IssueItem.viewissue": "View Issue",
"components.IssueList.issues": "Issues", "components.IssueList.issues": "Issues",
@ -879,7 +877,7 @@
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Pushbullet notification settings failed to save.", "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingsfailed": "Pushbullet notification settings failed to save.",
"components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Pushbullet notification settings saved successfully!", "components.UserProfile.UserSettings.UserNotificationSettings.pushbulletsettingssaved": "Pushbullet notification settings saved successfully!",
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Application API Token", "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationToken": "Application API Token",
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "<ApplicationRegistrationLink>Register an application</ApplicationRegistrationLink> for use with Overseerr", "components.UserProfile.UserSettings.UserNotificationSettings.pushoverApplicationTokenTip": "<ApplicationRegistrationLink>Register an application</ApplicationRegistrationLink> for use with {applicationTitle}",
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "User or Group Key", "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKey": "User or Group Key",
"components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Your 30-character <UsersGroupsLink>user or group identifier</UsersGroupsLink>", "components.UserProfile.UserSettings.UserNotificationSettings.pushoverUserKeyTip": "Your 30-character <UsersGroupsLink>user or group identifier</UsersGroupsLink>",
"components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Pushover notification settings failed to save.", "components.UserProfile.UserSettings.UserNotificationSettings.pushoversettingsfailed": "Pushover notification settings failed to save.",

@ -204,7 +204,7 @@
} }
img.avatar-sm { img.avatar-sm {
@apply w-5 h-5 mr-1.5 rounded-full transition duration-300 scale-100 transform-gpu group-hover:scale-105; @apply w-5 h-5 mr-1 transition duration-300 scale-100 rounded-full transform-gpu group-hover:scale-105;
} }
.card-field { .card-field {

Loading…
Cancel
Save