diff --git a/src/components/Blacklist/index.tsx b/src/components/Blacklist/index.tsx index 217f4cefd..06a791f9f 100644 --- a/src/components/Blacklist/index.tsx +++ b/src/components/Blacklist/index.tsx @@ -10,6 +10,7 @@ import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; import Error from '@app/pages/_error'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ChevronLeftIcon, @@ -238,7 +239,7 @@ const BlacklistedItem = ({ item, revalidateList }: BlacklistedItemProps) => { const removeFromBlacklist = async (tmdbId: number, title?: string) => { setIsUpdating(true); - const res = await fetch('/api/v1/blacklist/' + tmdbId, { + const res = await apiFetch('/api/v1/blacklist/' + tmdbId, { method: 'DELETE', }); diff --git a/src/components/BlacklistBlock/index.tsx b/src/components/BlacklistBlock/index.tsx index 0908d3735..c60a8cba2 100644 --- a/src/components/BlacklistBlock/index.tsx +++ b/src/components/BlacklistBlock/index.tsx @@ -3,6 +3,7 @@ import Button from '@app/components/Common/Button'; import Tooltip from '@app/components/Common/Tooltip'; import { useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { CalendarIcon, TrashIcon, UserIcon } from '@heroicons/react/24/solid'; import type { Blacklist } from '@server/entity/Blacklist'; @@ -35,7 +36,7 @@ const BlacklistBlock = ({ const removeFromBlacklist = async (tmdbId: number, title?: string) => { setIsUpdating(true); - const res = await fetch('/api/v1/blacklist/' + tmdbId, { + const res = await apiFetch('/api/v1/blacklist/' + tmdbId, { method: 'DELETE', }); diff --git a/src/components/Discover/CreateSlider/index.tsx b/src/components/Discover/CreateSlider/index.tsx index 1d558faa6..e990025a7 100644 --- a/src/components/Discover/CreateSlider/index.tsx +++ b/src/components/Discover/CreateSlider/index.tsx @@ -4,6 +4,7 @@ import { sliderTitles } from '@app/components/Discover/constants'; import MediaSlider from '@app/components/MediaSlider'; import { WatchProviderSelector } from '@app/components/Selector'; import { encodeURIExtraParams } from '@app/hooks/useDiscover'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import type { TmdbCompanySearchResponse, @@ -76,7 +77,7 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => { const keywords = await Promise.all( slider.data.split(',').map(async (keywordId) => { - const res = await fetch(`/api/v1/keyword/${keywordId}`); + const res = await apiFetch(`/api/v1/keyword/${keywordId}`); const keyword: Keyword = await res.json(); return keyword; }) @@ -95,7 +96,7 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => { return; } - const res = await fetch( + const res = await apiFetch( `/api/v1/genres/${ slider.type === DiscoverSliderType.TMDB_MOVIE_GENRE ? 'movie' : 'tv' }` @@ -116,7 +117,7 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => { return; } - const res = await fetch(`/api/v1/studio/${slider.data}`); + const res = await apiFetch(`/api/v1/studio/${slider.data}`); const studio: ProductionCompany = await res.json(); setDefaultDataValue([ @@ -160,7 +161,7 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => { ); const loadKeywordOptions = async (inputValue: string) => { - const res = await fetch( + const res = await apiFetch( `/api/v1/search/keyword?query=${encodeURIExtraParams(inputValue)}`, { headers: { @@ -181,7 +182,7 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => { return []; } - const res = await fetch( + const res = await apiFetch( `/api/v1/search/company?query=${encodeURIExtraParams(inputValue)}`, { headers: { @@ -198,7 +199,7 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => { }; const loadMovieGenreOptions = async () => { - const res = await fetch('/api/v1/discover/genreslider/movie'); + const res = await apiFetch('/api/v1/discover/genreslider/movie'); const results: GenreSliderItem[] = await res.json(); return results.map((result) => ({ @@ -208,7 +209,7 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => { }; const loadTvGenreOptions = async () => { - const res = await fetch('/api/v1/discover/genreslider/tv'); + const res = await apiFetch('/api/v1/discover/genreslider/tv'); const results: GenreSliderItem[] = await res.json(); return results.map((result) => ({ @@ -306,20 +307,23 @@ const CreateSlider = ({ onCreate, slider }: CreateSliderProps) => { onSubmit={async (values, { resetForm }) => { try { if (slider) { - 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, - }), - }); + const res = await apiFetch( + `/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 { - const res = await fetch('/api/v1/settings/discover/add', { + const res = await apiFetch('/api/v1/settings/discover/add', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/Discover/DiscoverSliderEdit/index.tsx b/src/components/Discover/DiscoverSliderEdit/index.tsx index cb58b9c5e..718642b05 100644 --- a/src/components/Discover/DiscoverSliderEdit/index.tsx +++ b/src/components/Discover/DiscoverSliderEdit/index.tsx @@ -8,6 +8,7 @@ import CreateSlider from '@app/components/Discover/CreateSlider'; import GenreTag from '@app/components/GenreTag'; import KeywordTag from '@app/components/KeywordTag'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'; import { @@ -77,7 +78,7 @@ const DiscoverSliderEdit = ({ const deleteSlider = async () => { try { - const res = await fetch(`/api/v1/settings/discover/${slider.id}`, { + const res = await apiFetch(`/api/v1/settings/discover/${slider.id}`, { method: 'DELETE', }); if (!res.ok) throw new Error(); diff --git a/src/components/Discover/index.tsx b/src/components/Discover/index.tsx index 370384f16..85d226a25 100644 --- a/src/components/Discover/index.tsx +++ b/src/components/Discover/index.tsx @@ -17,6 +17,7 @@ import MediaSlider from '@app/components/MediaSlider'; import { encodeURIExtraParams } from '@app/hooks/useDiscover'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { Transition } from '@headlessui/react'; import { @@ -75,7 +76,7 @@ const Discover = () => { const updateSliders = async () => { try { - const res = await fetch('/api/v1/settings/discover', { + const res = await apiFetch('/api/v1/settings/discover', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -100,7 +101,7 @@ const Discover = () => { const resetSliders = async () => { try { - const res = await fetch('/api/v1/settings/discover/reset', { + const res = await apiFetch('/api/v1/settings/discover/reset', { method: 'GET', }); if (!res.ok) throw new Error(); diff --git a/src/components/IssueDetails/IssueComment/index.tsx b/src/components/IssueDetails/IssueComment/index.tsx index ab3f59ad8..7c8147d24 100644 --- a/src/components/IssueDetails/IssueComment/index.tsx +++ b/src/components/IssueDetails/IssueComment/index.tsx @@ -2,6 +2,7 @@ import Button from '@app/components/Common/Button'; import CachedImage from '@app/components/Common/CachedImage'; import Modal from '@app/components/Common/Modal'; import { Permission, useUser } from '@app/hooks/useUser'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { Menu, Transition } from '@headlessui/react'; import { EllipsisVerticalIcon } from '@heroicons/react/24/solid'; @@ -48,7 +49,7 @@ const IssueComment = ({ const deleteComment = async () => { try { - const res = await fetch(`/api/v1/issueComment/${comment.id}`, { + const res = await apiFetch(`/api/v1/issueComment/${comment.id}`, { method: 'DELETE', }); if (!res.ok) throw new Error(); @@ -177,7 +178,7 @@ const IssueComment = ({ { - const res = await fetch( + const res = await apiFetch( `/api/v1/issueComment/${comment.id}`, { method: 'PUT', diff --git a/src/components/IssueDetails/index.tsx b/src/components/IssueDetails/index.tsx index a790fe9d1..7bb40373d 100644 --- a/src/components/IssueDetails/index.tsx +++ b/src/components/IssueDetails/index.tsx @@ -12,6 +12,7 @@ import useSettings from '@app/hooks/useSettings'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; import ErrorPage from '@app/pages/_error'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { Transition } from '@headlessui/react'; import { @@ -121,7 +122,7 @@ const IssueDetails = () => { const editFirstComment = async (newMessage: string) => { try { - const res = await fetch(`/api/v1/issueComment/${firstComment.id}`, { + const res = await apiFetch(`/api/v1/issueComment/${firstComment.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', @@ -145,7 +146,7 @@ const IssueDetails = () => { const updateIssueStatus = async (newStatus: 'open' | 'resolved') => { try { - const res = await fetch(`/api/v1/issue/${issueData.id}/${newStatus}`, { + const res = await apiFetch(`/api/v1/issue/${issueData.id}/${newStatus}`, { method: 'POST', }); if (!res.ok) throw new Error(); @@ -165,7 +166,7 @@ const IssueDetails = () => { const deleteIssue = async () => { try { - const res = await fetch(`/api/v1/issue/${issueData.id}`, { + const res = await apiFetch(`/api/v1/issue/${issueData.id}`, { method: 'DELETE', }); if (!res.ok) throw new Error(); @@ -499,7 +500,7 @@ const IssueDetails = () => { }} validationSchema={CommentSchema} onSubmit={async (values, { resetForm }) => { - const res = await fetch( + const res = await apiFetch( `/api/v1/issue/${issueData?.id}/comment`, { method: 'POST', diff --git a/src/components/IssueModal/CreateIssueModal/index.tsx b/src/components/IssueModal/CreateIssueModal/index.tsx index 8d8803855..48704e121 100644 --- a/src/components/IssueModal/CreateIssueModal/index.tsx +++ b/src/components/IssueModal/CreateIssueModal/index.tsx @@ -4,6 +4,7 @@ import { issueOptions } from '@app/components/IssueModal/constants'; import useSettings from '@app/hooks/useSettings'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { RadioGroup } from '@headlessui/react'; import { ArrowRightCircleIcon } from '@heroicons/react/24/solid'; @@ -100,7 +101,7 @@ const CreateIssueModal = ({ validationSchema={CreateIssueModalSchema} onSubmit={async (values) => { try { - const res = await fetch('/api/v1/issue', { + const res = await apiFetch('/api/v1/issue', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/Layout/UserDropdown/index.tsx b/src/components/Layout/UserDropdown/index.tsx index 58f97b445..6d58d05d1 100644 --- a/src/components/Layout/UserDropdown/index.tsx +++ b/src/components/Layout/UserDropdown/index.tsx @@ -1,6 +1,7 @@ import CachedImage from '@app/components/Common/CachedImage'; import MiniQuotaDisplay from '@app/components/Layout/UserDropdown/MiniQuotaDisplay'; import { useUser } from '@app/hooks/useUser'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { Menu, Transition } from '@headlessui/react'; import { @@ -38,7 +39,7 @@ const UserDropdown = () => { const { user, revalidate } = useUser(); const logout = async () => { - const res = await fetch('/api/v1/auth/logout', { + const res = await apiFetch('/api/v1/auth/logout', { method: 'POST', }); if (!res.ok) throw new Error(); diff --git a/src/components/Login/AddEmailModal.tsx b/src/components/Login/AddEmailModal.tsx index f5dafa7c7..9162a0f73 100644 --- a/src/components/Login/AddEmailModal.tsx +++ b/src/components/Login/AddEmailModal.tsx @@ -1,5 +1,6 @@ import Modal from '@app/components/Common/Modal'; import useSettings from '@app/hooks/useSettings'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { Transition } from '@headlessui/react'; import { Field, Formik } from 'formik'; @@ -57,7 +58,7 @@ const AddEmailModal: React.FC = ({ validationSchema={EmailSettingsSchema} onSubmit={async (values) => { try { - const res = await fetch('/api/v1/auth/jellyfin', { + const res = await apiFetch('/api/v1/auth/jellyfin', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/Login/JellyfinLogin.tsx b/src/components/Login/JellyfinLogin.tsx index ba59d11b1..6569e9e37 100644 --- a/src/components/Login/JellyfinLogin.tsx +++ b/src/components/Login/JellyfinLogin.tsx @@ -1,6 +1,7 @@ import Button from '@app/components/Common/Button'; import Tooltip from '@app/components/Common/Tooltip'; import useSettings from '@app/hooks/useSettings'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { InformationCircleIcon } from '@heroicons/react/24/solid'; import { ApiErrorCode } from '@server/constants/error'; @@ -114,8 +115,9 @@ const JellyfinLogin: React.FC = ({ // throw new Error('Invalid serverType'); // You can customize the error message // } - const res = await fetch('/api/v1/auth/jellyfin', { + const res = await apiFetch('/api/v1/auth/jellyfin', { method: 'POST', + credentials: 'same-origin', headers: { 'Content-Type': 'application/json', }, @@ -370,7 +372,7 @@ const JellyfinLogin: React.FC = ({ validationSchema={LoginSchema} onSubmit={async (values) => { try { - const res = await fetch('/api/v1/auth/jellyfin', { + const res = await apiFetch('/api/v1/auth/jellyfin', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/Login/LocalLogin.tsx b/src/components/Login/LocalLogin.tsx index 2f2e00ed5..fe2d6e004 100644 --- a/src/components/Login/LocalLogin.tsx +++ b/src/components/Login/LocalLogin.tsx @@ -1,6 +1,7 @@ import Button from '@app/components/Common/Button'; import SensitiveInput from '@app/components/Common/SensitiveInput'; import useSettings from '@app/hooks/useSettings'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowLeftOnRectangleIcon, @@ -55,7 +56,7 @@ const LocalLogin = ({ revalidate }: LocalLoginProps) => { validationSchema={LoginSchema} onSubmit={async (values) => { try { - const res = await fetch('/api/v1/auth/local', { + const res = await apiFetch('/api/v1/auth/local', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/Login/index.tsx b/src/components/Login/index.tsx index 7b95b9fcd..1395995fd 100644 --- a/src/components/Login/index.tsx +++ b/src/components/Login/index.tsx @@ -6,6 +6,7 @@ import LocalLogin from '@app/components/Login/LocalLogin'; import PlexLoginButton from '@app/components/PlexLoginButton'; import useSettings from '@app/hooks/useSettings'; import { useUser } from '@app/hooks/useUser'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { Transition } from '@headlessui/react'; import { XCircleIcon } from '@heroicons/react/24/solid'; @@ -41,7 +42,7 @@ const Login = () => { const login = async () => { setProcessing(true); try { - const res = await fetch('/api/v1/auth/plex', { + const res = await apiFetch('/api/v1/auth/plex', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/ManageSlideOver/index.tsx b/src/components/ManageSlideOver/index.tsx index 805113c4c..04d330c09 100644 --- a/src/components/ManageSlideOver/index.tsx +++ b/src/components/ManageSlideOver/index.tsx @@ -10,6 +10,7 @@ import RequestBlock from '@app/components/RequestBlock'; import useSettings from '@app/hooks/useSettings'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { Bars4Icon, ServerIcon } from '@heroicons/react/24/outline'; import { @@ -111,7 +112,7 @@ const ManageSlideOver = ({ const deleteMedia = async () => { if (data.mediaInfo) { - const res = await fetch(`/api/v1/media/${data.mediaInfo.id}`, { + const res = await apiFetch(`/api/v1/media/${data.mediaInfo.id}`, { method: 'DELETE', }); if (!res.ok) throw new Error(); @@ -122,12 +123,12 @@ const ManageSlideOver = ({ const deleteMediaFile = async () => { if (data.mediaInfo) { - const res1 = await fetch(`/api/v1/media/${data.mediaInfo.id}/file`, { + const res1 = await apiFetch(`/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}`, { + const res2 = await apiFetch(`/api/v1/media/${data.mediaInfo.id}`, { method: 'DELETE', }); if (!res2.ok) throw new Error(); @@ -160,15 +161,18 @@ const ManageSlideOver = ({ const markAvailable = async (is4k = false) => { if (data.mediaInfo) { - const res = await fetch(`/api/v1/media/${data.mediaInfo?.id}/available`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - is4k, - }), - }); + const res = await apiFetch( + `/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(); } diff --git a/src/components/MovieDetails/index.tsx b/src/components/MovieDetails/index.tsx index c6583e3df..48f730f42 100644 --- a/src/components/MovieDetails/index.tsx +++ b/src/components/MovieDetails/index.tsx @@ -28,6 +28,7 @@ import useSettings from '@app/hooks/useSettings'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; import ErrorPage from '@app/pages/_error'; +import apiFetch from '@app/utils/apiFetch'; import { sortCrewPriority } from '@app/utils/creditHelpers'; import defineMessages from '@app/utils/defineMessages'; import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper'; @@ -313,7 +314,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => { const onClickWatchlistBtn = async (): Promise => { setIsUpdating(true); - const res = await fetch('/api/v1/watchlist', { + const res = await apiFetch('/api/v1/watchlist', { method: 'POST', headers: { Accept: 'application/json', @@ -357,7 +358,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => { const onClickDeleteWatchlistBtn = async (): Promise => { setIsUpdating(true); try { - const res = await fetch(`/api/v1/watchlist/${movie?.id}`, { + const res = await apiFetch(`/api/v1/watchlist/${movie?.id}`, { method: 'DELETE', }); if (!res.ok) throw new Error(); @@ -387,7 +388,7 @@ const MovieDetails = ({ movie }: MovieDetailsProps) => { const onClickHideItemBtn = async (): Promise => { setIsBlacklistUpdating(true); - const res = await fetch('/api/v1/blacklist', { + const res = await apiFetch('/api/v1/blacklist', { method: 'POST', headers: { Accept: 'application/json', diff --git a/src/components/RequestBlock/index.tsx b/src/components/RequestBlock/index.tsx index 3c317465a..54279a900 100644 --- a/src/components/RequestBlock/index.tsx +++ b/src/components/RequestBlock/index.tsx @@ -5,6 +5,7 @@ import RequestModal from '@app/components/RequestModal'; import useRequestOverride from '@app/hooks/useRequestOverride'; import { useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { CalendarIcon, @@ -52,7 +53,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => { const updateRequest = async (type: 'approve' | 'decline'): Promise => { setIsUpdating(true); - const res = await fetch(`/api/v1/request/${request.id}/${type}`, { + const res = await apiFetch(`/api/v1/request/${request.id}/${type}`, { method: 'POST', }); if (!res.ok) throw new Error(); @@ -65,7 +66,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => { const deleteRequest = async () => { setIsUpdating(true); - const res = await fetch(`/api/v1/request/${request.id}`, { + const res = await apiFetch(`/api/v1/request/${request.id}`, { method: 'DELETE', }); if (!res.ok) throw new Error(); diff --git a/src/components/RequestButton/index.tsx b/src/components/RequestButton/index.tsx index cbe04fe3d..52cfd018a 100644 --- a/src/components/RequestButton/index.tsx +++ b/src/components/RequestButton/index.tsx @@ -3,6 +3,7 @@ import RequestModal from '@app/components/RequestModal'; import useSettings from '@app/hooks/useSettings'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownTrayIcon } from '@heroicons/react/24/outline'; import { @@ -93,7 +94,7 @@ const RequestButton = ({ request: MediaRequest, type: 'approve' | 'decline' ) => { - const res = await fetch(`/api/v1/request/${request.id}/${type}`, { + const res = await apiFetch(`/api/v1/request/${request.id}/${type}`, { method: 'POST', }); if (!res.ok) throw new Error(); @@ -114,7 +115,7 @@ const RequestButton = ({ await Promise.all( requests.map(async (request) => { - const res = await fetch(`/api/v1/request/${request.id}/${type}`, { + const res = await apiFetch(`/api/v1/request/${request.id}/${type}`, { method: 'POST', }); if (!res.ok) throw new Error(); diff --git a/src/components/RequestCard/index.tsx b/src/components/RequestCard/index.tsx index af49f75d8..969e767cb 100644 --- a/src/components/RequestCard/index.tsx +++ b/src/components/RequestCard/index.tsx @@ -7,6 +7,7 @@ import StatusBadge from '@app/components/StatusBadge'; import useDeepLinks from '@app/hooks/useDeepLinks'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper'; import { withProperties } from '@app/utils/typeHelpers'; @@ -73,7 +74,7 @@ const RequestCardError = ({ requestData }: RequestCardErrorProps) => { }); const deleteRequest = async () => { - const res = await fetch(`/api/v1/media/${requestData?.media.id}`, { + const res = await apiFetch(`/api/v1/media/${requestData?.media.id}`, { method: 'DELETE', }); if (!res.ok) throw new Error(); @@ -260,7 +261,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => { }); const modifyRequest = async (type: 'approve' | 'decline') => { - const res = await fetch(`/api/v1/request/${request.id}/${type}`, { + const res = await apiFetch(`/api/v1/request/${request.id}/${type}`, { method: 'POST', }); if (!res.ok) throw new Error(); @@ -272,7 +273,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => { }; const deleteRequest = async () => { - const res = await fetch(`/api/v1/request/${request.id}`, { + const res = await apiFetch(`/api/v1/request/${request.id}`, { method: 'DELETE', }); if (!res.ok) throw new Error(); @@ -283,7 +284,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => { setRetrying(true); try { - const res = await fetch(`/api/v1/request/${request.id}/retry`, { + const res = await apiFetch(`/api/v1/request/${request.id}/retry`, { method: 'POST', }); if (!res.ok) throw new Error(); diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index e989f2f6f..4d5aff7b0 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -7,6 +7,7 @@ import StatusBadge from '@app/components/StatusBadge'; import useDeepLinks from '@app/hooks/useDeepLinks'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper'; import { @@ -63,7 +64,7 @@ const RequestItemError = ({ const { hasPermission } = useUser(); const deleteRequest = async () => { - const res = await fetch(`/api/v1/media/${requestData?.media.id}`, { + const res = await apiFetch(`/api/v1/media/${requestData?.media.id}`, { method: 'DELETE', }); if (!res.ok) throw new Error(); @@ -322,7 +323,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { const [isRetrying, setRetrying] = useState(false); const modifyRequest = async (type: 'approve' | 'decline') => { - const res = await fetch(`/api/v1/request/${request.id}/${type}`, { + const res = await apiFetch(`/api/v1/request/${request.id}/${type}`, { method: 'POST', }); if (!res.ok) throw new Error(); @@ -334,7 +335,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { }; const deleteRequest = async () => { - const res = await fetch(`/api/v1/request/${request.id}`, { + const res = await apiFetch(`/api/v1/request/${request.id}`, { method: 'DELETE', }); if (!res.ok) throw new Error(); @@ -344,10 +345,10 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { const deleteMediaFile = async () => { if (request.media) { - await fetch(`/api/v1/media/${request.media.id}/file`, { + await apiFetch(`/api/v1/media/${request.media.id}/file`, { method: 'DELETE', }); - await fetch(`/api/v1/media/${request.media.id}`, { + await apiFetch(`/api/v1/media/${request.media.id}`, { method: 'DELETE', }); revalidateList(); @@ -358,7 +359,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { setRetrying(true); try { - const res = await fetch(`/api/v1/request/${request.id}/retry`, { + const res = await apiFetch(`/api/v1/request/${request.id}/retry`, { method: 'POST', }); if (!res.ok) throw new Error(); diff --git a/src/components/RequestModal/CollectionRequestModal.tsx b/src/components/RequestModal/CollectionRequestModal.tsx index b646f7b1d..ff4e4233d 100644 --- a/src/components/RequestModal/CollectionRequestModal.tsx +++ b/src/components/RequestModal/CollectionRequestModal.tsx @@ -7,6 +7,7 @@ import AdvancedRequester from '@app/components/RequestModal/AdvancedRequester'; import QuotaDisplay from '@app/components/RequestModal/QuotaDisplay'; import { useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { MediaRequestStatus, MediaStatus } from '@server/constants/media'; import type { MediaRequest } from '@server/entity/MediaRequest'; @@ -198,7 +199,7 @@ const CollectionRequestModal = ({ ( data?.parts.filter((part) => selectedParts.includes(part.id)) ?? [] ).map(async (part) => { - const res = await fetch('/api/v1/request', { + const res = await apiFetch('/api/v1/request', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/RequestModal/MovieRequestModal.tsx b/src/components/RequestModal/MovieRequestModal.tsx index 85af7aef4..a3e38730e 100644 --- a/src/components/RequestModal/MovieRequestModal.tsx +++ b/src/components/RequestModal/MovieRequestModal.tsx @@ -5,6 +5,7 @@ import AdvancedRequester from '@app/components/RequestModal/AdvancedRequester'; import QuotaDisplay from '@app/components/RequestModal/QuotaDisplay'; import { useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { MediaStatus } from '@server/constants/media'; import type { MediaRequest } from '@server/entity/MediaRequest'; @@ -89,7 +90,7 @@ const MovieRequestModal = ({ tags: requestOverrides.tags, }; } - const res = await fetch('/api/v1/request', { + const res = await apiFetch('/api/v1/request', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -144,7 +145,7 @@ const MovieRequestModal = ({ setIsUpdating(true); try { - const res = await fetch(`/api/v1/request/${editRequest?.id}`, { + const res = await apiFetch(`/api/v1/request/${editRequest?.id}`, { method: 'DELETE', }); if (!res.ok) throw new Error(); @@ -174,7 +175,7 @@ const MovieRequestModal = ({ setIsUpdating(true); try { - const res = await fetch(`/api/v1/request/${editRequest?.id}`, { + const res = await apiFetch(`/api/v1/request/${editRequest?.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', @@ -191,9 +192,12 @@ const MovieRequestModal = ({ if (!res.ok) throw new Error(); if (alsoApproveRequest) { - const res = await fetch(`/api/v1/request/${editRequest?.id}/approve`, { - method: 'POST', - }); + const res = await apiFetch( + `/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'); diff --git a/src/components/RequestModal/TvRequestModal.tsx b/src/components/RequestModal/TvRequestModal.tsx index 71750678c..eacb744b3 100644 --- a/src/components/RequestModal/TvRequestModal.tsx +++ b/src/components/RequestModal/TvRequestModal.tsx @@ -8,6 +8,7 @@ import SearchByNameModal from '@app/components/RequestModal/SearchByNameModal'; import useSettings from '@app/hooks/useSettings'; import { useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants'; import { MediaRequestStatus, MediaStatus } from '@server/constants/media'; @@ -111,7 +112,7 @@ const TvRequestModal = ({ try { if (selectedSeasons.length > 0) { - const res = await fetch(`/api/v1/request/${editRequest.id}`, { + const res = await apiFetch(`/api/v1/request/${editRequest.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', @@ -130,13 +131,16 @@ const TvRequestModal = ({ if (!res.ok) throw new Error(); if (alsoApproveRequest) { - const res = await fetch(`/api/v1/request/${editRequest.id}/approve`, { - method: 'POST', - }); + const res = await apiFetch( + `/api/v1/request/${editRequest.id}/approve`, + { + method: 'POST', + } + ); if (!res.ok) throw new Error(); } } else { - const res = await fetch(`/api/v1/request/${editRequest.id}`, { + const res = await apiFetch(`/api/v1/request/${editRequest.id}`, { method: 'DELETE', }); if (!res.ok) throw new Error(); @@ -204,7 +208,7 @@ const TvRequestModal = ({ tags: requestOverrides.tags, }; } - const res = await fetch('/api/v1/request', { + const res = await apiFetch('/api/v1/request', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/ResetPassword/RequestResetLink.tsx b/src/components/ResetPassword/RequestResetLink.tsx index af74a6f80..9c50fbdca 100644 --- a/src/components/ResetPassword/RequestResetLink.tsx +++ b/src/components/ResetPassword/RequestResetLink.tsx @@ -2,6 +2,7 @@ import Button from '@app/components/Common/Button'; import ImageFader from '@app/components/Common/ImageFader'; import PageTitle from '@app/components/Common/PageTitle'; import LanguagePicker from '@app/components/Layout/LanguagePicker'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowLeftIcon, EnvelopeIcon } from '@heroicons/react/24/solid'; import { Field, Form, Formik } from 'formik'; @@ -84,7 +85,7 @@ const ResetPassword = () => { }} validationSchema={ResetSchema} onSubmit={async (values) => { - const res = await fetch(`/api/v1/auth/reset-password`, { + const res = await apiFetch(`/api/v1/auth/reset-password`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/ResetPassword/index.tsx b/src/components/ResetPassword/index.tsx index 45c717cf7..fb0caf918 100644 --- a/src/components/ResetPassword/index.tsx +++ b/src/components/ResetPassword/index.tsx @@ -3,6 +3,7 @@ import ImageFader from '@app/components/Common/ImageFader'; import SensitiveInput from '@app/components/Common/SensitiveInput'; import LanguagePicker from '@app/components/Layout/LanguagePicker'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { LifebuoyIcon } from '@heroicons/react/24/outline'; import { Form, Formik } from 'formik'; @@ -99,7 +100,7 @@ const ResetPassword = () => { }} validationSchema={ResetSchema} onSubmit={async (values) => { - const res = await fetch( + const res = await apiFetch( `/api/v1/auth/reset-password/${guid}`, { method: 'POST', diff --git a/src/components/Selector/index.tsx b/src/components/Selector/index.tsx index a371b7f98..67645d5ec 100644 --- a/src/components/Selector/index.tsx +++ b/src/components/Selector/index.tsx @@ -4,6 +4,7 @@ import Tooltip from '@app/components/Common/Tooltip'; import RegionSelector from '@app/components/RegionSelector'; import { encodeURIExtraParams } from '@app/hooks/useDiscover'; import useSettings from '@app/hooks/useSettings'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownIcon, ArrowUpIcon } from '@heroicons/react/20/solid'; import { CheckCircleIcon } from '@heroicons/react/24/solid'; @@ -75,7 +76,7 @@ export const CompanySelector = ({ return; } - const res = await fetch(`/api/v1/studio/${defaultValue}`); + const res = await apiFetch(`/api/v1/studio/${defaultValue}`); if (!res.ok) throw new Error(); const studio: ProductionCompany = await res.json(); @@ -95,7 +96,7 @@ export const CompanySelector = ({ return []; } - const res = await fetch( + const res = await apiFetch( `/api/v1/search/company?query=${encodeURIExtraParams(inputValue)}` ); if (!res.ok) { @@ -157,7 +158,7 @@ export const GenreSelector = ({ const genres = defaultValue.split(','); - const res = await fetch(`/api/v1/genres/${type}`); + const res = await apiFetch(`/api/v1/genres/${type}`); if (!res.ok) { throw new Error('Network response was not ok'); } @@ -178,7 +179,7 @@ export const GenreSelector = ({ }, [defaultValue, type]); const loadGenreOptions = async (inputValue: string) => { - const res = await fetch(`/api/v1/discover/genreslider/${type}`); + const res = await apiFetch(`/api/v1/discover/genreslider/${type}`); if (!res.ok) throw new Error(); const results: GenreSliderItem[] = await res.json(); @@ -298,7 +299,7 @@ export const KeywordSelector = ({ const keywords = await Promise.all( defaultValue.split(',').map(async (keywordId) => { - const res = await fetch(`/api/v1/keyword/${keywordId}`); + const res = await apiFetch(`/api/v1/keyword/${keywordId}`); if (!res.ok) { throw new Error('Network response was not ok'); } @@ -320,7 +321,7 @@ export const KeywordSelector = ({ }, [defaultValue]); const loadKeywordOptions = async (inputValue: string) => { - const res = await fetch( + const res = await apiFetch( `/api/v1/search/keyword?query=${encodeURIExtraParams(inputValue)}` ); if (!res.ok) { diff --git a/src/components/ServiceWorkerSetup/index.tsx b/src/components/ServiceWorkerSetup/index.tsx index f9b42cd39..10c4ec673 100644 --- a/src/components/ServiceWorkerSetup/index.tsx +++ b/src/components/ServiceWorkerSetup/index.tsx @@ -1,6 +1,7 @@ /* eslint-disable no-console */ import useSettings from '@app/hooks/useSettings'; import { useUser } from '@app/hooks/useUser'; +import apiFetch from '@app/utils/apiFetch'; import { useEffect } from 'react'; const ServiceWorkerSetup = () => { @@ -25,17 +26,20 @@ const ServiceWorkerSetup = () => { const parsedSub = JSON.parse(JSON.stringify(sub)); if (parsedSub.keys.p256dh && 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, - }), - }); + const res = await apiFetch( + '/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(); } } diff --git a/src/components/Settings/Notifications/NotificationsDiscord.tsx b/src/components/Settings/Notifications/NotificationsDiscord.tsx index b62263fbc..92b585b50 100644 --- a/src/components/Settings/Notifications/NotificationsDiscord.tsx +++ b/src/components/Settings/Notifications/NotificationsDiscord.tsx @@ -3,6 +3,7 @@ import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import NotificationTypeSelector from '@app/components/NotificationTypeSelector'; import useSettings from '@app/hooks/useSettings'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline'; import { Field, Form, Formik } from 'formik'; @@ -72,7 +73,7 @@ const NotificationsDiscord = () => { validationSchema={NotificationsDiscordSchema} onSubmit={async (values) => { try { - const res = await fetch('/api/v1/settings/notifications/discord', { + const res = await apiFetch('/api/v1/settings/notifications/discord', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -127,7 +128,7 @@ const NotificationsDiscord = () => { toastId = id; } ); - const res = await fetch( + const res = await apiFetch( '/api/v1/settings/notifications/discord/test', { method: 'POST', diff --git a/src/components/Settings/Notifications/NotificationsEmail.tsx b/src/components/Settings/Notifications/NotificationsEmail.tsx index fdb292d32..b43defc82 100644 --- a/src/components/Settings/Notifications/NotificationsEmail.tsx +++ b/src/components/Settings/Notifications/NotificationsEmail.tsx @@ -3,6 +3,7 @@ import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import SensitiveInput from '@app/components/Common/SensitiveInput'; import SettingsBadge from '@app/components/Settings/SettingsBadge'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline'; import { Field, Form, Formik } from 'formik'; @@ -147,7 +148,7 @@ const NotificationsEmail = () => { validationSchema={NotificationsEmailSchema} onSubmit={async (values) => { try { - const res = await fetch('/api/v1/settings/notifications/email', { + const res = await apiFetch('/api/v1/settings/notifications/email', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -203,7 +204,7 @@ const NotificationsEmail = () => { toastId = id; } ); - const res = await fetch( + const res = await apiFetch( '/api/v1/settings/notifications/email/test', { method: 'POST', diff --git a/src/components/Settings/Notifications/NotificationsGotify/index.tsx b/src/components/Settings/Notifications/NotificationsGotify/index.tsx index e6c4ebee5..8431eeecb 100644 --- a/src/components/Settings/Notifications/NotificationsGotify/index.tsx +++ b/src/components/Settings/Notifications/NotificationsGotify/index.tsx @@ -2,6 +2,7 @@ import Button from '@app/components/Common/Button'; import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import NotificationTypeSelector from '@app/components/NotificationTypeSelector'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/solid'; import { Field, Form, Formik } from 'formik'; @@ -82,7 +83,7 @@ const NotificationsGotify = () => { validationSchema={NotificationsGotifySchema} onSubmit={async (values) => { try { - const res = await fetch('/api/v1/settings/notifications/gotify', { + const res = await apiFetch('/api/v1/settings/notifications/gotify', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -134,7 +135,7 @@ const NotificationsGotify = () => { toastId = id; } ); - const res = await fetch( + const res = await apiFetch( '/api/v1/settings/notifications/gotify/test', { method: 'POST', diff --git a/src/components/Settings/Notifications/NotificationsLunaSea/index.tsx b/src/components/Settings/Notifications/NotificationsLunaSea/index.tsx index bd6bd067c..7480a7d88 100644 --- a/src/components/Settings/Notifications/NotificationsLunaSea/index.tsx +++ b/src/components/Settings/Notifications/NotificationsLunaSea/index.tsx @@ -2,6 +2,7 @@ import Button from '@app/components/Common/Button'; import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import NotificationTypeSelector from '@app/components/NotificationTypeSelector'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline'; import { Field, Form, Formik } from 'formik'; @@ -68,7 +69,7 @@ const NotificationsLunaSea = () => { validationSchema={NotificationsLunaSeaSchema} onSubmit={async (values) => { try { - const res = await fetch('/api/v1/settings/notifications/lunasea', { + const res = await apiFetch('/api/v1/settings/notifications/lunasea', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -120,7 +121,7 @@ const NotificationsLunaSea = () => { toastId = id; } ); - const res = await fetch( + const res = await apiFetch( '/api/v1/settings/notifications/lunasea/test', { method: 'POST', diff --git a/src/components/Settings/Notifications/NotificationsPushbullet/index.tsx b/src/components/Settings/Notifications/NotificationsPushbullet/index.tsx index 2dbcb8813..5b4d87d9d 100644 --- a/src/components/Settings/Notifications/NotificationsPushbullet/index.tsx +++ b/src/components/Settings/Notifications/NotificationsPushbullet/index.tsx @@ -3,6 +3,7 @@ import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import SensitiveInput from '@app/components/Common/SensitiveInput'; import NotificationTypeSelector from '@app/components/NotificationTypeSelector'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline'; import { Field, Form, Formik } from 'formik'; @@ -67,20 +68,23 @@ const NotificationsPushbullet = () => { validationSchema={NotificationsPushbulletSchema} onSubmit={async (values) => { try { - 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, + const res = await apiFetch( + '/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', @@ -119,7 +123,7 @@ const NotificationsPushbullet = () => { toastId = id; } ); - const res = await fetch( + const res = await apiFetch( '/api/v1/settings/notifications/pushbullet/test', { method: 'POST', diff --git a/src/components/Settings/Notifications/NotificationsPushover/index.tsx b/src/components/Settings/Notifications/NotificationsPushover/index.tsx index 602555183..98abfc379 100644 --- a/src/components/Settings/Notifications/NotificationsPushover/index.tsx +++ b/src/components/Settings/Notifications/NotificationsPushover/index.tsx @@ -2,6 +2,7 @@ import Button from '@app/components/Common/Button'; import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import NotificationTypeSelector from '@app/components/NotificationTypeSelector'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline'; import type { PushoverSound } from '@server/api/pushover'; @@ -93,20 +94,23 @@ const NotificationsPushover = () => { validationSchema={NotificationsPushoverSchema} onSubmit={async (values) => { try { - 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, + const res = await apiFetch( + '/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', @@ -145,7 +149,7 @@ const NotificationsPushover = () => { toastId = id; } ); - const res = await fetch( + const res = await apiFetch( '/api/v1/settings/notifications/pushover/test', { method: 'POST', diff --git a/src/components/Settings/Notifications/NotificationsSlack/index.tsx b/src/components/Settings/Notifications/NotificationsSlack/index.tsx index e09e59e74..4e2684098 100644 --- a/src/components/Settings/Notifications/NotificationsSlack/index.tsx +++ b/src/components/Settings/Notifications/NotificationsSlack/index.tsx @@ -2,6 +2,7 @@ import Button from '@app/components/Common/Button'; import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import NotificationTypeSelector from '@app/components/NotificationTypeSelector'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline'; import { Field, Form, Formik } from 'formik'; @@ -64,7 +65,7 @@ const NotificationsSlack = () => { validationSchema={NotificationsSlackSchema} onSubmit={async (values) => { try { - const res = await fetch('/api/v1/settings/notifications/slack', { + const res = await apiFetch('/api/v1/settings/notifications/slack', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -115,7 +116,7 @@ const NotificationsSlack = () => { toastId = id; } ); - const res = await fetch( + const res = await apiFetch( '/api/v1/settings/notifications/slack/test', { method: 'POST', diff --git a/src/components/Settings/Notifications/NotificationsTelegram.tsx b/src/components/Settings/Notifications/NotificationsTelegram.tsx index 53ee47877..a4098e426 100644 --- a/src/components/Settings/Notifications/NotificationsTelegram.tsx +++ b/src/components/Settings/Notifications/NotificationsTelegram.tsx @@ -3,6 +3,7 @@ import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import SensitiveInput from '@app/components/Common/SensitiveInput'; import NotificationTypeSelector from '@app/components/NotificationTypeSelector'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline'; import { Field, Form, Formik } from 'formik'; @@ -83,22 +84,25 @@ const NotificationsTelegram = () => { validationSchema={NotificationsTelegramSchema} onSubmit={async (values) => { try { - 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, + const res = await apiFetch( + '/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), { @@ -138,7 +142,7 @@ const NotificationsTelegram = () => { toastId = id; } ); - const res = await fetch( + const res = await apiFetch( '/api/v1/settings/notifications/telegram/test', { method: 'POST', diff --git a/src/components/Settings/Notifications/NotificationsWebPush/index.tsx b/src/components/Settings/Notifications/NotificationsWebPush/index.tsx index 3c9a3994b..cecb56429 100644 --- a/src/components/Settings/Notifications/NotificationsWebPush/index.tsx +++ b/src/components/Settings/Notifications/NotificationsWebPush/index.tsx @@ -2,6 +2,7 @@ import Alert from '@app/components/Common/Alert'; import Button from '@app/components/Common/Button'; import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline'; import { Field, Form, Formik } from 'formik'; @@ -57,16 +58,19 @@ const NotificationsWebPush = () => { }} onSubmit={async (values) => { try { - const res = await fetch('/api/v1/settings/notifications/webpush', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - enabled: values.enabled, - options: {}, - }), - }); + const res = await apiFetch( + '/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), { @@ -98,7 +102,7 @@ const NotificationsWebPush = () => { toastId = id; } ); - const res = await fetch( + const res = await apiFetch( '/api/v1/settings/notifications/webpush/test', { method: 'POST', diff --git a/src/components/Settings/Notifications/NotificationsWebhook/index.tsx b/src/components/Settings/Notifications/NotificationsWebhook/index.tsx index 634446519..51c22a451 100644 --- a/src/components/Settings/Notifications/NotificationsWebhook/index.tsx +++ b/src/components/Settings/Notifications/NotificationsWebhook/index.tsx @@ -2,6 +2,7 @@ import Button from '@app/components/Common/Button'; import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import NotificationTypeSelector from '@app/components/NotificationTypeSelector'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline'; import { @@ -149,7 +150,7 @@ const NotificationsWebhook = () => { validationSchema={NotificationsWebhookSchema} onSubmit={async (values) => { try { - const res = await fetch('/api/v1/settings/notifications/webhook', { + const res = await apiFetch('/api/v1/settings/notifications/webhook', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -213,7 +214,7 @@ const NotificationsWebhook = () => { toastId = id; } ); - const res = await fetch( + const res = await apiFetch( '/api/v1/settings/notifications/webhook/test', { method: 'POST', diff --git a/src/components/Settings/RadarrModal/index.tsx b/src/components/Settings/RadarrModal/index.tsx index 59f16065d..d186ae774 100644 --- a/src/components/Settings/RadarrModal/index.tsx +++ b/src/components/Settings/RadarrModal/index.tsx @@ -1,6 +1,7 @@ import Modal from '@app/components/Common/Modal'; import SensitiveInput from '@app/components/Common/SensitiveInput'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { Transition } from '@headlessui/react'; import type { RadarrSettings } from '@server/lib/settings'; @@ -165,7 +166,7 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => { }) => { setIsTesting(true); try { - const res = await fetch('/api/v1/settings/radarr/test', { + const res = await apiFetch('/api/v1/settings/radarr/test', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -275,7 +276,7 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => { tagRequests: values.tagRequests, }; if (!radarr) { - const res = await fetch('/api/v1/settings/radarr', { + const res = await apiFetch('/api/v1/settings/radarr', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -284,13 +285,16 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => { }); if (!res.ok) throw new Error(); } else { - const res = await fetch(`/api/v1/settings/radarr/${radarr.id}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(submission), - }); + const res = await apiFetch( + `/api/v1/settings/radarr/${radarr.id}`, + { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(submission), + } + ); if (!res.ok) throw new Error(); } diff --git a/src/components/Settings/SettingsJellyfin.tsx b/src/components/Settings/SettingsJellyfin.tsx index 316dc48ef..1d5ef67f8 100644 --- a/src/components/Settings/SettingsJellyfin.tsx +++ b/src/components/Settings/SettingsJellyfin.tsx @@ -5,6 +5,7 @@ import SensitiveInput from '@app/components/Common/SensitiveInput'; import LibraryItem from '@app/components/Settings/LibraryItem'; import useSettings from '@app/hooks/useSettings'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline'; import { ApiErrorCode } from '@server/constants/error'; @@ -176,7 +177,7 @@ const SettingsJellyfin: React.FC = ({ sync: params.sync ? 'true' : 'false', ...(params.enable ? { enable: params.enable } : {}), }); - const res = await fetch( + const res = await apiFetch( `/api/v1/settings/jellyfin/library?${searchParams.toString()}` ); if (!res.ok) throw new Error(res.statusText, { cause: res }); @@ -223,7 +224,7 @@ const SettingsJellyfin: React.FC = ({ }; const startScan = async () => { - const res = await fetch('/api/v1/settings/jellyfin/sync', { + const res = await apiFetch('/api/v1/settings/jellyfin/sync', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -238,7 +239,7 @@ const SettingsJellyfin: React.FC = ({ }; const cancelScan = async () => { - const res = await fetch('/api/v1/settings/jellyfin/sync', { + const res = await apiFetch('/api/v1/settings/jellyfin/sync', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -264,7 +265,7 @@ const SettingsJellyfin: React.FC = ({ } const searchParams = new URLSearchParams(params.enable ? params : {}); - const res = await fetch( + const res = await apiFetch( `/api/v1/settings/jellyfin/library?${searchParams.toString()}` ); if (!res.ok) throw new Error(); @@ -272,7 +273,7 @@ const SettingsJellyfin: React.FC = ({ const searchParams = new URLSearchParams({ enable: [...activeLibraries, libraryId].join(','), }); - const res = await fetch( + const res = await apiFetch( `/api/v1/settings/jellyfin/library?${searchParams.toString()}` ); if (!res.ok) throw new Error(); @@ -485,7 +486,7 @@ const SettingsJellyfin: React.FC = ({ validationSchema={JellyfinSettingsSchema} onSubmit={async (values) => { try { - const res = await fetch('/api/v1/settings/jellyfin', { + const res = await apiFetch('/api/v1/settings/jellyfin', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/Settings/SettingsJobsCache/index.tsx b/src/components/Settings/SettingsJobsCache/index.tsx index 975de36c7..062a518aa 100644 --- a/src/components/Settings/SettingsJobsCache/index.tsx +++ b/src/components/Settings/SettingsJobsCache/index.tsx @@ -9,6 +9,7 @@ import useLocale from '@app/hooks/useLocale'; import useSettings from '@app/hooks/useSettings'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { formatBytes } from '@app/utils/numberHelpers'; import { Transition } from '@headlessui/react'; @@ -188,7 +189,7 @@ const SettingsJobs = () => { } const runJob = async (job: Job) => { - const res = await fetch(`/api/v1/settings/jobs/${job.id}/run`, { + const res = await apiFetch(`/api/v1/settings/jobs/${job.id}/run`, { method: 'POST', }); if (!res.ok) throw new Error(); @@ -205,7 +206,7 @@ const SettingsJobs = () => { }; const cancelJob = async (job: Job) => { - const res = await fetch(`/api/v1/settings/jobs/${job.id}/cancel`, { + const res = await apiFetch(`/api/v1/settings/jobs/${job.id}/cancel`, { method: 'POST', }); if (!res.ok) throw new Error(); @@ -222,7 +223,7 @@ const SettingsJobs = () => { }; const flushCache = async (cache: CacheItem) => { - const res = await fetch(`/api/v1/settings/cache/${cache.id}/flush`, { + const res = await apiFetch(`/api/v1/settings/cache/${cache.id}/flush`, { method: 'POST', }); if (!res.ok) throw new Error(); @@ -252,7 +253,7 @@ const SettingsJobs = () => { } setIsSaving(true); - const res = await fetch( + const res = await apiFetch( `/api/v1/settings/jobs/${jobModalState.job.id}/schedule`, { method: 'POST', diff --git a/src/components/Settings/SettingsMain/index.tsx b/src/components/Settings/SettingsMain/index.tsx index f7aac0d96..c5759a57d 100644 --- a/src/components/Settings/SettingsMain/index.tsx +++ b/src/components/Settings/SettingsMain/index.tsx @@ -12,6 +12,7 @@ import { availableLanguages } from '@app/context/LanguageContext'; import useLocale from '@app/hooks/useLocale'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline'; import { ArrowPathIcon } from '@heroicons/react/24/solid'; @@ -86,7 +87,7 @@ const SettingsMain = () => { const regenerate = async () => { try { - const res = await fetch('/api/v1/settings/main/regenerate', { + const res = await apiFetch('/api/v1/settings/main/regenerate', { method: 'POST', }); if (!res.ok) throw new Error(); @@ -142,7 +143,7 @@ const SettingsMain = () => { validationSchema={MainSettingsSchema} onSubmit={async (values) => { try { - const res = await fetch('/api/v1/settings/main', { + const res = await apiFetch('/api/v1/settings/main', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/Settings/SettingsPlex.tsx b/src/components/Settings/SettingsPlex.tsx index a20fc4836..a089ac1d5 100644 --- a/src/components/Settings/SettingsPlex.tsx +++ b/src/components/Settings/SettingsPlex.tsx @@ -7,6 +7,7 @@ import SensitiveInput from '@app/components/Common/SensitiveInput'; import LibraryItem from '@app/components/Settings/LibraryItem'; import SettingsBadge from '@app/components/Settings/SettingsBadge'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline'; import { @@ -244,7 +245,7 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => { sync: params.sync ? 'true' : 'false', ...(params.enable ? { enable: params.enable } : {}), }); - const res = await fetch( + const res = await apiFetch( `/api/v1/settings/plex/library?${searchParams.toString()}` ); if (!res.ok) throw new Error(); @@ -267,7 +268,7 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => { toastId = id; } ); - const res = await fetch('/api/v1/settings/plex/devices/servers'); + const res = await apiFetch('/api/v1/settings/plex/devices/servers'); if (!res.ok) throw new Error(); const data: PlexDevice[] = await res.json(); @@ -295,7 +296,7 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => { }; const startScan = async () => { - const res = await fetch('/api/v1/settings/plex/sync', { + const res = await apiFetch('/api/v1/settings/plex/sync', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -309,7 +310,7 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => { }; const cancelScan = async () => { - const res = await fetch('/api/v1/settings/plex/sync', { + const res = await apiFetch('/api/v1/settings/plex/sync', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -334,7 +335,7 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => { } const searchParams = new URLSearchParams(params.enable ? params : {}); - const res = await fetch( + const res = await apiFetch( `/api/v1/settings/plex/library?${searchParams.toString()}` ); if (!res.ok) throw new Error(); @@ -342,7 +343,7 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => { const searchParams = new URLSearchParams({ enable: [...activeLibraries, libraryId].join(','), }); - const res = await fetch( + const res = await apiFetch( `/api/v1/settings/plex/library?${searchParams.toString()}` ); if (!res.ok) throw new Error(); @@ -409,7 +410,7 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => { toastId = id; } ); - const res = await fetch('/api/v1/settings/plex', { + const res = await apiFetch('/api/v1/settings/plex', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -779,7 +780,7 @@ const SettingsPlex = ({ onComplete }: SettingsPlexProps) => { validationSchema={TautulliSettingsSchema} onSubmit={async (values) => { try { - const res = await fetch('/api/v1/settings/tautulli', { + const res = await apiFetch('/api/v1/settings/tautulli', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/Settings/SettingsServices.tsx b/src/components/Settings/SettingsServices.tsx index 63cd463f7..93dd55bb1 100644 --- a/src/components/Settings/SettingsServices.tsx +++ b/src/components/Settings/SettingsServices.tsx @@ -9,6 +9,7 @@ import PageTitle from '@app/components/Common/PageTitle'; import RadarrModal from '@app/components/Settings/RadarrModal'; import SonarrModal from '@app/components/Settings/SonarrModal'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { Transition } from '@headlessui/react'; import { PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/solid'; @@ -195,7 +196,7 @@ const SettingsServices = () => { }); const deleteServer = async () => { - const res = await fetch( + const res = await apiFetch( `/api/v1/settings/${deleteServerModal.type}/${deleteServerModal.serverId}`, { method: 'DELETE', diff --git a/src/components/Settings/SettingsUsers/index.tsx b/src/components/Settings/SettingsUsers/index.tsx index 7f6fa1fcf..b158723a1 100644 --- a/src/components/Settings/SettingsUsers/index.tsx +++ b/src/components/Settings/SettingsUsers/index.tsx @@ -5,6 +5,7 @@ import PermissionEdit from '@app/components/PermissionEdit'; import QuotaSelector from '@app/components/QuotaSelector'; import useSettings from '@app/hooks/useSettings'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline'; import { MediaServerType } from '@server/constants/server'; @@ -83,7 +84,7 @@ const SettingsUsers = () => { enableReinitialize onSubmit={async (values) => { try { - const res = await fetch('/api/v1/settings/main', { + const res = await apiFetch('/api/v1/settings/main', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/Settings/SonarrModal/index.tsx b/src/components/Settings/SonarrModal/index.tsx index ed6d1f564..ba879f79f 100644 --- a/src/components/Settings/SonarrModal/index.tsx +++ b/src/components/Settings/SonarrModal/index.tsx @@ -1,6 +1,7 @@ import Modal from '@app/components/Common/Modal'; import SensitiveInput from '@app/components/Common/SensitiveInput'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { Transition } from '@headlessui/react'; import type { SonarrSettings } from '@server/lib/settings'; @@ -176,7 +177,7 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => { }) => { setIsTesting(true); try { - const res = await fetch('/api/v1/settings/sonarr/test', { + const res = await apiFetch('/api/v1/settings/sonarr/test', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -310,7 +311,7 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => { tagRequests: values.tagRequests, }; if (!sonarr) { - const res = await fetch('/api/v1/settings/sonarr', { + const res = await apiFetch('/api/v1/settings/sonarr', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -319,13 +320,16 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => { }); if (!res.ok) throw new Error(); } else { - const res = await fetch(`/api/v1/settings/sonarr/${sonarr.id}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(submission), - }); + const res = await apiFetch( + `/api/v1/settings/sonarr/${sonarr.id}`, + { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(submission), + } + ); if (!res.ok) throw new Error(); } diff --git a/src/components/Setup/LoginWithPlex.tsx b/src/components/Setup/LoginWithPlex.tsx index 15ffe9562..5a819554d 100644 --- a/src/components/Setup/LoginWithPlex.tsx +++ b/src/components/Setup/LoginWithPlex.tsx @@ -1,5 +1,6 @@ import PlexLoginButton from '@app/components/PlexLoginButton'; import { useUser } from '@app/hooks/useUser'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; @@ -24,7 +25,7 @@ const LoginWithPlex = ({ onComplete }: LoginWithPlexProps) => { useEffect(() => { const login = async () => { - const res = await fetch('/api/v1/auth/plex', { + const res = await apiFetch('/api/v1/auth/plex', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/Setup/SetupLogin.tsx b/src/components/Setup/SetupLogin.tsx index 9af4de9d9..721ebe6a3 100644 --- a/src/components/Setup/SetupLogin.tsx +++ b/src/components/Setup/SetupLogin.tsx @@ -2,6 +2,7 @@ import Button from '@app/components/Common/Button'; import JellyfinLogin from '@app/components/Login/JellyfinLogin'; import PlexLoginButton from '@app/components/PlexLoginButton'; import { useUser } from '@app/hooks/useUser'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { MediaServerType } from '@server/constants/server'; import { useEffect, useState } from 'react'; @@ -40,7 +41,7 @@ const SetupLogin: React.FC = ({ useEffect(() => { const login = async () => { - const res = await fetch('/api/v1/auth/plex', { + const res = await apiFetch('/api/v1/auth/plex', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/Setup/index.tsx b/src/components/Setup/index.tsx index 684b01adc..150db7330 100644 --- a/src/components/Setup/index.tsx +++ b/src/components/Setup/index.tsx @@ -12,6 +12,7 @@ import SettingsServices from '@app/components/Settings/SettingsServices'; import SetupSteps from '@app/components/Setup/SetupSteps'; import useLocale from '@app/hooks/useLocale'; import useSettings from '@app/hooks/useSettings'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { MediaServerType } from '@server/constants/server'; import Image from 'next/image'; @@ -52,7 +53,7 @@ const Setup = () => { const finishSetup = async () => { setIsUpdating(true); - const res = await fetch('/api/v1/settings/initialize', { + const res = await apiFetch('/api/v1/settings/initialize', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -63,7 +64,7 @@ const Setup = () => { setIsUpdating(false); if (data.initialized) { - const mainRes = await fetch('/api/v1/settings/main', { + const mainRes = await apiFetch('/api/v1/settings/main', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/TitleCard/ErrorCard.tsx b/src/components/TitleCard/ErrorCard.tsx index 3333e5bb3..ba49e9643 100644 --- a/src/components/TitleCard/ErrorCard.tsx +++ b/src/components/TitleCard/ErrorCard.tsx @@ -1,5 +1,6 @@ import Button from '@app/components/Common/Button'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { CheckIcon, TrashIcon } from '@heroicons/react/24/solid'; import { useIntl } from 'react-intl'; @@ -24,7 +25,7 @@ const ErrorCard = ({ id, tmdbId, tvdbId, type, canExpand }: ErrorCardProps) => { const intl = useIntl(); const deleteMedia = async () => { - const res = await fetch(`/api/v1/media/${id}`, { + const res = await apiFetch(`/api/v1/media/${id}`, { method: 'DELETE', }); if (!res.ok) throw new Error(); diff --git a/src/components/TitleCard/index.tsx b/src/components/TitleCard/index.tsx index 2d10fdf1c..d2c22ceee 100644 --- a/src/components/TitleCard/index.tsx +++ b/src/components/TitleCard/index.tsx @@ -10,6 +10,7 @@ import Placeholder from '@app/components/TitleCard/Placeholder'; import { useIsTouch } from '@app/hooks/useIsTouch'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { withProperties } from '@app/utils/typeHelpers'; import { Transition } from '@headlessui/react'; @@ -108,7 +109,7 @@ const TitleCard = ({ const onClickWatchlistBtn = async (): Promise => { setIsUpdating(true); try { - const res = await fetch('/api/v1/watchlist', { + const res = await apiFetch('/api/v1/watchlist', { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -147,7 +148,7 @@ const TitleCard = ({ const onClickDeleteWatchlistBtn = async (): Promise => { setIsUpdating(true); try { - const res = await fetch('/api/v1/watchlist/' + id, { + const res = await apiFetch('/api/v1/watchlist/' + id, { method: 'DELETE', }); if (!res.ok) throw new Error(); @@ -182,7 +183,7 @@ const TitleCard = ({ const topNode = cardRef.current; if (topNode) { - const res = await fetch('/api/v1/blacklist', { + const res = await apiFetch('/api/v1/blacklist', { method: 'POST', headers: { Accept: 'application/json', @@ -239,7 +240,7 @@ const TitleCard = ({ const topNode = cardRef.current; if (topNode) { - const res = await fetch('/api/v1/blacklist/' + id, { + const res = await apiFetch('/api/v1/blacklist/' + id, { method: 'DELETE', }); diff --git a/src/components/TvDetails/index.tsx b/src/components/TvDetails/index.tsx index cf788237b..bad814e3b 100644 --- a/src/components/TvDetails/index.tsx +++ b/src/components/TvDetails/index.tsx @@ -31,6 +31,7 @@ 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 apiFetch from '@app/utils/apiFetch'; import { sortCrewPriority } from '@app/utils/creditHelpers'; import defineMessages from '@app/utils/defineMessages'; import { refreshIntervalHelper } from '@app/utils/refreshIntervalHelper'; @@ -334,7 +335,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => { const onClickWatchlistBtn = async (): Promise => { setIsUpdating(true); - const res = await fetch('/api/v1/watchlist', { + const res = await apiFetch('/api/v1/watchlist', { method: 'POST', headers: { Accept: 'application/json', @@ -378,7 +379,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => { const onClickDeleteWatchlistBtn = async (): Promise => { setIsUpdating(true); - const res = await fetch('/api/v1/watchlist/' + tv?.id, { + const res = await apiFetch('/api/v1/watchlist/' + tv?.id, { method: 'DELETE', }); @@ -410,7 +411,7 @@ const TvDetails = ({ tv }: TvDetailsProps) => { const onClickHideItemBtn = async (): Promise => { setIsBlacklistUpdating(true); - const res = await fetch('/api/v1/blacklist', { + const res = await apiFetch('/api/v1/blacklist', { method: 'POST', headers: { Accept: 'application/json', diff --git a/src/components/UserList/BulkEditModal.tsx b/src/components/UserList/BulkEditModal.tsx index 3706d2d10..756f6f4b5 100644 --- a/src/components/UserList/BulkEditModal.tsx +++ b/src/components/UserList/BulkEditModal.tsx @@ -3,6 +3,7 @@ import PermissionEdit from '@app/components/PermissionEdit'; import type { User } from '@app/hooks/useUser'; import { useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { useEffect, useState } from 'react'; import { useIntl } from 'react-intl'; @@ -44,7 +45,7 @@ const BulkEditModal = ({ const updateUsers = async () => { try { setIsSaving(true); - const res = await fetch('/api/v1/user', { + const res = await apiFetch('/api/v1/user', { method: 'PUT', headers: { 'Content-Type': 'application/json', diff --git a/src/components/UserList/JellyfinImportModal.tsx b/src/components/UserList/JellyfinImportModal.tsx index e95a0a7d3..b1ce5edca 100644 --- a/src/components/UserList/JellyfinImportModal.tsx +++ b/src/components/UserList/JellyfinImportModal.tsx @@ -3,6 +3,7 @@ import CachedImage from '@app/components/Common/CachedImage'; import Modal from '@app/components/Common/Modal'; import useSettings from '@app/hooks/useSettings'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { MediaServerType } from '@server/constants/server'; import type { UserResultsResponse } from '@server/interfaces/api/userInterfaces'; @@ -59,7 +60,7 @@ const JellyfinImportModal: React.FC = ({ setImporting(true); try { - const res = await fetch('/api/v1/user/import-from-jellyfin', { + const res = await apiFetch('/api/v1/user/import-from-jellyfin', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/UserList/PlexImportModal.tsx b/src/components/UserList/PlexImportModal.tsx index 078d1f1a1..73bf6ada9 100644 --- a/src/components/UserList/PlexImportModal.tsx +++ b/src/components/UserList/PlexImportModal.tsx @@ -2,6 +2,7 @@ import Alert from '@app/components/Common/Alert'; import Modal from '@app/components/Common/Modal'; import useSettings from '@app/hooks/useSettings'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import Image from 'next/image'; import { useState } from 'react'; @@ -47,7 +48,7 @@ const PlexImportModal = ({ onCancel, onComplete }: PlexImportProps) => { setImporting(true); try { - const res = await fetch('/api/v1/user/import-from-plex', { + const res = await apiFetch('/api/v1/user/import-from-plex', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/UserList/index.tsx b/src/components/UserList/index.tsx index 27dbdd8e0..3cc65da68 100644 --- a/src/components/UserList/index.tsx +++ b/src/components/UserList/index.tsx @@ -15,6 +15,7 @@ import { useUpdateQueryParams } from '@app/hooks/useUpdateQueryParams'; import type { User } from '@app/hooks/useUser'; import { Permission, UserType, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { Transition } from '@headlessui/react'; import { @@ -181,7 +182,7 @@ const UserList = () => { setDeleting(true); try { - const res = await fetch(`/api/v1/user/${deleteModal.user?.id}`, { + const res = await apiFetch(`/api/v1/user/${deleteModal.user?.id}`, { method: 'DELETE', }); if (!res.ok) throw new Error(); @@ -284,7 +285,7 @@ const UserList = () => { validationSchema={CreateUserSchema} onSubmit={async (values) => { try { - const res = await fetch('/api/v1/user', { + const res = await apiFetch('/api/v1/user', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx b/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx index 502a9d84f..146e7a6c9 100644 --- a/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx +++ b/src/components/UserProfile/UserSettings/UserGeneralSettings/index.tsx @@ -12,6 +12,7 @@ import useSettings from '@app/hooks/useSettings'; import { Permission, UserType, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; import ErrorPage from '@app/pages/_error'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline'; import { ApiErrorCode } from '@server/constants/error'; @@ -155,31 +156,36 @@ const UserGeneralSettings = () => { enableReinitialize onSubmit={async (values) => { try { - 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 || user?.jellyfinUsername || user?.plexUsername, - 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 apiFetch( + `/api/v1/user/${user?.id}/settings/main`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username: values.displayName, + email: + values.email || + user?.jellyfinUsername || + user?.plexUsername, + 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(res.statusText, { cause: res }); if (currentUser?.id === user?.id && setLocale) { diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsDiscord.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsDiscord.tsx index 061805ccb..d3ce2b924 100644 --- a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsDiscord.tsx +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsDiscord.tsx @@ -3,6 +3,7 @@ import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import NotificationTypeSelector from '@app/components/NotificationTypeSelector'; import { useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline'; import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces'; @@ -67,7 +68,7 @@ const UserNotificationsDiscord = () => { enableReinitialize onSubmit={async (values) => { try { - const res = await fetch( + const res = await apiFetch( `/api/v1/user/${user?.id}/settings/notifications`, { method: 'POST', diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsEmail.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsEmail.tsx index 08028a5b5..92bee1830 100644 --- a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsEmail.tsx +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsEmail.tsx @@ -8,6 +8,7 @@ import { OpenPgpLink } from '@app/components/Settings/Notifications/Notification import SettingsBadge from '@app/components/Settings/SettingsBadge'; import { useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline'; import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces'; @@ -66,7 +67,7 @@ const UserEmailSettings = () => { enableReinitialize onSubmit={async (values) => { try { - const res = await fetch( + const res = await apiFetch( `/api/v1/user/${user?.id}/settings/notifications`, { method: 'POST', diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushbullet.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushbullet.tsx index f4f5c1dfa..0f13c8aff 100644 --- a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushbullet.tsx +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushbullet.tsx @@ -4,6 +4,7 @@ import SensitiveInput from '@app/components/Common/SensitiveInput'; import NotificationTypeSelector from '@app/components/NotificationTypeSelector'; import { useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces'; import { Form, Formik } from 'formik'; @@ -64,7 +65,7 @@ const UserPushbulletSettings = () => { enableReinitialize onSubmit={async (values) => { try { - const res = await fetch( + const res = await apiFetch( `/api/v1/user/${user?.id}/settings/notifications`, { method: 'POST', diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover.tsx index 7834b2c5c..70692b813 100644 --- a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover.tsx +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsPushover.tsx @@ -4,6 +4,7 @@ import NotificationTypeSelector from '@app/components/NotificationTypeSelector'; import useSettings from '@app/hooks/useSettings'; import { useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import type { PushoverSound } from '@server/api/pushover'; import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces'; @@ -96,7 +97,7 @@ const UserPushoverSettings = () => { enableReinitialize onSubmit={async (values) => { try { - const res = await fetch( + const res = await apiFetch( `/api/v1/user/${user?.id}/settings/notifications`, { method: 'POST', diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsTelegram.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsTelegram.tsx index 2826711e2..0c637f2d2 100644 --- a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsTelegram.tsx +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsTelegram.tsx @@ -3,6 +3,7 @@ import LoadingSpinner from '@app/components/Common/LoadingSpinner'; import NotificationTypeSelector from '@app/components/NotificationTypeSelector'; import { useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline'; import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces'; @@ -70,7 +71,7 @@ const UserTelegramSettings = () => { enableReinitialize onSubmit={async (values) => { try { - const res = await fetch( + const res = await apiFetch( `/api/v1/user/${user?.id}/settings/notifications`, { method: 'POST', diff --git a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx index e338c9f0a..c8187e27f 100644 --- a/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx +++ b/src/components/UserProfile/UserSettings/UserNotificationSettings/UserNotificationsWebPush.tsx @@ -5,6 +5,7 @@ import NotificationTypeSelector, { } from '@app/components/NotificationTypeSelector'; import { useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline'; import type { UserSettingsNotificationsResponse } from '@server/interfaces/api/userSettingsInterfaces'; @@ -47,7 +48,7 @@ const UserWebPushSettings = () => { enableReinitialize onSubmit={async (values) => { try { - const res = await fetch( + const res = await apiFetch( `/api/v1/user/${user?.id}/settings/notifications`, { method: 'POST', diff --git a/src/components/UserProfile/UserSettings/UserPasswordChange/index.tsx b/src/components/UserProfile/UserSettings/UserPasswordChange/index.tsx index b7f320b06..c3b7ace9c 100644 --- a/src/components/UserProfile/UserSettings/UserPasswordChange/index.tsx +++ b/src/components/UserProfile/UserSettings/UserPasswordChange/index.tsx @@ -6,6 +6,7 @@ import SensitiveInput from '@app/components/Common/SensitiveInput'; import { Permission, useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; import ErrorPage from '@app/pages/_error'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline'; import { Form, Formik } from 'formik'; @@ -122,7 +123,7 @@ const UserPasswordChange = () => { enableReinitialize onSubmit={async (values, { resetForm }) => { try { - const res = await fetch( + const res = await apiFetch( `/api/v1/user/${user?.id}/settings/password`, { method: 'POST', diff --git a/src/components/UserProfile/UserSettings/UserPermissions/index.tsx b/src/components/UserProfile/UserSettings/UserPermissions/index.tsx index 7f8f30960..b92982a3a 100644 --- a/src/components/UserProfile/UserSettings/UserPermissions/index.tsx +++ b/src/components/UserProfile/UserSettings/UserPermissions/index.tsx @@ -6,6 +6,7 @@ import PermissionEdit from '@app/components/PermissionEdit'; import { useUser } from '@app/hooks/useUser'; import globalMessages from '@app/i18n/globalMessages'; import ErrorPage from '@app/pages/_error'; +import apiFetch from '@app/utils/apiFetch'; import defineMessages from '@app/utils/defineMessages'; import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline'; import { Form, Formik } from 'formik'; @@ -83,7 +84,7 @@ const UserPermissions = () => { enableReinitialize onSubmit={async (values) => { try { - const res = await fetch( + const res = await apiFetch( `/api/v1/user/${user?.id}/settings/permissions`, { method: 'POST', diff --git a/src/utils/apiFetch.ts b/src/utils/apiFetch.ts new file mode 100644 index 000000000..8af39c12f --- /dev/null +++ b/src/utils/apiFetch.ts @@ -0,0 +1,26 @@ +const getCsrfToken = (): string => { + const token = document.cookie + .split('; ') + .find((row) => row.startsWith('XSRF-TOKEN=')) + ?.split('=')[1]; + return token || ''; +}; + +const apiFetch = async ( + url: string, + options?: RequestInit +): Promise => { + const csrfToken = getCsrfToken(); + + const updatedOptions: RequestInit = { + ...options, + headers: { + ...options?.headers, + 'X-XSRF-TOKEN': csrfToken, + }, + }; + + return fetch(url, updatedOptions); +}; + +export default apiFetch;