diff --git a/src/apps/patron-page/PatronPage.tsx b/src/apps/patron-page/PatronPage.tsx index 32f910b866..a2a78c1807 100644 --- a/src/apps/patron-page/PatronPage.tsx +++ b/src/apps/patron-page/PatronPage.tsx @@ -1,11 +1,5 @@ import React, { useEffect, useState, FC, FormEvent } from "react"; import { set } from "lodash"; -import { useQueryClient } from "react-query"; -import { PatronV5, UpdatePatronRequestV4 } from "../../core/fbs/model"; -import { - useUpdateV5, - getGetPatronInformationByPatronIdV2QueryKey -} from "../../core/fbs/fbs"; import { useText } from "../../core/utils/text"; import Link from "../../components/atoms/links/Link"; import BasicDetailsSection from "./sections/BasicDetailsSection"; @@ -17,15 +11,15 @@ import { useUrls } from "../../core/utils/url"; import { useNotificationMessage } from "../../core/utils/useNotificationMessage"; import { usePatronData } from "../../core/utils/helpers/usePatronData"; import PatronPageSkeleton from "./PatronPageSkeleton"; +import useSavePatron from "../../core/utils/useSavePatron"; +import { Patron } from "../../core/utils/types/entities"; const PatronPage: FC = () => { - const queryClient = useQueryClient(); const t = useText(); const u = useUrls(); const deletePatronUrl = u("deletePatronUrl"); - const { mutate } = useUpdateV5(); const { data: patronData, isLoading } = usePatronData(); - const [patron, setPatron] = useState(null); + const [patron, setPatron] = useState(null); const [pin, setPin] = useState(null); const [isPinChangeValid, setIsPinChangeValid] = useState(true); const [disableSubmitButton, setDisableSubmitButton] = useState(false); @@ -34,6 +28,34 @@ const PatronPage: FC = () => { ); const [NotificationComponent, handleNotificationMessage] = useNotificationMessage(); + const { savePatron, savePincode } = useSavePatron({ + patron: patron || undefined, + fetchHandlers: { + savePatron: { + onSuccess: () => { + setDisableSubmitButton(false); + handleNotificationMessage( + t("patronPageHandleResponseInformationText") + ); + }, + onError: () => { + setDisableSubmitButton(false); + } + }, + savePincode: { + onSuccess: () => { + setDisableSubmitButton(false); + setSuccessPinMessage(t("patronPinSavedSuccessText")); + handleNotificationMessage( + t("patronPageHandleResponseInformationText") + ); + }, + onError: () => { + setDisableSubmitButton(false); + } + } + } + }); useEffect(() => { if (patronData && patronData.patron) { @@ -46,7 +68,7 @@ const PatronPage: FC = () => { } // Changes the patron object by key. - // So using the paramters 123 and "phoneNumber" would change the phoneNumber to 123. + // So using the parameters 123 and "phoneNumber" would change the phoneNumber to 123. const changePatron = (newValue: string | boolean, key: string) => { // Deeeep copy const copyUser = JSON.parse(JSON.stringify(patron)); @@ -57,50 +79,15 @@ const PatronPage: FC = () => { const save = () => { if (patron) { setDisableSubmitButton(true); - // The save patron request is done in another section of the code, but in that section - // it is saved differently. Here, we save the input from the user, in the other scenario, - // the checkboxes (subscribe to texts, subscribe to emails) will be set automatically - const data: UpdatePatronRequestV4 = { - patron: { - emailAddress: patron.emailAddress, - receivePostalMail: patron.receivePostalMail, - phoneNumber: patron.phoneNumber, - onHold: patron.onHold, - preferredPickupBranch: patron.preferredPickupBranch, - receiveEmail: patron.receiveEmail, - receiveSms: patron.receiveSms - } - }; // If pincode is changed, the pincode should be updated. if (pin) { - data.pincodeChange = { + savePincode({ pincode: pin, libraryCardNumber: patron.patronId.toString() - }; + }); + } else { + savePatron(patron); } - mutate( - { - data - }, - { - onSuccess: () => { - queryClient.invalidateQueries( - getGetPatronInformationByPatronIdV2QueryKey() - ); - if (pin) { - setSuccessPinMessage(t("patronPinSavedSuccessText")); - } - setDisableSubmitButton(false); - handleNotificationMessage( - t("patronPageHandleResponseInformationText") - ); - }, - // todo error handling, missing in figma - onError: () => { - setDisableSubmitButton(false); - } - } - ); } }; diff --git a/src/apps/reservation-list/modal/pause-reservation/pause-reservation.tsx b/src/apps/reservation-list/modal/pause-reservation/pause-reservation.tsx index acb892c934..9a9c02fd30 100644 --- a/src/apps/reservation-list/modal/pause-reservation/pause-reservation.tsx +++ b/src/apps/reservation-list/modal/pause-reservation/pause-reservation.tsx @@ -1,78 +1,68 @@ import React, { FC, useCallback, useState, useEffect, useId } from "react"; import dayjs from "dayjs"; -import { useQueryClient } from "react-query"; import Link from "../../../../components/atoms/links/Link"; import Modal, { useModalButtonHandler } from "../../../../core/utils/modal"; import { useText } from "../../../../core/utils/text"; -import { - useUpdateV5, - getGetPatronInformationByPatronIdV2QueryKey -} from "../../../../core/fbs/fbs"; -import { Patron, PatronV5 } from "../../../../core/fbs/model"; import { useUrls } from "../../../../core/utils/url"; import { getModalIds } from "../../../../core/utils/helpers/modal-helpers"; import DateRangeInput from "../../../../components/date-inputs/DateRangeInput"; +import useSavePatron from "../../../../core/utils/useSavePatron"; +import { Patron } from "../../../../core/utils/types/entities"; interface PauseReservationProps { id: string; - user: PatronV5; + user: Patron; } const PauseReservation: FC = ({ id, user }) => { const t = useText(); const u = useUrls(); const pauseReservationInfoUrl = u("pauseReservationInfoUrl"); - - const queryClient = useQueryClient(); - const { mutate } = useUpdateV5(); const { close } = useModalButtonHandler(); const { pauseReservation } = getModalIds(); + const [isLoading, setIsLoading] = useState(false); + const { savePatron } = useSavePatron({ + patron: user, + fetchHandlers: { + savePatron: { + onSuccess: () => { + setIsLoading(false); + close(pauseReservation as string); + }, + onError: () => { + setIsLoading(false); + } + } + } + }); const saveFormId = useId(); const currentDate = dayjs().format("YYYY-MM-DD"); const [startDate, setStartDate] = useState(currentDate); const [endDate, setEndDate] = useState(""); const pauseActive = user?.onHold?.from && user?.onHold?.to; - const [isLoading, setIsLoading] = useState(false); - const save = useCallback( - (localStartDate?: string, localEndDate?: string) => { + const saveDates = useCallback( + (start?: string, end?: string) => { if (!user) { return; } + setIsLoading(true); - // TODO: Create a hook for this that fetches + updates user data. - const saveData = { ...user } as Patron; - saveData.onHold = { - from: localStartDate === "" ? undefined : localStartDate, - to: localEndDate === "" ? undefined : localEndDate - }; - mutate( - { - data: { patron: saveData } - }, - { - onSuccess: () => { - queryClient.invalidateQueries( - getGetPatronInformationByPatronIdV2QueryKey() - ); - setIsLoading(false); - close(pauseReservation as string); - }, - // todo error handling, missing in figma - onError: () => { - setIsLoading(false); - } + savePatron({ + onHold: { + from: start === "" ? undefined : start, + to: end === "" ? undefined : end } - ); + }); }, - [close, mutate, pauseReservation, queryClient, user] + [savePatron, user] ); const resetPauseDates = useCallback(() => { setStartDate(currentDate); setEndDate(""); - save(); - }, [currentDate, save]); + saveDates(); + }, [currentDate, saveDates]); useEffect(() => { if (user?.onHold?.from) { @@ -106,7 +96,7 @@ const PauseReservation: FC = ({ id, user }) => { onSubmit={(e) => { e.preventDefault(); if (startDate && endDate) { - save(startDate, endDate); + saveDates(startDate, endDate); } }} > diff --git a/src/components/reservation/forms/ModalReservationFormText.tsx b/src/components/reservation/forms/ModalReservationFormText.tsx index 79aa362c35..d4e66abc39 100644 --- a/src/components/reservation/forms/ModalReservationFormText.tsx +++ b/src/components/reservation/forms/ModalReservationFormText.tsx @@ -1,10 +1,5 @@ import { isEqual } from "lodash"; import React, { memo, useState } from "react"; -import { useQueryClient } from "react-query"; -import { - getGetPatronInformationByPatronIdV2QueryKey, - useUpdateV5 -} from "../../../core/fbs/fbs"; import { PatronV5 } from "../../../core/fbs/model"; import { stringifyValue } from "../../../core/utils/helpers/general"; import Modal, { useModalButtonHandler } from "../../../core/utils/modal"; @@ -17,6 +12,7 @@ import { saveText } from "./helper"; import ReservationForm from "./ReservationForm"; +import useSavePatron from "../../../core/utils/useSavePatron"; export interface ModalReservationFormTextProps { type: ModalReservationFormTextType; @@ -53,10 +49,23 @@ const ModalReservationFormText = ({ patron }: ModalReservationFormTextProps) => { const { close } = useModalButtonHandler(); - const queryClient = useQueryClient(); const t = useText(); const [text, setText] = useState(stringifyValue(defaultText)); - const { mutate } = useUpdateV5(); + const { savePatron } = useSavePatron({ + patron, + fetchHandlers: { + savePatron: { + onSuccess: () => { + close(modalReservationFormId(type)); + }, + // If an error occurred make sure to reset the text to the old value. + onError: () => { + setText(stringifyValue(defaultText)); + close(modalReservationFormId(type)); + } + } + } + }); const onChange = (input: string) => { setText(input); }; @@ -66,26 +75,8 @@ const ModalReservationFormText = ({ changedText: text, savedText: defaultText, patron, - mutate - }) - .then((response) => { - // If we succeeded in saving we can cache the new data. - if (response) { - queryClient.setQueryData( - getGetPatronInformationByPatronIdV2QueryKey(), - response - ); - } - }) - .catch((e) => { - // If an error ocurred make sure to reset the text to the old value. - setText(stringifyValue(defaultText)); - throw e; - }) - .finally(() => { - // Close modal no matter what. - close(modalReservationFormId(type)); - }); + savePatron + }); }; const { modalId, screenReaderModalDescriptionText, closeModalAriaLabelText } = modalProps(type, t); diff --git a/src/components/reservation/forms/helper.ts b/src/components/reservation/forms/helper.ts index 57fb88b84f..97c50bf00a 100644 --- a/src/components/reservation/forms/helper.ts +++ b/src/components/reservation/forms/helper.ts @@ -1,9 +1,5 @@ -import { UseMutateFunction } from "react-query"; -import { - AuthenticatedPatronV6, - PatronV5, - UpdatePatronRequestV4 -} from "../../../core/fbs/model"; +import { PatronV5 } from "../../../core/fbs/model"; +import useSavePatron from "../../../core/utils/useSavePatron"; export type ModalReservationFormTextType = | "email" @@ -63,14 +59,7 @@ type SaveText = { changedText: string; savedText?: string; patron: PatronV5; - mutate: UseMutateFunction< - AuthenticatedPatronV6 | null, - void, - { - data: UpdatePatronRequestV4; - }, - unknown - >; + savePatron: ReturnType["savePatron"]; }; export const saveText = ({ @@ -78,48 +67,26 @@ export const saveText = ({ changedText, savedText, patron, - mutate + savePatron }: SaveText) => { - return new Promise((resolve, reject) => { - const textDiffers = changedText !== savedText; - const updatedPatronData = constructPatronSaveData({ - type, - value: changedText, - patron - }); + const textDiffers = changedText !== savedText; + const updatedPatronData = constructPatronSaveData({ + type, + value: changedText, + patron + }); - // If we cannot construct the updated patron data we do not want to save anything. - if (!updatedPatronData) { - reject(new Error("Cannot construct updated patron data")); - return; - } - // If the email address is the same we do not want to save anything. - if (!textDiffers) { - resolve(""); - return; - } + // If we cannot construct the updated patron data we do not want to save anything. + if (!updatedPatronData) { + throw new Error("Cannot construct updated patron data"); + } + // If the text has not changed we do not want to save anything. + if (!textDiffers) { + return; + } - // Update user data. - mutate( - { - data: { - patron: updatedPatronData - } - }, - { - onSuccess: (response) => { - if (!response) { - reject(new Error("We did not get a response from the server")); - return; - } - resolve(response); - }, - onError: (e) => { - reject(e); - } - } - ); - }); + // Update patron data. + savePatron(updatedPatronData); }; export function modalReservationFormSelectTypeIsInterestPeriod( diff --git a/src/core/utils/useSavePatron.tsx b/src/core/utils/useSavePatron.tsx new file mode 100644 index 0000000000..b6a1481d83 --- /dev/null +++ b/src/core/utils/useSavePatron.tsx @@ -0,0 +1,88 @@ +import { useQueryClient } from "react-query"; +import { Patron } from "./types/entities"; +import { PatronSettingsV4, PincodeChange } from "../fbs/model"; +import { + getGetPatronInformationByPatronIdV2QueryKey, + useUpdateV5 +} from "../fbs/fbs"; + +export interface FetchHandlers { + onSuccess?: () => void; + onError?: () => void; +} + +interface UseSavePatron { + patron?: Patron; + fetchHandlers?: { + savePatron?: FetchHandlers; + savePincode?: FetchHandlers; + }; +} + +const useSavePatron = ({ patron, fetchHandlers }: UseSavePatron) => { + const { mutate } = useUpdateV5(); + const queryClient = useQueryClient(); + + const savePatron = (data: Partial) => { + const { onSuccess, onError } = fetchHandlers?.savePatron || {}; + + if (!patron) { + return; + } + + mutate( + { + data: { patron: { ...patron, ...data } } + }, + { + onSuccess: () => { + queryClient.invalidateQueries( + getGetPatronInformationByPatronIdV2QueryKey() + ); + if (onSuccess) { + onSuccess(); + } + }, + // todo error handling + onError: () => { + if (onError) { + onError(); + } + } + } + ); + }; + + const savePincode = (data: PincodeChange) => { + const { onSuccess, onError } = fetchHandlers?.savePincode || {}; + if (!patron) { + return; + } + + mutate( + { + data: { pincodeChange: data } + }, + { + onSuccess: () => { + queryClient.invalidateQueries( + getGetPatronInformationByPatronIdV2QueryKey() + ); + if (onSuccess) { + onSuccess(); + } + }, + // todo error handling + onError: () => { + if (onError) { + onError(); + } + } + } + ); + }; + + return { savePatron, savePincode }; +}; + +export default useSavePatron;