diff --git a/apps/client/src/common/hooks-query/useProjectData.ts b/apps/client/src/common/hooks-query/useProjectData.ts index ebace82e3..e530e581c 100644 --- a/apps/client/src/common/hooks-query/useProjectData.ts +++ b/apps/client/src/common/hooks-query/useProjectData.ts @@ -1,8 +1,8 @@ -import { useQuery } from '@tanstack/react-query'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { queryRefetchIntervalSlow } from '../../ontimeConfig'; import { PROJECT_DATA } from '../api/constants'; -import { getProjectData } from '../api/project'; +import { getProjectData, postProjectData } from '../api/project'; import { projectDataPlaceholder } from '../models/ProjectData'; export default function useProjectData() { @@ -10,11 +10,25 @@ export default function useProjectData() { queryKey: PROJECT_DATA, queryFn: getProjectData, placeholderData: (previousData, _previousQuery) => previousData, - retry: 5, - retryDelay: (attempt) => attempt * 2500, refetchInterval: queryRefetchIntervalSlow, - networkMode: 'always', }); return { data: data ?? projectDataPlaceholder, status, isFetching, isError, refetch }; } + +export function useUpdateProjectData() { + const queryClient = useQueryClient(); + + const updateFn = useMutation({ + mutationFn: postProjectData, + onSuccess: (newProjectData) => { + queryClient.setQueryData(PROJECT_DATA, newProjectData); + }, + }); + + return { + updateProjectData: updateFn.mutateAsync, + isMutating: updateFn.isPending, + isMutatingError: updateFn.isError, + }; +} diff --git a/apps/client/src/features/app-settings/panel/settings-panel/ProjectData.tsx b/apps/client/src/features/app-settings/panel/settings-panel/ProjectData.tsx index 4e19d9191..9cd130010 100644 --- a/apps/client/src/features/app-settings/panel/settings-panel/ProjectData.tsx +++ b/apps/client/src/features/app-settings/panel/settings-panel/ProjectData.tsx @@ -4,12 +4,12 @@ import { IoAdd, IoDownloadOutline, IoTrash } from 'react-icons/io5'; import { type ProjectData } from 'ontime-types'; import { projectLogoPath } from '../../../../common/api/constants'; -import { postProjectData, uploadProjectLogo } from '../../../../common/api/project'; +import { uploadProjectLogo } from '../../../../common/api/project'; import { maybeAxiosError } from '../../../../common/api/utils'; import Button from '../../../../common/components/buttons/Button'; import Input from '../../../../common/components/input/input/Input'; import Textarea from '../../../../common/components/input/textarea/Textarea'; -import useProjectData from '../../../../common/hooks-query/useProjectData'; +import useProjectData, { useUpdateProjectData } from '../../../../common/hooks-query/useProjectData'; import { preventEscape } from '../../../../common/utils/keyEvent'; import { validateLogo } from '../../../../common/utils/uploadUtils'; import { documentationUrl } from '../../../../externals'; @@ -19,7 +19,8 @@ import style from './SettingsPanel.module.scss'; export default function ProjectData() { 'use no memo'; // RHF and react-compiler don't seem to get along - const { data, status, refetch } = useProjectData(); + const { data, status } = useProjectData(); + const { updateProjectData } = useUpdateProjectData(); const { handleSubmit, @@ -93,16 +94,14 @@ export default function ProjectData() { const onSubmit = async (formData: ProjectData) => { try { - await postProjectData(formData); + await updateProjectData(formData); } catch (error) { const message = maybeAxiosError(error); setError('root', { message }); - } finally { - await refetch(); } }; - // populate with new data if we get an update + // reset data to the initial state const onReset = () => { reset(data); }; diff --git a/apps/server/src/api-data/project-data/projectData.validation.ts b/apps/server/src/api-data/project-data/projectData.validation.ts index e55d1428f..272efe471 100644 --- a/apps/server/src/api-data/project-data/projectData.validation.ts +++ b/apps/server/src/api-data/project-data/projectData.validation.ts @@ -11,7 +11,7 @@ export const projectSanitiser = [ body('custom').optional().isArray(), body('custom.*.title').optional().isString().trim().notEmpty(), body('custom.*.value').optional().isString().trim().notEmpty(), - body('custom.*.url').optional().isString().trim().notEmpty(), + body('custom.*.url').optional().isString().trim(), requestValidationFunction, ];