refactor: switch frontend from axios to fetch api

This commit is contained in:
gauthier-th
2024-06-27 19:20:05 +02:00
committed by Gauthier
parent 0f6ba544f8
commit 15568b1f6b
68 changed files with 1369 additions and 790 deletions

View File

@@ -43,8 +43,6 @@
"@svgr/webpack": "6.5.1",
"@tanem/react-nprogress": "5.0.30",
"ace-builds": "1.15.2",
"axios": "1.3.4",
"axios-rate-limit": "1.3.0",
"bcrypt": "5.1.0",
"bowser": "2.11.0",
"connect-typeorm": "1.1.4",

72
pnpm-lock.yaml generated
View File

@@ -41,12 +41,6 @@ importers:
ace-builds:
specifier: 1.15.2
version: 1.15.2
axios:
specifier: 1.3.4
version: 1.3.4
axios-rate-limit:
specifier: 1.3.0
version: 1.3.0(axios@1.3.4)
bcrypt:
specifier: 5.1.0
version: 5.1.0(encoding@0.1.13)
@@ -3396,14 +3390,6 @@ packages:
resolution: {integrity: sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==}
engines: {node: '>=4'}
axios-rate-limit@1.3.0:
resolution: {integrity: sha512-cKR5wTbU/CeeyF1xVl5hl6FlYsmzDVqxlN4rGtfO5x7J83UxKDckudsW0yW21/ZJRcO0Qrfm3fUFbhEbWTLayw==}
peerDependencies:
axios: '*'
axios@1.3.4:
resolution: {integrity: sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==}
axobject-query@3.1.1:
resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==}
@@ -5028,15 +5014,6 @@ packages:
fn.name@1.1.0:
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
follow-redirects@1.15.6:
resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@@ -5051,10 +5028,6 @@ packages:
resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==}
engines: {node: '>= 0.12'}
form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
formik@2.4.6:
resolution: {integrity: sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g==}
peerDependencies:
@@ -7408,9 +7381,6 @@ packages:
proxy-from-env@1.0.0:
resolution: {integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==}
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
psl@1.9.0:
resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==}
@@ -10773,14 +10743,14 @@ snapshots:
dependencies:
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/node': 17.0.36
'@types/node': 20.14.8
jest-mock: 29.7.0
'@jest/fake-timers@29.7.0':
dependencies:
'@jest/types': 29.6.3
'@sinonjs/fake-timers': 10.3.0
'@types/node': 17.0.36
'@types/node': 20.14.8
jest-message-util: 29.7.0
jest-mock: 29.7.0
jest-util: 29.7.0
@@ -10793,7 +10763,7 @@ snapshots:
dependencies:
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
'@types/node': 17.0.36
'@types/node': 20.14.8
'@types/yargs': 15.0.19
chalk: 4.1.2
@@ -10802,7 +10772,7 @@ snapshots:
'@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
'@types/node': 17.0.36
'@types/node': 20.14.8
'@types/yargs': 17.0.32
chalk: 4.1.2
@@ -12712,7 +12682,7 @@ snapshots:
'@types/node-forge@1.3.11':
dependencies:
'@types/node': 17.0.36
'@types/node': 20.14.8
'@types/node-schedule@2.1.0':
dependencies:
@@ -13287,18 +13257,6 @@ snapshots:
axe-core@4.9.1: {}
axios-rate-limit@1.3.0(axios@1.3.4):
dependencies:
axios: 1.3.4
axios@1.3.4:
dependencies:
follow-redirects: 1.15.6
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
axobject-query@3.1.1:
dependencies:
deep-equal: 2.2.3
@@ -13658,7 +13616,7 @@ snapshots:
chrome-launcher@0.15.2:
dependencies:
'@types/node': 17.0.36
'@types/node': 20.14.8
escape-string-regexp: 4.0.0
is-wsl: 2.2.0
lighthouse-logger: 1.4.2
@@ -15318,8 +15276,6 @@ snapshots:
fn.name@1.1.0: {}
follow-redirects@1.15.6: {}
for-each@0.3.3:
dependencies:
is-callable: 1.2.7
@@ -15337,12 +15293,6 @@ snapshots:
combined-stream: 1.0.8
mime-types: 2.1.35
form-data@4.0.0:
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
formik@2.4.6(react@18.3.1):
dependencies:
'@types/hoist-non-react-statics': 3.3.5
@@ -16135,7 +16085,7 @@ snapshots:
'@jest/environment': 29.7.0
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/node': 17.0.36
'@types/node': 20.14.8
jest-mock: 29.7.0
jest-util: 29.7.0
@@ -16156,13 +16106,13 @@ snapshots:
jest-mock@29.7.0:
dependencies:
'@jest/types': 29.6.3
'@types/node': 17.0.36
'@types/node': 20.14.8
jest-util: 29.7.0
jest-util@29.7.0:
dependencies:
'@jest/types': 29.6.3
'@types/node': 17.0.36
'@types/node': 20.14.8
chalk: 4.1.2
ci-info: 3.9.0
graceful-fs: 4.2.11
@@ -16179,7 +16129,7 @@ snapshots:
jest-worker@29.7.0:
dependencies:
'@types/node': 17.0.36
'@types/node': 20.14.8
jest-util: 29.7.0
merge-stream: 2.0.0
supports-color: 8.1.1
@@ -17892,8 +17842,6 @@ snapshots:
proxy-from-env@1.0.0: {}
proxy-from-env@1.1.0: {}
psl@1.9.0: {}
pstree.remy@1.1.8: {}

View File

@@ -14,7 +14,6 @@ import { DiscoverSliderType } from '@server/constants/discover';
import type DiscoverSlider from '@server/entity/DiscoverSlider';
import type { GenreSliderItem } from '@server/interfaces/api/discoverInterfaces';
import type { Keyword, ProductionCompany } from '@server/models/common';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useCallback, useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -77,11 +76,9 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => {
const keywords = await Promise.all(
slider.data.split(',').map(async (keywordId) => {
const keyword = await axios.get<Keyword>(
`/api/v1/keyword/${keywordId}`
);
return keyword.data;
const res = await fetch(`/api/v1/keyword/${keywordId}`);
const keyword: Keyword = await res.json();
return keyword;
})
);
@@ -98,15 +95,13 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => {
return;
}
const response = await axios.get<TmdbGenre[]>(
const res = await fetch(
`/api/v1/genres/${
slider.type === DiscoverSliderType.TMDB_MOVIE_GENRE ? 'movie' : 'tv'
}`
);
const genre = response.data.find(
(genre) => genre.id === Number(slider.data)
);
const genres: TmdbGenre[] = await res.json();
const genre = genres.find((genre) => genre.id === Number(slider.data));
setDefaultDataValue([
{
@@ -121,11 +116,8 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => {
return;
}
const response = await axios.get<ProductionCompany>(
`/api/v1/studio/${slider.data}`
);
const studio = response.data;
const res = await fetch(`/api/v1/studio/${slider.data}`);
const studio: ProductionCompany = await res.json();
setDefaultDataValue([
{
@@ -168,16 +160,17 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => {
);
const loadKeywordOptions = async (inputValue: string) => {
const results = await axios.get<TmdbKeywordSearchResponse>(
'/api/v1/search/keyword',
const res = await fetch(
`/api/v1/search/keyword?query=${encodeURIExtraParams(inputValue)}`,
{
params: {
query: encodeURIExtraParams(inputValue),
headers: {
'Content-Type': 'application/json',
},
}
);
const results: TmdbKeywordSearchResponse = await res.json();
return results.data.results.map((result) => ({
return results.results.map((result) => ({
label: result.name,
value: result.id,
}));
@@ -188,38 +181,37 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => {
return [];
}
const results = await axios.get<TmdbCompanySearchResponse>(
'/api/v1/search/company',
const res = await fetch(
`/api/v1/search/company?query=${encodeURIExtraParams(inputValue)}`,
{
params: {
query: encodeURIExtraParams(inputValue),
headers: {
'Content-Type': 'application/json',
},
}
);
const results: TmdbCompanySearchResponse = await res.json();
return results.data.results.map((result) => ({
return results.results.map((result) => ({
label: result.name,
value: result.id,
}));
};
const loadMovieGenreOptions = async () => {
const results = await axios.get<GenreSliderItem[]>(
'/api/v1/discover/genreslider/movie'
);
const res = await fetch('/api/v1/discover/genreslider/movie');
const results: GenreSliderItem[] = await res.json();
return results.data.map((result) => ({
return results.map((result) => ({
label: result.name,
value: result.id,
}));
};
const loadTvGenreOptions = async () => {
const results = await axios.get<GenreSliderItem[]>(
'/api/v1/discover/genreslider/tv'
);
const res = await fetch('/api/v1/discover/genreslider/tv');
const results: GenreSliderItem[] = await res.json();
return results.data.map((result) => ({
return results.map((result) => ({
label: result.name,
value: result.id,
}));
@@ -314,17 +306,31 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => {
onSubmit={async (values, { resetForm }) => {
try {
if (slider) {
await axios.put(`/api/v1/settings/discover/${slider.id}`, {
type: Number(values.sliderType),
title: values.title,
data: values.data,
const res = await fetch(`/api/v1/settings/discover/${slider.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: Number(values.sliderType),
title: values.title,
data: values.data,
}),
});
if (!res.ok) throw new Error();
} else {
await axios.post('/api/v1/settings/discover/add', {
type: Number(values.sliderType),
title: values.title,
data: values.data,
const res = await fetch('/api/v1/settings/discover/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: Number(values.sliderType),
title: values.title,
data: values.data,
}),
});
if (!res.ok) throw new Error();
}
addToast(

View File

@@ -20,7 +20,6 @@ import {
} from '@heroicons/react/24/solid';
import { DiscoverSliderType } from '@server/constants/discover';
import type DiscoverSlider from '@server/entity/DiscoverSlider';
import axios from 'axios';
import { useRef, useState } from 'react';
import { useDrag, useDrop } from 'react-aria';
import { useIntl } from 'react-intl';
@@ -78,7 +77,10 @@ const DiscoverSliderEdit = ({
const deleteSlider = async () => {
try {
await axios.delete(`/api/v1/settings/discover/${slider.id}`);
const res = await fetch(`/api/v1/settings/discover/${slider.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.deletesuccess), {
appearance: 'success',
autoDismiss: true,

View File

@@ -28,7 +28,6 @@ import {
} from '@heroicons/react/24/solid';
import { DiscoverSliderType } from '@server/constants/discover';
import type DiscoverSlider from '@server/entity/DiscoverSlider';
import axios from 'axios';
import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -76,7 +75,14 @@ const Discover = () => {
const updateSliders = async () => {
try {
await axios.post('/api/v1/settings/discover', sliders);
const res = await fetch('/api/v1/settings/discover', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(sliders),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.updatesuccess), {
appearance: 'success',
@@ -94,7 +100,10 @@ const Discover = () => {
const resetSliders = async () => {
try {
await axios.get('/api/v1/settings/discover/reset');
const res = await fetch('/api/v1/settings/discover/reset', {
method: 'GET',
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.resetsuccess), {
appearance: 'success',

View File

@@ -5,7 +5,6 @@ import defineMessages from '@app/utils/defineMessages';
import { Menu, Transition } from '@headlessui/react';
import { EllipsisVerticalIcon } from '@heroicons/react/24/solid';
import type { default as IssueCommentType } from '@server/entity/IssueComment';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import Image from 'next/image';
import Link from 'next/link';
@@ -49,7 +48,10 @@ const IssueComment = ({
const deleteComment = async () => {
try {
await axios.delete(`/api/v1/issueComment/${comment.id}`);
const res = await fetch(`/api/v1/issueComment/${comment.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
} catch (e) {
// something went wrong deleting the comment
} finally {
@@ -175,9 +177,14 @@ const IssueComment = ({
<Formik
initialValues={{ newMessage: comment.message }}
onSubmit={async (values) => {
await axios.put(`/api/v1/issueComment/${comment.id}`, {
message: values.newMessage,
});
const res = await fetch(
`/api/v1/issueComment/${comment.id}`,
{
method: 'PUT',
body: JSON.stringify({ message: values.newMessage }),
}
);
if (!res.ok) throw new Error();
if (onUpdate) {
onUpdate();

View File

@@ -11,7 +11,7 @@ import useDeepLinks from '@app/hooks/useDeepLinks';
import useSettings from '@app/hooks/useSettings';
import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import Error from '@app/pages/_error';
import ErrorPage from '@app/pages/_error';
import defineMessages from '@app/utils/defineMessages';
import { Transition } from '@headlessui/react';
import {
@@ -27,7 +27,6 @@ import { MediaServerType } from '@server/constants/server';
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, Form, Formik } from 'formik';
import getConfig from 'next/config';
import Image from 'next/image';
@@ -116,7 +115,7 @@ const IssueDetails = () => {
}
if (!data || !issueData) {
return <Error statusCode={404} />;
return <ErrorPage statusCode={404} />;
}
const belongsToUser = issueData.createdBy.id === currentUser?.id;
@@ -125,9 +124,11 @@ const IssueDetails = () => {
const editFirstComment = async (newMessage: string) => {
try {
await axios.put(`/api/v1/issueComment/${firstComment.id}`, {
message: newMessage,
const res = await fetch(`/api/v1/issueComment/${firstComment.id}`, {
method: 'PUT',
body: JSON.stringify({ message: newMessage }),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.toasteditdescriptionsuccess), {
appearance: 'success',
@@ -144,7 +145,10 @@ const IssueDetails = () => {
const updateIssueStatus = async (newStatus: 'open' | 'resolved') => {
try {
await axios.post(`/api/v1/issue/${issueData.id}/${newStatus}`);
const res = await fetch(`/api/v1/issue/${issueData.id}/${newStatus}`, {
method: 'POST',
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.toaststatusupdated), {
appearance: 'success',
@@ -161,7 +165,10 @@ const IssueDetails = () => {
const deleteIssue = async () => {
try {
await axios.delete(`/api/v1/issue/${issueData.id}`);
const res = await fetch(`/api/v1/issue/${issueData.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.toastissuedeleted), {
appearance: 'success',
@@ -490,9 +497,14 @@ const IssueDetails = () => {
}}
validationSchema={CommentSchema}
onSubmit={async (values, { resetForm }) => {
await axios.post(`/api/v1/issue/${issueData?.id}/comment`, {
message: values.message,
});
const res = await fetch(
`/api/v1/issue/${issueData?.id}/comment`,
{
method: 'POST',
body: JSON.stringify({ message: values.message }),
}
);
if (!res.ok) throw new Error();
revalidateIssue();
resetForm();
}}

View File

@@ -11,7 +11,6 @@ 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 { useIntl } from 'react-intl';
@@ -101,14 +100,19 @@ const CreateIssueModal = ({
validationSchema={CreateIssueModalSchema}
onSubmit={async (values) => {
try {
const newIssue = await axios.post<Issue>('/api/v1/issue', {
issueType: values.selectedIssue.issueType,
message: values.message,
mediaId: data?.mediaInfo?.id,
problemSeason: values.problemSeason,
problemEpisode:
values.problemSeason > 0 ? values.problemEpisode : 0,
const res = await fetch('/api/v1/issue', {
method: 'POST',
body: JSON.stringify({
issueType: values.selectedIssue.issueType,
message: values.message,
mediaId: data?.mediaInfo?.id,
problemSeason: values.problemSeason,
problemEpisode:
values.problemSeason > 0 ? values.problemEpisode : 0,
}),
});
if (!res.ok) throw new Error();
const newIssue: Issue = await res.json();
if (data) {
addToast(
@@ -119,7 +123,7 @@ const CreateIssueModal = ({
strong: (msg: React.ReactNode) => <strong>{msg}</strong>,
})}
</div>
<Link href={`/issues/${newIssue.data.id}`} legacyBehavior>
<Link href={`/issues/${newIssue.id}`} legacyBehavior>
<Button as="a" className="mt-4">
<span>{intl.formatMessage(messages.toastviewissue)}</span>
<ArrowRightCircleIcon />

View File

@@ -7,7 +7,6 @@ import {
ClockIcon,
} from '@heroicons/react/24/outline';
import { CogIcon, UserIcon } from '@heroicons/react/24/solid';
import axios from 'axios';
import Image from 'next/image';
import type { LinkProps } from 'next/link';
import Link from 'next/link';
@@ -39,9 +38,13 @@ const UserDropdown = () => {
const { user, revalidate } = useUser();
const logout = async () => {
const response = await axios.post('/api/v1/auth/logout');
const res = await fetch('/api/v1/auth/logout', {
method: 'POST',
});
if (!res.ok) throw new Error();
const data = await res.json();
if (response.data?.status === 'ok') {
if (data?.status === 'ok') {
revalidate();
}
};

View File

@@ -24,6 +24,7 @@ const UserWarnings: React.FC<UserWarningsProps> = ({ onClick }) => {
let res = null;
//check if a user has warnings
console.log(user);
if (user.warnings.length > 0) {
user.warnings.forEach((warning) => {
let link = '';

View File

@@ -2,7 +2,6 @@ import Modal from '@app/components/Common/Modal';
import useSettings from '@app/hooks/useSettings';
import defineMessages from '@app/utils/defineMessages';
import { Transition } from '@headlessui/react';
import axios from 'axios';
import { Field, Formik } from 'formik';
import { useIntl } from 'react-intl';
import * as Yup from 'yup';
@@ -58,11 +57,15 @@ const AddEmailModal: React.FC<AddEmailModalProps> = ({
validationSchema={EmailSettingsSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/auth/jellyfin', {
username: username,
password: password,
email: values.email,
const res = await fetch('/api/v1/auth/jellyfin', {
method: 'POST',
body: JSON.stringify({
username: username,
password: password,
email: values.email,
}),
});
if (!res.ok) throw new Error();
onSave();
} catch (e) {

View File

@@ -4,7 +4,6 @@ import useSettings from '@app/hooks/useSettings';
import defineMessages from '@app/utils/defineMessages';
import { InformationCircleIcon } from '@heroicons/react/24/solid';
import { ApiErrorCode } from '@server/constants/error';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import getConfig from 'next/config';
import { useIntl } from 'react-intl';
@@ -105,15 +104,22 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
validationSchema={LoginSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/auth/jellyfin', {
username: values.username,
password: values.password,
hostname: values.hostname,
port: values.port,
useSsl: values.useSsl,
urlBase: values.urlBase,
email: values.email,
const res = await fetch('/api/v1/auth/jellyfin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: values.username,
password: values.password,
hostname: values.hostname,
port: values.port,
useSsl: values.useSsl,
urlBase: values.urlBase,
email: values.email,
}),
});
if (!res.ok) throw new Error();
} catch (e) {
let errorMessage = null;
switch (e.response?.data?.message) {
@@ -339,11 +345,18 @@ const JellyfinLogin: React.FC<JellyfinLoginProps> = ({
validationSchema={LoginSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/auth/jellyfin', {
username: values.username,
password: values.password,
email: values.username,
const res = await fetch('/api/v1/auth/jellyfin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: values.username,
password: values.password,
email: values.username,
}),
});
if (!res.ok) throw new Error();
} catch (e) {
toasts.addToast(
intl.formatMessage(

View File

@@ -6,7 +6,6 @@ import {
ArrowLeftOnRectangleIcon,
LifebuoyIcon,
} from '@heroicons/react/24/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import Link from 'next/link';
import { useState } from 'react';
@@ -56,10 +55,17 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => {
validationSchema={LoginSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/auth/local', {
email: values.email,
password: values.password,
const res = await fetch('/api/v1/auth/local', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: values.email,
password: values.password,
}),
});
if (!res.ok) throw new Error();
} catch (e) {
setLoginError(intl.formatMessage(messages.loginerror));
} finally {

View File

@@ -10,7 +10,6 @@ import defineMessages from '@app/utils/defineMessages';
import { Transition } from '@headlessui/react';
import { XCircleIcon } from '@heroicons/react/24/solid';
import { MediaServerType } from '@server/constants/server';
import axios from 'axios';
import getConfig from 'next/config';
import { useRouter } from 'next/dist/client/router';
import Image from 'next/image';
@@ -44,9 +43,14 @@ const Login = () => {
const login = async () => {
setProcessing(true);
try {
const response = await axios.post('/api/v1/auth/plex', { authToken });
const res = await fetch('/api/v1/auth/plex', {
method: 'POST',
body: JSON.stringify({ authToken }),
});
if (!res.ok) throw new Error();
const data = await res.json();
if (response.data?.id) {
if (data?.id) {
revalidate();
}
} catch (e) {

View File

@@ -26,7 +26,6 @@ import type { MediaWatchDataResponse } from '@server/interfaces/api/mediaInterfa
import type { RadarrSettings, SonarrSettings } from '@server/lib/settings';
import type { MovieDetails } from '@server/models/Movie';
import type { TvDetails } from '@server/models/Tv';
import axios from 'axios';
import getConfig from 'next/config';
import Image from 'next/image';
import Link from 'next/link';
@@ -113,15 +112,26 @@ const ManageSlideOver = ({
const deleteMedia = async () => {
if (data.mediaInfo) {
await axios.delete(`/api/v1/media/${data.mediaInfo.id}`);
const res = await fetch(`/api/v1/media/${data.mediaInfo.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
revalidate();
}
};
const deleteMediaFile = async () => {
if (data.mediaInfo) {
await axios.delete(`/api/v1/media/${data.mediaInfo.id}/file`);
await axios.delete(`/api/v1/media/${data.mediaInfo.id}`);
const res1 = await fetch(`/api/v1/media/${data.mediaInfo.id}/file`, {
method: 'DELETE',
});
if (!res1.ok) throw new Error();
const res2 = await fetch(`/api/v1/media/${data.mediaInfo.id}`, {
method: 'DELETE',
});
if (!res2.ok) throw new Error();
revalidate();
}
};
@@ -149,9 +159,16 @@ const ManageSlideOver = ({
const markAvailable = async (is4k = false) => {
if (data.mediaInfo) {
await axios.post(`/api/v1/media/${data.mediaInfo?.id}/available`, {
is4k,
const res = await fetch(`/api/v1/media/${data.mediaInfo?.id}/available`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
is4k,
}),
});
if (!res.ok) throw new Error();
revalidate();
}
};

View File

@@ -17,7 +17,6 @@ import {
} from '@heroicons/react/24/solid';
import { MediaRequestStatus } from '@server/constants/media';
import type { MediaRequest } from '@server/entity/MediaRequest';
import axios from 'axios';
import Link from 'next/link';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -53,7 +52,10 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
const updateRequest = async (type: 'approve' | 'decline'): Promise<void> => {
setIsUpdating(true);
await axios.post(`/api/v1/request/${request.id}/${type}`);
const res = await fetch(`/api/v1/request/${request.id}/${type}`, {
method: 'POST',
});
if (!res.ok) throw new Error();
if (onUpdate) {
onUpdate();
@@ -63,7 +65,10 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
const deleteRequest = async () => {
setIsUpdating(true);
await axios.delete(`/api/v1/request/${request.id}`);
const res = await fetch(`/api/v1/request/${request.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
if (onUpdate) {
onUpdate();

View File

@@ -13,7 +13,6 @@ import {
import { MediaRequestStatus, MediaStatus } from '@server/constants/media';
import type Media from '@server/entity/Media';
import type { MediaRequest } from '@server/entity/MediaRequest';
import axios from 'axios';
import { useMemo, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -94,9 +93,13 @@ const RequestButton = ({
request: MediaRequest,
type: 'approve' | 'decline'
) => {
const response = await axios.post(`/api/v1/request/${request.id}/${type}`);
const res = await fetch(`/api/v1/request/${request.id}/${type}`, {
method: 'POST',
});
if (!res.ok) throw new Error();
const data = await res.json();
if (response) {
if (data) {
onUpdate();
}
};
@@ -111,7 +114,11 @@ const RequestButton = ({
await Promise.all(
requests.map(async (request) => {
return axios.post(`/api/v1/request/${request.id}/${type}`);
const res = await fetch(`/api/v1/request/${request.id}/${type}`, {
method: 'POST',
});
if (!res.ok) throw new Error();
return res.json();
})
);

View File

@@ -21,7 +21,6 @@ import { MediaRequestStatus } from '@server/constants/media';
import type { MediaRequest } from '@server/entity/MediaRequest';
import type { MovieDetails } from '@server/models/Movie';
import type { TvDetails } from '@server/models/Tv';
import axios from 'axios';
import Image from 'next/image';
import Link from 'next/link';
import { useEffect, useState } from 'react';
@@ -74,7 +73,10 @@ const RequestCardError = ({ requestData }: RequestCardErrorProps) => {
});
const deleteRequest = async () => {
await axios.delete(`/api/v1/media/${requestData?.media.id}`);
const res = await fetch(`/api/v1/media/${requestData?.media.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
mutate('/api/v1/media?filter=allavailable&take=20&sort=mediaAdded');
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
};
@@ -255,15 +257,22 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
});
const modifyRequest = async (type: 'approve' | 'decline') => {
const response = await axios.post(`/api/v1/request/${request.id}/${type}`);
const res = await fetch(`/api/v1/request/${request.id}/${type}`, {
method: 'POST',
});
if (!res.ok) throw new Error();
const data = await res.json();
if (response) {
if (data) {
revalidate();
}
};
const deleteRequest = async () => {
await axios.delete(`/api/v1/request/${request.id}`);
const res = await fetch(`/api/v1/request/${request.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
};
@@ -271,9 +280,13 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
setRetrying(true);
try {
const response = await axios.post(`/api/v1/request/${request.id}/retry`);
const res = await fetch(`/api/v1/request/${request.id}/retry`, {
method: 'POST',
});
if (!res.ok) throw new Error();
const data = await res.json();
if (response) {
if (data) {
revalidate();
}
} catch (e) {

View File

@@ -20,7 +20,6 @@ import { MediaRequestStatus } from '@server/constants/media';
import type { MediaRequest } from '@server/entity/MediaRequest';
import type { MovieDetails } from '@server/models/Movie';
import type { TvDetails } from '@server/models/Tv';
import axios from 'axios';
import Image from 'next/image';
import Link from 'next/link';
import { useState } from 'react';
@@ -62,7 +61,10 @@ const RequestItemError = ({
const { hasPermission } = useUser();
const deleteRequest = async () => {
await axios.delete(`/api/v1/media/${requestData?.media.id}`);
const res = await fetch(`/api/v1/media/${requestData?.media.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
revalidateList();
};
@@ -319,15 +321,22 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
const [isRetrying, setRetrying] = useState(false);
const modifyRequest = async (type: 'approve' | 'decline') => {
const response = await axios.post(`/api/v1/request/${request.id}/${type}`);
const res = await fetch(`/api/v1/request/${request.id}/${type}`, {
method: 'POST',
});
if (!res.ok) throw new Error();
const data = await res.json();
if (response) {
if (data) {
revalidate();
}
};
const deleteRequest = async () => {
await axios.delete(`/api/v1/request/${request.id}`);
const res = await fetch(`/api/v1/request/${request.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
revalidateList();
};
@@ -336,7 +345,12 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
setRetrying(true);
try {
const result = await axios.post(`/api/v1/request/${request.id}/retry`);
const res = await fetch(`/api/v1/request/${request.id}/retry`, {
method: 'POST',
});
if (!res.ok) throw new Error();
const result = await res.json();
revalidate(result.data);
} catch (e) {
addToast(intl.formatMessage(messages.failedretry), {

View File

@@ -13,7 +13,6 @@ import type { MediaRequest } from '@server/entity/MediaRequest';
import type { QuotaResponse } from '@server/interfaces/api/userInterfaces';
import { Permission } from '@server/lib/permissions';
import type { Collection } from '@server/models/Collection';
import axios from 'axios';
import { useCallback, useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -197,12 +196,19 @@ const CollectionRequestModal = ({
(
data?.parts.filter((part) => selectedParts.includes(part.id)) ?? []
).map(async (part) => {
await axios.post<MediaRequest>('/api/v1/request', {
mediaId: part.id,
mediaType: 'movie',
is4k,
...overrideParams,
const res = await fetch('/api/v1/request', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
mediaId: part.id,
mediaType: 'movie',
is4k,
...overrideParams,
}),
});
if (!res.ok) throw new Error();
})
);

View File

@@ -11,7 +11,6 @@ import type { MediaRequest } from '@server/entity/MediaRequest';
import type { QuotaResponse } from '@server/interfaces/api/userInterfaces';
import { Permission } from '@server/lib/permissions';
import type { MovieDetails } from '@server/models/Movie';
import axios from 'axios';
import { useCallback, useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -89,15 +88,23 @@ const MovieRequestModal = ({
tags: requestOverrides.tags,
};
}
const response = await axios.post<MediaRequest>('/api/v1/request', {
mediaId: data?.id,
mediaType: 'movie',
is4k,
...overrideParams,
const res = await fetch('/api/v1/request', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
mediaId: data?.id,
mediaType: 'movie',
is4k,
...overrideParams,
}),
});
if (!res.ok) throw new Error();
const mediaRequest: MediaRequest = await res.json();
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
if (response.data) {
if (mediaRequest) {
if (onComplete) {
onComplete(
hasPermission(
@@ -136,12 +143,14 @@ const MovieRequestModal = ({
setIsUpdating(true);
try {
const response = await axios.delete<MediaRequest>(
`/api/v1/request/${editRequest?.id}`
);
const res = await fetch(`/api/v1/request/${editRequest?.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
if (response.status === 204) {
if (res.status === 204) {
if (onComplete) {
onComplete(MediaStatus.UNKNOWN);
}
@@ -164,17 +173,27 @@ const MovieRequestModal = ({
setIsUpdating(true);
try {
await axios.put(`/api/v1/request/${editRequest?.id}`, {
mediaType: 'movie',
serverId: requestOverrides?.server,
profileId: requestOverrides?.profile,
rootFolder: requestOverrides?.folder,
userId: requestOverrides?.user?.id,
tags: requestOverrides?.tags,
const res = await fetch(`/api/v1/request/${editRequest?.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
mediaType: 'movie',
serverId: requestOverrides?.server,
profileId: requestOverrides?.profile,
rootFolder: requestOverrides?.folder,
userId: requestOverrides?.user?.id,
tags: requestOverrides?.tags,
}),
});
if (!res.ok) throw new Error();
if (alsoApproveRequest) {
await axios.post(`/api/v1/request/${editRequest?.id}/approve`);
const res = await fetch(`/api/v1/request/${editRequest?.id}/approve`, {
method: 'POST',
});
if (!res.ok) throw new Error();
}
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');

View File

@@ -16,7 +16,6 @@ import type SeasonRequest from '@server/entity/SeasonRequest';
import type { QuotaResponse } from '@server/interfaces/api/userInterfaces';
import { Permission } from '@server/lib/permissions';
import type { TvDetails } from '@server/models/Tv';
import axios from 'axios';
import { useState } from 'react';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -111,22 +110,35 @@ const TvRequestModal = ({
try {
if (selectedSeasons.length > 0) {
await axios.put(`/api/v1/request/${editRequest.id}`, {
mediaType: 'tv',
serverId: requestOverrides?.server,
profileId: requestOverrides?.profile,
rootFolder: requestOverrides?.folder,
languageProfileId: requestOverrides?.language,
userId: requestOverrides?.user?.id,
tags: requestOverrides?.tags,
seasons: selectedSeasons,
const res = await fetch(`/api/v1/request/${editRequest.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
mediaType: 'tv',
serverId: requestOverrides?.server,
profileId: requestOverrides?.profile,
rootFolder: requestOverrides?.folder,
languageProfileId: requestOverrides?.language,
userId: requestOverrides?.user?.id,
tags: requestOverrides?.tags,
seasons: selectedSeasons,
}),
});
if (!res.ok) throw new Error();
if (alsoApproveRequest) {
await axios.post(`/api/v1/request/${editRequest.id}/approve`);
const res = await fetch(`/api/v1/request/${editRequest.id}/approve`, {
method: 'POST',
});
if (!res.ok) throw new Error();
}
} else {
await axios.delete(`/api/v1/request/${editRequest.id}`);
const res = await fetch(`/api/v1/request/${editRequest.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
}
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
@@ -191,23 +203,32 @@ const TvRequestModal = ({
tags: requestOverrides.tags,
};
}
const response = await axios.post<MediaRequest>('/api/v1/request', {
mediaId: data?.id,
tvdbId: tvdbId ?? data?.externalIds.tvdbId,
mediaType: 'tv',
is4k,
seasons: settings.currentSettings.partialRequestsEnabled
? selectedSeasons
: getAllSeasons().filter(
(season) => !getAllRequestedSeasons().includes(season)
),
...overrideParams,
const res = await fetch('/api/v1/request', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
mediaId: data?.id,
tvdbId: tvdbId ?? data?.externalIds.tvdbId,
mediaType: 'tv',
is4k,
seasons: settings.currentSettings.partialRequestsEnabled
? selectedSeasons
: getAllSeasons().filter(
(season) => !getAllRequestedSeasons().includes(season)
),
...overrideParams,
}),
});
if (!res.ok) throw new Error();
const mediaRequest: MediaRequest = await res.json();
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
if (response.data) {
if (mediaRequest) {
if (onComplete) {
onComplete(response.data.media.status);
onComplete(mediaRequest.media.status);
}
addToast(
<span>

View File

@@ -4,7 +4,6 @@ import PageTitle from '@app/components/Common/PageTitle';
import LanguagePicker from '@app/components/Layout/LanguagePicker';
import defineMessages from '@app/utils/defineMessages';
import { ArrowLeftIcon, EnvelopeIcon } from '@heroicons/react/24/solid';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import Image from 'next/image';
import Link from 'next/link';
@@ -85,14 +84,18 @@ const ResetPassword = () => {
}}
validationSchema={ResetSchema}
onSubmit={async (values) => {
const response = await axios.post(
`/api/v1/auth/reset-password`,
{
const res = await fetch(`/api/v1/auth/reset-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: values.email,
}
);
}),
});
if (!res.ok) throw new Error();
if (response.status === 200) {
if (res.status === 200) {
setSubmitted(true);
}
}}

View File

@@ -5,7 +5,6 @@ import LanguagePicker from '@app/components/Layout/LanguagePicker';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { LifebuoyIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Form, Formik } from 'formik';
import Image from 'next/image';
import Link from 'next/link';
@@ -100,14 +99,21 @@ const ResetPassword = () => {
}}
validationSchema={ResetSchema}
onSubmit={async (values) => {
const response = await axios.post(
const res = await fetch(
`/api/v1/auth/reset-password/${guid}`,
{
password: values.password,
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
password: values.password,
}),
}
);
if (!res.ok) throw new Error();
if (response.status === 200) {
if (res.status === 200) {
setSubmitted(true);
}
}}

View File

@@ -18,7 +18,6 @@ import type {
ProductionCompany,
WatchProviderDetails,
} from '@server/models/common';
import axios from 'axios';
import { orderBy } from 'lodash';
import { useEffect, useMemo, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -69,11 +68,9 @@ export const CompanySelector = ({
return;
}
const response = await axios.get<ProductionCompany>(
`/api/v1/studio/${defaultValue}`
);
const studio = response.data;
const res = await fetch(`/api/v1/studio/${defaultValue}`);
if (!res.ok) throw new Error();
const studio: ProductionCompany = await res.json();
setDefaultDataValue([
{
@@ -91,16 +88,15 @@ export const CompanySelector = ({
return [];
}
const results = await axios.get<TmdbCompanySearchResponse>(
'/api/v1/search/company',
{
params: {
query: encodeURIExtraParams(inputValue),
},
}
const res = await fetch(
`/api/v1/search/company?query=${encodeURIExtraParams(inputValue)}`
);
if (!res.ok) {
throw new Error('Network response was not ok');
}
const results: TmdbCompanySearchResponse = await res.json();
return results.data.results.map((result) => ({
return results.results.map((result) => ({
label: result.name,
value: result.id,
}));
@@ -154,11 +150,15 @@ export const GenreSelector = ({
const genres = defaultValue.split(',');
const response = await axios.get<TmdbGenre[]>(`/api/v1/genres/${type}`);
const res = await fetch(`/api/v1/genres/${type}`);
if (!res.ok) {
throw new Error('Network response was not ok');
}
const response: TmdbGenre[] = await res.json();
const genreData = genres
.filter((genre) => response.data.find((gd) => gd.id === Number(genre)))
.map((g) => response.data.find((gd) => gd.id === Number(g)))
.filter((genre) => response.find((gd) => gd.id === Number(genre)))
.map((g) => response.find((gd) => gd.id === Number(g)))
.map((g) => ({
label: g?.name ?? '',
value: g?.id ?? 0,
@@ -171,11 +171,11 @@ export const GenreSelector = ({
}, [defaultValue, type]);
const loadGenreOptions = async (inputValue: string) => {
const results = await axios.get<GenreSliderItem[]>(
`/api/v1/discover/genreslider/${type}`
);
const res = await fetch(`/api/v1/discover/genreslider/${type}`);
if (!res.ok) throw new Error();
const results: GenreSliderItem[] = await res.json();
return results.data
return results
.map((result) => ({
label: result.name,
value: result.id,
@@ -222,11 +222,13 @@ export const KeywordSelector = ({
const keywords = await Promise.all(
defaultValue.split(',').map(async (keywordId) => {
const keyword = await axios.get<Keyword>(
`/api/v1/keyword/${keywordId}`
);
const res = await fetch(`/api/v1/keyword/${keywordId}`);
if (!res.ok) {
throw new Error('Network response was not ok');
}
const keyword: Keyword = await res.json();
return keyword.data;
return keyword;
})
);
@@ -242,16 +244,15 @@ export const KeywordSelector = ({
}, [defaultValue]);
const loadKeywordOptions = async (inputValue: string) => {
const results = await axios.get<TmdbKeywordSearchResponse>(
'/api/v1/search/keyword',
{
params: {
query: encodeURIExtraParams(inputValue),
},
}
const res = await fetch(
`/api/v1/search/keyword?query=${encodeURIExtraParams(inputValue)}`
);
if (!res.ok) {
throw new Error('Network response was not ok');
}
const results: TmdbKeywordSearchResponse = await res.json();
return results.data.results.map((result) => ({
return results.results.map((result) => ({
label: result.name,
value: result.id,
}));

View File

@@ -1,7 +1,6 @@
/* eslint-disable no-console */
import useSettings from '@app/hooks/useSettings';
import { useUser } from '@app/hooks/useUser';
import axios from 'axios';
import { useEffect } from 'react';
const ServiceWorkerSetup = () => {
@@ -26,11 +25,18 @@ const ServiceWorkerSetup = () => {
const parsedSub = JSON.parse(JSON.stringify(sub));
if (parsedSub.keys.p256dh && parsedSub.keys.auth) {
await axios.post('/api/v1/user/registerPushSubscription', {
endpoint: parsedSub.endpoint,
p256dh: parsedSub.keys.p256dh,
auth: parsedSub.keys.auth,
const res = await fetch('/api/v1/user/registerPushSubscription', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
endpoint: parsedSub.endpoint,
p256dh: parsedSub.keys.p256dh,
auth: parsedSub.keys.auth,
}),
});
if (!res.ok) throw new Error();
}
}
})

View File

@@ -5,7 +5,6 @@ import useSettings from '@app/hooks/useSettings';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -73,16 +72,23 @@ const NotificationsDiscord = () => {
validationSchema={NotificationsDiscordSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/notifications/discord', {
enabled: values.enabled,
types: values.types,
options: {
botUsername: values.botUsername,
botAvatarUrl: values.botAvatarUrl,
webhookUrl: values.webhookUrl,
enableMentions: values.enableMentions,
const res = await fetch('/api/v1/settings/notifications/discord', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: values.enabled,
types: values.types,
options: {
botUsername: values.botUsername,
botAvatarUrl: values.botAvatarUrl,
webhookUrl: values.webhookUrl,
enableMentions: values.enableMentions,
},
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.discordsettingssaved), {
appearance: 'success',
@@ -121,16 +127,26 @@ const NotificationsDiscord = () => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/discord/test', {
enabled: true,
types: values.types,
options: {
botUsername: values.botUsername,
botAvatarUrl: values.botAvatarUrl,
webhookUrl: values.webhookUrl,
enableMentions: values.enableMentions,
},
});
const res = await fetch(
'/api/v1/settings/notifications/discord/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
types: values.types,
options: {
botUsername: values.botUsername,
botAvatarUrl: values.botAvatarUrl,
webhookUrl: values.webhookUrl,
enableMentions: values.enableMentions,
},
}),
}
);
if (!res.ok) throw new Error();
if (toastId) {
removeToast(toastId);

View File

@@ -5,7 +5,6 @@ import SettingsBadge from '@app/components/Settings/SettingsBadge';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -148,24 +147,31 @@ const NotificationsEmail = () => {
validationSchema={NotificationsEmailSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/notifications/email', {
enabled: values.enabled,
options: {
userEmailRequired: values.userEmailRequired,
emailFrom: values.emailFrom,
smtpHost: values.smtpHost,
smtpPort: Number(values.smtpPort),
secure: values.encryption === 'implicit',
ignoreTls: values.encryption === 'none',
requireTls: values.encryption === 'opportunistic',
authUser: values.authUser,
authPass: values.authPass,
allowSelfSigned: values.allowSelfSigned,
senderName: values.senderName,
pgpPrivateKey: values.pgpPrivateKey,
pgpPassword: values.pgpPassword,
const res = await fetch('/api/v1/settings/notifications/email', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: values.enabled,
options: {
userEmailRequired: values.userEmailRequired,
emailFrom: values.emailFrom,
smtpHost: values.smtpHost,
smtpPort: Number(values.smtpPort),
secure: values.encryption === 'implicit',
ignoreTls: values.encryption === 'none',
requireTls: values.encryption === 'opportunistic',
authUser: values.authUser,
authPass: values.authPass,
allowSelfSigned: values.allowSelfSigned,
senderName: values.senderName,
pgpPrivateKey: values.pgpPrivateKey,
pgpPassword: values.pgpPassword,
},
}),
});
if (!res.ok) throw new Error();
mutate('/api/v1/settings/public');
addToast(intl.formatMessage(messages.emailsettingssaved), {
@@ -197,22 +203,32 @@ const NotificationsEmail = () => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/email/test', {
enabled: true,
options: {
emailFrom: values.emailFrom,
smtpHost: values.smtpHost,
smtpPort: Number(values.smtpPort),
secure: values.encryption === 'implicit',
ignoreTls: values.encryption === 'none',
requireTls: values.encryption === 'opportunistic',
authUser: values.authUser,
authPass: values.authPass,
senderName: values.senderName,
pgpPrivateKey: values.pgpPrivateKey,
pgpPassword: values.pgpPassword,
},
});
const res = await fetch(
'/api/v1/settings/notifications/email/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
options: {
emailFrom: values.emailFrom,
smtpHost: values.smtpHost,
smtpPort: Number(values.smtpPort),
secure: values.encryption === 'implicit',
ignoreTls: values.encryption === 'none',
requireTls: values.encryption === 'opportunistic',
authUser: values.authUser,
authPass: values.authPass,
senderName: values.senderName,
pgpPrivateKey: values.pgpPrivateKey,
pgpPassword: values.pgpPassword,
},
}),
}
);
if (!res.ok) throw new Error();
if (toastId) {
removeToast(toastId);

View File

@@ -4,7 +4,6 @@ import NotificationTypeSelector from '@app/components/NotificationTypeSelector';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/solid';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -83,14 +82,21 @@ const NotificationsGotify = () => {
validationSchema={NotificationsGotifySchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/notifications/gotify', {
enabled: values.enabled,
types: values.types,
options: {
url: values.url,
token: values.token,
const res = await fetch('/api/v1/settings/notifications/gotify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: values.enabled,
types: values.types,
options: {
url: values.url,
token: values.token,
},
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.gotifysettingssaved), {
appearance: 'success',
autoDismiss: true,
@@ -128,14 +134,24 @@ const NotificationsGotify = () => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/gotify/test', {
enabled: true,
types: values.types,
options: {
url: values.url,
token: values.token,
},
});
const res = await fetch(
'/api/v1/settings/notifications/gotify/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
types: values.types,
options: {
url: values.url,
token: values.token,
},
}),
}
);
if (!res.ok) throw new Error();
if (toastId) {
removeToast(toastId);

View File

@@ -4,7 +4,6 @@ import NotificationTypeSelector from '@app/components/NotificationTypeSelector';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -69,14 +68,21 @@ const NotificationsLunaSea = () => {
validationSchema={NotificationsLunaSeaSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/notifications/lunasea', {
enabled: values.enabled,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
profileName: values.profileName,
const res = await fetch('/api/v1/settings/notifications/lunasea', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: values.enabled,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
profileName: values.profileName,
},
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.settingsSaved), {
appearance: 'success',
autoDismiss: true,
@@ -114,14 +120,24 @@ const NotificationsLunaSea = () => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/lunasea/test', {
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
profileName: values.profileName,
},
});
const res = await fetch(
'/api/v1/settings/notifications/lunasea/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
profileName: values.profileName,
},
}),
}
);
if (!res.ok) throw new Error();
if (toastId) {
removeToast(toastId);

View File

@@ -5,7 +5,6 @@ import NotificationTypeSelector from '@app/components/NotificationTypeSelector';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -68,14 +67,21 @@ const NotificationsPushbullet = () => {
validationSchema={NotificationsPushbulletSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/notifications/pushbullet', {
enabled: values.enabled,
types: values.types,
options: {
accessToken: values.accessToken,
channelTag: values.channelTag,
const res = await fetch('/api/v1/settings/notifications/pushbullet', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: values.enabled,
types: values.types,
options: {
accessToken: values.accessToken,
channelTag: values.channelTag,
},
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.pushbulletSettingsSaved), {
appearance: 'success',
autoDismiss: true,
@@ -113,14 +119,24 @@ const NotificationsPushbullet = () => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/pushbullet/test', {
enabled: true,
types: values.types,
options: {
accessToken: values.accessToken,
channelTag: values.channelTag,
},
});
const res = await fetch(
'/api/v1/settings/notifications/pushbullet/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
types: values.types,
options: {
accessToken: values.accessToken,
channelTag: values.channelTag,
},
}),
}
);
if (!res.ok) throw new Error();
if (toastId) {
removeToast(toastId);

View File

@@ -5,7 +5,6 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
import type { PushoverSound } from '@server/api/pushover';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -94,14 +93,21 @@ const NotificationsPushover = () => {
validationSchema={NotificationsPushoverSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/notifications/pushover', {
enabled: values.enabled,
types: values.types,
options: {
accessToken: values.accessToken,
userToken: values.userToken,
const res = await fetch('/api/v1/settings/notifications/pushover', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: values.enabled,
types: values.types,
options: {
accessToken: values.accessToken,
userToken: values.userToken,
},
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.pushoversettingssaved), {
appearance: 'success',
autoDismiss: true,
@@ -139,16 +145,25 @@ const NotificationsPushover = () => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/pushover/test', {
enabled: true,
types: values.types,
options: {
accessToken: values.accessToken,
userToken: values.userToken,
sound: values.sound,
},
});
const res = await fetch(
'/api/v1/settings/notifications/pushover/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
types: values.types,
options: {
accessToken: values.accessToken,
userToken: values.userToken,
sound: values.sound,
},
}),
}
);
if (!res.ok) throw new Error();
if (toastId) {
removeToast(toastId);
}

View File

@@ -4,7 +4,6 @@ import NotificationTypeSelector from '@app/components/NotificationTypeSelector';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -65,13 +64,20 @@ const NotificationsSlack = () => {
validationSchema={NotificationsSlackSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/notifications/slack', {
enabled: values.enabled,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
const res = await fetch('/api/v1/settings/notifications/slack', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: values.enabled,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
},
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.slacksettingssaved), {
appearance: 'success',
autoDismiss: true,
@@ -109,13 +115,23 @@ const NotificationsSlack = () => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/slack/test', {
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
},
});
const res = await fetch(
'/api/v1/settings/notifications/slack/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
},
}),
}
);
if (!res.ok) throw new Error();
if (toastId) {
removeToast(toastId);

View File

@@ -5,7 +5,6 @@ import NotificationTypeSelector from '@app/components/NotificationTypeSelector';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -84,16 +83,23 @@ const NotificationsTelegram = () => {
validationSchema={NotificationsTelegramSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/notifications/telegram', {
enabled: values.enabled,
types: values.types,
options: {
botAPI: values.botAPI,
chatId: values.chatId,
sendSilently: values.sendSilently,
botUsername: values.botUsername,
const res = await fetch('/api/v1/settings/notifications/telegram', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: values.enabled,
types: values.types,
options: {
botAPI: values.botAPI,
chatId: values.chatId,
sendSilently: values.sendSilently,
botUsername: values.botUsername,
},
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.telegramsettingssaved), {
appearance: 'success',
@@ -132,16 +138,26 @@ const NotificationsTelegram = () => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/telegram/test', {
enabled: true,
types: values.types,
options: {
botAPI: values.botAPI,
chatId: values.chatId,
sendSilently: values.sendSilently,
botUsername: values.botUsername,
},
});
const res = await fetch(
'/api/v1/settings/notifications/telegram/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
types: values.types,
options: {
botAPI: values.botAPI,
chatId: values.chatId,
sendSilently: values.sendSilently,
botUsername: values.botUsername,
},
}),
}
);
if (!res.ok) throw new Error();
if (toastId) {
removeToast(toastId);

View File

@@ -4,7 +4,6 @@ import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -58,10 +57,17 @@ const NotificationsWebPush = () => {
}}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/notifications/webpush', {
enabled: values.enabled,
options: {},
const res = await fetch('/api/v1/settings/notifications/webpush', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: values.enabled,
options: {},
}),
});
if (!res.ok) throw new Error();
mutate('/api/v1/settings/public');
addToast(intl.formatMessage(messages.webpushsettingssaved), {
appearance: 'success',
@@ -92,10 +98,20 @@ const NotificationsWebPush = () => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/webpush/test', {
enabled: true,
options: {},
});
const res = await fetch(
'/api/v1/settings/notifications/webpush/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
options: {},
}),
}
);
if (!res.ok) throw new Error();
if (toastId) {
removeToast(toastId);

View File

@@ -8,7 +8,6 @@ import {
ArrowPathIcon,
QuestionMarkCircleIcon,
} from '@heroicons/react/24/solid';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import dynamic from 'next/dynamic';
import Link from 'next/link';
@@ -150,15 +149,22 @@ const NotificationsWebhook = () => {
validationSchema={NotificationsWebhookSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/notifications/webhook', {
enabled: values.enabled,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
jsonPayload: JSON.stringify(values.jsonPayload),
authHeader: values.authHeader,
const res = await fetch('/api/v1/settings/notifications/webhook', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: values.enabled,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
jsonPayload: JSON.stringify(values.jsonPayload),
authHeader: values.authHeader,
},
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.webhooksettingssaved), {
appearance: 'success',
autoDismiss: true,
@@ -207,16 +213,25 @@ const NotificationsWebhook = () => {
toastId = id;
}
);
await axios.post('/api/v1/settings/notifications/webhook/test', {
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
jsonPayload: JSON.stringify(values.jsonPayload),
authHeader: values.authHeader,
},
});
const res = await fetch(
'/api/v1/settings/notifications/webhook/test',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
enabled: true,
types: values.types,
options: {
webhookUrl: values.webhookUrl,
jsonPayload: JSON.stringify(values.jsonPayload),
authHeader: values.authHeader,
},
}),
}
);
if (!res.ok) throw new Error();
if (toastId) {
removeToast(toastId);
}

View File

@@ -4,7 +4,6 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { Transition } from '@headlessui/react';
import type { RadarrSettings } from '@server/lib/settings';
import axios from 'axios';
import { Field, Formik } from 'formik';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -166,19 +165,24 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => {
}) => {
setIsTesting(true);
try {
const response = await axios.post<TestResponse>(
'/api/v1/settings/radarr/test',
{
const res = await fetch('/api/v1/settings/radarr/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
hostname,
apiKey,
port: Number(port),
baseUrl,
useSsl,
}
);
}),
});
if (!res.ok) throw new Error();
const data = await res.json();
setIsValidated(true);
setTestResponse(response.data);
setTestResponse(data);
if (initialLoad.current) {
addToast(intl.formatMessage(messages.toastRadarrTestSuccess), {
appearance: 'success',
@@ -271,12 +275,23 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => {
tagRequests: values.tagRequests,
};
if (!radarr) {
await axios.post('/api/v1/settings/radarr', submission);
const res = await fetch('/api/v1/settings/radarr', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(submission),
});
if (!res.ok) throw new Error();
} else {
await axios.put(
`/api/v1/settings/radarr/${radarr.id}`,
submission
);
const res = await fetch(`/api/v1/settings/radarr/${radarr.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(submission),
});
if (!res.ok) throw new Error();
}
onSave();

View File

@@ -7,7 +7,6 @@ import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import { ApiErrorCode } from '@server/constants/error';
import type { JellyfinSettings } from '@server/lib/settings';
import axios from 'axios';
import { Field, Formik } from 'formik';
import getConfig from 'next/config';
import { useState } from 'react';
@@ -167,9 +166,13 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
}
try {
await axios.get('/api/v1/settings/jellyfin/library', {
params,
});
const url = new URL('/api/v1/settings/jellyfin/library');
url.search = new URLSearchParams({
sync: params.sync ? 'true' : 'false',
...(params.enable ? { enable: params.enable } : {}),
}).toString();
const res = await fetch(url);
if (!res.ok) throw new Error();
setIsSyncing(false);
revalidate();
} catch (e) {
@@ -206,16 +209,32 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
};
const startScan = async () => {
await axios.post('/api/v1/settings/jellyfin/sync', {
start: true,
const res = await fetch('/api/v1/settings/jellyfin/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
start: true,
}),
});
if (!res.ok) throw new Error();
revalidateSync();
};
const cancelScan = async () => {
await axios.post('/api/v1/settings/jellyfin/sync', {
cancel: true,
const res = await fetch('/api/v1/settings/jellyfin/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
cancel: true,
}),
});
if (!res.ok) throw new Error();
revalidateSync();
};
@@ -230,15 +249,17 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
.join(',');
}
await axios.get('/api/v1/settings/jellyfin/library', {
params,
});
const url = new URL('/api/v1/settings/jellyfin/library');
url.search = new URLSearchParams(params.enable ? params : {}).toString();
const res = await fetch(url);
if (!res.ok) throw new Error();
} else {
await axios.get('/api/v1/settings/jellyfin/library', {
params: {
enable: [...activeLibraries, libraryId].join(','),
},
});
const url = new URL('/api/v1/settings/jellyfin/library');
url.search = new URLSearchParams({
enable: [...activeLibraries, libraryId].join(','),
}).toString();
const res = await fetch(url);
if (!res.ok) throw new Error();
}
if (onComplete) {
onComplete();
@@ -447,14 +468,21 @@ const SettingsJellyfin: React.FC<SettingsJellyfinProps> = ({
validationSchema={JellyfinSettingsSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/jellyfin', {
ip: values.hostname,
port: Number(values.port),
useSsl: values.useSsl,
urlBase: values.urlBase,
externalHostname: values.jellyfinExternalUrl,
jellyfinForgotPasswordUrl: values.jellyfinForgotPasswordUrl,
} as JellyfinSettings);
const res = await fetch('/api/v1/settings/jellyfin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
ip: values.hostname,
port: Number(values.port),
useSsl: values.useSsl,
urlBase: values.urlBase,
externalHostname: values.jellyfinExternalUrl,
jellyfinForgotPasswordUrl: values.jellyfinForgotPasswordUrl,
} as JellyfinSettings),
});
if (!res.ok) throw new Error();
addToast(
intl.formatMessage(messages.jellyfinSettingsSuccess, {

View File

@@ -19,7 +19,6 @@ import type {
CacheResponse,
} from '@server/interfaces/api/settingsInterfaces';
import type { JobId } from '@server/lib/settings';
import axios from 'axios';
import cronstrue from 'cronstrue/i18n';
import { Fragment, useReducer, useState } from 'react';
import type { MessageDescriptor } from 'react-intl';
@@ -173,7 +172,10 @@ const SettingsJobs = () => {
}
const runJob = async (job: Job) => {
await axios.post(`/api/v1/settings/jobs/${job.id}/run`);
const res = await fetch(`/api/v1/settings/jobs/${job.id}/run`, {
method: 'POST',
});
if (!res.ok) throw new Error();
addToast(
intl.formatMessage(messages.jobstarted, {
jobname: intl.formatMessage(messages[job.id] ?? messages.unknownJob),
@@ -187,7 +189,10 @@ const SettingsJobs = () => {
};
const cancelJob = async (job: Job) => {
await axios.post(`/api/v1/settings/jobs/${job.id}/cancel`);
const res = await fetch(`/api/v1/settings/jobs/${job.id}/cancel`, {
method: 'POST',
});
if (!res.ok) throw new Error();
addToast(
intl.formatMessage(messages.jobcancelled, {
jobname: intl.formatMessage(messages[job.id] ?? messages.unknownJob),
@@ -201,7 +206,10 @@ const SettingsJobs = () => {
};
const flushCache = async (cache: CacheItem) => {
await axios.post(`/api/v1/settings/cache/${cache.id}/flush`);
const res = await fetch(`/api/v1/settings/cache/${cache.id}/flush`, {
method: 'POST',
});
if (!res.ok) throw new Error();
addToast(
intl.formatMessage(messages.cacheflushed, { cachename: cache.name }),
{
@@ -228,12 +236,19 @@ const SettingsJobs = () => {
}
setIsSaving(true);
await axios.post(
const res = await fetch(
`/api/v1/settings/jobs/${jobModalState.job.id}/schedule`,
{
schedule: jobScheduleCron.join(' '),
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
schedule: jobScheduleCron.join(' '),
}),
}
);
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.jobScheduleEditSaved), {
appearance: 'success',

View File

@@ -17,7 +17,6 @@ import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import { ArrowPathIcon } from '@heroicons/react/24/solid';
import type { UserSettingsGeneralResponse } from '@server/interfaces/api/userSettingsInterfaces';
import type { MainSettings } from '@server/lib/settings';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -87,7 +86,10 @@ const SettingsMain = () => {
const regenerate = async () => {
try {
await axios.post('/api/v1/settings/main/regenerate');
const res = await fetch('/api/v1/settings/main/regenerate', {
method: 'POST',
});
if (!res.ok) throw new Error();
revalidate();
addToast(intl.formatMessage(messages.toastApiKeySuccess), {
@@ -140,18 +142,25 @@ const SettingsMain = () => {
validationSchema={MainSettingsSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/main', {
applicationTitle: values.applicationTitle,
applicationUrl: values.applicationUrl,
csrfProtection: values.csrfProtection,
hideAvailable: values.hideAvailable,
locale: values.locale,
region: values.region,
originalLanguage: values.originalLanguage,
partialRequestsEnabled: values.partialRequestsEnabled,
trustProxy: values.trustProxy,
cacheImages: values.cacheImages,
const res = await fetch('/api/v1/settings/main', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
applicationTitle: values.applicationTitle,
applicationUrl: values.applicationUrl,
csrfProtection: values.csrfProtection,
hideAvailable: values.hideAvailable,
locale: values.locale,
region: values.region,
originalLanguage: values.originalLanguage,
partialRequestsEnabled: values.partialRequestsEnabled,
trustProxy: values.trustProxy,
cacheImages: values.cacheImages,
}),
});
if (!res.ok) throw new Error();
mutate('/api/v1/settings/public');
mutate('/api/v1/status');

View File

@@ -16,7 +16,6 @@ import {
} from '@heroicons/react/24/solid';
import type { PlexDevice } from '@server/interfaces/api/plexInterfaces';
import type { PlexSettings, TautulliSettings } from '@server/lib/settings';
import axios from 'axios';
import { Field, Formik } from 'formik';
import { orderBy } from 'lodash';
import { useMemo, useState } from 'react';
@@ -241,9 +240,14 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
params.enable = activeLibraries.join(',');
}
await axios.get('/api/v1/settings/plex/library', {
params,
});
const url = new URL('/api/v1/settings/plex/library');
url.search = new URLSearchParams({
sync: params.sync ? 'true' : 'false',
...(params.enable ? { enable: params.enable } : {}),
}).toString();
const res = await fetch(url);
if (!res.ok) throw new Error();
setIsSyncing(false);
revalidate();
};
@@ -262,11 +266,12 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
toastId = id;
}
);
const response = await axios.get<PlexDevice[]>(
'/api/v1/settings/plex/devices/servers'
);
if (response.data) {
setAvailableServers(response.data);
const res = await fetch('/api/v1/settings/plex/devices/servers');
if (!res.ok) throw new Error();
const data: PlexDevice[] = await res.json();
if (data) {
setAvailableServers(data);
}
if (toastId) {
removeToast(toastId);
@@ -289,16 +294,30 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
};
const startScan = async () => {
await axios.post('/api/v1/settings/plex/sync', {
start: true,
const res = await fetch('/api/v1/settings/plex/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
start: true,
}),
});
if (!res.ok) throw new Error();
revalidateSync();
};
const cancelScan = async () => {
await axios.post('/api/v1/settings/plex/sync', {
cancel: true,
const res = await fetch('/api/v1/settings/plex/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
cancel: true,
}),
});
if (!res.ok) throw new Error();
revalidateSync();
};
@@ -313,15 +332,17 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
.join(',');
}
await axios.get('/api/v1/settings/plex/library', {
params,
});
const url = new URL('/api/v1/settings/plex/library');
url.search = new URLSearchParams(params.enable ? params : {}).toString();
const res = await fetch(url);
if (!res.ok) throw new Error();
} else {
await axios.get('/api/v1/settings/plex/library', {
params: {
enable: [...activeLibraries, libraryId].join(','),
},
});
const url = new URL('/api/v1/settings/plex/library');
url.search = new URLSearchParams({
enable: [...activeLibraries, libraryId].join(','),
}).toString();
const res = await fetch(url);
if (!res.ok) throw new Error();
}
setIsSyncing(false);
revalidate();
@@ -385,12 +406,19 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
toastId = id;
}
);
await axios.post('/api/v1/settings/plex', {
ip: values.hostname,
port: Number(values.port),
useSsl: values.useSsl,
webAppUrl: values.webAppUrl,
} as PlexSettings);
const res = await fetch('/api/v1/settings/plex', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
ip: values.hostname,
port: Number(values.port),
useSsl: values.useSsl,
webAppUrl: values.webAppUrl,
} as PlexSettings),
});
if (!res.ok) throw new Error();
syncLibraries();
@@ -748,14 +776,27 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => {
validationSchema={TautulliSettingsSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/tautulli', {
hostname: values.tautulliHostname,
port: Number(values.tautulliPort),
useSsl: values.tautulliUseSsl,
urlBase: values.tautulliUrlBase,
apiKey: values.tautulliApiKey,
externalUrl: values.tautulliExternalUrl,
} as TautulliSettings);
const res = await fetch('/api/v1/settings/tautulli', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
hostname: values.tautulliHostname,
port: Number(values.tautulliPort),
useSsl: values.tautulliUseSsl,
urlBase: values.tautulliUrlBase,
apiKey: values.tautulliApiKey,
externalUrl: values.tautulliExternalUrl,
} as TautulliSettings),
});
if (!res.ok) throw new Error();
if (!res.ok) {
throw new Error('Failed to fetch');
}
// Continue with any necessary processing
addToast(
intl.formatMessage(messages.toastTautulliSettingsSuccess),

View File

@@ -13,7 +13,6 @@ import defineMessages from '@app/utils/defineMessages';
import { Transition } from '@headlessui/react';
import { PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/solid';
import type { RadarrSettings, SonarrSettings } from '@server/lib/settings';
import axios from 'axios';
import { Fragment, useState } from 'react';
import { useIntl } from 'react-intl';
import useSWR, { mutate } from 'swr';
@@ -196,9 +195,14 @@ const SettingsServices = () => {
});
const deleteServer = async () => {
await axios.delete(
`/api/v1/settings/${deleteServerModal.type}/${deleteServerModal.serverId}`
const res = await fetch(
`/api/v1/settings/${deleteServerModal.type}/${deleteServerModal.serverId}`,
{
method: 'DELETE',
}
);
if (!res.ok) throw new Error();
setDeleteServerModal({ open: false, serverId: null, type: 'radarr' });
revalidateRadarr();
revalidateSonarr();

View File

@@ -9,7 +9,6 @@ import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import { MediaServerType } from '@server/constants/server';
import type { MainSettings } from '@server/lib/settings';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import getConfig from 'next/config';
import { useIntl } from 'react-intl';
@@ -77,21 +76,28 @@ const SettingsUsers = () => {
enableReinitialize
onSubmit={async (values) => {
try {
await axios.post('/api/v1/settings/main', {
localLogin: values.localLogin,
newPlexLogin: values.newPlexLogin,
defaultQuotas: {
movie: {
quotaLimit: values.movieQuotaLimit,
quotaDays: values.movieQuotaDays,
},
tv: {
quotaLimit: values.tvQuotaLimit,
quotaDays: values.tvQuotaDays,
},
const res = await fetch('/api/v1/settings/main', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
defaultPermissions: values.defaultPermissions,
body: JSON.stringify({
localLogin: values.localLogin,
newPlexLogin: values.newPlexLogin,
defaultQuotas: {
movie: {
quotaLimit: values.movieQuotaLimit,
quotaDays: values.movieQuotaDays,
},
tv: {
quotaLimit: values.tvQuotaLimit,
quotaDays: values.tvQuotaDays,
},
},
defaultPermissions: values.defaultPermissions,
}),
});
if (!res.ok) throw new Error();
mutate('/api/v1/settings/public');
addToast(intl.formatMessage(messages.toastSettingsSuccess), {

View File

@@ -4,7 +4,6 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { Transition } from '@headlessui/react';
import type { SonarrSettings } from '@server/lib/settings';
import axios from 'axios';
import { Field, Formik } from 'formik';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -177,19 +176,24 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
}) => {
setIsTesting(true);
try {
const response = await axios.post<TestResponse>(
'/api/v1/settings/sonarr/test',
{
const res = await fetch('/api/v1/settings/sonarr/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
hostname,
apiKey,
port: Number(port),
baseUrl,
useSsl,
}
);
}),
});
if (!res.ok) throw new Error();
const data: TestResponse = await res.json();
setIsValidated(true);
setTestResponse(response.data);
setTestResponse(data);
if (initialLoad.current) {
addToast(intl.formatMessage(messages.toastSonarrTestSuccess), {
appearance: 'success',
@@ -306,12 +310,23 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
tagRequests: values.tagRequests,
};
if (!sonarr) {
await axios.post('/api/v1/settings/sonarr', submission);
const res = await fetch('/api/v1/settings/sonarr', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(submission),
});
if (!res.ok) throw new Error();
} else {
await axios.put(
`/api/v1/settings/sonarr/${sonarr.id}`,
submission
);
const res = await fetch(`/api/v1/settings/sonarr/${sonarr.id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(submission),
});
if (!res.ok) throw new Error();
}
onSave();

View File

@@ -1,7 +1,6 @@
import PlexLoginButton from '@app/components/PlexLoginButton';
import { useUser } from '@app/hooks/useUser';
import defineMessages from '@app/utils/defineMessages';
import axios from 'axios';
import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -25,9 +24,19 @@ const LoginWithPlex = ({ onComplete }: LoginWithPlexProps) => {
useEffect(() => {
const login = async () => {
const response = await axios.post('/api/v1/auth/plex', { authToken });
const res = await fetch('/api/v1/auth/plex', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
authToken,
}),
});
if (!res.ok) throw new Error();
const data = await res.json();
if (response.data?.id) {
if (data?.id) {
revalidate();
}
};

View File

@@ -4,7 +4,6 @@ import PlexLoginButton from '@app/components/PlexLoginButton';
import { useUser } from '@app/hooks/useUser';
import defineMessages from '@app/utils/defineMessages';
import { MediaServerType } from '@server/constants/server';
import axios from 'axios';
import getConfig from 'next/config';
import { useEffect, useState } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
@@ -34,11 +33,19 @@ const SetupLogin: React.FC<LoginWithMediaServerProps> = ({ onComplete }) => {
useEffect(() => {
const login = async () => {
const response = await axios.post('/api/v1/auth/plex', {
authToken: authToken,
const res = await fetch('/api/v1/auth/plex', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
authToken: authToken,
}),
});
if (!res.ok) throw new Error();
const data = await res.json();
if (response.data?.email) {
if (data?.email) {
revalidate();
}
};

View File

@@ -11,7 +11,6 @@ import SetupSteps from '@app/components/Setup/SetupSteps';
import useLocale from '@app/hooks/useLocale';
import defineMessages from '@app/utils/defineMessages';
import { MediaServerType } from '@server/constants/server';
import axios from 'axios';
import Image from 'next/image';
import { useRouter } from 'next/router';
import { useState } from 'react';
@@ -46,15 +45,27 @@ const Setup = () => {
const finishSetup = async () => {
setIsUpdating(true);
const response = await axios.post<{ initialized: boolean }>(
'/api/v1/settings/initialize'
);
const res = await fetch('/api/v1/settings/initialize', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
if (!res.ok) throw new Error();
const data: { initialized: boolean } = await res.json();
setIsUpdating(false);
if (response.data.initialized) {
await axios.post('/api/v1/settings/main', { locale });
mutate('/api/v1/settings/public');
if (data.initialized) {
const mainRes = await fetch('/api/v1/settings/main', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ locale }),
});
if (!mainRes.ok) throw new Error();
mutate('/api/v1/settings/public');
router.push('/');
}
};

View File

@@ -2,7 +2,6 @@ import Button from '@app/components/Common/Button';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { CheckIcon, TrashIcon } from '@heroicons/react/24/solid';
import axios from 'axios';
import { useIntl } from 'react-intl';
import { mutate } from 'swr';
@@ -21,11 +20,14 @@ const messages = defineMessages('components.TitleCard', {
cleardata: 'Clear Data',
});
const Error = ({ id, tmdbId, tvdbId, type, canExpand }: ErrorCardProps) => {
const ErrorCard = ({ id, tmdbId, tvdbId, type, canExpand }: ErrorCardProps) => {
const intl = useIntl();
const deleteMedia = async () => {
await axios.delete(`/api/v1/media/${id}`);
const res = await fetch(`/api/v1/media/${id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
mutate('/api/v1/media?filter=allavailable&take=20&sort=mediaAdded');
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
};
@@ -129,4 +131,4 @@ const Error = ({ id, tmdbId, tvdbId, type, canExpand }: ErrorCardProps) => {
</div>
);
};
export default Error;
export default ErrorCard;

View File

@@ -19,7 +19,6 @@ import {
import { MediaStatus } from '@server/constants/media';
import type { Watchlist } from '@server/entity/Watchlist';
import type { MediaType } from '@server/models/Search';
import axios from 'axios';
import Link from 'next/link';
import { Fragment, useCallback, useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -98,13 +97,21 @@ const TitleCard = ({
const onClickWatchlistBtn = async (): Promise<void> => {
setIsUpdating(true);
try {
const response = await axios.post<Watchlist>('/api/v1/watchlist', {
tmdbId: id,
mediaType,
title,
const res = await fetch('/api/v1/watchlist', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
tmdbId: id,
mediaType,
title,
}),
});
if (!res.ok) throw new Error();
const data: Watchlist = await res.json();
mutate('/api/v1/discover/watchlist');
if (response.data) {
if (data) {
addToast(
<span>
{intl.formatMessage(messages.watchlistSuccess, {
@@ -129,9 +136,11 @@ const TitleCard = ({
const onClickDeleteWatchlistBtn = async (): Promise<void> => {
setIsUpdating(true);
try {
const response = await axios.delete<Watchlist>('/api/v1/watchlist/' + id);
if (response.status === 204) {
const res = await fetch('/api/v1/watchlist/' + id, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
if (res.status === 204) {
addToast(
<span>
{intl.formatMessage(messages.watchlistDeleted, {

View File

@@ -4,7 +4,6 @@ import type { User } from '@app/hooks/useUser';
import { useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import axios from 'axios';
import { useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -45,10 +44,18 @@ const BulkEditModal = ({
const updateUsers = async () => {
try {
setIsSaving(true);
const { data: updated } = await axios.put<User[]>(`/api/v1/user`, {
ids: selectedUserIds,
permissions: currentPermission,
const res = await fetch('/api/v1/user', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
ids: selectedUserIds,
permissions: currentPermission,
}),
});
if (!res.ok) throw new Error();
const updated: User[] = await res.json();
if (onComplete) {
onComplete(updated);
}

View File

@@ -4,7 +4,6 @@ import useSettings from '@app/hooks/useSettings';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import type { UserResultsResponse } from '@server/interfaces/api/userInterfaces';
import axios from 'axios';
import getConfig from 'next/config';
import Image from 'next/image';
import { useState } from 'react';
@@ -69,10 +68,17 @@ const JellyfinImportModal: React.FC<JellyfinImportProps> = ({
setImporting(true);
try {
const { data: createdUsers } = await axios.post(
'/api/v1/user/import-from-jellyfin',
{ jellyfinUserIds: selectedUsers }
);
const res = await fetch('/api/v1/user/import-from-jellyfin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
jellyfinUserIds: selectedUsers,
}),
});
if (!res.ok) throw new Error();
const { data: createdUsers } = await res.json();
if (!createdUsers.length) {
throw new Error('No users were imported from Jellyfin.');

View File

@@ -3,7 +3,6 @@ import Modal from '@app/components/Common/Modal';
import useSettings from '@app/hooks/useSettings';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import axios from 'axios';
import Image from 'next/image';
import { useState } from 'react';
import { useIntl } from 'react-intl';
@@ -48,10 +47,17 @@ const PlexImportModal = ({ onCancel, onComplete }: PlexImportProps) => {
setImporting(true);
try {
const { data: createdUsers } = await axios.post(
'/api/v1/user/import-from-plex',
{ plexIds: selectedUsers }
);
const res = await fetch('/api/v1/user/import-from-plex', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
plexIds: selectedUsers,
}),
});
if (!res.ok) throw new Error();
const { data: createdUsers } = await res.json();
if (!createdUsers.length) {
throw new Error('No users were imported from Plex.');

View File

@@ -27,7 +27,6 @@ import {
import { MediaServerType } from '@server/constants/server';
import type { UserResultsResponse } from '@server/interfaces/api/userInterfaces';
import { hasPermission } from '@server/lib/permissions';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import getConfig from 'next/config';
import Image from 'next/image';
@@ -183,7 +182,10 @@ const UserList = () => {
setDeleting(true);
try {
await axios.delete(`/api/v1/user/${deleteModal.user?.id}`);
const res = await fetch(`/api/v1/user/${deleteModal.user?.id}`, {
method: 'DELETE',
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.userdeleted), {
autoDismiss: true,
@@ -282,11 +284,18 @@ const UserList = () => {
validationSchema={CreateUserSchema}
onSubmit={async (values) => {
try {
await axios.post('/api/v1/user', {
username: values.displayName,
email: values.email,
password: values.genpassword ? null : values.password,
const res = await fetch('/api/v1/user', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: values.displayName,
email: values.email,
password: values.genpassword ? null : values.password,
}),
});
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.usercreatedsuccess), {
appearance: 'success',
autoDismiss: true,

View File

@@ -11,11 +11,10 @@ import useLocale from '@app/hooks/useLocale';
import useSettings from '@app/hooks/useSettings';
import { Permission, UserType, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import Error from '@app/pages/_error';
import ErrorPage from '@app/pages/_error';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import type { UserSettingsGeneralResponse } from '@server/interfaces/api/userSettingsInterfaces';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import getConfig from 'next/config';
import { useRouter } from 'next/router';
@@ -116,7 +115,7 @@ const UserGeneralSettings = () => {
}
if (!data) {
return <Error statusCode={500} />;
return <ErrorPage statusCode={500} />;
}
return (
@@ -151,22 +150,31 @@ const UserGeneralSettings = () => {
enableReinitialize
onSubmit={async (values) => {
try {
await axios.post(`/api/v1/user/${user?.id}/settings/main`, {
username: values.displayName,
email: values.email,
discordId: values.discordId,
locale: values.locale,
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,
watchlistSyncMovies: values.watchlistSyncMovies,
watchlistSyncTv: values.watchlistSyncTv,
const res = await fetch(`/api/v1/user/${user?.id}/settings/main`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: values.displayName,
email: values.email,
discordId: values.discordId,
locale: values.locale,
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,
watchlistSyncMovies: values.watchlistSyncMovies,
watchlistSyncTv: values.watchlistSyncTv,
}),
});
if (!res.ok) throw new Error();
if (currentUser?.id === user?.id && setLocale) {
setLocale(

View File

@@ -6,7 +6,6 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useIntl } from 'react-intl';
@@ -68,18 +67,28 @@ const UserNotificationsDiscord = () => {
enableReinitialize
onSubmit={async (values) => {
try {
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
pgpKey: data?.pgpKey,
discordId: values.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
discord: values.types,
},
});
const res = await fetch(
`/api/v1/user/${user?.id}/settings/notifications`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pgpKey: data?.pgpKey,
discordId: values.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
discord: values.types,
},
}),
}
);
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.discordsettingssaved), {
appearance: 'success',
autoDismiss: true,

View File

@@ -11,7 +11,6 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces';
import axios from 'axios';
import { Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useIntl } from 'react-intl';
@@ -67,18 +66,28 @@ const UserEmailSettings = () => {
enableReinitialize
onSubmit={async (values) => {
try {
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
pgpKey: values.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
email: values.types,
},
});
const res = await fetch(
`/api/v1/user/${user?.id}/settings/notifications`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pgpKey: values.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
email: values.types,
},
}),
}
);
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.emailsettingssaved), {
appearance: 'success',
autoDismiss: true,

View File

@@ -6,7 +6,6 @@ import { useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces';
import axios from 'axios';
import { Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useIntl } from 'react-intl';
@@ -65,18 +64,28 @@ const UserPushbulletSettings = () => {
enableReinitialize
onSubmit={async (values) => {
try {
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
pgpKey: data?.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: values.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
pushbullet: values.types,
},
});
const res = await fetch(
`/api/v1/user/${user?.id}/settings/notifications`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pgpKey: data?.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: values.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
pushbullet: values.types,
},
}),
}
);
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.pushbulletsettingssaved), {
appearance: 'success',
autoDismiss: true,

View File

@@ -7,7 +7,6 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import type { PushoverSound } from '@server/api/pushover';
import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useIntl } from 'react-intl';
@@ -97,18 +96,28 @@ const UserPushoverSettings = () => {
enableReinitialize
onSubmit={async (values) => {
try {
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
pgpKey: data?.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: values.pushoverApplicationToken,
pushoverUserKey: values.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
pushover: values.types,
},
});
const res = await fetch(
`/api/v1/user/${user?.id}/settings/notifications`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pgpKey: data?.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: values.pushoverApplicationToken,
pushoverUserKey: values.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
pushover: values.types,
},
}),
}
);
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.pushoversettingssaved), {
appearance: 'success',
autoDismiss: true,

View File

@@ -6,7 +6,6 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces';
import axios from 'axios';
import { Field, Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useIntl } from 'react-intl';
@@ -71,18 +70,28 @@ const UserTelegramSettings = () => {
enableReinitialize
onSubmit={async (values) => {
try {
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
pgpKey: data?.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: values.telegramChatId,
telegramSendSilently: values.telegramSendSilently,
notificationTypes: {
telegram: values.types,
},
});
const res = await fetch(
`/api/v1/user/${user?.id}/settings/notifications`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pgpKey: data?.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: values.telegramChatId,
telegramSendSilently: values.telegramSendSilently,
notificationTypes: {
telegram: values.types,
},
}),
}
);
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.telegramsettingssaved), {
appearance: 'success',
autoDismiss: true,

View File

@@ -8,7 +8,6 @@ import globalMessages from '@app/i18n/globalMessages';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces';
import axios from 'axios';
import { Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useIntl } from 'react-intl';
@@ -48,18 +47,28 @@ const UserWebPushSettings = () => {
enableReinitialize
onSubmit={async (values) => {
try {
await axios.post(`/api/v1/user/${user?.id}/settings/notifications`, {
pgpKey: data?.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
webpush: values.types,
},
});
const res = await fetch(
`/api/v1/user/${user?.id}/settings/notifications`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
pgpKey: data?.pgpKey,
discordId: data?.discordId,
pushbulletAccessToken: data?.pushbulletAccessToken,
pushoverApplicationToken: data?.pushoverApplicationToken,
pushoverUserKey: data?.pushoverUserKey,
telegramChatId: data?.telegramChatId,
telegramSendSilently: data?.telegramSendSilently,
notificationTypes: {
webpush: values.types,
},
}),
}
);
if (!res.ok) throw new Error();
mutate('/api/v1/settings/public');
addToast(intl.formatMessage(messages.webpushsettingssaved), {
appearance: 'success',

View File

@@ -5,10 +5,9 @@ import PageTitle from '@app/components/Common/PageTitle';
import SensitiveInput from '@app/components/Common/SensitiveInput';
import { Permission, useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import Error from '@app/pages/_error';
import ErrorPage from '@app/pages/_error';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useIntl } from 'react-intl';
@@ -80,7 +79,7 @@ const UserPasswordChange = () => {
}
if (!data) {
return <Error statusCode={500} />;
return <ErrorPage statusCode={500} />;
}
if (
@@ -123,11 +122,21 @@ const UserPasswordChange = () => {
enableReinitialize
onSubmit={async (values, { resetForm }) => {
try {
await axios.post(`/api/v1/user/${user?.id}/settings/password`, {
currentPassword: values.currentPassword,
newPassword: values.newPassword,
confirmPassword: values.confirmPassword,
});
const res = await fetch(
`/api/v1/user/${user?.id}/settings/password`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
currentPassword: values.currentPassword,
newPassword: values.newPassword,
confirmPassword: values.confirmPassword,
}),
}
);
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.toastSettingsSuccess), {
autoDismiss: true,

View File

@@ -5,10 +5,9 @@ import PageTitle from '@app/components/Common/PageTitle';
import PermissionEdit from '@app/components/PermissionEdit';
import { useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import Error from '@app/pages/_error';
import ErrorPage from '@app/pages/_error';
import defineMessages from '@app/utils/defineMessages';
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline';
import axios from 'axios';
import { Form, Formik } from 'formik';
import { useRouter } from 'next/router';
import { useIntl } from 'react-intl';
@@ -46,7 +45,7 @@ const UserPermissions = () => {
}
if (!data) {
return <Error statusCode={500} />;
return <ErrorPage statusCode={500} />;
}
if (currentUser?.id !== 1 && currentUser?.id === user?.id) {
@@ -84,10 +83,19 @@ const UserPermissions = () => {
enableReinitialize
onSubmit={async (values) => {
try {
await axios.post(`/api/v1/user/${user?.id}/settings/permissions`, {
permissions: values.currentPermissions ?? 0,
});
const res = await fetch(
`/api/v1/user/${user?.id}/settings/permissions`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
permissions: values.currentPermissions ?? 0,
}),
}
);
if (!res.ok) throw new Error();
addToast(intl.formatMessage(messages.toastSettingsSuccess), {
autoDismiss: true,
appearance: 'success',

View File

@@ -15,7 +15,6 @@ import '@app/styles/globals.css';
import { polyfillIntl } from '@app/utils/polyfillIntl';
import { MediaServerType } from '@server/constants/server';
import type { PublicSettingsResponse } from '@server/interfaces/api/settingsInterfaces';
import axios from 'axios';
import type { AppInitialProps, AppProps } from 'next/app';
import App from 'next/app';
import Head from 'next/head';
@@ -139,7 +138,11 @@ const CoreApp: Omit<NextAppComponentType, 'origGetInitialProps'> = ({
return (
<SWRConfig
value={{
fetcher: (url) => axios.get(url).then((res) => res.data),
fetcher: async (resource, init) => {
const res = await fetch(resource, init);
if (!res.ok) throw new Error();
return await res.json();
},
fallback: {
'/api/v1/auth/me': user,
},
@@ -202,13 +205,13 @@ CoreApp.getInitialProps = async (initialProps) => {
if (ctx.res) {
// Check if app is initialized and redirect if necessary
const response = await axios.get<PublicSettingsResponse>(
const res = await fetch(
`http://localhost:${process.env.PORT || 5055}/api/v1/settings/public`
);
if (!res.ok) throw new Error();
currentSettings = await res.json();
currentSettings = response.data;
const initialized = response.data.initialized;
const initialized = currentSettings.initialized;
if (!initialized) {
if (!router.pathname.match(/(setup|login\/plex)/)) {
@@ -220,7 +223,7 @@ CoreApp.getInitialProps = async (initialProps) => {
} else {
try {
// Attempt to get the user by running a request to the local api
const response = await axios.get<User>(
const res = await fetch(
`http://localhost:${process.env.PORT || 5055}/api/v1/auth/me`,
{
headers:
@@ -229,7 +232,8 @@ CoreApp.getInitialProps = async (initialProps) => {
: undefined,
}
);
user = response.data;
if (!res.ok) throw new Error();
user = await res.json();
if (router.pathname.match(/(setup|login)/)) {
ctx.res.writeHead(307, {

View File

@@ -1,6 +1,5 @@
import CollectionDetails from '@app/components/CollectionDetails';
import type { Collection } from '@server/models/Collection';
import axios from 'axios';
import type { GetServerSideProps, NextPage } from 'next';
interface CollectionPageProps {
@@ -14,7 +13,7 @@ const CollectionPage: NextPage<CollectionPageProps> = ({ collection }) => {
export const getServerSideProps: GetServerSideProps<
CollectionPageProps
> = async (ctx) => {
const response = await axios.get<Collection>(
const res = await fetch(
`http://localhost:${process.env.PORT || 5055}/api/v1/collection/${
ctx.query.collectionId
}`,
@@ -24,10 +23,12 @@ export const getServerSideProps: GetServerSideProps<
: undefined,
}
);
if (!res.ok) throw new Error();
const collection: Collection = await res.json();
return {
props: {
collection: response.data,
collection,
},
};
};

View File

@@ -1,6 +1,5 @@
import MovieDetails from '@app/components/MovieDetails';
import type { MovieDetails as MovieDetailsType } from '@server/models/Movie';
import axios from 'axios';
import type { GetServerSideProps, NextPage } from 'next';
interface MoviePageProps {
@@ -14,7 +13,7 @@ const MoviePage: NextPage<MoviePageProps> = ({ movie }) => {
export const getServerSideProps: GetServerSideProps<MoviePageProps> = async (
ctx
) => {
const response = await axios.get<MovieDetailsType>(
const res = await fetch(
`http://localhost:${process.env.PORT || 5055}/api/v1/movie/${
ctx.query.movieId
}`,
@@ -24,10 +23,12 @@ export const getServerSideProps: GetServerSideProps<MoviePageProps> = async (
: undefined,
}
);
if (!res.ok) throw new Error();
const movie: MovieDetailsType = await res.json();
return {
props: {
movie: response.data,
movie,
},
};
};

View File

@@ -1,6 +1,5 @@
import TvDetails from '@app/components/TvDetails';
import type { TvDetails as TvDetailsType } from '@server/models/Tv';
import axios from 'axios';
import type { GetServerSideProps, NextPage } from 'next';
interface TvPageProps {
@@ -14,7 +13,7 @@ const TvPage: NextPage<TvPageProps> = ({ tv }) => {
export const getServerSideProps: GetServerSideProps<TvPageProps> = async (
ctx
) => {
const response = await axios.get<TvDetailsType>(
const res = await fetch(
`http://localhost:${process.env.PORT || 5055}/api/v1/tv/${ctx.query.tvId}`,
{
headers: ctx.req?.headers?.cookie
@@ -22,10 +21,12 @@ export const getServerSideProps: GetServerSideProps<TvPageProps> = async (
: undefined,
}
);
if (!res.ok) throw new Error();
const tv: TvDetailsType = await res.json();
return {
props: {
tv: response.data,
tv,
},
};
};

View File

@@ -1,6 +1,3 @@
import type { AxiosError, AxiosResponse } from 'axios';
import axios from 'axios';
interface JellyfinAuthenticationResult {
Id: string;
AccessToken: string;
@@ -18,30 +15,34 @@ class JellyAPI {
resolve: (result: JellyfinAuthenticationResult) => void,
reject: (e: Error) => void
) => {
axios
.post(
Hostname + '/Users/AuthenticateByName',
{
Username: Username,
Pw: Password,
},
{
headers: {
'X-Emby-Authorization':
'MediaBrowser Client="Jellyfin Web", Device="Firefox", DeviceId="TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NDsgcnY6ODUuMCkgR2Vja28vMjAxMDAxMDEgRmlyZWZveC84NS4wfDE2MTI5MjcyMDM5NzM1", Version="10.8.0"',
},
fetch(Hostname + '/Users/AuthenticateByName', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Emby-Authorization':
'MediaBrowser Client="Jellyfin Web", Device="Firefox", DeviceId="TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NDsgcnY6ODUuMCkgR2Vja28vMjAxMDAxMDEgRmlyZWZveC84NS4wfDE2MTI5MjcyMDM5NzM1", Version="10.8.0"',
},
body: JSON.stringify({
Username: Username,
Pw: Password,
}),
})
.then((res) => {
if (!res.ok) {
throw new Error('Network response was not ok');
}
)
.then((resp: AxiosResponse) => {
return res.json();
})
.then((data) => {
const response: JellyfinAuthenticationResult = {
Id: resp.data.User.Id,
AccessToken: resp.data.AccessToken,
ServerId: resp.data.ServerId,
Id: data.User.Id,
AccessToken: data.AccessToken,
ServerId: data.ServerId,
};
resolve(response);
})
.catch((e: AxiosError) => {
reject(e);
.catch((error) => {
reject(error);
});
}
);

View File

@@ -1,4 +1,3 @@
import axios from 'axios';
import Bowser from 'bowser';
interface PlexHeaders extends Record<string, string> {
@@ -78,13 +77,14 @@ class PlexOAuth {
'You must initialize the plex headers clientside to login'
);
}
const response = await axios.post(
'https://plex.tv/api/v2/pins?strong=true',
undefined,
{ headers: this.plexHeaders }
);
const res = await fetch('https://plex.tv/api/v2/pins?strong=true', {
method: 'POST',
headers: this.plexHeaders,
});
if (!res.ok) throw new Error();
const data = await res.json();
this.pin = { id: response.data.id, code: response.data.code };
this.pin = { id: data.id, code: data.code };
return this.pin;
}
@@ -136,16 +136,17 @@ class PlexOAuth {
throw new Error('Unable to poll when pin is not initialized.');
}
const response = await axios.get(
`https://plex.tv/api/v2/pins/${this.pin.id}`,
{ headers: this.plexHeaders }
);
const res = await fetch(`https://plex.tv/api/v2/pins/${this.pin.id}`, {
headers: this.plexHeaders,
});
if (!res.ok) throw new Error();
const data = await res.json();
if (response.data?.authToken) {
this.authToken = response.data.authToken as string;
if (data?.authToken) {
this.authToken = data.authToken as string;
this.closePopup();
resolve(this.authToken);
} else if (!response.data?.authToken && !this.popup?.closed) {
} else if (!data?.authToken && !this.popup?.closed) {
setTimeout(executePoll, 1000, resolve, reject);
} else {
reject(new Error('Popup closed without completing login'));