feat: Add tax inclusivity management to currenices (#8112)
This commit is contained in:
@@ -31,7 +31,7 @@ export const usePricePreference = (
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryFn: () => sdk.admin.pricePreference.retrieve(id, query),
|
||||
queryKey: pricePreferencesQueryKeys.detail(id),
|
||||
queryKey: pricePreferencesQueryKeys.detail(),
|
||||
...options,
|
||||
})
|
||||
|
||||
@@ -52,7 +52,7 @@ export const usePricePreferences = (
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryFn: () => sdk.admin.pricePreference.list(query),
|
||||
queryKey: pricePreferencesQueryKeys.list(query),
|
||||
queryKey: pricePreferencesQueryKeys.list(),
|
||||
...options,
|
||||
})
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { sdk } from "../../lib/client"
|
||||
import { queryClient } from "../../lib/query-client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import { pricePreferencesQueryKeys } from "./price-preferences"
|
||||
|
||||
const REGIONS_QUERY_KEY = "regions" as const
|
||||
export const regionsQueryKeys = queryKeysFactory(REGIONS_QUERY_KEY)
|
||||
@@ -67,6 +68,14 @@ export const useCreateRegion = (
|
||||
mutationFn: (payload) => sdk.admin.region.create(payload),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({ queryKey: regionsQueryKeys.lists() })
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pricePreferencesQueryKeys.list(),
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pricePreferencesQueryKeys.details(),
|
||||
})
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
@@ -87,6 +96,13 @@ export const useUpdateRegion = (
|
||||
queryClient.invalidateQueries({ queryKey: regionsQueryKeys.lists() })
|
||||
queryClient.invalidateQueries({ queryKey: regionsQueryKeys.detail(id) })
|
||||
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pricePreferencesQueryKeys.list(),
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pricePreferencesQueryKeys.details(),
|
||||
})
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
|
||||
@@ -11,6 +11,7 @@ import { HttpTypes } from "@medusajs/types"
|
||||
import { sdk } from "../../lib/client"
|
||||
import { queryClient } from "../../lib/query-client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import { pricePreferencesQueryKeys } from "./price-preferences"
|
||||
|
||||
const STORE_QUERY_KEY = "store" as const
|
||||
export const storeQueryKeys = queryKeysFactory(STORE_QUERY_KEY)
|
||||
@@ -67,7 +68,14 @@ export const useUpdateStore = (
|
||||
return useMutation({
|
||||
mutationFn: (payload) => sdk.admin.store.update(id, payload),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pricePreferencesQueryKeys.list(),
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: pricePreferencesQueryKeys.details(),
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: storeQueryKeys.details() })
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
|
||||
@@ -1710,6 +1710,8 @@
|
||||
"inviteLinkTemplate": "Invite link template",
|
||||
"currencies": "Currencies",
|
||||
"addCurrencies": "Add currencies",
|
||||
"enableTaxInclusivePricing": "Enable tax inclusive pricing",
|
||||
"disableTaxInclusivePricing": "Disable tax inclusive pricing",
|
||||
"removeCurrencyWarning_one": "You are about to remove {{count}} currency from your store. Ensure that you have removed all prices using the currency before proceeding.",
|
||||
"removeCurrencyWarning_other": "You are about to remove {{count}} currencies from your store. Ensure that you have removed all prices using the currencies before proceeding.",
|
||||
"currencyAlreadyAdded": "The currency has already been added to your store.",
|
||||
@@ -1719,7 +1721,8 @@
|
||||
"toast": {
|
||||
"update": "Store successfully updated",
|
||||
"currenciesUpdated": "Currencies updated successfully",
|
||||
"currenciesRemoved": "Removed currencies from the store successfully"
|
||||
"currenciesRemoved": "Removed currencies from the store successfully",
|
||||
"updatedTaxInclusivitySuccessfully": "Tax inclusive pricing updated successfully"
|
||||
}
|
||||
},
|
||||
"regions": {
|
||||
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
import { CurrencyDTO } from "@medusajs/types"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
const columnHelper = createColumnHelper<CurrencyDTO>()
|
||||
const columnHelper = createColumnHelper<HttpTypes.AdminCurrency>()
|
||||
|
||||
export const useCurrenciesTableColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
+89
-35
@@ -1,16 +1,15 @@
|
||||
import { Currency } from "@medusajs/medusa"
|
||||
import { Button, Checkbox, Hint, toast, Tooltip } from "@medusajs/ui"
|
||||
import { Button, Checkbox, Hint, Switch, toast, Tooltip } from "@medusajs/ui"
|
||||
import {
|
||||
createColumnHelper,
|
||||
OnChangeFn,
|
||||
RowSelectionState,
|
||||
} from "@tanstack/react-table"
|
||||
import { useMemo, useState } from "react"
|
||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { StoreDTO } from "@medusajs/types"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { keepPreviousData } from "@tanstack/react-query"
|
||||
import { useForm } from "react-hook-form"
|
||||
import {
|
||||
@@ -23,13 +22,15 @@ import { useUpdateStore } from "../../../../../hooks/api/store"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useCurrenciesTableColumns } from "../../../common/hooks/use-currencies-table-columns"
|
||||
import { useCurrenciesTableQuery } from "../../../common/hooks/use-currencies-table-query"
|
||||
import { usePricePreferences } from "../../../../../hooks/api/price-preferences"
|
||||
|
||||
type AddCurrenciesFormProps = {
|
||||
store: StoreDTO
|
||||
store: HttpTypes.AdminStore
|
||||
}
|
||||
|
||||
const AddCurrenciesSchema = zod.object({
|
||||
currencies: zod.array(zod.string()).min(1),
|
||||
pricePreferences: zod.record(zod.boolean()),
|
||||
})
|
||||
|
||||
const PAGE_SIZE = 50
|
||||
@@ -39,30 +40,6 @@ export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof AddCurrenciesSchema>>({
|
||||
defaultValues: {
|
||||
currencies: [],
|
||||
},
|
||||
resolver: zodResolver(AddCurrenciesSchema),
|
||||
})
|
||||
|
||||
const { setValue } = form
|
||||
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||
|
||||
const updater: OnChangeFn<RowSelectionState> = (fn) => {
|
||||
const updated = typeof fn === "function" ? fn(rowSelection) : fn
|
||||
|
||||
const ids = Object.keys(updated)
|
||||
|
||||
setValue("currencies", ids, {
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
})
|
||||
|
||||
setRowSelection(updated)
|
||||
}
|
||||
|
||||
const { raw, searchParams } = useCurrenciesTableQuery({
|
||||
pageSize: 50,
|
||||
prefix: PREFIX,
|
||||
@@ -78,10 +55,61 @@ export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
|
||||
placeholderData: keepPreviousData,
|
||||
})
|
||||
|
||||
const {
|
||||
price_preferences: pricePreferences,
|
||||
isPending: isPricePreferencesPending,
|
||||
isError: isPricePreferencesError,
|
||||
error: pricePreferencesError,
|
||||
} = usePricePreferences({
|
||||
attribute: "currency_code",
|
||||
value: store.supported_currencies?.map((c) => c.currency_code),
|
||||
})
|
||||
|
||||
const form = useForm<zod.infer<typeof AddCurrenciesSchema>>({
|
||||
defaultValues: {
|
||||
currencies: [],
|
||||
pricePreferences: {},
|
||||
},
|
||||
resolver: zodResolver(AddCurrenciesSchema),
|
||||
})
|
||||
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||
|
||||
const { setValue, watch } = form
|
||||
const pricePreferenceValues = watch("pricePreferences")
|
||||
|
||||
const updater: OnChangeFn<RowSelectionState> = (fn) => {
|
||||
const updated = typeof fn === "function" ? fn(rowSelection) : fn
|
||||
|
||||
const ids = Object.keys(updated)
|
||||
setValue("currencies", ids, {
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
})
|
||||
|
||||
setRowSelection(updated)
|
||||
}
|
||||
|
||||
const preSelectedRows =
|
||||
store.supported_currencies?.map((c) => c.currency_code) ?? []
|
||||
|
||||
const columns = useColumns()
|
||||
const setPricePreferences = useCallback(
|
||||
(values: Record<string, boolean>) => {
|
||||
setValue("pricePreferences", values)
|
||||
},
|
||||
[setValue]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setPricePreferences(
|
||||
pricePreferences?.reduce((acc: Record<string, boolean>, curr) => {
|
||||
acc[curr.value] = curr.is_tax_inclusive
|
||||
return acc
|
||||
}, {})
|
||||
)
|
||||
}, [pricePreferences, setPricePreferences])
|
||||
|
||||
const columns = useColumns(pricePreferenceValues, setPricePreferences)
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: currencies ?? [],
|
||||
@@ -118,8 +146,7 @@ export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
|
||||
supported_currencies: currencies.map((c) => ({
|
||||
currency_code: c,
|
||||
is_default: c === defaultCurrency,
|
||||
// TODO: Add UI to manage this
|
||||
is_tax_inclsuive: false,
|
||||
is_tax_inclusive: data.pricePreferences[c],
|
||||
})),
|
||||
},
|
||||
{
|
||||
@@ -185,9 +212,12 @@ export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<Currency>()
|
||||
const columnHelper = createColumnHelper<HttpTypes.AdminCurrency>()
|
||||
|
||||
const useColumns = () => {
|
||||
const useColumns = (
|
||||
pricePreferences: Record<string, boolean>,
|
||||
setPricePreferences: any
|
||||
) => {
|
||||
const { t } = useTranslation()
|
||||
const base = useCurrenciesTableColumns()
|
||||
|
||||
@@ -236,7 +266,31 @@ const useColumns = () => {
|
||||
},
|
||||
}),
|
||||
...base,
|
||||
columnHelper.display({
|
||||
id: "select",
|
||||
header: () => (
|
||||
<div className="whitespace-nowrap">
|
||||
{t("fields.taxInclusivePricing")}
|
||||
</div>
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const isPreSelected = !row.getCanSelect()
|
||||
const isTaxInclusive = pricePreferences[row.original.code]
|
||||
return (
|
||||
<Switch
|
||||
disabled={isPreSelected}
|
||||
checked={isTaxInclusive ?? false}
|
||||
onCheckedChange={(val) => {
|
||||
setPricePreferences({
|
||||
...pricePreferences,
|
||||
[row.original.code]: val,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
],
|
||||
[t, base]
|
||||
[t, base, pricePreferences, setPricePreferences]
|
||||
)
|
||||
}
|
||||
|
||||
+110
-15
@@ -1,5 +1,5 @@
|
||||
import { Plus, Trash } from "@medusajs/icons"
|
||||
import { CurrencyDTO, StoreCurrencyDTO } from "@medusajs/types"
|
||||
import { CheckCircle, Plus, Trash, XCircle } from "@medusajs/icons"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
Checkbox,
|
||||
CommandBar,
|
||||
@@ -20,6 +20,8 @@ import { useDataTable } from "../../../../../../hooks/use-data-table"
|
||||
import { ExtendedStoreDTO } from "../../../../../../types/api-responses"
|
||||
import { useCurrenciesTableColumns } from "../../../../common/hooks/use-currencies-table-columns"
|
||||
import { useCurrenciesTableQuery } from "../../../../common/hooks/use-currencies-table-query"
|
||||
import { usePricePreferences } from "../../../../../../hooks/api/price-preferences"
|
||||
import { StatusCell } from "../../../../../../components/table/table-cells/common/status-cell"
|
||||
|
||||
type StoreCurrencySectionProps = {
|
||||
store: ExtendedStoreDTO
|
||||
@@ -32,7 +34,13 @@ export const StoreCurrencySection = ({ store }: StoreCurrencySectionProps) => {
|
||||
|
||||
const { searchParams, raw } = useCurrenciesTableQuery({ pageSize: PAGE_SIZE })
|
||||
|
||||
const { currencies, count, isPending, isError, error } = useCurrencies(
|
||||
const {
|
||||
currencies,
|
||||
count,
|
||||
isPending: isCurrenciesPending,
|
||||
isError: isCurrenciesError,
|
||||
error: currenciesError,
|
||||
} = useCurrencies(
|
||||
{
|
||||
code: store.supported_currencies?.map((c) => c.currency_code),
|
||||
...searchParams,
|
||||
@@ -43,10 +51,33 @@ export const StoreCurrencySection = ({ store }: StoreCurrencySectionProps) => {
|
||||
}
|
||||
)
|
||||
|
||||
const {
|
||||
price_preferences: pricePreferences,
|
||||
isPending: isPricePreferencesPending,
|
||||
isError: isPricePreferencesError,
|
||||
error: pricePreferencesError,
|
||||
} = usePricePreferences(
|
||||
{
|
||||
attribute: "currency_code",
|
||||
value: store.supported_currencies?.map((c) => c.currency_code),
|
||||
},
|
||||
{
|
||||
enabled: !!store.supported_currencies?.length,
|
||||
}
|
||||
)
|
||||
|
||||
const columns = useColumns()
|
||||
const prefMap = useMemo(() => {
|
||||
return new Map(pricePreferences?.map((pref) => [pref.value!, pref]))
|
||||
}, [pricePreferences])
|
||||
|
||||
const withTaxInclusivity = currencies?.map((c) => ({
|
||||
...c,
|
||||
is_tax_inclusive: prefMap.get(c.code)?.is_tax_inclusive,
|
||||
}))
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: currencies ?? [],
|
||||
data: withTaxInclusivity ?? [],
|
||||
columns,
|
||||
count: count,
|
||||
getRowId: (row) => row.code,
|
||||
@@ -62,6 +93,7 @@ export const StoreCurrencySection = ({ store }: StoreCurrencySectionProps) => {
|
||||
supportedCurrencies: store.supported_currencies,
|
||||
defaultCurrencyCode: store.supported_currencies?.find((c) => c.is_default)
|
||||
?.currency_code,
|
||||
preferencesMap: prefMap,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -104,10 +136,16 @@ export const StoreCurrencySection = ({ store }: StoreCurrencySectionProps) => {
|
||||
)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
if (isCurrenciesError) {
|
||||
throw currenciesError
|
||||
}
|
||||
|
||||
if (isPricePreferencesError) {
|
||||
throw pricePreferencesError
|
||||
}
|
||||
|
||||
const isLoading = isCurrenciesPending || isPricePreferencesPending
|
||||
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
@@ -134,7 +172,7 @@ export const StoreCurrencySection = ({ store }: StoreCurrencySectionProps) => {
|
||||
pageSize={PAGE_SIZE}
|
||||
columns={columns}
|
||||
count={!store.supported_currencies?.length ? 0 : count}
|
||||
isLoading={!store.supported_currencies?.length ? false : isPending}
|
||||
isLoading={!store.supported_currencies?.length ? false : isLoading}
|
||||
queryObject={raw}
|
||||
/>
|
||||
<CommandBar open={!!Object.keys(rowSelection).length}>
|
||||
@@ -161,14 +199,15 @@ const CurrencyActions = ({
|
||||
currency,
|
||||
supportedCurrencies,
|
||||
defaultCurrencyCode,
|
||||
preferencesMap,
|
||||
}: {
|
||||
storeId: string
|
||||
currency: CurrencyDTO
|
||||
supportedCurrencies: StoreCurrencyDTO[]
|
||||
currency: HttpTypes.AdminCurrency
|
||||
supportedCurrencies: HttpTypes.AdminStoreCurrency[]
|
||||
defaultCurrencyCode: string
|
||||
preferencesMap: Map<string, HttpTypes.AdminPricePreference>
|
||||
}) => {
|
||||
const { mutateAsync } = useUpdateStore(storeId)
|
||||
|
||||
const { t } = useTranslation()
|
||||
const prompt = usePrompt()
|
||||
|
||||
@@ -205,6 +244,31 @@ const CurrencyActions = ({
|
||||
)
|
||||
}
|
||||
|
||||
const handleToggleTaxInclusivity = async () => {
|
||||
await mutateAsync(
|
||||
{
|
||||
supported_currencies: supportedCurrencies.map((c) => {
|
||||
const pref = preferencesMap.get(c.currency_code)
|
||||
return {
|
||||
...c,
|
||||
is_tax_inclusive:
|
||||
c.currency_code === currency.code
|
||||
? !pref?.is_tax_inclusive
|
||||
: undefined,
|
||||
}
|
||||
}),
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success(t("store.toast.updatedTaxInclusivitySuccessfully"))
|
||||
},
|
||||
onError: (e) => {
|
||||
toast.error(e.message)
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ActionMenu
|
||||
groups={[
|
||||
@@ -216,6 +280,17 @@ const CurrencyActions = ({
|
||||
onClick: handleRemove,
|
||||
disabled: currency.code === defaultCurrencyCode,
|
||||
},
|
||||
{
|
||||
icon: preferencesMap.get(currency.code)?.is_tax_inclusive ? (
|
||||
<XCircle />
|
||||
) : (
|
||||
<CheckCircle />
|
||||
),
|
||||
label: preferencesMap.get(currency.code)?.is_tax_inclusive
|
||||
? t("store.disableTaxInclusivePricing")
|
||||
: t("store.enableTaxInclusivePricing"),
|
||||
onClick: handleToggleTaxInclusivity,
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
@@ -223,10 +298,13 @@ const CurrencyActions = ({
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<CurrencyDTO>()
|
||||
const columnHelper = createColumnHelper<
|
||||
HttpTypes.AdminCurrency & { is_tax_inclusive?: boolean }
|
||||
>()
|
||||
|
||||
const useColumns = () => {
|
||||
const base = useCurrenciesTableColumns()
|
||||
const { t } = useTranslation()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
@@ -259,14 +337,30 @@ const useColumns = () => {
|
||||
},
|
||||
}),
|
||||
...base,
|
||||
columnHelper.accessor("is_tax_inclusive", {
|
||||
header: t("fields.taxInclusivePricing"),
|
||||
cell: ({ getValue }) => {
|
||||
const isTaxInclusive = getValue()
|
||||
return (
|
||||
<StatusCell color={isTaxInclusive ? "green" : "grey"}>
|
||||
{isTaxInclusive ? t("fields.true") : t("fields.false")}
|
||||
</StatusCell>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: ({ row, table }) => {
|
||||
const { supportedCurrencies, storeId, defaultCurrencyCode } = table
|
||||
.options.meta as {
|
||||
supportedCurrencies: StoreCurrencyDTO[]
|
||||
const {
|
||||
supportedCurrencies,
|
||||
storeId,
|
||||
defaultCurrencyCode,
|
||||
preferencesMap,
|
||||
} = table.options.meta as {
|
||||
supportedCurrencies: HttpTypes.AdminStoreCurrency[]
|
||||
storeId: string
|
||||
defaultCurrencyCode: string
|
||||
preferencesMap: Map<string, HttpTypes.AdminPricePreference>
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -275,11 +369,12 @@ const useColumns = () => {
|
||||
currency={row.original}
|
||||
supportedCurrencies={supportedCurrencies}
|
||||
defaultCurrencyCode={defaultCurrencyCode}
|
||||
preferencesMap={preferencesMap}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}),
|
||||
],
|
||||
[base]
|
||||
[base, t]
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user