feat(api): refactor fetch calls to axios

Replaces the native fetch() API with axios for all OIDC-related frontend API calls. This improves
consistency with other parts of the codebase and simplifies error handling.

Addresses https://github.com/fallenbagel/jellyseerr/pull/1505#pullrequestreview-3001436908
This commit is contained in:
Puranjay Savar Mattas
2025-08-01 10:28:18 +00:00
committed by Michael Thomas
parent 205aa5da92
commit 67bbc73564
5 changed files with 40 additions and 45 deletions

View File

@@ -1,6 +1,7 @@
import defineMessages from '@app/utils/defineMessages';
import { processCallback } from '@app/utils/oidc';
import type { PublicOidcProvider } from '@server/lib/settings';
import axios from 'axios';
import { useRouter, useSearchParams } from 'next/navigation';
import { useCallback, useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
@@ -26,15 +27,11 @@ export default function OidcLoginButton({
const [loading, setLoading] = useState(false);
const redirectToLogin = useCallback(async () => {
let redirectUrl: string;
try {
const res = await fetch(`/api/v1/auth/oidc/login/${provider.slug}`);
if (res.ok) {
const data = await res.json();
redirectUrl = data.redirectUrl;
} else {
throw new Error();
}
const res = await axios.get<{ redirectUrl: string }>(
`/api/v1/auth/oidc/login/${provider.slug}`
);
window.location.href = res.data.redirectUrl;
} catch (e) {
setLoading(false);
onError?.(
@@ -42,10 +39,7 @@ export default function OidcLoginButton({
provider: provider.name,
})
);
return;
}
window.location.href = redirectUrl;
}, [provider, intl, onError]);
const handleCallback = useCallback(async () => {

View File

@@ -7,6 +7,7 @@ import { Transition } from '@headlessui/react';
import { ChevronRightIcon } from '@heroicons/react/20/solid';
import { MagnifyingGlassIcon } from '@heroicons/react/24/solid';
import type { OidcProvider } from '@server/lib/settings';
import axios from 'axios'; // <-- Import axios
import {
ErrorMessage,
Field,
@@ -99,24 +100,14 @@ export default function EditOidcModal(props: EditOidcModalProps) {
const onSubmit = async ({ slug, ...provider }: OidcProvider) => {
try {
const res = await fetch(`/api/v1/settings/oidc/${slug}`, {
method: 'PUT',
body: JSON.stringify(provider),
headers: {
'Content-Type': 'application/json',
},
await axios.put(`/api/v1/settings/oidc/${slug}`, provider);
addToast(intl.formatMessage(messages.saveSuccess), {
appearance: 'success',
autoDismiss: true,
});
if (res.status === 200) {
addToast(intl.formatMessage(messages.saveSuccess), {
appearance: 'success',
autoDismiss: true,
});
props.onOk();
} else {
throw new Error(`Request failed with code ${res.status}`);
}
props.onOk();
} catch (e) {
addToast(intl.formatMessage(messages.saveError), {
appearance: 'error',

View File

@@ -8,6 +8,7 @@ import { Transition } from '@headlessui/react';
import { PlusIcon } from '@heroicons/react/24/outline';
import { PencilIcon, TrashIcon } from '@heroicons/react/24/solid';
import type { OidcProvider, OidcSettings } from '@server/lib/settings';
import axios from 'axios'; // <-- Import axios
import { useState } from 'react';
import { useIntl } from 'react-intl';
import { useToasts } from 'react-toast-notifications';
@@ -47,14 +48,12 @@ export default function SettingsOidc(props: SettingsOidcProps) {
async function onDelete(provider: OidcProvider) {
try {
const response = await fetch(`/api/v1/settings/oidc/${provider.slug}`, {
method: 'DELETE',
});
if (response.status !== 200)
throw new Error(`Request failed with status ${response.status}`);
revalidate(await response.json());
// The fetch call is replaced with axios.delete.
// Axios automatically throws for non-2xx responses and handles JSON parsing.
const response = await axios.delete<OidcSettings>(
`/api/v1/settings/oidc/${provider.slug}`
);
revalidate(response.data);
} catch (e) {
addToast(intl.formatMessage(messages.deleteError), {
autoDismiss: true,

View File

@@ -159,10 +159,14 @@ const UserLinkedAccountsSettings = () => {
...settings.currentSettings.openIdProviders.map((p) => ({
name: p.name,
action: async () => {
const res = await fetch(`/api/v1/auth/oidc/login/${p.slug}`);
if (!res.ok) setError(intl.formatMessage(messages.errorUnknown));
const json = await res.json();
window.location.href = json.redirectUrl;
try {
const res = await axios.get<{ redirectUrl: string }>(
`/api/v1/auth/oidc/login/${p.slug}`
);
window.location.href = res.data.redirectUrl;
} catch (e) {
setError(intl.formatMessage(messages.errorUnknown));
}
},
hide: accounts.some(
(a) =>

View File

@@ -1,3 +1,5 @@
import axios, { isAxiosError } from 'axios'; // <-- Import axios and isAxiosError
export async function processCallback(
params: URLSearchParams,
provider: string
@@ -9,17 +11,22 @@ export async function processCallback(
url.search = params.toString();
try {
const result = await fetch(url);
const message = await result.json();
// The fetch call is replaced with axios.get.
// On success, the JSON response is automatically parsed and available in res.data.
const res = await axios.get(url.toString());
if (!result.ok) {
return { type: 'error', message: message.message };
}
return {
type: 'success',
message,
message: res.data,
};
} catch (e) {
// Axios throws an error for non-2xx responses. We can check if it's an axios error
// to safely access the error message from the server's response payload.
if (isAxiosError(e) && e.response?.data?.message) {
return { type: 'error', message: e.response.data.message };
}
// Fallback for generic errors
return {
type: 'error',
message: e.message,