fix(dashboard): payment providers select (#13592)

**What**
- use lazy loading for payment providers select on region create/edit
This commit is contained in:
Frane Polić authored and GitHub committed 2025-09-25 17:54:07 +00:00
1 parent 9538df2eaf
commit c3ae529b40
7 files changed
+62 -38

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@medusajs/dashboard": patch
---
fix(dashboard): payment providers select
@@ -58,6 +58,7 @@ interface ComboboxProps<T extends Value = Value>
onCreateOption?: (value: string) => void onCreateOption?: (value: string) => void
noResultsPlaceholder?: ReactNode noResultsPlaceholder?: ReactNode
allowClear?: boolean allowClear?: boolean
forceHideInput?: boolean // always hide input -> used for singe value select that don't have query/filter
} }
const ComboboxImpl = <T extends Value = string>( const ComboboxImpl = <T extends Value = string>(
@@ -74,6 +75,7 @@ const ComboboxImpl = <T extends Value = string>(
onCreateOption, onCreateOption,
noResultsPlaceholder, noResultsPlaceholder,
allowClear, allowClear,
forceHideInput,
...inputProps ...inputProps
}: ComboboxProps<T>, }: ComboboxProps<T>,
ref: ForwardedRef<HTMLInputElement> ref: ForwardedRef<HTMLInputElement>
@@ -152,10 +154,15 @@ const ComboboxImpl = <T extends Value = string>(
return [] return []
} }
// do not use `matcher` if the input is hidden
if (forceHideInput) {
return options
}
return matchSorter(options, defferedSearchValue, { return matchSorter(options, defferedSearchValue, {
keys: ["label"], keys: ["label"],
}) })
}, [options, defferedSearchValue, isSearchControlled]) }, [options, defferedSearchValue, isSearchControlled, forceHideInput])
const observer = useRef( const observer = useRef(
new IntersectionObserver( new IntersectionObserver(
@@ -197,7 +204,7 @@ const ComboboxImpl = <T extends Value = string>(
const showTag = hasValue && isArrayValue const showTag = hasValue && isArrayValue
const showSelected = showTag && !searchValue && !open const showSelected = showTag && !searchValue && !open
const hideInput = !isArrayValue && hasValue && !open const hideInput = forceHideInput || (!isArrayValue && hasValue && !open)
const selectedLabel = options.find((o) => o.value === selectedValues)?.label const selectedLabel = options.find((o) => o.value === selectedValues)?.label
const hidePlaceholder = showSelected || open const hidePlaceholder = showSelected || open
@@ -251,7 +258,7 @@ const ComboboxImpl = <T extends Value = string>(
e.preventDefault() e.preventDefault()
handleValueChange(isArrayValue ? ([] as unknown as T) : undefined) handleValueChange(isArrayValue ? ([] as unknown as T) : undefined)
}} }}
className="bg-ui-bg-base hover:bg-ui-bg-base-hover txt-compact-small-plus text-ui-fg-subtle focus-within:border-ui-fg-interactive transition-fg absolute start-0.5 top-0.5 z-[1] flex h-[28px] items-center rounded-[4px] border py-[3px] ps-1.5 pe-1 outline-none" className="bg-ui-bg-base hover:bg-ui-bg-base-hover txt-compact-small-plus text-ui-fg-subtle focus-within:border-ui-fg-interactive transition-fg absolute start-0.5 top-0.5 z-[1] flex h-[28px] items-center rounded-[4px] border py-[3px] pe-1 ps-1.5 outline-none"
> >
<span className="tabular-nums">{selectedValues.length}</span> <span className="tabular-nums">{selectedValues.length}</span>
<XMarkMini className="text-ui-fg-muted" /> <XMarkMini className="text-ui-fg-muted" />
@@ -293,7 +300,7 @@ const ComboboxImpl = <T extends Value = string>(
ref={comboboxRef} ref={comboboxRef}
onFocus={() => setOpen(true)} onFocus={() => setOpen(true)}
className={clx( className={clx(
"txt-compact-small text-ui-fg-base !placeholder:text-ui-fg-muted transition-fg size-full cursor-pointer bg-transparent ps-2 pe-8 outline-none focus:cursor-text", "txt-compact-small text-ui-fg-base !placeholder:text-ui-fg-muted transition-fg size-full cursor-pointer bg-transparent pe-8 ps-2 outline-none focus:cursor-text",
"hover:bg-ui-bg-field-hover", "hover:bg-ui-bg-field-hover",
{ {
"opacity-0": hideInput, "opacity-0": hideInput,
@@ -15,6 +15,11 @@ import { FetchError } from "@medusajs/js-sdk"
const PAYMENT_QUERY_KEY = "payment" as const const PAYMENT_QUERY_KEY = "payment" as const
export const paymentQueryKeys = queryKeysFactory(PAYMENT_QUERY_KEY) export const paymentQueryKeys = queryKeysFactory(PAYMENT_QUERY_KEY)
const PAYMENT_PROVIDERS_QUERY_KEY = "payment_providers" as const
export const paymentProvidersQueryKeys = queryKeysFactory(
PAYMENT_PROVIDERS_QUERY_KEY
)
export const usePaymentProviders = ( export const usePaymentProviders = (
query?: HttpTypes.AdminGetPaymentProvidersParams, query?: HttpTypes.AdminGetPaymentProvidersParams,
options?: Omit< options?: Omit<
@@ -29,7 +34,7 @@ export const usePaymentProviders = (
) => { ) => {
const { data, ...rest } = useQuery({ const { data, ...rest } = useQuery({
queryFn: async () => sdk.admin.payment.listPaymentProviders(query), queryFn: async () => sdk.admin.payment.listPaymentProviders(query),
queryKey: [], queryKey: paymentProvidersQueryKeys.list(query),
...options, ...options,
}) })
@@ -17,7 +17,7 @@ import { useForm, useWatch } from "react-hook-form"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import * as zod from "zod" import * as zod from "zod"
import { PaymentProviderDTO, RegionCountryDTO } from "@medusajs/types" import { RegionCountryDTO } from "@medusajs/types"
import { Form } from "../../../../../components/common/form" import { Form } from "../../../../../components/common/form"
import { Combobox } from "../../../../../components/inputs/combobox" import { Combobox } from "../../../../../components/inputs/combobox"
@@ -38,10 +38,11 @@ import { useCountries } from "../../../common/hooks/use-countries"
import { useCountryTableColumns } from "../../../common/hooks/use-country-table-columns" import { useCountryTableColumns } from "../../../common/hooks/use-country-table-columns"
import { useCountryTableQuery } from "../../../common/hooks/use-country-table-query" import { useCountryTableQuery } from "../../../common/hooks/use-country-table-query"
import { useDocumentDirection } from "../../../../../hooks/use-document-direction" import { useDocumentDirection } from "../../../../../hooks/use-document-direction"
import { useComboboxData } from "../../../../../hooks/use-combobox-data"
import { sdk } from "../../../../../lib/client"
type CreateRegionFormProps = { type CreateRegionFormProps = {
currencies: CurrencyInfo[] currencies: CurrencyInfo[]
paymentProviders: PaymentProviderDTO[]
} }
const CreateRegionSchema = zod.object({ const CreateRegionSchema = zod.object({
@@ -58,10 +59,7 @@ const PAGE_SIZE = 50
const STACKED_MODAL_ID = "countries-modal" const STACKED_MODAL_ID = "countries-modal"
export const CreateRegionForm = ({ export const CreateRegionForm = ({ currencies }: CreateRegionFormProps) => {
currencies,
paymentProviders,
}: CreateRegionFormProps) => {
const { setIsOpen } = useStackedModal() const { setIsOpen } = useStackedModal()
const [rowSelection, setRowSelection] = useState<RowSelectionState>({}) const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
const { handleSuccess } = useRouteModal() const { handleSuccess } = useRouteModal()
@@ -181,6 +179,17 @@ export const CreateRegionForm = ({
setRowSelection({}) setRowSelection({})
} }
const comboboxProviders = useComboboxData({
queryFn: (params) =>
sdk.admin.payment.listPaymentProviders({ ...params, is_enabled: true }),
queryKey: ["payment_providers"],
getOptions: (data) =>
data.payment_providers.map((pp) => ({
label: formatProvider(pp.id),
value: pp.id,
})),
})
return ( return (
<RouteFocusModal.Form form={form}> <RouteFocusModal.Form form={form}>
<KeyboundForm <KeyboundForm
@@ -415,10 +424,9 @@ export const CreateRegionForm = ({
</Form.Label> </Form.Label>
<Form.Control> <Form.Control>
<Combobox <Combobox
options={paymentProviders.map((pp) => ({ forceHideInput
label: formatProvider(pp.id), options={comboboxProviders.options}
value: pp.id, fetchNextPage={comboboxProviders.fetchNextPage}
}))}
{...field} {...field}
/> />
</Form.Control> </Form.Control>
@@ -1,5 +1,4 @@
import { RouteFocusModal } from "../../../components/modals/route-focus-modal" import { RouteFocusModal } from "../../../components/modals/route-focus-modal"
import { usePaymentProviders } from "../../../hooks/api/payments"
import { useStore } from "../../../hooks/api/store" import { useStore } from "../../../hooks/api/store"
import { currencies } from "../../../lib/data/currencies" import { currencies } from "../../../lib/data/currencies"
import { CreateRegionForm } from "./components/create-region-form" import { CreateRegionForm } from "./components/create-region-form"
@@ -10,9 +9,6 @@ export const RegionCreate = () => {
const storeCurrencies = (store?.supported_currencies ?? []).map( const storeCurrencies = (store?.supported_currencies ?? []).map(
(c) => currencies[c.currency_code.toUpperCase()] (c) => currencies[c.currency_code.toUpperCase()]
) )
const { payment_providers: paymentProviders = [] } = usePaymentProviders({
is_enabled: true,
})
if (isError) { if (isError) {
throw error throw error
@@ -20,12 +16,7 @@ export const RegionCreate = () => {
return ( return (
<RouteFocusModal> <RouteFocusModal>
{!isLoading && store && ( {!isLoading && store && <CreateRegionForm currencies={storeCurrencies} />}
<CreateRegionForm
currencies={storeCurrencies}
paymentProviders={paymentProviders}
/>
)}
</RouteFocusModal> </RouteFocusModal>
) )
} }
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next"
import * as zod from "zod" import * as zod from "zod"
import { Form } from "../../../../../components/common/form/index.ts" import { Form } from "../../../../../components/common/form/index.ts"
import { Combobox } from "../../../../../components/inputs/combobox/index.ts" import { Combobox } from "../../../../../components/inputs/combobox"
import { import {
RouteDrawer, RouteDrawer,
useRouteModal, useRouteModal,
@@ -15,11 +15,12 @@ import { useUpdateRegion } from "../../../../../hooks/api/regions.tsx"
import { CurrencyInfo } from "../../../../../lib/data/currencies.ts" import { CurrencyInfo } from "../../../../../lib/data/currencies.ts"
import { formatProvider } from "../../../../../lib/format-provider.ts" import { formatProvider } from "../../../../../lib/format-provider.ts"
import { useDocumentDirection } from "../../../../../hooks/use-document-direction" import { useDocumentDirection } from "../../../../../hooks/use-document-direction"
import { useComboboxData } from "../../../../../hooks/use-combobox-data.tsx"
import { sdk } from "../../../../../lib/client/index.ts"
type EditRegionFormProps = { type EditRegionFormProps = {
region: HttpTypes.AdminRegion region: HttpTypes.AdminRegion
currencies: CurrencyInfo[] currencies: CurrencyInfo[]
paymentProviders: PaymentProviderDTO[]
pricePreferences: HttpTypes.AdminPricePreference[] pricePreferences: HttpTypes.AdminPricePreference[]
} }
@@ -34,7 +35,6 @@ const EditRegionSchema = zod.object({
export const EditRegionForm = ({ export const EditRegionForm = ({
region, region,
currencies, currencies,
paymentProviders,
pricePreferences, pricePreferences,
}: EditRegionFormProps) => { }: EditRegionFormProps) => {
const { t } = useTranslation() const { t } = useTranslation()
@@ -54,6 +54,17 @@ export const EditRegionForm = ({
}, },
}) })
const comboboxProviders = useComboboxData({
queryKey: ["payment_providers"],
queryFn: (params) =>
sdk.admin.payment.listPaymentProviders({ ...params, is_enabled: true }),
getOptions: (data) =>
data.payment_providers.map((pp) => ({
label: formatProvider(pp.id),
value: pp.id,
})),
})
const { mutateAsync: updateRegion, isPending: isPendingRegion } = const { mutateAsync: updateRegion, isPending: isPendingRegion } =
useUpdateRegion(region.id) useUpdateRegion(region.id)
@@ -80,7 +91,10 @@ export const EditRegionForm = ({
return ( return (
<RouteDrawer.Form form={form}> <RouteDrawer.Form form={form}>
<KeyboundForm onSubmit={handleSubmit} className="flex flex-1 flex-col overflow-hidden"> <KeyboundForm
onSubmit={handleSubmit}
className="flex flex-1 flex-col overflow-hidden"
>
<RouteDrawer.Body className="overflow-y-auto"> <RouteDrawer.Body className="overflow-y-auto">
<div className="flex flex-col gap-y-8"> <div className="flex flex-col gap-y-8">
<div className="flex flex-col gap-y-4"> <div className="flex flex-col gap-y-4">
@@ -205,10 +219,9 @@ export const EditRegionForm = ({
<Form.Label>{t("fields.paymentProviders")}</Form.Label> <Form.Label>{t("fields.paymentProviders")}</Form.Label>
<Form.Control> <Form.Control>
<Combobox <Combobox
options={paymentProviders.map((pp) => ({ forceHideInput
label: formatProvider(pp.id), options={comboboxProviders.options}
value: pp.id, fetchNextPage={comboboxProviders.fetchNextPage}
}))}
{...field} {...field}
/> />
</Form.Control> </Form.Control>
@@ -48,10 +48,6 @@ export const RegionEdit = () => {
const storeCurrencies = (store?.supported_currencies ?? []).map( const storeCurrencies = (store?.supported_currencies ?? []).map(
(c) => currencies[c.currency_code.toUpperCase()] (c) => currencies[c.currency_code.toUpperCase()]
) )
const { payment_providers: paymentProviders = [] } = usePaymentProviders({
limit: 999,
is_enabled: true,
})
if (isRegionError) { if (isRegionError) {
throw regionError throw regionError
@@ -74,7 +70,6 @@ export const RegionEdit = () => {
<EditRegionForm <EditRegionForm
region={region} region={region}
currencies={storeCurrencies} currencies={storeCurrencies}
paymentProviders={paymentProviders}
pricePreferences={pricePreferences} pricePreferences={pricePreferences}
/> />
)} )}