diff options
Diffstat (limited to 'packages/merchant-backoffice-ui/src/hooks/order.ts')
-rw-r--r-- | packages/merchant-backoffice-ui/src/hooks/order.ts | 315 |
1 files changed, 62 insertions, 253 deletions
diff --git a/packages/merchant-backoffice-ui/src/hooks/order.ts b/packages/merchant-backoffice-ui/src/hooks/order.ts index e7a893f2c..d0513dc40 100644 --- a/packages/merchant-backoffice-ui/src/hooks/order.ts +++ b/packages/merchant-backoffice-ui/src/hooks/order.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2021-2023 Taler Systems S.A. + (C) 2021-2024 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -13,277 +13,86 @@ You should have received a copy of the GNU General Public License along with GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { - HttpResponse, - HttpResponseOk, - HttpResponsePaginated, - RequestError, -} from "@gnu-taler/web-util/browser"; -import { useEffect, useState } from "preact/hooks"; -import { MerchantBackend } from "../declaration.js"; -import { MAX_RESULT_SIZE, PAGE_SIZE } from "../utils/constants.js"; -import { useBackendInstanceRequest, useMatchMutate } from "./backend.js"; +import { PAGINATED_LIST_REQUEST } from "../utils/constants.js"; // FIX default import https://github.com/microsoft/TypeScript/issues/49189 -import _useSWR, { SWRHook } from "swr"; +import { AbsoluteTime, AccessToken, TalerHttpError, TalerMerchantManagementResultByMethod } from "@gnu-taler/taler-util"; +import _useSWR, { SWRHook, mutate } from "swr"; +import { useSessionContext } from "../context/session.js"; +import { buildPaginatedResult } from "./webhooks.js"; const useSWR = _useSWR as unknown as SWRHook; -export interface OrderAPI { - //FIXME: add OutOfStockResponse on 410 - createOrder: ( - data: MerchantBackend.Orders.PostOrderRequest, - ) => Promise<HttpResponseOk<MerchantBackend.Orders.PostOrderResponse>>; - forgetOrder: ( - id: string, - data: MerchantBackend.Orders.ForgetRequest, - ) => Promise<HttpResponseOk<void>>; - refundOrder: ( - id: string, - data: MerchantBackend.Orders.RefundRequest, - ) => Promise<HttpResponseOk<MerchantBackend.Orders.MerchantRefundResponse>>; - deleteOrder: (id: string) => Promise<HttpResponseOk<void>>; - getPaymentURL: (id: string) => Promise<HttpResponseOk<string>>; -} - -type YesOrNo = "yes" | "no"; - -export function useOrderAPI(): OrderAPI { - const mutateAll = useMatchMutate(); - const { request } = useBackendInstanceRequest(); - - const createOrder = async ( - data: MerchantBackend.Orders.PostOrderRequest, - ): Promise<HttpResponseOk<MerchantBackend.Orders.PostOrderResponse>> => { - const res = await request<MerchantBackend.Orders.PostOrderResponse>( - `/private/orders`, - { - method: "POST", - data, - }, - ); - await mutateAll(/.*private\/orders.*/); - // mutate('') - return res; - }; - const refundOrder = async ( - orderId: string, - data: MerchantBackend.Orders.RefundRequest, - ): Promise<HttpResponseOk<MerchantBackend.Orders.MerchantRefundResponse>> => { - mutateAll(/@"\/private\/orders"@/); - const res = request<MerchantBackend.Orders.MerchantRefundResponse>( - `/private/orders/${orderId}/refund`, - { - method: "POST", - data, - }, - ); - - // order list returns refundable information, so we must evict everything - await mutateAll(/.*private\/orders.*/); - return res; - }; - - const forgetOrder = async ( - orderId: string, - data: MerchantBackend.Orders.ForgetRequest, - ): Promise<HttpResponseOk<void>> => { - mutateAll(/@"\/private\/orders"@/); - const res = request<void>(`/private/orders/${orderId}/forget`, { - method: "PATCH", - data, - }); - // we may be forgetting some fields that are pare of the listing, so we must evict everything - await mutateAll(/.*private\/orders.*/); - return res; - }; - const deleteOrder = async ( - orderId: string, - ): Promise<HttpResponseOk<void>> => { - mutateAll(/@"\/private\/orders"@/); - const res = request<void>(`/private/orders/${orderId}`, { - method: "DELETE", - }); - await mutateAll(/.*private\/orders.*/); - return res; - }; - const getPaymentURL = async ( - orderId: string, - ): Promise<HttpResponseOk<string>> => { - return request<MerchantBackend.Orders.MerchantOrderStatusResponse>( - `/private/orders/${orderId}`, - { - method: "GET", - }, - ).then((res) => { - const url = - res.data.order_status === "unpaid" - ? res.data.taler_pay_uri - : res.data.contract_terms.fulfillment_url; - const response: HttpResponseOk<string> = res as any; - response.data = url || ""; - return response; - }); - }; - return { createOrder, forgetOrder, deleteOrder, refundOrder, getPaymentURL }; +export function revalidateOrderDetails() { + return mutate( + (key) => Array.isArray(key) && key[key.length - 1] === "getOrderDetails", + undefined, + { revalidate: true }, + ); } +export function useOrderDetails(oderId: string) { + const { state: session } = useSessionContext(); + const { lib: { instance } } = useSessionContext(); -export function useOrderDetails( - oderId: string, -): HttpResponse< - MerchantBackend.Orders.MerchantOrderStatusResponse, - MerchantBackend.ErrorDetail -> { - const { fetcher } = useBackendInstanceRequest(); + async function fetcher([dId, token]: [string, AccessToken]) { + return await instance.getOrderDetails(token, dId); + } - const { data, error, isValidating } = useSWR< - HttpResponseOk<MerchantBackend.Orders.MerchantOrderStatusResponse>, - RequestError<MerchantBackend.ErrorDetail> - >([`/private/orders/${oderId}`], fetcher, { - refreshInterval: 0, - refreshWhenHidden: false, - revalidateOnFocus: false, - revalidateOnReconnect: false, - refreshWhenOffline: false, - }); + const { data, error } = useSWR< + TalerMerchantManagementResultByMethod<"getOrderDetails">, + TalerHttpError + >([oderId, session.token, "getOrderDetails"], fetcher); - if (isValidating) return { loading: true, data: data?.data }; if (data) return data; - if (error) return error.cause; - return { loading: true }; + if (error) return error; + return undefined; } export interface InstanceOrderFilter { - paid?: YesOrNo; - refunded?: YesOrNo; - wired?: YesOrNo; - date?: Date; + paid?: boolean; + refunded?: boolean; + wired?: boolean; + date?: AbsoluteTime; + position?: string; } +export function revalidateInstanceOrders() { + return mutate( + (key) => Array.isArray(key) && key[key.length - 1] === "listOrders", + undefined, + { revalidate: true }, + ); +} export function useInstanceOrders( args?: InstanceOrderFilter, - updateFilter?: (d: Date) => void, -): HttpResponsePaginated< - MerchantBackend.Orders.OrderHistory, - MerchantBackend.ErrorDetail -> { - const { orderFetcher } = useBackendInstanceRequest(); - - const [pageBefore, setPageBefore] = useState(1); - const [pageAfter, setPageAfter] = useState(1); - - const totalAfter = pageAfter * PAGE_SIZE; - const totalBefore = args?.date ? pageBefore * PAGE_SIZE : 0; - - /** - * FIXME: this can be cleaned up a little - * - * the logic of double query should be inside the orderFetch so from the hook perspective and cache - * is just one query and one error status - */ - const { - data: beforeData, - error: beforeError, - isValidating: loadingBefore, - } = useSWR< - HttpResponseOk<MerchantBackend.Orders.OrderHistory>, - RequestError<MerchantBackend.ErrorDetail> - >( - [ - `/private/orders`, - args?.paid, - args?.refunded, - args?.wired, - args?.date, - totalBefore, - ], - orderFetcher, - ); - const { - data: afterData, - error: afterError, - isValidating: loadingAfter, - } = useSWR< - HttpResponseOk<MerchantBackend.Orders.OrderHistory>, - RequestError<MerchantBackend.ErrorDetail> - >( - [ - `/private/orders`, - args?.paid, - args?.refunded, - args?.wired, - args?.date, - -totalAfter, - ], - orderFetcher, - ); - - //this will save last result - const [lastBefore, setLastBefore] = useState< - HttpResponse< - MerchantBackend.Orders.OrderHistory, - MerchantBackend.ErrorDetail - > - >({ loading: true }); - const [lastAfter, setLastAfter] = useState< - HttpResponse< - MerchantBackend.Orders.OrderHistory, - MerchantBackend.ErrorDetail - > - >({ loading: true }); - useEffect(() => { - if (afterData) setLastAfter(afterData); - if (beforeData) setLastBefore(beforeData); - }, [afterData, beforeData]); - - if (beforeError) return beforeError.cause; - if (afterError) return afterError.cause; + updatePosition: (d: string | undefined) => void = () => { }, +) { + const { state: session } = useSessionContext(); + const { lib: { instance } } = useSessionContext(); + + // const [offset, setOffset] = useState<string | undefined>(args?.position); + + async function fetcher([token, o, p, r, w, d]: [AccessToken, string, boolean, boolean, boolean, AbsoluteTime]) { + return await instance.listOrders(token, { + limit: PAGINATED_LIST_REQUEST, + offset: o, + order: "dec", + paid: p, + refunded: r, + wired: w, + date: d, + }); + } - // if the query returns less that we ask, then we have reach the end or beginning - const isReachingEnd = afterData && afterData.data.orders.length < totalAfter; - const isReachingStart = - args?.date === undefined || - (beforeData && beforeData.data.orders.length < totalBefore); + const { data, error } = useSWR< + TalerMerchantManagementResultByMethod<"listOrders">, + TalerHttpError + >([session.token, args?.position, args?.paid, args?.refunded, args?.wired, args?.date, "listOrders"], fetcher); - const pagination = { - isReachingEnd, - isReachingStart, - loadMore: () => { - if (!afterData || isReachingEnd) return; - if (afterData.data.orders.length < MAX_RESULT_SIZE) { - setPageAfter(pageAfter + 1); - } else { - const from = - afterData.data.orders[afterData.data.orders.length - 1].timestamp.t_s; - if (from && from !== "never" && updateFilter) - updateFilter(new Date(from * 1000)); - } - }, - loadMorePrev: () => { - if (!beforeData || isReachingStart) return; - if (beforeData.data.orders.length < MAX_RESULT_SIZE) { - setPageBefore(pageBefore + 1); - } else if (beforeData) { - const from = - beforeData.data.orders[beforeData.data.orders.length - 1].timestamp - .t_s; - if (from && from !== "never" && updateFilter) - updateFilter(new Date(from * 1000)); - } - }, - }; + if (error) return error; + if (data === undefined) return undefined; + if (data.type !== "ok") return data; - const orders = - !beforeData || !afterData - ? [] - : (beforeData || lastBefore).data.orders - .slice() - .reverse() - .concat((afterData || lastAfter).data.orders); - if (loadingAfter || loadingBefore) return { loading: true, data: { orders } }; - if (beforeData && afterData) { - return { ok: true, data: { orders }, ...pagination }; - } - return { loading: true }; + return buildPaginatedResult(data.body.orders, args?.position, updatePosition, (d) => String(d.row_id)) } |