feat: Add tax inclusivity management to currenices (#8112)

This commit is contained in:
Stevche Radevski
2024-07-14 18:03:44 +02:00
committed by GitHub
parent c88d161587
commit 874d511e13
11 changed files with 316 additions and 64 deletions
@@ -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": {
@@ -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()
@@ -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]
)
}
@@ -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]
)
}