import Button from '@app/components/Common/Button'; import Modal from '@app/components/Common/Modal'; import { issueOptions } from '@app/components/IssueModal/constants'; import useSettings from '@app/hooks/useSettings'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; import { RadioGroup } from '@headlessui/react'; import { ExclamationIcon } from '@heroicons/react/outline'; import { ArrowCircleRightIcon } from '@heroicons/react/solid'; import { MediaStatus } from '@server/constants/media'; import type Issue from '@server/entity/Issue'; import type { MovieDetails } from '@server/models/Movie'; import type { TvDetails } from '@server/models/Tv'; import axios from 'axios'; import { Field, Formik } from 'formik'; import Link from 'next/link'; import { defineMessages, useIntl } from 'react-intl'; import { useToasts } from 'react-toast-notifications'; import useSWR from 'swr'; import * as Yup from 'yup'; const messages = defineMessages({ validationMessageRequired: 'You must provide a description', issomethingwrong: 'Is there a problem with {title}?', whatswrong: "What's wrong?", providedetail: 'Please provide a detailed explanation of the issue you encountered.', extras: 'Extras', season: 'Season {seasonNumber}', episode: 'Episode {episodeNumber}', allseasons: 'All Seasons', allepisodes: 'All Episodes', problemseason: 'Affected Season', problemepisode: 'Affected Episode', toastSuccessCreate: 'Issue report for {title} submitted successfully!', toastFailedCreate: 'Something went wrong while submitting the issue.', toastviewissue: 'View Issue', reportissue: 'Report an Issue', submitissue: 'Submit Issue', }); const isMovie = (movie: MovieDetails | TvDetails): movie is MovieDetails => { return (movie as MovieDetails).title !== undefined; }; const classNames = (...classes: string[]) => { return classes.filter(Boolean).join(' '); }; interface CreateIssueModalProps { mediaType: 'movie' | 'tv'; tmdbId?: number; onCancel?: () => void; } const CreateIssueModal = ({ onCancel, mediaType, tmdbId, }: CreateIssueModalProps) => { const intl = useIntl(); const settings = useSettings(); const { hasPermission } = useUser(); const { addToast } = useToasts(); const { data, error } = useSWR( tmdbId ? `/api/v1/${mediaType}/${tmdbId}` : null ); if (!tmdbId) { 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) ), }); return ( { try { const newIssue = await axios.post('/api/v1/issue', { issueType: values.selectedIssue.issueType, message: values.message, mediaId: data?.mediaInfo?.id, problemSeason: values.problemSeason, problemEpisode: values.problemSeason > 0 ? values.problemEpisode : 0, }); if (data) { addToast( <> {intl.formatMessage(messages.toastSuccessCreate, { title: isMovie(data) ? data.title : data.name, strong: (msg: React.ReactNode) => {msg}, })} {intl.formatMessage(messages.toastviewissue)} >, { appearance: 'success', autoDismiss: true, } ); } if (onCancel) { onCancel(); } } catch (e) { addToast(intl.formatMessage(messages.toastFailedCreate), { appearance: 'error', autoDismiss: true, }); } }} > {({ handleSubmit, values, setFieldValue, errors, touched }) => { return ( } title={intl.formatMessage(messages.reportissue)} cancelText={intl.formatMessage(globalMessages.close)} onOk={() => handleSubmit()} okText={intl.formatMessage(messages.submitissue)} loading={!data && !error} backdrop={`https://image.tmdb.org/t/p/w1920_and_h800_multi_faces/${data?.backdropPath}`} > {data && ( {intl.formatMessage(messages.issomethingwrong, { title: isMovie(data) ? data.title : data.name, })} )} {mediaType === 'tv' && data && !isMovie(data) && ( <> {intl.formatMessage(messages.problemseason)} * {availableSeasons.length > 1 && ( {intl.formatMessage(messages.allseasons)} )} {availableSeasons.map((season) => ( {season === 0 ? intl.formatMessage(messages.extras) : intl.formatMessage(messages.season, { seasonNumber: season, })} ))} {values.problemSeason > 0 && ( {intl.formatMessage(messages.problemepisode)} * {intl.formatMessage(messages.allepisodes)} {[ ...Array( data.seasons.find( (season) => Number(values.problemSeason) === season.seasonNumber )?.episodeCount ?? 0 ), ].map((i, index) => ( {intl.formatMessage(messages.episode, { episodeNumber: index + 1, })} ))} )} > )} setFieldValue('selectedIssue', issue)} className="mt-4" > Select an Issue {issueOptions.map((setting, index) => ( classNames( index === 0 ? 'rounded-tl-md rounded-tr-md' : '', index === issueOptions.length - 1 ? 'rounded-bl-md rounded-br-md' : '', checked ? 'z-10 border-indigo-500 bg-indigo-600' : 'border-gray-500', 'relative flex cursor-pointer border p-4 focus:outline-none' ) } > {({ active, checked }) => ( <> {intl.formatMessage(setting.name)} > )} ))} {intl.formatMessage(messages.whatswrong)} * {errors.message && touched.message && typeof errors.message === 'string' && ( {errors.message} )} ); }} ); }; export default CreateIssueModal;