diff --git a/package.json b/package.json
index 83f8f6e8f..ae6c8dc53 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,10 @@
"@tanstack/react-query": "^4.2.1",
"@tanstack/react-query-devtools": "^4.24.4",
"@tanstack/react-query-persist-client": "^4.28.0",
+ "@trpc/client": "^10.29.1",
+ "@trpc/next": "^10.29.1",
+ "@trpc/react-query": "^10.29.1",
+ "@trpc/server": "^10.29.1",
"@vitejs/plugin-react": "^4.0.0",
"axios": "^1.0.0",
"consola": "^3.0.0",
@@ -111,6 +115,7 @@
"turbo": "latest",
"typescript": "^5.0.4",
"video.js": "^8.0.3",
+ "vite-tsconfig-paths": "^4.2.0",
"vitest": "^0.32.0",
"vitest-fetch-mock": "^0.2.2"
},
diff --git a/src/components/Config/ConfigChanger.tsx b/src/components/Config/ConfigChanger.tsx
index 4826fcdd8..19a88b179 100644
--- a/src/components/Config/ConfigChanger.tsx
+++ b/src/components/Config/ConfigChanger.tsx
@@ -1,12 +1,12 @@
import { Center, Dialog, Loader, Notification, Select, Tooltip } from '@mantine/core';
import { useToggle } from '@mantine/hooks';
-import { useQuery } from '@tanstack/react-query';
+import { notifications } from '@mantine/notifications';
+import { IconCheck } from '@tabler/icons-react';
import { setCookie } from 'cookies-next';
import { useTranslation } from 'next-i18next';
import { useRouter } from 'next/router';
import { useState } from 'react';
-import { notifications } from '@mantine/notifications';
-import { IconCheck } from '@tabler/icons-react';
+import { api } from '~/utils/api';
import { useConfigContext } from '../../config/provider';
export default function ConfigChanger() {
@@ -95,10 +95,4 @@ export default function ConfigChanger() {
);
}
-const useConfigsQuery = () =>
- useQuery({
- queryKey: ['config/get-all'],
- queryFn: fetchConfigs,
- });
-
-const fetchConfigs = async () => (await (await fetch('/api/configs')).json()) as string[];
+const useConfigsQuery = () => api.config.all.useQuery();
diff --git a/src/components/Config/LoadConfig.tsx b/src/components/Config/LoadConfig.tsx
index dfa31f7cd..d9c64c558 100644
--- a/src/components/Config/LoadConfig.tsx
+++ b/src/components/Config/LoadConfig.tsx
@@ -4,18 +4,20 @@ import { showNotification } from '@mantine/notifications';
import { IconCheck as Check, IconPhoto, IconUpload, IconX, IconX as X } from '@tabler/icons-react';
import { setCookie } from 'cookies-next';
import { useTranslation } from 'next-i18next';
+import { useRouter } from 'next/router';
import { useConfigStore } from '../../config/store';
import { ConfigType } from '../../types/config';
+import { api } from '~/utils/api';
export const LoadConfigComponent = () => {
- const { addConfig } = useConfigStore();
const theme = useMantineTheme();
const { t } = useTranslation('settings/general/config-changer');
+ const { mutateAsync: loadAsync } = useLoadConfig();
return (
{
- const fileName = files[0].name.replaceAll('.json', '');
+ const configName = files[0].name.replaceAll('.json', '');
const fileText = await files[0].text();
try {
@@ -32,26 +34,7 @@ export const LoadConfigComponent = () => {
}
const newConfig: ConfigType = JSON.parse(fileText);
-
- await addConfig(fileName, newConfig, true);
- showNotification({
- autoClose: 5000,
- radius: 'md',
- title: (
-
- {t('dropzone.notifications.loadedSuccessfully.title', {
- configName: fileName,
- })}
-
- ),
- color: 'green',
- icon: ,
- message: undefined,
- });
- setCookie('config-name', fileName, {
- maxAge: 60 * 60 * 24 * 30,
- sameSite: 'strict',
- });
+ await loadAsync({ name: configName, config: newConfig });
}}
accept={['application/json']}
>
@@ -89,3 +72,34 @@ export const LoadConfigComponent = () => {
);
};
+
+const useLoadConfig = () => {
+ const { t } = useTranslation('settings/general/config-changer');
+ const { addConfig } = useConfigStore();
+ const router = useRouter();
+ return api.config.save.useMutation({
+ async onSuccess(_data, variables) {
+ await addConfig(variables.name, variables.config);
+
+ showNotification({
+ autoClose: 5000,
+ radius: 'md',
+ title: (
+
+ {t('dropzone.notifications.loadedSuccessfully.title', {
+ configName: variables.name,
+ })}
+
+ ),
+ color: 'green',
+ icon: ,
+ message: undefined,
+ });
+ setCookie('config-name', variables.name, {
+ maxAge: 60 * 60 * 24 * 30,
+ sameSite: 'strict',
+ });
+ router.push(`/${variables.name}`);
+ },
+ });
+};
diff --git a/src/components/Dashboard/Tiles/Apps/AppPing.tsx b/src/components/Dashboard/Tiles/Apps/AppPing.tsx
index b2e3bd559..5af4b0938 100644
--- a/src/components/Dashboard/Tiles/Apps/AppPing.tsx
+++ b/src/components/Dashboard/Tiles/Apps/AppPing.tsx
@@ -1,10 +1,10 @@
import { Indicator, Tooltip } from '@mantine/core';
import Consola from 'consola';
-import { useQuery } from '@tanstack/react-query';
import { motion } from 'framer-motion';
import { useTranslation } from 'next-i18next';
import { useConfigContext } from '../../../../config/provider';
import { AppType } from '../../../../types/app';
+import { api } from '~/utils/api';
interface AppPingProps {
app: AppType;
@@ -16,18 +16,7 @@ export const AppPing = ({ app }: AppPingProps) => {
const active =
(config?.settings.customization.layout.enabledPing && app.network.enabledStatusChecker) ??
false;
- const { data, isLoading } = useQuery({
- queryKey: ['ping', { id: app.id, name: app.name }],
- queryFn: async () => {
- const response = await fetch(`/api/modules/ping?url=${encodeURI(app.url)}`);
- const isOk = getIsOk(app, response.status);
- return {
- status: response.status,
- state: isOk ? 'online' : 'down',
- };
- },
- enabled: active,
- });
+ const { data, isLoading, error } = usePingQuery(app, active);
const isOnline = data?.state === 'online';
@@ -49,7 +38,7 @@ export const AppPing = ({ app }: AppPingProps) => {
? t('states.loading')
: isOnline
? t('states.online', { response: data.status })
- : t('states.offline', { response: data?.status })
+ : t('states.offline', { response: data?.status ?? error?.data?.httpStatus })
}
>
{
);
};
+const usePingQuery = (app: AppType, isEnabled: boolean) =>
+ api.app.ping.useQuery(
+ {
+ url: app.url,
+ },
+ {
+ enabled: isEnabled,
+ select: (data) => {
+ const statusCode = data.status;
+ const isOk = getIsOk(app, statusCode);
+ return {
+ status: statusCode,
+ state: isOk ? ('online' as const) : ('down' as const),
+ };
+ },
+ }
+ );
+
const getIsOk = (app: AppType, status: number) => {
-if (app.network.okStatus === undefined || app.network.statusCodes.length >= 1) {
-Consola.log('Using new status codes');
-return app.network.statusCodes.includes(status.toString());
-}
-Consola.warn('Using deprecated okStatus');
-return app.network.okStatus.includes(status);
+ if (app.network.okStatus === undefined || app.network.statusCodes.length >= 1) {
+ Consola.log('Using new status codes');
+ return app.network.statusCodes.includes(status.toString());
+ }
+ Consola.warn('Using deprecated okStatus');
+ return app.network.okStatus.includes(status);
};
diff --git a/src/components/IconSelector/IconSelector.tsx b/src/components/IconSelector/IconSelector.tsx
index 55a8ea377..5929fc22c 100644
--- a/src/components/IconSelector/IconSelector.tsx
+++ b/src/components/IconSelector/IconSelector.tsx
@@ -15,9 +15,9 @@ import {
} from '@mantine/core';
import { IconSearch } from '@tabler/icons-react';
import { useTranslation } from 'next-i18next';
-import { useGetDashboardIcons } from '../../hooks/icons/useGetDashboardIcons';
import { humanFileSize } from '../../tools/humanFileSize';
import { DebouncedImage } from './DebouncedImage';
+import { api } from '~/utils/api';
export const IconSelector = forwardRef(
(
@@ -175,3 +175,12 @@ interface ItemProps extends SelectItemProps {
size: number;
copyright: string | undefined;
}
+
+const useGetDashboardIcons = () =>
+ api.icon.all.useQuery(undefined, {
+ refetchOnMount: false,
+ // Cache for infinity, refetch every so often.
+ cacheTime: Infinity,
+ staleTime: 1000 * 60 * 5, // 5 minutes
+ refetchOnWindowFocus: false,
+ });
diff --git a/src/components/Settings/Common/Config/ConfigActions.tsx b/src/components/Settings/Common/Config/ConfigActions.tsx
index 2cce6ac20..700ccd70d 100644
--- a/src/components/Settings/Common/Config/ConfigActions.tsx
+++ b/src/components/Settings/Common/Config/ConfigActions.tsx
@@ -10,23 +10,28 @@ import {
import { useDisclosure } from '@mantine/hooks';
import { openConfirmModal } from '@mantine/modals';
import { showNotification } from '@mantine/notifications';
-import { IconAlertTriangle, IconCheck, IconCopy, IconDownload, IconTrash } from '@tabler/icons-react';
+import {
+ IconAlertTriangle,
+ IconCheck,
+ IconCopy,
+ IconDownload,
+ IconTrash,
+ IconX,
+} from '@tabler/icons-react';
import fileDownload from 'js-file-download';
import { Trans, useTranslation } from 'next-i18next';
import { useRouter } from 'next/router';
import { useConfigContext } from '../../../../config/provider';
import { useConfigStore } from '../../../../config/store';
-import { useDeleteConfigMutation } from '../../../../tools/config/mutations/useDeleteConfigMutation';
import Tip from '../../../layout/Tip';
import { CreateConfigCopyModal } from './CreateCopyModal';
+import { api } from '~/utils/api';
export default function ConfigActions() {
- const router = useRouter();
const { t } = useTranslation(['settings/general/config-changer', 'settings/common', 'common']);
const [createCopyModalOpened, createCopyModal] = useDisclosure(false);
const { config } = useConfigContext();
- const { removeConfig } = useConfigStore();
- const { mutateAsync } = useDeleteConfigMutation(config?.configProperties.name ?? 'default');
+ const { mutateAsync } = useDeleteConfigMutation();
if (!config) return null;
@@ -61,28 +66,9 @@ export default function ConfigActions() {
},
zIndex: 201,
onConfirm: async () => {
- const response = await mutateAsync();
-
- if (response.error) {
- showNotification({
- title: t('buttons.delete.notifications.deleteFailedDefaultConfig.title'),
- message: t('buttons.delete.notifications.deleteFailedDefaultConfig.message'),
- });
- return;
- }
-
- showNotification({
- title: t('buttons.delete.notifications.deleted.title'),
- icon: ,
- color: 'green',
- autoClose: 1500,
- radius: 'md',
- message: t('buttons.delete.notifications.deleted.message'),
+ const response = await mutateAsync({
+ name: config?.configProperties.name ?? 'default',
});
-
- removeConfig(config?.configProperties.name ?? 'default');
-
- router.push('/');
},
});
};
@@ -124,6 +110,49 @@ export default function ConfigActions() {
);
}
+const useDeleteConfigMutation = () => {
+ const { t } = useTranslation(['settings/general/config-changer']);
+ const router = useRouter();
+ const { removeConfig } = useConfigStore();
+
+ return api.config.delete.useMutation({
+ onError(error) {
+ if (error.data?.code === 'FORBIDDEN') {
+ showNotification({
+ title: t('buttons.delete.notifications.deleteFailedDefaultConfig.title'),
+ icon: ,
+ color: 'red',
+ autoClose: 1500,
+ radius: 'md',
+ message: t('buttons.delete.notifications.deleteFailedDefaultConfig.message'),
+ });
+ }
+ showNotification({
+ title: t('buttons.delete.notifications.deleteFailed.title'),
+ icon: ,
+ color: 'red',
+ autoClose: 1500,
+ radius: 'md',
+ message: t('buttons.delete.notifications.deleteFailed.message'),
+ });
+ },
+ onSuccess(data, variables) {
+ showNotification({
+ title: t('buttons.delete.notifications.deleted.title'),
+ icon: ,
+ color: 'green',
+ autoClose: 1500,
+ radius: 'md',
+ message: t('buttons.delete.notifications.deleted.message'),
+ });
+
+ removeConfig(variables.name);
+
+ router.push('/');
+ },
+ });
+};
+
const useStyles = createStyles(() => ({
actionIcon: {
width: 'auto',
diff --git a/src/components/Settings/Common/Config/CreateCopyModal.tsx b/src/components/Settings/Common/Config/CreateCopyModal.tsx
index fb924f804..bf1409d1f 100644
--- a/src/components/Settings/Common/Config/CreateCopyModal.tsx
+++ b/src/components/Settings/Common/Config/CreateCopyModal.tsx
@@ -1,8 +1,11 @@
import { Button, Group, Modal, TextInput, Title } from '@mantine/core';
import { useForm } from '@mantine/form';
import { useTranslation } from 'next-i18next';
+import { IconCheck, IconX } from '@tabler/icons-react';
+import { showNotification } from '@mantine/notifications';
import { useConfigStore } from '../../../../config/store';
-import { useCopyConfigMutation } from '../../../../tools/config/mutations/useCopyConfigMutation';
+import { useConfigContext } from '~/config/provider';
+import { api } from '~/utils/api';
interface CreateConfigCopyModalProps {
opened: boolean;
@@ -16,6 +19,7 @@ export const CreateConfigCopyModal = ({
initialConfigName,
}: CreateConfigCopyModalProps) => {
const { configs } = useConfigStore();
+ const { config } = useConfigContext();
const { t } = useTranslation(['settings/general/config-changer']);
const form = useForm({
@@ -40,7 +44,7 @@ export const CreateConfigCopyModal = ({
validateInputOnBlur: true,
});
- const { mutateAsync } = useCopyConfigMutation(form.values.configName);
+ const { mutateAsync } = useCopyConfigMutation();
const handleClose = () => {
form.setFieldValue('configName', initialConfigName);
@@ -50,7 +54,17 @@ export const CreateConfigCopyModal = ({
const handleSubmit = async (values: typeof form.values) => {
if (!form.isValid) return;
- await mutateAsync();
+ if (!config) {
+ throw new Error('config is not defiend');
+ }
+
+ const copiedConfig = config;
+ copiedConfig.configProperties.name = form.values.configName;
+
+ await mutateAsync({
+ name: form.values.configName,
+ config: copiedConfig,
+ });
closeModal();
};
@@ -76,3 +90,33 @@ export const CreateConfigCopyModal = ({
);
};
+
+const useCopyConfigMutation = () => {
+ const { t } = useTranslation(['settings/general/config-changer']);
+ const utils = api.useContext();
+
+ return api.config.save.useMutation({
+ onSuccess(_data, variables) {
+ showNotification({
+ title: t('modal.copy.events.configCopied.title'),
+ icon: ,
+ color: 'green',
+ autoClose: 1500,
+ radius: 'md',
+ message: t('modal.copy.events.configCopied.message', { configName: variables.name }),
+ });
+ // Invalidate a query to fetch new config
+ utils.config.all.invalidate();
+ },
+ onError(_error, variables) {
+ showNotification({
+ title: t('modal.events.configNotCopied.title'),
+ icon: ,
+ color: 'red',
+ autoClose: 1500,
+ radius: 'md',
+ message: t('modal.events.configNotCopied.message', { configName: variables.name }),
+ });
+ },
+ });
+};
diff --git a/src/components/layout/header/Actions/ToggleEditMode/ToggleEditMode.tsx b/src/components/layout/header/Actions/ToggleEditMode/ToggleEditMode.tsx
index 481e61f42..3cbe17e15 100644
--- a/src/components/layout/header/Actions/ToggleEditMode/ToggleEditMode.tsx
+++ b/src/components/layout/header/Actions/ToggleEditMode/ToggleEditMode.tsx
@@ -2,13 +2,13 @@ import { ActionIcon, Button, Group, Text, Title, Tooltip } from '@mantine/core';
import { useHotkeys, useWindowEvent } from '@mantine/hooks';
import { hideNotification, showNotification } from '@mantine/notifications';
import { IconEditCircle, IconEditCircleOff } from '@tabler/icons-react';
-import axios from 'axios';
import Consola from 'consola';
import { getCookie } from 'cookies-next';
import { Trans, useTranslation } from 'next-i18next';
import { useConfigContext } from '../../../../../config/provider';
import { useScreenSmallerThan } from '../../../../../hooks/useScreenSmallerThan';
+import { api } from '~/utils/api';
import { useEditModeStore } from '../../../../Dashboard/Views/useEditModeStore';
import { useNamedWrapperColumnCount } from '../../../../Dashboard/Wrappers/gridstack/store';
import { useCardStyles } from '../../../useCardStyles';
@@ -28,6 +28,7 @@ export const ToggleEditModeAction = () => {
const smallerThanSm = useScreenSmallerThan('sm');
const { config } = useConfigContext();
const { classes } = useCardStyles(true);
+ const { mutateAsync: saveConfig } = api.config.save.useMutation();
useHotkeys([['mod+E', toggleEditMode]]);
@@ -41,11 +42,12 @@ export const ToggleEditModeAction = () => {
return undefined;
});
- const toggleButtonClicked = () => {
+ const toggleButtonClicked = async () => {
toggleEditMode();
- if (enabled || config === undefined || config?.schemaVersion === undefined) {
+ if (config === undefined || config?.schemaVersion === undefined) return;
+ if (enabled) {
const configName = getCookie('config-name')?.toString() ?? 'default';
- axios.put(`/api/configs/${configName}`, { ...config });
+ await saveConfig({ name: configName, config });
Consola.log('Saved config to server', configName);
hideNotification('toggle-edit-mode');
} else if (!enabled) {
diff --git a/src/components/layout/header/Search.tsx b/src/components/layout/header/Search.tsx
index 40d6202cb..1327d617e 100644
--- a/src/components/layout/header/Search.tsx
+++ b/src/components/layout/header/Search.tsx
@@ -13,10 +13,9 @@ import {
import { useDebouncedValue, useHotkeys } from '@mantine/hooks';
import { showNotification } from '@mantine/notifications';
import { IconBrandYoutube, IconDownload, IconMovie, IconSearch } from '@tabler/icons-react';
-import { useQuery } from '@tanstack/react-query';
-import axios from 'axios';
import { useTranslation } from 'next-i18next';
import React, { forwardRef, useEffect, useRef, useState } from 'react';
+import { api } from '~/utils/api';
import { useConfigContext } from '../../../config/provider';
import { OverseerrMediaDisplay } from '../../../modules/common';
import { IModule } from '../../../modules/ModuleTypes';
@@ -141,26 +140,12 @@ export function Search() {
const openTarget = getOpenTarget(config);
const [opened, setOpened] = useState(false);
- const {
- data: OverseerrResults,
- isLoading,
- error,
- } = useQuery(
- ['overseerr', debounced],
- async () => {
- const res = await axios.get(`/api/modules/overseerr?query=${debounced}`);
- return res.data.results ?? [];
- },
- {
- enabled:
- isOverseerrEnabled === true &&
- selectedSearchEngine.value === 'overseerr' &&
- debounced.length > 3,
- refetchOnWindowFocus: false,
- refetchOnMount: false,
- refetchInterval: false,
- }
- );
+ const isOverseerrSearchEnabled =
+ isOverseerrEnabled === true &&
+ selectedSearchEngine.value === 'overseerr' &&
+ debounced.length > 3;
+
+ const { data: overseerrResults } = useOverseerrSearchQuery(debounced, isOverseerrSearchEnabled);
const isModuleEnabled = config?.settings.customization.layout.enabledSearchbar;
if (!isModuleEnabled) {
@@ -173,7 +158,7 @@ export function Search() {
0 && opened && searchQuery.length > 3) ??
+ (overseerrResults && overseerrResults.length > 0 && opened && searchQuery.length > 3) ??
false
}
position="bottom"
@@ -224,11 +209,11 @@ export function Search() {
- {OverseerrResults &&
- OverseerrResults.slice(0, 4).map((result: any, index: number) => (
+ {overseerrResults &&
+ overseerrResults.slice(0, 4).map((result: any, index: number) => (
- {index < OverseerrResults.length - 1 && index < 3 && (
+ {index < overseerrResults.length - 1 && index < 3 && (
)}
@@ -312,3 +297,19 @@ const getOpenTarget = (config: ConfigType | undefined): '_blank' | '_self' => {
return config.settings.common.searchEngine.properties.openInNewTab ? '_blank' : '_self';
};
+
+const useOverseerrSearchQuery = (query: string, isEnabled: boolean) => {
+ const { name: configName } = useConfigContext();
+ return api.overseerr.all.useQuery(
+ {
+ query,
+ configName: configName!,
+ },
+ {
+ enabled: isEnabled,
+ refetchOnWindowFocus: false,
+ refetchOnMount: false,
+ refetchInterval: false,
+ }
+ );
+};
diff --git a/src/config/store.ts b/src/config/store.ts
index bca63e0b7..6c0a45279 100644
--- a/src/config/store.ts
+++ b/src/config/store.ts
@@ -1,5 +1,5 @@
-import axios from 'axios';
import { create } from 'zustand';
+import { trcpProxyClient } from '~/utils/api';
import { ConfigType } from '../types/config';
export const useConfigStore = create((set, get) => ({
@@ -13,7 +13,7 @@ export const useConfigStore = create((set, get) => ({
],
}));
},
- addConfig: async (name: string, config: ConfigType, shouldSaveConfigToFileSystem = true) => {
+ addConfig: async (name: string, config: ConfigType) => {
set((old) => ({
...old,
configs: [
@@ -21,11 +21,6 @@ export const useConfigStore = create((set, get) => ({
{ value: config, increaseVersion: () => {} },
],
}));
-
- if (!shouldSaveConfigToFileSystem) {
- return;
- }
- axios.put(`/api/configs/${name}`, { ...config });
},
removeConfig: (name: string) => {
set((old) => ({
@@ -66,7 +61,10 @@ export const useConfigStore = create((set, get) => ({
}
if (shouldSaveConfigToFileSystem) {
- axios.put(`/api/configs/${name}`, { ...updatedConfig });
+ trcpProxyClient.config.save.mutate({
+ name,
+ config: updatedConfig,
+ });
}
},
}));
@@ -74,11 +72,7 @@ export const useConfigStore = create((set, get) => ({
interface UseConfigStoreType {
configs: { increaseVersion: () => void; value: ConfigType }[];
initConfig: (name: string, config: ConfigType, increaseVersion: () => void) => void;
- addConfig: (
- name: string,
- config: ConfigType,
- shouldSaveConfigToFileSystem: boolean
- ) => Promise;
+ addConfig: (name: string, config: ConfigType) => Promise;
removeConfig: (name: string) => void;
updateConfig: (
name: string,
diff --git a/src/hooks/icons/useGetDashboardIcons.tsx b/src/hooks/icons/useGetDashboardIcons.tsx
deleted file mode 100644
index bd1cdc6f7..000000000
--- a/src/hooks/icons/useGetDashboardIcons.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { useQuery } from '@tanstack/react-query';
-import { NormalizedIconRepositoryResult } from '../../tools/server/images/abstract-icons-repository';
-
-export const useGetDashboardIcons = () =>
- useQuery({
- queryKey: ['repository-icons'],
- queryFn: async () => {
- const response = await fetch('/api/icons/');
- const data = await response.json();
- return data as NormalizedIconRepositoryResult[];
- },
- refetchOnMount: false,
- // Cache for infinity, refetch every so often.
- cacheTime: Infinity,
- staleTime: 1000 * 60 * 5, // 5 minutes
- refetchOnWindowFocus: false,
- });
diff --git a/src/hooks/widgets/dashDot/api.ts b/src/hooks/widgets/dashDot/api.ts
index 36c82a55e..c072b3379 100644
--- a/src/hooks/widgets/dashDot/api.ts
+++ b/src/hooks/widgets/dashDot/api.ts
@@ -1,154 +1,94 @@
-import { useMutation, useQuery } from '@tanstack/react-query';
-import axios from 'axios';
-import { Results } from 'sabnzbd-api';
-import type {
- UsenetQueueRequestParams,
- UsenetQueueResponse,
-} from '../../../pages/api/modules/usenet/queue';
-import type {
- UsenetHistoryRequestParams,
- UsenetHistoryResponse,
-} from '../../../pages/api/modules/usenet/history';
-import { UsenetInfoRequestParams, UsenetInfoResponse } from '../../../pages/api/modules/usenet';
+import { useConfigContext } from '~/config/provider';
+import { RouterInputs, api } from '~/utils/api';
+import { UsenetInfoRequestParams } from '../../../pages/api/modules/usenet';
+import type { UsenetHistoryRequestParams } from '../../../pages/api/modules/usenet/history';
import { UsenetPauseRequestParams } from '../../../pages/api/modules/usenet/pause';
-import { queryClient } from '../../../tools/server/configurations/tanstack/queryClient.tool';
+import type { UsenetQueueRequestParams } from '../../../pages/api/modules/usenet/queue';
import { UsenetResumeRequestParams } from '../../../pages/api/modules/usenet/resume';
const POLLING_INTERVAL = 2000;
-export const useGetUsenetInfo = (params: UsenetInfoRequestParams) =>
- useQuery(
- ['usenetInfo', params.appId],
- async () =>
- (
- await axios.get('/api/modules/usenet', {
- params,
- })
- ).data,
+export const useGetUsenetInfo = ({ appId }: UsenetInfoRequestParams) => {
+ const { name: configName } = useConfigContext();
+
+ return api.usenet.info.useQuery(
+ {
+ appId,
+ configName: configName!,
+ },
{
refetchInterval: POLLING_INTERVAL,
keepPreviousData: true,
retry: 2,
- enabled: Boolean(params.appId),
+ enabled: !!appId,
}
);
+};
-export const useGetUsenetDownloads = (params: UsenetQueueRequestParams) =>
- useQuery(
- ['usenetDownloads', ...Object.values(params)],
- async () =>
- (
- await axios.get('/api/modules/usenet/queue', {
- params,
- })
- ).data,
+export const useGetUsenetDownloads = (params: UsenetQueueRequestParams) => {
+ const { name: configName } = useConfigContext();
+ return api.usenet.queue.useQuery(
+ {
+ configName: configName!,
+ ...params,
+ },
{
refetchInterval: POLLING_INTERVAL,
keepPreviousData: true,
retry: 2,
}
);
+};
-export const useGetUsenetHistory = (params: UsenetHistoryRequestParams) =>
- useQuery(
- ['usenetHistory', ...Object.values(params)],
- async () =>
- (
- await axios.get('/api/modules/usenet/history', {
- params,
- })
- ).data,
+export const useGetUsenetHistory = (params: UsenetHistoryRequestParams) => {
+ const { name: configName } = useConfigContext();
+ return api.usenet.history.useQuery(
+ {
+ configName: configName!,
+ ...params,
+ },
{
refetchInterval: POLLING_INTERVAL,
keepPreviousData: true,
retry: 2,
}
);
+};
-export const usePauseUsenetQueue = (params: UsenetPauseRequestParams) =>
- useMutation(
- ['usenetPause', ...Object.values(params)],
- async () =>
- (
- await axios.post(
- '/api/modules/usenet/pause',
- {},
- {
- params,
- }
- )
- ).data,
- {
- async onMutate() {
- await queryClient.cancelQueries(['usenetInfo', params.appId]);
- const previousInfo = queryClient.getQueryData([
- 'usenetInfo',
- params.appId,
- ]);
+export const usePauseUsenetQueueMutation = (params: UsenetPauseRequestParams) => {
+ const { name: configName } = useConfigContext();
+ const { mutateAsync } = api.usenet.pause.useMutation();
+ const utils = api.useContext();
+ return async (variables: Omit) => {
+ await mutateAsync(
+ {
+ configName: configName!,
+ ...variables,
+ },
+ {
+ onSettled() {
+ utils.usenet.info.invalidate({ appId: params.appId });
+ },
+ }
+ );
+ };
+};
- if (previousInfo) {
- queryClient.setQueryData(['usenetInfo', params.appId], {
- ...previousInfo,
- paused: true,
- });
- }
-
- return { previousInfo };
+export const useResumeUsenetQueueMutation = (params: UsenetResumeRequestParams) => {
+ const { name: configName } = useConfigContext();
+ const { mutateAsync } = api.usenet.resume.useMutation();
+ const utils = api.useContext();
+ return async (variables: Omit) => {
+ await mutateAsync(
+ {
+ configName: configName!,
+ ...variables,
},
- onError(err, _, context) {
- if (context?.previousInfo) {
- queryClient.setQueryData(
- ['usenetInfo', params.appId],
- context.previousInfo
- );
- }
- },
- onSettled() {
- queryClient.invalidateQueries(['usenetInfo', params.appId]);
- },
- }
- );
-
-export const useResumeUsenetQueue = (params: UsenetResumeRequestParams) =>
- useMutation(
- ['usenetResume', ...Object.values(params)],
- async () =>
- (
- await axios.post(
- '/api/modules/usenet/resume',
- {},
- {
- params,
- }
- )
- ).data,
- {
- async onMutate() {
- await queryClient.cancelQueries(['usenetInfo', params.appId]);
- const previousInfo = queryClient.getQueryData([
- 'usenetInfo',
- params.appId,
- ]);
-
- if (previousInfo) {
- queryClient.setQueryData(['usenetInfo', params.appId], {
- ...previousInfo,
- paused: false,
- });
- }
-
- return { previousInfo };
- },
- onError(err, _, context) {
- if (context?.previousInfo) {
- queryClient.setQueryData(
- ['usenetInfo', params.appId],
- context.previousInfo
- );
- }
- },
- onSettled() {
- queryClient.invalidateQueries(['usenetInfo', params.appId]);
- },
- }
- );
+ {
+ onSettled() {
+ utils.usenet.info.invalidate({ appId: params.appId });
+ },
+ }
+ );
+ };
+};
diff --git a/src/hooks/widgets/download-speed/useGetNetworkSpeed.tsx b/src/hooks/widgets/download-speed/useGetNetworkSpeed.tsx
index 2af50a67e..27adce34b 100644
--- a/src/hooks/widgets/download-speed/useGetNetworkSpeed.tsx
+++ b/src/hooks/widgets/download-speed/useGetNetworkSpeed.tsx
@@ -1,12 +1,14 @@
-import { useQuery } from '@tanstack/react-query';
-import { NormalizedDownloadQueueResponse } from '../../../types/api/downloads/queue/NormalizedDownloadQueueResponse';
+import { useConfigContext } from '~/config/provider';
+import { api } from '~/utils/api';
-export const useGetDownloadClientsQueue = () =>
- useQuery({
- queryKey: ['network-speed'],
- queryFn: async (): Promise => {
- const response = await fetch('/api/modules/downloads');
- return response.json();
+export const useGetDownloadClientsQueue = () => {
+ const { name: configName } = useConfigContext();
+ return api.download.get.useQuery(
+ {
+ configName: configName!,
},
- refetchInterval: 3000,
- });
+ {
+ refetchInterval: 3000,
+ }
+ );
+};
diff --git a/src/hooks/widgets/media-servers/useGetMediaServers.tsx b/src/hooks/widgets/media-servers/useGetMediaServers.tsx
index 7b3d10164..7a0b86029 100644
--- a/src/hooks/widgets/media-servers/useGetMediaServers.tsx
+++ b/src/hooks/widgets/media-servers/useGetMediaServers.tsx
@@ -1,17 +1,20 @@
-import { useQuery } from '@tanstack/react-query';
-import { MediaServersResponseType } from '../../../types/api/media-server/response';
+import { useConfigContext } from '~/config/provider';
+import { api } from '~/utils/api';
interface GetMediaServersParams {
enabled: boolean;
}
-export const useGetMediaServers = ({ enabled }: GetMediaServersParams) =>
- useQuery({
- queryKey: ['media-servers'],
- queryFn: async (): Promise => {
- const response = await fetch('/api/modules/media-server');
- return response.json();
+export const useGetMediaServers = ({ enabled }: GetMediaServersParams) => {
+ const { name: configName } = useConfigContext();
+
+ return api.mediaServer.all.useQuery(
+ {
+ configName: configName!,
},
- enabled,
- refetchInterval: 10 * 1000,
- });
+ {
+ enabled,
+ refetchInterval: 10 * 1000,
+ }
+ );
+};
diff --git a/src/modules/Docker/ContainerActionBar.tsx b/src/modules/Docker/ContainerActionBar.tsx
index 4a8e98177..27dc4c400 100644
--- a/src/modules/Docker/ContainerActionBar.tsx
+++ b/src/modules/Docker/ContainerActionBar.tsx
@@ -10,56 +10,16 @@ import {
IconRotateClockwise,
IconTrash,
} from '@tabler/icons-react';
-import axios from 'axios';
import Dockerode from 'dockerode';
import { useTranslation } from 'next-i18next';
import { useState } from 'react';
import { v4 as uuidv4 } from 'uuid';
+import { RouterInputs, api } from '~/utils/api';
import { useConfigContext } from '../../config/provider';
import { openContextModalGeneric } from '../../tools/mantineModalManagerExtensions';
import { MatchingImages, ServiceType, tryMatchPort } from '../../tools/types';
import { AppType } from '../../types/app';
-function sendDockerCommand(
- action: string,
- containerId: string,
- containerName: string,
- reload: () => void,
- t: (key: string) => string,
-) {
- notifications.show({
- id: containerId,
- loading: true,
- title: `${t(`actions.${action}.start`)} ${containerName}`,
- message: undefined,
- autoClose: false,
- withCloseButton: false,
- });
- axios
- .get(`/api/docker/container/${containerId}?action=${action}`)
- .then((res) => {
- notifications.show({
- id: containerId,
- title: containerName,
- message: `${t(`actions.${action}.end`)} ${containerName}`,
- icon: ,
- autoClose: 2000,
- });
- })
- .catch((err) => {
- notifications.update({
- id: containerId,
- color: 'red',
- title: t('errors.unknownError.title'),
- message: err.response.data.reason,
- autoClose: 2000,
- });
- })
- .finally(() => {
- reload();
- });
-}
-
export interface ContainerActionBarProps {
selected: Dockerode.ContainerInfo[];
reload: () => void;
@@ -68,8 +28,9 @@ export interface ContainerActionBarProps {
export default function ContainerActionBar({ selected, reload }: ContainerActionBarProps) {
const { t } = useTranslation('modules/docker');
const [isLoading, setisLoading] = useState(false);
- const { name: configName, config } = useConfigContext();
+ const { config } = useConfigContext();
const getLowestWrapper = () => config?.wrappers.sort((a, b) => a.position - b.position)[0];
+ const sendDockerCommand = useDockerActionMutation();
if (process.env.DISABLE_EDIT_MODE === 'true') {
return null;
@@ -96,11 +57,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
}
onClick={() =>
- Promise.all(
- selected.map((container) =>
- sendDockerCommand('restart', container.Id, container.Names[0].substring(1), reload, t)
- )
- )
+ Promise.all(selected.map((container) => sendDockerCommand(container, 'restart')))
}
variant="light"
color="orange"
@@ -112,11 +69,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
}
onClick={() =>
- Promise.all(
- selected.map((container) =>
- sendDockerCommand('stop', container.Id, container.Names[0].substring(1), reload, t)
- )
- )
+ Promise.all(selected.map((container) => sendDockerCommand(container, 'stop')))
}
variant="light"
color="red"
@@ -128,11 +81,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
}
onClick={() =>
- Promise.all(
- selected.map((container) =>
- sendDockerCommand('start', container.Id, container.Names[0].substring(1), reload, t)
- )
- )
+ Promise.all(selected.map((container) => sendDockerCommand(container, 'start')))
}
variant="light"
color="green"
@@ -147,11 +96,7 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
variant="light"
radius="md"
onClick={() =>
- Promise.all(
- selected.map((container) =>
- sendDockerCommand('remove', container.Id, container.Names[0].substring(1), reload, t)
- )
- )
+ Promise.all(selected.map((container) => sendDockerCommand(container, 'remove')))
}
disabled={selected.length === 0}
>
@@ -210,6 +155,55 @@ export default function ContainerActionBar({ selected, reload }: ContainerAction
);
}
+const useDockerActionMutation = () => {
+ const { t } = useTranslation('modules/docker');
+ const utils = api.useContext();
+ const mutation = api.docker.action.useMutation();
+
+ return async (
+ container: Dockerode.ContainerInfo,
+ action: RouterInputs['docker']['action']['action']
+ ) => {
+ const containerName = container.Names[0].substring(1);
+
+ notifications.show({
+ id: container.Id,
+ loading: true,
+ title: `${t(`actions.${action}.start`)} ${containerName}`,
+ message: undefined,
+ autoClose: false,
+ withCloseButton: false,
+ });
+
+ await mutation.mutateAsync(
+ { action, id: container.Id },
+ {
+ onSuccess: () => {
+ notifications.show({
+ id: container.Id,
+ title: containerName,
+ message: `${t(`actions.${action}.end`)} ${containerName}`,
+ icon: ,
+ autoClose: 2000,
+ });
+ },
+ onError: (err) => {
+ notifications.update({
+ id: container.Id,
+ color: 'red',
+ title: t('errors.unknownError.title'),
+ message: err.message,
+ autoClose: 2000,
+ });
+ },
+ onSettled: () => {
+ utils.docker.containers.invalidate();
+ },
+ }
+ );
+ };
+};
+
/**
* @deprecated legacy code
*/
diff --git a/src/modules/Docker/DockerModule.tsx b/src/modules/Docker/DockerModule.tsx
index 5c4e83d8e..f11bb18b2 100644
--- a/src/modules/Docker/DockerModule.tsx
+++ b/src/modules/Docker/DockerModule.tsx
@@ -1,14 +1,13 @@
import { ActionIcon, Drawer, Tooltip } from '@mantine/core';
import { useHotkeys } from '@mantine/hooks';
import { IconBrandDocker } from '@tabler/icons-react';
-import axios from 'axios';
import Docker from 'dockerode';
import { useTranslation } from 'next-i18next';
-import { useEffect, useState } from 'react';
-import { useQuery } from '@tanstack/react-query';
+import { useState } from 'react';
import { useCardStyles } from '../../components/layout/useCardStyles';
import { useConfigContext } from '../../config/provider';
+import { api } from '~/utils/api';
import ContainerActionBar from './ContainerActionBar';
import DockerTable from './DockerTable';
@@ -20,22 +19,13 @@ export default function DockerMenuButton(props: any) {
const dockerEnabled = config?.settings.customization.layout.enabledDocker || false;
- const { data, isLoading, refetch } = useQuery({
- queryKey: ['containers'],
- queryFn: async () => {
- const containers = await axios.get('/api/docker/containers');
- return containers.data;
- },
+ const { data, refetch } = api.docker.containers.useQuery(undefined, {
enabled: dockerEnabled,
});
useHotkeys([['mod+B', () => setOpened(!opened)]]);
const { t } = useTranslation('modules/docker');
- useEffect(() => {
- refetch();
- }, [config?.settings]);
-
const reload = () => {
refetch();
setSelection([]);
diff --git a/src/modules/index.ts b/src/modules/index.ts
index 3f69cd0de..54bebdbc1 100644
--- a/src/modules/index.ts
+++ b/src/modules/index.ts
@@ -1,2 +1 @@
-export * from './ping';
export * from './overseerr';
diff --git a/src/modules/overseerr/RequestModal.tsx b/src/modules/overseerr/RequestModal.tsx
index 61ad678ea..227ab0e73 100644
--- a/src/modules/overseerr/RequestModal.tsx
+++ b/src/modules/overseerr/RequestModal.tsx
@@ -1,14 +1,15 @@
import { Alert, Button, Checkbox, createStyles, Group, Modal, Stack, Table } from '@mantine/core';
import { showNotification, updateNotification } from '@mantine/notifications';
import { IconAlertCircle, IconCheck, IconDownload } from '@tabler/icons-react';
-import axios from 'axios';
import Consola from 'consola';
import { useTranslation } from 'next-i18next';
import { useState } from 'react';
+import { useConfigContext } from '~/config/provider';
+import { api } from '~/utils/api';
import { useColorTheme } from '../../tools/color';
import { MovieResult } from './Movie.d';
-import { MediaType, Result } from './SearchResult.d';
+import { Result } from './SearchResult.d';
import { TvShowResult, TvShowResultSeason } from './TvShow.d';
interface RequestModalProps {
@@ -27,20 +28,22 @@ const useStyles = createStyles((theme) => ({
}));
export function RequestModal({ base, opened, setOpened }: RequestModalProps) {
- const [result, setResult] = useState();
- const { secondaryColor } = useColorTheme();
+ const { name: configName } = useConfigContext();
+ const { data: result } = api.overseerr.byId.useQuery(
+ {
+ id: base.id,
+ type: base.mediaType,
+ configName: configName!,
+ },
+ {
+ enabled: opened,
+ }
+ );
- function getResults(base: Result) {
- axios.get(`/api/modules/overseerr/${base.id}?type=${base.mediaType}`).then((res) => {
- setResult(res.data);
- });
- }
- if (opened && !result) {
- getResults(base);
- }
if (!result || !opened) {
return null;
}
+
return base.mediaType === 'movie' ? (
) : (
@@ -58,6 +61,7 @@ export function MovieRequestModal({
setOpened: (opened: boolean) => void;
}) {
const { secondaryColor } = useColorTheme();
+ const requestMediaAsync = useMediaRequestMutation();
const { t } = useTranslation('modules/overseerr');
return (
@@ -93,7 +97,7 @@ export function MovieRequestModal({
- refetch()}
- bottom={10}
- styles={{
- root: {
- borderColor: 'red',
- },
- }}
- >
- {isFetching ? : }
-
+
);
}
+export const useGetRssFeeds = (
+ configName: string | undefined,
+ feedUrls: string[],
+ refreshInterval: number,
+ widgetId: string
+) =>
+ api.rss.all.useQuery(
+ {
+ configName: configName ?? '',
+ feedUrls,
+ widgetId,
+ },
+ {
+ // Cache the results for 24 hours
+ cacheTime: 1000 * 60 * 60 * 24,
+ staleTime: 1000 * 60 * refreshInterval,
+ enabled: !!configName,
+ }
+ );
+
+interface RefetchButtonProps {
+ refetch: () => void;
+ isFetching: boolean;
+}
+
+const RefetchButton = ({ isFetching, refetch }: RefetchButtonProps) => (
+ refetch()}
+ bottom={10}
+ styles={{
+ root: {
+ borderColor: 'red',
+ },
+ }}
+ >
+ {isFetching ? : }
+
+);
+
const InfoDisplay = ({ date, title }: { date: string; title: string | undefined }) => (
diff --git a/src/widgets/useNet/UseNetTile.tsx b/src/widgets/useNet/UseNetTile.tsx
index 0fac00916..79aee3136 100644
--- a/src/widgets/useNet/UseNetTile.tsx
+++ b/src/widgets/useNet/UseNetTile.tsx
@@ -10,8 +10,8 @@ import { useConfigContext } from '../../config/provider';
import { MIN_WIDTH_MOBILE } from '../../constants/constants';
import {
useGetUsenetInfo,
- usePauseUsenetQueue,
- useResumeUsenetQueue,
+ usePauseUsenetQueueMutation,
+ useResumeUsenetQueueMutation,
} from '../../hooks/widgets/dashDot/api';
import { humanFileSize } from '../../tools/humanFileSize';
import { AppIntegrationType } from '../../types/app';
@@ -60,8 +60,8 @@ function UseNetTile({ widget }: UseNetTileProps) {
}
}, [downloadApps, selectedAppId]);
- const { mutate: pause } = usePauseUsenetQueue({ appId: selectedAppId! });
- const { mutate: resume } = useResumeUsenetQueue({ appId: selectedAppId! });
+ const pauseAsync = usePauseUsenetQueueMutation({ appId: selectedAppId! });
+ const resumeAsync = useResumeUsenetQueueMutation({ appId: selectedAppId! });
if (downloadApps.length === 0) {
return (
@@ -107,11 +107,25 @@ function UseNetTile({ widget }: UseNetTileProps) {
{!data ? null : data.paused ? (
- resume()} radius="xl" size="xs" fullWidth mt="sm">
+ resumeAsync({ appId: selectedAppId })}
+ radius="xl"
+ size="xs"
+ fullWidth
+ mt="sm"
+ >
{t('info.paused')}
) : (
- pause()} radius="xl" size="xs" fullWidth mt="sm">
+ pauseAsync({ appId: selectedAppId })}
+ radius="xl"
+ size="xs"
+ fullWidth
+ mt="sm"
+ >
{' '}
{dayjs.duration(data.eta, 's').format('HH:mm')}
diff --git a/src/widgets/useNet/UsenetHistoryList.tsx b/src/widgets/useNet/UsenetHistoryList.tsx
index a012e4689..1c129fb93 100644
--- a/src/widgets/useNet/UsenetHistoryList.tsx
+++ b/src/widgets/useNet/UsenetHistoryList.tsx
@@ -13,14 +13,13 @@ import {
} from '@mantine/core';
import { useElementSize } from '@mantine/hooks';
import { IconAlertCircle } from '@tabler/icons-react';
-import { AxiosError } from 'axios';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import { useTranslation } from 'next-i18next';
import { FunctionComponent, useState } from 'react';
import { useGetUsenetHistory } from '../../hooks/widgets/dashDot/api';
-import { humanFileSize } from '../../tools/humanFileSize';
import { parseDuration } from '../../tools/client/parseDuration';
+import { humanFileSize } from '../../tools/humanFileSize';
dayjs.extend(duration);
@@ -65,7 +64,7 @@ export const UsenetHistoryList: FunctionComponent = ({ a
>
{t('modules/usenet:history.error.message')}
- {(error as AxiosError)?.response?.data as string}
+ {error.message}
diff --git a/src/widgets/useNet/UsenetQueueList.tsx b/src/widgets/useNet/UsenetQueueList.tsx
index b3ed643dd..e5a613e86 100644
--- a/src/widgets/useNet/UsenetQueueList.tsx
+++ b/src/widgets/useNet/UsenetQueueList.tsx
@@ -16,7 +16,6 @@ import {
} from '@mantine/core';
import { useElementSize } from '@mantine/hooks';
import { IconAlertCircle, IconPlayerPause, IconPlayerPlay } from '@tabler/icons-react';
-import { AxiosError } from 'axios';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import { useTranslation } from 'next-i18next';
@@ -70,7 +69,7 @@ export const UsenetQueueList: FunctionComponent = ({ appId
>
{t('queue.error.message')}
- {(error as AxiosError)?.response?.data as string}
+ {error.data}
diff --git a/tsconfig.json b/tsconfig.json
index 922f76b10..1ca49efb1 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -22,7 +22,10 @@
{
"name": "next"
}
- ]
+ ],
+ "paths": {
+ "~/*": ["./src/*"]
+ },
},
"include": [
"next-env.d.ts",
diff --git a/vitest.config.ts b/vitest.config.ts
index fdeb8aa25..566d1cfcc 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -1,10 +1,11 @@
import react from '@vitejs/plugin-react';
+import tsconfigPaths from 'vite-tsconfig-paths';
import { configDefaults, defineConfig } from 'vitest/config';
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [react()],
+ plugins: [react(), tsconfigPaths()],
test: {
environment: 'happy-dom',
coverage: {
@@ -14,9 +15,6 @@ export default defineConfig({
exclude: ['.next/', '.yarn/', 'data/'],
},
setupFiles: ['./tests/setupVitest.ts'],
- exclude: [
- ...configDefaults.exclude,
- '.next',
- ],
+ exclude: [...configDefaults.exclude, '.next'],
},
});
diff --git a/yarn.lock b/yarn.lock
index 81f3f4a7e..00cbee4d1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2011,6 +2011,52 @@ __metadata:
languageName: node
linkType: hard
+"@trpc/client@npm:^10.29.1":
+ version: 10.29.1
+ resolution: "@trpc/client@npm:10.29.1"
+ peerDependencies:
+ "@trpc/server": 10.29.1
+ checksum: b617edb56e9ec7fa0665703761c666244e58b8a9da5a2fffcdef880cdde7ec8c92c20d1362ac1c836904518d94db247ec286cc17e01fc4eea41b7cafbf3479fe
+ languageName: node
+ linkType: hard
+
+"@trpc/next@npm:^10.29.1":
+ version: 10.29.1
+ resolution: "@trpc/next@npm:10.29.1"
+ dependencies:
+ react-ssr-prepass: ^1.5.0
+ peerDependencies:
+ "@tanstack/react-query": ^4.18.0
+ "@trpc/client": 10.29.1
+ "@trpc/react-query": 10.29.1
+ "@trpc/server": 10.29.1
+ next: "*"
+ react: ">=16.8.0"
+ react-dom: ">=16.8.0"
+ checksum: 6afd6d7b1702eda5a26e7e8d2d381729fceb180952a2382314f08dd945b6e3204716c532b0ac6ddef10135816879c61dbe4b27aa88bcd98100890ede8d42fb25
+ languageName: node
+ linkType: hard
+
+"@trpc/react-query@npm:^10.29.1":
+ version: 10.29.1
+ resolution: "@trpc/react-query@npm:10.29.1"
+ peerDependencies:
+ "@tanstack/react-query": ^4.18.0
+ "@trpc/client": 10.29.1
+ "@trpc/server": 10.29.1
+ react: ">=16.8.0"
+ react-dom: ">=16.8.0"
+ checksum: 3e52195eb894c0110b7fd99b0ab4fde38ade0b28d76c788d602c97789b76c66c91f678460fd68f3dc67c9b454340c7fe08363dfec69d4127982f9329bb42f503
+ languageName: node
+ linkType: hard
+
+"@trpc/server@npm:^10.29.1":
+ version: 10.29.1
+ resolution: "@trpc/server@npm:10.29.1"
+ checksum: d31ac9921764aea774ef14ae3d9d4c7cb245582dde7f75facd355732043eb9d697a8584c19377dce9acdf9b21f473917f77842fda29eb77fd604484e23b42ded
+ languageName: node
+ linkType: hard
+
"@tsconfig/node10@npm:^1.0.7":
version: 1.0.9
resolution: "@tsconfig/node10@npm:1.0.9"
@@ -5617,6 +5663,13 @@ __metadata:
languageName: node
linkType: hard
+"globrex@npm:^0.1.2":
+ version: 0.1.2
+ resolution: "globrex@npm:0.1.2"
+ checksum: adca162494a176ce9ecf4dd232f7b802956bb1966b37f60c15e49d2e7d961b66c60826366dc2649093cad5a0d69970cfa8875bd1695b5a1a2f33dcd2aa88da3c
+ languageName: node
+ linkType: hard
+
"gopd@npm:^1.0.1":
version: 1.0.1
resolution: "gopd@npm:1.0.1"
@@ -5828,6 +5881,10 @@ __metadata:
"@tanstack/react-query-persist-client": ^4.28.0
"@testing-library/jest-dom": ^5.16.5
"@testing-library/react": ^14.0.0
+ "@trpc/client": ^10.29.1
+ "@trpc/next": ^10.29.1
+ "@trpc/react-query": ^10.29.1
+ "@trpc/server": ^10.29.1
"@types/dockerode": ^3.3.9
"@types/node": 18.16.17
"@types/prismjs": ^1.26.0
@@ -5880,6 +5937,7 @@ __metadata:
typescript: ^5.0.4
uuid: ^9.0.0
video.js: ^8.0.3
+ vite-tsconfig-paths: ^4.2.0
vitest: ^0.32.0
vitest-fetch-mock: ^0.2.2
xml-js: ^1.6.11
@@ -8414,6 +8472,15 @@ __metadata:
languageName: node
linkType: hard
+"react-ssr-prepass@npm:^1.5.0":
+ version: 1.5.0
+ resolution: "react-ssr-prepass@npm:1.5.0"
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ checksum: efe89b9f8b2053474613f56dfdbeb41d8ee2e572bf819a39377d95d3e4a9acf8a4a16e28d8d8034cb9ac2b316d11dc9e62217743e4322046d08175eb3b4fed3e
+ languageName: node
+ linkType: hard
+
"react-style-singleton@npm:^2.2.1":
version: 2.2.1
resolution: "react-style-singleton@npm:2.2.1"
@@ -9507,6 +9574,20 @@ __metadata:
languageName: node
linkType: hard
+"tsconfck@npm:^2.1.0":
+ version: 2.1.1
+ resolution: "tsconfck@npm:2.1.1"
+ peerDependencies:
+ typescript: ^4.3.5 || ^5.0.0
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ bin:
+ tsconfck: bin/tsconfck.js
+ checksum: c531525f39763cbbd7e6dbf5e29f12a7ae67eb8712816c14d06a9db6cbdc9dda9ac3cd6db07ef645f8a4cdea906447ab44e2c8679e320871cf9dd598756e8c83
+ languageName: node
+ linkType: hard
+
"tsconfig-paths@npm:^3.14.1":
version: 3.14.2
resolution: "tsconfig-paths@npm:3.14.2"
@@ -9981,6 +10062,22 @@ __metadata:
languageName: node
linkType: hard
+"vite-tsconfig-paths@npm:^4.2.0":
+ version: 4.2.0
+ resolution: "vite-tsconfig-paths@npm:4.2.0"
+ dependencies:
+ debug: ^4.1.1
+ globrex: ^0.1.2
+ tsconfck: ^2.1.0
+ peerDependencies:
+ vite: "*"
+ peerDependenciesMeta:
+ vite:
+ optional: true
+ checksum: 73a8467de72d7ac502328454fd00c19571cd4bad2dd5982643b24718bb95e449a3f4153cfc2d58a358bfc8f37e592fb442fc10884b59ae82138c1329160cd952
+ languageName: node
+ linkType: hard
+
"vite@npm:^3.0.0 || ^4.0.0":
version: 4.3.9
resolution: "vite@npm:4.3.9"