From 0a2fb8515926acfe6d0f28bdfa6192dc1ef6ebe6 Mon Sep 17 00:00:00 2001 From: ilia makarov Date: Wed, 8 Jun 2022 00:22:39 +0300 Subject: [PATCH] Linking of multiple customers on LinkCustomer page --- .../ViewOrder/Information/Information.tsx | 2 +- src/context/StoreProvider/reducer.ts | 8 ---- src/pages/EditCustomer.tsx | 12 ++--- src/pages/Home.tsx | 45 +++++++++---------- src/pages/LinkCustomer.tsx | 10 ++--- src/pages/ListOrders.tsx | 34 +++++++------- src/pages/ViewCustomer.tsx | 25 ++++++----- src/services/shopify/getCustomers.ts | 2 +- src/utils/index.ts | 2 + src/utils/retryUntilResolve.ts | 29 ++++++++++++ src/utils/sleep.ts | 5 +++ 11 files changed, 97 insertions(+), 77 deletions(-) create mode 100644 src/utils/retryUntilResolve.ts create mode 100644 src/utils/sleep.ts diff --git a/src/components/ViewOrder/Information/Information.tsx b/src/components/ViewOrder/Information/Information.tsx index 4178fac..c39f27a 100644 --- a/src/components/ViewOrder/Information/Information.tsx +++ b/src/components/ViewOrder/Information/Information.tsx @@ -6,7 +6,7 @@ import { Pill, useDeskproAppTheme, } from "@deskpro/app-sdk"; -import { Order, Address } from "../../../services/shopify/types"; +import { Order } from "../../../services/shopify/types"; import {TextBlockWithLabel} from "../../common"; import { getTime, diff --git a/src/context/StoreProvider/reducer.ts b/src/context/StoreProvider/reducer.ts index 91009ec..9c821e3 100644 --- a/src/context/StoreProvider/reducer.ts +++ b/src/context/StoreProvider/reducer.ts @@ -20,13 +20,5 @@ export const reducer: StoreReducer = (state: State, action: Action): State => { ...prevState, _error: action.error, })) - .with([__, { type: "linkedCustomer" }], ([prevState, action]) => ({ - ...prevState, - customer: action.customer, - })) - .with([__, { type: "linkedOrders" }], ([prevState, action]) => ({ - ...prevState, - orders: action.orders, - })) .otherwise(() => state); }; diff --git a/src/pages/EditCustomer.tsx b/src/pages/EditCustomer.tsx index c46c297..bff734a 100644 --- a/src/pages/EditCustomer.tsx +++ b/src/pages/EditCustomer.tsx @@ -9,6 +9,7 @@ export const EditCustomer: FC = () => { const { client } = useDeskproAppClient(); const [state, dispatch] = useStore(); const [loading, setLoading] = useState(true); + const [customer, setCustomer] = useState(null); useEffect(() => { client?.setTitle("Edit Customer Details"); @@ -34,21 +35,16 @@ export const EditCustomer: FC = () => { return; } - if (state?.customer && state.customer.id === state.pageParams.customerId) { - setLoading(false); - return; - } - getCustomer(client, state.pageParams.customerId) .then(({ customer }) => { setLoading(false); - dispatch({ type: "linkedCustomer", customer }); + setCustomer(customer); }) .catch((error) => dispatch({ type: "error", error })); // eslint-disable-next-line react-hooks/exhaustive-deps }, [client, state.pageParams?.customerId]); - return (loading || !state.customer) + return (loading || !customer) ? (<>Loading...) - : (); + : (); }; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 2e872a4..9765e21 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect } from "react"; +import { FC, useState, useEffect } from "react"; import { useDeskproAppClient } from "@deskpro/app-sdk"; import { useStore } from "../context/StoreProvider/hooks"; @@ -6,11 +6,13 @@ import { CustomerInfo, Orders, Comments } from "../components/Home"; import { getEntityCustomerList } from "../services/entityAssociation"; import { getCustomer } from "../services/shopify"; import { getShopName } from "../utils"; -import { Order } from "../services/shopify/types"; +import { CustomerType, Order } from "../services/shopify/types"; export const Home: FC = () => { const { client } = useDeskproAppClient(); const [state, dispatch] = useStore(); + const [customer, setCustomer] = useState(null); + const [orders, setOrders] = useState(null); const userId = state.context?.data.ticket?.primaryUser.id || state.context?.data.user.id; useEffect(() => { @@ -44,47 +46,40 @@ export const Home: FC = () => { } getEntityCustomerList(client, userId) - .then(async (customers: string[]) => { - const customerId = customers[0]; - - try { - const { customer } = await getCustomer(client, customerId); - const { orders } = customer; - - dispatch({ type: "linkedCustomer", customer }); - dispatch({ type: "linkedOrders", orders }); - } catch (e) { - const error = e as Error; - - throw new Error(error.message || "Failed to fetch"); - } + .then((customers: string[]) => { + return getCustomer(client, customers[0]); + }) + .then(({ customer }) => { + const { orders } = customer; + setCustomer(customer); + setOrders(orders); }) .catch((error: Error) => dispatch({ type: "error", error })); // eslint-disable-next-line react-hooks/exhaustive-deps }, [client, userId]); - return !state?.customer + return !customer ? state._error ? null : <>Loading... : ( <> - {state?.customer && ( + {customer && ( dispatch({ type: "changePage", page: "view_customer" })} /> )} - {state?.orders && ( + {orders && ( dispatch({ type: "changePage", page: "list_orders" })} onChangePageOrder={onChangePageOrder} /> )} - {state?.customer?.comments && ( - + {customer?.comments && ( + )} ); diff --git a/src/pages/LinkCustomer.tsx b/src/pages/LinkCustomer.tsx index 9e1ea7f..6beb4b7 100644 --- a/src/pages/LinkCustomer.tsx +++ b/src/pages/LinkCustomer.tsx @@ -51,8 +51,6 @@ export const LinkCustomer: FC = () => { } getCustomers(client, { querySearch: q }) - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore .then(({ customers }) => { if (Array.isArray(customers)) { setCustomers(customers); @@ -84,9 +82,7 @@ export const LinkCustomer: FC = () => { setEntityCustomer(client, user.id, selectedCustomerId) .then(() => dispatch({ type: "changePage", page: "home" })) - .catch((error: Error) => { - dispatch({ type: "error", error }); - }); + .catch((error: Error) => dispatch({ type: "error", error })); }; return ( @@ -100,14 +96,14 @@ export const LinkCustomer: FC = () => { ))} {!customers.length && }
-
) diff --git a/src/pages/ListOrders.tsx b/src/pages/ListOrders.tsx index 1c8a1e1..1a88ea4 100644 --- a/src/pages/ListOrders.tsx +++ b/src/pages/ListOrders.tsx @@ -1,20 +1,22 @@ -import { FC, useEffect } from "react"; +import { FC, useState, useEffect } from "react"; import { useDeskproAppClient } from "@deskpro/app-sdk"; import { useStore } from "../context/StoreProvider/hooks"; import { getEntityCustomerList } from "../services/entityAssociation"; import { getCustomer } from "../services/shopify"; -import { Order } from "../services/shopify/types"; +import { CustomerType, Order } from "../services/shopify/types"; import { getShopName } from "../utils"; import { OrderInfo } from "../components/common"; export const ListOrders: FC = () => { const [state, dispatch] = useStore(); const { client } = useDeskproAppClient(); + const [customer, setCustomer] = useState(null); + const [orders, setOrders] = useState(null); const userId = state.context?.data.ticket?.primaryUser.id || state.context?.data.user.id; useEffect(() => { client?.setTitle( - `Orders ${state.customer?.numberOfOrders ? `(${state.customer?.numberOfOrders})` : ''}` + `Orders ${customer?.numberOfOrders ? `(${customer?.numberOfOrders})` : ''}` ); client?.deregisterElement("shopifyMenu"); @@ -27,26 +29,24 @@ export const ListOrders: FC = () => { payload: { type: "changePage", page: "home" } }); client?.registerElement("shopifyRefreshButton", { type: "refresh_button" }); - }, [client, state]); + }, [client, customer?.numberOfOrders]); useEffect(() => { if (!client) { return; } - if (!state.orders) { - getEntityCustomerList(client, userId) - .then((customers: string[]) => { - return getCustomer(client, customers[0]); - }) - .then(({ customer }) => { - const { orders } = customer; + getEntityCustomerList(client, userId) + .then((customers: string[]) => { + return getCustomer(client, customers[0]); + }) + .then(({ customer }) => { + const { orders } = customer; - dispatch({ type: "linkedCustomer", customer }) - dispatch({ type: "linkedOrders", orders }) - }) - .catch((error: Error) => dispatch({ type: "error", error })); - } + setCustomer(customer); + setOrders(orders); + }) + .catch((error: Error) => dispatch({ type: "error", error })); // eslint-disable-next-line react-hooks/exhaustive-deps }, [client, userId]); @@ -56,7 +56,7 @@ export const ListOrders: FC = () => { return ( <> - {(state?.orders || []).map((order) => ( + {(orders || []).map((order) => ( { const [state, dispatch] = useStore(); const { client } = useDeskproAppClient(); const { theme } = useDeskproAppTheme(); + const [customer, setCustomer] = useState(null); const shopName = getShopName(state); const userId = state.context?.data.ticket?.primaryUser.id || state.context?.data.user.id; @@ -31,7 +33,7 @@ export const ViewCustomer: FC = () => { if (shopName) { client?.registerElement("shopifyExternalCtaLink", { type: "cta_external_link", - url: `https://${shopName}.myshopify.com/admin/customers/${state.customer?.legacyResourceId}`, + url: `https://${shopName}.myshopify.com/admin/customers/${customer?.legacyResourceId}`, hasIcon: true, }); } @@ -41,11 +43,11 @@ export const ViewCustomer: FC = () => { }); client?.registerElement("shopifyEditButton", { type: "edit_button", - payload: { type: "changePage", page: "edit_customer", params: { customerId: state.customer?.id } }, + payload: { type: "changePage", page: "edit_customer", params: { customerId: customer?.id } }, }); client?.registerElement("shopifyRefreshButton", { type: "refresh_button" }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [client, state.customer]); + }, [client, customer]); useEffect(() => { if (!client) { @@ -54,7 +56,10 @@ export const ViewCustomer: FC = () => { getEntityCustomerList(client, userId) .then((customers: string[]) => getCustomer(client, customers[0])) - .then(({ customer }) => dispatch({ type: "linkedCustomer", customer })) + .then(({ customer }) => { + client?.setTitle(customer.displayName); + setCustomer(customer); + }) .catch((error: Error) => dispatch({ type: "error", error })); // eslint-disable-next-line react-hooks/exhaustive-deps }, [client, userId]); @@ -63,17 +68,17 @@ export const ViewCustomer: FC = () => { <> - {state.customer?.tags.map((tag) => ( + {customer?.tags.map((tag) => ( { )} /> ); diff --git a/src/services/shopify/getCustomers.ts b/src/services/shopify/getCustomers.ts index ec14cf9..184ffa3 100644 --- a/src/services/shopify/getCustomers.ts +++ b/src/services/shopify/getCustomers.ts @@ -11,7 +11,7 @@ type ResponseType = { export const getCustomers = ( client: IDeskproClient, params: CustomerSearchParams = {} -): Promise<{ customers: CustomerType[] } | void> => { +): Promise<{ customers: CustomerType[] }> => { const { querySearch = '', email = '' } = params; const search = `${querySearch}${!email ? '' : `email:${email}`}`; diff --git a/src/utils/index.ts b/src/utils/index.ts index 3ba8b57..7f9d073 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -12,3 +12,5 @@ export { getShippingStatusName, getShippingStatusColorSchema, } from "./getShippingStatus"; +export { sleep } from "./sleep"; +export { retryUntilResolve } from "./retryUntilResolve"; diff --git a/src/utils/retryUntilResolve.ts b/src/utils/retryUntilResolve.ts new file mode 100644 index 0000000..453ff05 --- /dev/null +++ b/src/utils/retryUntilResolve.ts @@ -0,0 +1,29 @@ +import { sleep } from "./sleep"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type PromiseCallback = (...args: any[]) => Promise; + +const retryUntilResolve = ( + fn: PromiseCallback, + pause = 1000, + retryCount = 0, +): PromiseCallback => { + return (...args) => { + let retry = 0; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const run: () => Promise = () => + fn(...args).catch((error) => { + if (retryCount > 0 && retry >= retryCount) { + retry = 0; + throw error; + } + + retry++; + return sleep(pause).then(run); + }) + + return run(); + } +}; + +export { retryUntilResolve }; diff --git a/src/utils/sleep.ts b/src/utils/sleep.ts new file mode 100644 index 0000000..b8f3d01 --- /dev/null +++ b/src/utils/sleep.ts @@ -0,0 +1,5 @@ +const sleep = (time?: number): Promise => { + return new Promise((resolve) => setTimeout(resolve, time)); +} + +export { sleep };