From 161b8c95fbf83e1890687f0bc952938ad570d671 Mon Sep 17 00:00:00 2001 From: Iveta Date: Tue, 31 Oct 2023 12:57:04 -0400 Subject: [PATCH 1/2] [SDP-949] State refactor: countries (#42) * State refactor: countries * Keep fetched data for a bit longer * Fix stale time --- src/api/getAssetsByWallet.ts | 18 ------ src/api/getCountries.ts | 15 ----- src/apiQueries/useCountries.ts | 17 ++++++ src/components/DisbursementDetails/index.tsx | 36 ++++------- src/store/ducks/countries.ts | 63 -------------------- src/store/index.ts | 2 - src/types/index.ts | 7 --- 7 files changed, 29 insertions(+), 129 deletions(-) delete mode 100644 src/api/getAssetsByWallet.ts delete mode 100644 src/api/getCountries.ts create mode 100644 src/apiQueries/useCountries.ts delete mode 100644 src/store/ducks/countries.ts diff --git a/src/api/getAssetsByWallet.ts b/src/api/getAssetsByWallet.ts deleted file mode 100644 index eff9538..0000000 --- a/src/api/getAssetsByWallet.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { handleApiResponse } from "api/handleApiResponse"; -import { API_URL } from "constants/settings"; -import { ApiAsset } from "types"; - -export const getAssetsByWallet = async ( - token: string, - walletId: string, -): Promise => { - const response = await fetch(`${API_URL}/assets?wallet=${walletId}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - }); - - return handleApiResponse(response); -}; diff --git a/src/api/getCountries.ts b/src/api/getCountries.ts deleted file mode 100644 index 835e0ae..0000000 --- a/src/api/getCountries.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { handleApiResponse } from "api/handleApiResponse"; -import { API_URL } from "constants/settings"; -import { ApiCountry } from "types"; - -export const getCountries = async (token: string): Promise => { - const response = await fetch(`${API_URL}/countries`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - }); - - return handleApiResponse(response); -}; diff --git a/src/apiQueries/useCountries.ts b/src/apiQueries/useCountries.ts new file mode 100644 index 0000000..64dcf0f --- /dev/null +++ b/src/apiQueries/useCountries.ts @@ -0,0 +1,17 @@ +import { useQuery } from "@tanstack/react-query"; +import { API_URL } from "constants/settings"; +import { fetchApi } from "helpers/fetchApi"; +import { ApiCountry, AppError } from "types"; + +export const useCountries = () => { + const query = useQuery({ + queryKey: ["countries"], + queryFn: async () => { + return await fetchApi(`${API_URL}/countries`); + }, + // Keeping the fetched data for longer since it won't change that often + staleTime: 5 * 60 * 1000, + }); + + return query; +}; diff --git a/src/components/DisbursementDetails/index.tsx b/src/components/DisbursementDetails/index.tsx index 15c9ec1..e2179dd 100644 --- a/src/components/DisbursementDetails/index.tsx +++ b/src/components/DisbursementDetails/index.tsx @@ -1,4 +1,3 @@ -import { useEffect } from "react"; import { Card, Input, @@ -6,16 +5,12 @@ import { Title, Notification, } from "@stellar/design-system"; -import { useDispatch } from "react-redux"; - -import { AppDispatch } from "store"; -import { getCountriesAction } from "store/ducks/countries"; import { useWallets } from "apiQueries/useWallets"; import { useAssetsByWallet } from "apiQueries/useAssetsByWallet"; +import { useCountries } from "apiQueries/useCountries"; import { InfoTooltip } from "components/InfoTooltip"; import { formatUploadedFileDisplayName } from "helpers/formatUploadedFileDisplayName"; -import { useRedux } from "hooks/useRedux"; import { ApiAsset, ApiCountry, @@ -61,8 +56,6 @@ export const DisbursementDetails: React.FC = ({ onChange, onValidate, }: DisbursementDetailsProps) => { - const { countries } = useRedux("countries"); - enum FieldId { NAME = "name", COUNTRY_CODE = "country_code", @@ -76,23 +69,20 @@ export const DisbursementDetails: React.FC = ({ isLoading: isWalletsLoading, } = useWallets(); + const { + data: countries, + error: countriesError, + isLoading: isCountriesLoading, + } = useCountries(); + const { data: walletAssets, error: walletError, isFetching: isWalletAssetsFetching, } = useAssetsByWallet(details.wallet.id); - const dispatch: AppDispatch = useDispatch(); - - // Don't fetch again if we already have them in store - useEffect(() => { - if (!countries.status) { - dispatch(getCountriesAction()); - } - }, [dispatch, countries.status]); - const apiErrors = [ - countries.errorString, + countriesError?.message, walletsError?.message, walletError?.message, ]; @@ -141,9 +131,7 @@ export const DisbursementDetails: React.FC = ({ switch (id) { case FieldId.COUNTRY_CODE: // eslint-disable-next-line no-case-declarations - const country = countries.items.find( - (c: ApiCountry) => c.code === value, - ); + const country = countries?.find((c: ApiCountry) => c.code === value); updateState({ country: { @@ -244,10 +232,10 @@ export const DisbursementDetails: React.FC = ({ fieldSize="sm" onChange={updateDraftDetails} value={details.country.code} - disabled={countries.status === "PENDING"} + disabled={isCountriesLoading} > - {renderDropdownDefault(countries.status === "PENDING")} - {countries.items.map((country: ApiCountry) => ( + {renderDropdownDefault(isCountriesLoading)} + {countries?.map((country: ApiCountry) => ( diff --git a/src/store/ducks/countries.ts b/src/store/ducks/countries.ts deleted file mode 100644 index a58eb1a..0000000 --- a/src/store/ducks/countries.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; -import { RootState } from "store"; -import { getCountries } from "api/getCountries"; -import { handleApiErrorString } from "api/handleApiErrorString"; -import { endSessionIfTokenInvalid } from "helpers/endSessionIfTokenInvalid"; -import { - ApiCountry, - ApiError, - CountriesInitialState, - RejectMessage, -} from "types"; - -export const getCountriesAction = createAsyncThunk< - ApiCountry[], - undefined, - { rejectValue: RejectMessage; state: RootState } ->( - "countries/getCountriesAction", - async (_, { rejectWithValue, getState, dispatch }) => { - const { token } = getState().userAccount; - - try { - const countries = await getCountries(token); - return countries; - } catch (error: unknown) { - const errorString = handleApiErrorString(error as ApiError); - endSessionIfTokenInvalid(errorString, dispatch); - - return rejectWithValue({ - errorString: `Error fetching countries: ${errorString}`, - }); - } - }, -); - -const initialState: CountriesInitialState = { - items: [], - status: undefined, - errorString: undefined, -}; - -const countriesSlice = createSlice({ - name: "countries", - initialState, - reducers: {}, - extraReducers: (builder) => { - builder.addCase(getCountriesAction.pending, (state = initialState) => { - state.status = "PENDING"; - }); - builder.addCase(getCountriesAction.fulfilled, (state, action) => { - state.items = action.payload; - state.status = "SUCCESS"; - state.errorString = undefined; - }); - builder.addCase(getCountriesAction.rejected, (state, action) => { - state.status = "ERROR"; - state.errorString = action.payload?.errorString; - }); - }, -}); - -export const countriesSelector = (state: RootState) => state.countries; -export const { reducer } = countriesSlice; diff --git a/src/store/index.ts b/src/store/index.ts index 9a62def..2adf36f 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -9,7 +9,6 @@ import BigNumber from "bignumber.js"; import { RESET_STORE_ACTION_TYPE } from "constants/settings"; -import { reducer as countries } from "store/ducks/countries"; import { reducer as disbursementDetails } from "store/ducks/disbursementDetails"; import { reducer as disbursementDrafts } from "store/ducks/disbursementDrafts"; import { reducer as disbursements } from "store/ducks/disbursements"; @@ -32,7 +31,6 @@ const isSerializable = (value: any) => BigNumber.isBigNumber(value) || isPlain(value); const reducers = combineReducers({ - countries, disbursementDetails, disbursementDrafts, disbursements, diff --git a/src/types/index.ts b/src/types/index.ts index 4e4f80d..cb094bd 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -60,12 +60,6 @@ export type UserAccountInitialState = { restoredPathname?: string; }; -export type CountriesInitialState = { - items: ApiCountry[]; - status: ActionStatus | undefined; - errorString?: string; -}; - export type DisbursementDraftsInitialState = { items: DisbursementDraft[]; status: ActionStatus | undefined; @@ -119,7 +113,6 @@ export type ProfileInitialState = { }; export interface Store { - countries: CountriesInitialState; disbursementDetails: DisbursementDetailsInitialState; disbursementDrafts: DisbursementDraftsInitialState; disbursements: DisbursementsInitialState; From 332c04e27b2b0b50d8840e24b2f87882a91758c3 Mon Sep 17 00:00:00 2001 From: Iveta Date: Tue, 31 Oct 2023 13:05:25 -0400 Subject: [PATCH 2/2] Settings: fix input entry for Payments cancellation and SMS retry (#43) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Settings: fix input entry for Payments cancellation and SMS retry * Update src/components/SettingsEnablePaymentCancellation.tsx Co-authored-by: Cecília Romão --------- Co-authored-by: Cecília Romão --- .../SettingsEnablePaymentCancellation.tsx | 19 ++++++++++++------- src/components/SettingsEnableSmsRetry.tsx | 17 ++++++++++++----- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/src/components/SettingsEnablePaymentCancellation.tsx b/src/components/SettingsEnablePaymentCancellation.tsx index 5a638bc..6cee1e1 100644 --- a/src/components/SettingsEnablePaymentCancellation.tsx +++ b/src/components/SettingsEnablePaymentCancellation.tsx @@ -21,7 +21,7 @@ export const SettingsEnablePaymentCancellation = () => { const { organization } = useRedux("organization"); const [paymentCancellationPeriodDays, setPaymentCancellationPeriodDays] = - useState(0); + useState(null); const [isEditMode, setIsEditMode] = useState(false); const dispatch: AppDispatch = useDispatch(); @@ -51,7 +51,10 @@ export const SettingsEnablePaymentCancellation = () => { event: React.FormEvent, ) => { event.preventDefault(); - mutateAsync(paymentCancellationPeriodDays); + + if (paymentCancellationPeriodDays) { + mutateAsync(paymentCancellationPeriodDays); + } }; const handlePaymentCancellationReset = ( @@ -108,13 +111,15 @@ export const SettingsEnablePaymentCancellation = () => { id="payment-cancellation-period" label="Payments Cancellation Period (days)" type="number" - value={paymentCancellationPeriodDays} - onChange={(e) => - setPaymentCancellationPeriodDays(Number(e.target.value)) - } + value={paymentCancellationPeriodDays ?? ""} + onChange={(e) => { + e.target.value !== "" + ? setPaymentCancellationPeriodDays(Number(e.target.value)) + : setPaymentCancellationPeriodDays(null); + }} disabled={!isEditMode} error={ - paymentCancellationPeriodDays === 0 + paymentCancellationPeriodDays == 0 ? "Cancellation period cannot be 0" : "" } diff --git a/src/components/SettingsEnableSmsRetry.tsx b/src/components/SettingsEnableSmsRetry.tsx index 1ce75b7..6cf57ad 100644 --- a/src/components/SettingsEnableSmsRetry.tsx +++ b/src/components/SettingsEnableSmsRetry.tsx @@ -20,7 +20,7 @@ import { getOrgInfoAction } from "store/ducks/organization"; export const SettingsEnableSmsRetry = () => { const { organization } = useRedux("organization"); - const [smsRetryInterval, setSmsRetryInterval] = useState(0); + const [smsRetryInterval, setSmsRetryInterval] = useState(null); const [isEditMode, setIsEditMode] = useState(false); const dispatch: AppDispatch = useDispatch(); @@ -46,7 +46,10 @@ export const SettingsEnableSmsRetry = () => { const handleSmsRetrySubmit = (event: React.FormEvent) => { event.preventDefault(); - mutateAsync(smsRetryInterval); + + if (smsRetryInterval) { + mutateAsync(smsRetryInterval); + } }; const handleSmsRetryReset = (event: React.FormEvent) => { @@ -94,11 +97,15 @@ export const SettingsEnableSmsRetry = () => { id="sms-retry-interval" label="SMS retry interval (days)" type="number" - value={smsRetryInterval} - onChange={(e) => setSmsRetryInterval(Number(e.target.value))} + value={smsRetryInterval ?? ""} + onChange={(e) => { + e.target.value !== "" + ? setSmsRetryInterval(Number(e.target.value)) + : setSmsRetryInterval(null); + }} disabled={!isEditMode} error={ - smsRetryInterval === 0 ? "Retry interval cannot be 0" : "" + smsRetryInterval == 0 ? "Retry interval cannot be 0" : "" } /> {!isEditMode ? (