diff --git a/packages/admin-next/dashboard/src/components/common/condition-block/condition-block.tsx b/packages/admin-next/dashboard/src/components/common/condition-block/condition-block.tsx deleted file mode 100644 index fa9a230b94..0000000000 --- a/packages/admin-next/dashboard/src/components/common/condition-block/condition-block.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { PropsWithChildren, createContext } from "react" - -type ConditionOperator = - | "eq" - | "ne" - | "gt" - | "lt" - | "gte" - | "lte" - | "in" - | "nin" - -type ConditionBlockValue = { - attribute: string - operator: ConditionOperator - value: TValue -} - -type ConditionBlockState = { - defaultValue?: ConditionBlockValue - value?: ConditionBlockValue - onChange: (value: ConditionBlockValue) => void -} - -const ConditionBlockContext = createContext | null>( - null -) - -const useConditionBlock = () => { - const context = ConditionBlockContext - - if (!context) { - throw new Error("useConditionBlock must be used within a ConditionBlock") - } - - return context -} - -type ConditionBlockProps = PropsWithChildren< - ConditionBlockState -> - -const Root = ({ children, ...props }: ConditionBlockProps) => { - return ( - - {children} - - ) -} - -const Divider = () => {} - -const Operator = () => {} - -const Item = () => {} - -export const ConditionBlock = Object.assign(Root, { - Divider, -}) diff --git a/packages/admin-next/dashboard/src/components/common/icon-avatar/icon-avatar.tsx b/packages/admin-next/dashboard/src/components/common/icon-avatar/icon-avatar.tsx index 1226cbd7b0..9d5cdc3e4e 100644 --- a/packages/admin-next/dashboard/src/components/common/icon-avatar/icon-avatar.tsx +++ b/packages/admin-next/dashboard/src/components/common/icon-avatar/icon-avatar.tsx @@ -3,6 +3,7 @@ import { PropsWithChildren } from "react" type IconAvatarProps = PropsWithChildren<{ className?: string + size?: "small" | "large" | "xlarge" }> /** @@ -10,12 +11,24 @@ type IconAvatarProps = PropsWithChildren<{ * * The `` component from `@medusajs/ui` does not support passing an icon as a child. */ -export const IconAvatar = ({ children, className }: IconAvatarProps) => { +export const IconAvatar = ({ + size = "small", + children, + className, +}: IconAvatarProps) => { return (
div]:bg-ui-bg-field [&>div]:text-ui-fg-subtle [&>div]:flex [&>div]:size-6 [&>div]:items-center [&>div]:justify-center [&>div]:rounded-[4px]", + "shadow-borders-base flex size-7 items-center justify-center", + "[&>div]:bg-ui-bg-field [&>div]:text-ui-fg-subtle [&>div]:flex [&>div]:size-6 [&>div]:items-center [&>div]:justify-center", + { + "size-7 rounded-md [&>div]:size-6 [&>div]:rounded-[4px]": + size === "small", + "size-10 rounded-lg [&>div]:size-9 [&>div]:rounded-[6px]": + size === "large", + "size-12 rounded-xl [&>div]:size-11 [&>div]:rounded-[10px]": + size === "xlarge", + }, className )} > diff --git a/packages/admin-next/dashboard/src/components/common/skeleton/skeleton.tsx b/packages/admin-next/dashboard/src/components/common/skeleton/skeleton.tsx index 04eef58ec1..db31eaed4e 100644 --- a/packages/admin-next/dashboard/src/components/common/skeleton/skeleton.tsx +++ b/packages/admin-next/dashboard/src/components/common/skeleton/skeleton.tsx @@ -136,6 +136,23 @@ export const GeneralSectionSkeleton = ({ ) } +export const TableFooterSkeleton = ({ layout }: { layout: "fill" | "fit" }) => { + return ( +
+ +
+ + + +
+
+ ) +} + type TableSkeletonProps = { rowCount?: number search?: boolean @@ -182,20 +199,7 @@ export const TableSkeleton = ({ ))}
- {pagination && ( -
- -
- - - -
-
- )} + {pagination && } ) } diff --git a/packages/admin-next/dashboard/src/components/data-grid/data-grid-cells/data-grid-country-select-cell.tsx b/packages/admin-next/dashboard/src/components/data-grid/data-grid-cells/data-grid-country-select-cell.tsx index 3f84fee2ea..8e0db51505 100644 --- a/packages/admin-next/dashboard/src/components/data-grid/data-grid-cells/data-grid-country-select-cell.tsx +++ b/packages/admin-next/dashboard/src/components/data-grid/data-grid-cells/data-grid-country-select-cell.tsx @@ -3,7 +3,7 @@ import { clx } from "@medusajs/ui" import { ComponentPropsWithoutRef, forwardRef, memo } from "react" import { Controller } from "react-hook-form" -import { countries } from "../../../lib/countries" +import { countries } from "../../../lib/data/countries" import { useDataGridCell } from "../hooks" import { DataGridCellProps } from "../types" import { DataGridCellContainer } from "./data-grid-cell-container" diff --git a/packages/admin-next/dashboard/src/components/data-grid/data-grid-cells/data-grid-currency-cell.tsx b/packages/admin-next/dashboard/src/components/data-grid/data-grid-cells/data-grid-currency-cell.tsx index 2abf28e4e1..ae048126fe 100644 --- a/packages/admin-next/dashboard/src/components/data-grid/data-grid-cells/data-grid-currency-cell.tsx +++ b/packages/admin-next/dashboard/src/components/data-grid/data-grid-cells/data-grid-currency-cell.tsx @@ -1,7 +1,7 @@ import CurrencyInput from "react-currency-input-field" import { Controller } from "react-hook-form" -import { currencies } from "../../../lib/currencies" +import { currencies } from "../../../lib/data/currencies" import { useDataGridCell } from "../hooks" import { DataGridCellProps } from "../types" import { DataGridCellContainer } from "./data-grid-cell-container" diff --git a/packages/admin-next/dashboard/src/components/inputs/country-select/country-select.tsx b/packages/admin-next/dashboard/src/components/inputs/country-select/country-select.tsx index 99c23e148d..c51b59e934 100644 --- a/packages/admin-next/dashboard/src/components/inputs/country-select/country-select.tsx +++ b/packages/admin-next/dashboard/src/components/inputs/country-select/country-select.tsx @@ -8,7 +8,7 @@ import { import { TrianglesMini } from "@medusajs/icons" import { clx } from "@medusajs/ui" import { useTranslation } from "react-i18next" -import { countries } from "../../../lib/countries" +import { countries } from "../../../lib/data/countries" export const CountrySelect = forwardRef< HTMLSelectElement, diff --git a/packages/admin-next/dashboard/src/components/inputs/percentage-input/percentage-input.tsx b/packages/admin-next/dashboard/src/components/inputs/percentage-input/percentage-input.tsx index 31d4f3a86a..945b7693b0 100644 --- a/packages/admin-next/dashboard/src/components/inputs/percentage-input/percentage-input.tsx +++ b/packages/admin-next/dashboard/src/components/inputs/percentage-input/percentage-input.tsx @@ -1,7 +1,11 @@ -import { Input, Text } from "@medusajs/ui" +import { Input, Text, clx } from "@medusajs/ui" import { ComponentProps, ElementRef, forwardRef } from "react" +import Primitive from "react-currency-input-field" -export const PercentageInput = forwardRef< +/** + * @deprecated Use `PercentageInput` instead + */ +export const DeprecatedPercentageInput = forwardRef< ElementRef, Omit, "type"> >(({ min = 0, max = 100, step = 0.0001, ...props }, ref) => { @@ -29,4 +33,43 @@ export const PercentageInput = forwardRef< ) }) -PercentageInput.displayName = "HandleInput" +DeprecatedPercentageInput.displayName = "PercentageInput" + +export const PercentageInput = forwardRef< + ElementRef<"input">, + ComponentProps +>(({ min = 0, decimalScale = 2, className, ...props }, ref) => { + return ( +
+ +
+ + % + +
+
+ ) +}) +PercentageInput.displayName = "PercentageInput" diff --git a/packages/admin-next/dashboard/src/components/inputs/province-select/index.ts b/packages/admin-next/dashboard/src/components/inputs/province-select/index.ts new file mode 100644 index 0000000000..f1baa16a61 --- /dev/null +++ b/packages/admin-next/dashboard/src/components/inputs/province-select/index.ts @@ -0,0 +1 @@ +export * from "./province-select" diff --git a/packages/admin-next/dashboard/src/components/inputs/province-select/province-select.tsx b/packages/admin-next/dashboard/src/components/inputs/province-select/province-select.tsx new file mode 100644 index 0000000000..158be08bc1 --- /dev/null +++ b/packages/admin-next/dashboard/src/components/inputs/province-select/province-select.tsx @@ -0,0 +1,111 @@ +import { + ComponentPropsWithoutRef, + forwardRef, + useImperativeHandle, + useRef, +} from "react" + +import { TrianglesMini } from "@medusajs/icons" +import { clx } from "@medusajs/ui" +import { useTranslation } from "react-i18next" +import { getCountryProvinceObjectByIso2 } from "../../../lib/data/country-states" + +interface ProvinceSelectProps extends ComponentPropsWithoutRef<"select"> { + /** + * ISO 3166-1 alpha-2 country code + */ + country_code: string + /** + * Whether to use the ISO 3166-1 alpha-2 code or the name of the province as the value + * + * @default "iso_2" + */ + valueAs?: "iso_2" | "name" + placeholder?: string +} + +export const ProvinceSelect = forwardRef< + HTMLSelectElement, + ProvinceSelectProps +>( + ( + { + className, + disabled, + placeholder, + country_code, + valueAs = "iso_2", + ...props + }, + ref + ) => { + const { t } = useTranslation() + const innerRef = useRef(null) + + useImperativeHandle(ref, () => innerRef.current as HTMLSelectElement) + + const isPlaceholder = innerRef.current?.value === "" + + const provinceObject = getCountryProvinceObjectByIso2(country_code) + + if (!provinceObject) { + disabled = true + } + + const options = Object.entries(provinceObject?.options ?? {}).map( + ([iso2, name]) => { + return ( + + ) + } + ) + + const placeholderText = provinceObject + ? t(`taxRegions.fields.sublevels.placeholders.${provinceObject.type}`) + : "" + + const placeholderOption = provinceObject ? ( + + ) : null + + return ( +
+ + +
+ ) + } +) +ProvinceSelect.displayName = "CountrySelect" diff --git a/packages/admin-next/dashboard/src/components/layout/nav-item/nav-item.tsx b/packages/admin-next/dashboard/src/components/layout/nav-item/nav-item.tsx index 3f97904b97..b0bea62dd3 100644 --- a/packages/admin-next/dashboard/src/components/layout/nav-item/nav-item.tsx +++ b/packages/admin-next/dashboard/src/components/layout/nav-item/nav-item.tsx @@ -107,10 +107,7 @@ export const NavItem = ({
    {items.map((item) => { return ( -
  • +
  • ({ + children, + widgets, + data, + hasOutlet, + showJSON, +}: PageProps) => { + const { before, after } = widgets + const widgetProps = { data } + + if (showJSON && !data) { + if (process.env.NODE_ENV === "development") { + console.warn( + "`showJSON` is true but no data is provided. To display JSON, provide data prop." + ) + } + + showJSON = false + } + + return ( +
    + {before.widgets.map((w, i) => { + return + })} + {children} + {after.widgets.map((w, i) => { + return + })} + {showJSON && } + {hasOutlet && } +
    + ) +} diff --git a/packages/admin-next/dashboard/src/components/layout/pages/two-column-page/index.ts b/packages/admin-next/dashboard/src/components/layout/pages/two-column-page/index.ts new file mode 100644 index 0000000000..157faf0eee --- /dev/null +++ b/packages/admin-next/dashboard/src/components/layout/pages/two-column-page/index.ts @@ -0,0 +1 @@ +export * from "./two-column-page" diff --git a/packages/admin-next/dashboard/src/components/layout/pages/two-column-page/two-column-page.tsx b/packages/admin-next/dashboard/src/components/layout/pages/two-column-page/two-column-page.tsx new file mode 100644 index 0000000000..99d4e2cc04 --- /dev/null +++ b/packages/admin-next/dashboard/src/components/layout/pages/two-column-page/two-column-page.tsx @@ -0,0 +1,123 @@ +import { clx } from "@medusajs/ui" +import { Children, ComponentPropsWithoutRef } from "react" +import { Outlet } from "react-router-dom" +import { JsonViewSection } from "../../../common/json-view-section" +import { PageProps, WidgetImport, WidgetProps } from "../types" + +interface TwoColumnWidgetProps extends WidgetProps { + sideBefore: WidgetImport + sideAfter: WidgetImport +} + +interface TwoColumnPageProps extends PageProps { + widgets: TwoColumnWidgetProps +} + +const Root = ({ + children, + /** + * Widgets to be rendered in the main content area and sidebar. + */ + widgets, + /** + * Data to be passed to widgets and the JSON view. + */ + data, + /** + * Whether to show JSON view of the data. Defaults to true. + */ + showJSON = false, + /** + * Whether to render an outlet for children routes. Defaults to true. + */ + hasOutlet = true, +}: TwoColumnPageProps) => { + const widgetProps = { data } + const { before, after, sideBefore, sideAfter } = widgets + + if (showJSON && !data) { + if (process.env.NODE_ENV === "development") { + console.warn( + "`showJSON` is true but no data is provided. To display JSON, provide data prop." + ) + } + + showJSON = false + } + + const childrenArray = Children.toArray(children) + + if (childrenArray.length !== 2) { + throw new Error("TwoColumnPage expects exactly two children") + } + + const [main, sidebar] = childrenArray + + return ( +
    + {before.widgets.map((w, i) => { + return + })} +
    +
    + {main} + {after.widgets.map((w, i) => { + return + })} + {showJSON && ( +
    + +
    + )} +
    +
    + {sideBefore.widgets.map((w, i) => { + return + })} + {sidebar} + {sideAfter.widgets.map((w, i) => { + return + })} + {showJSON && ( +
    + +
    + )} +
    +
    + {hasOutlet && } +
    + ) +} + +const Main = ({ + children, + className, + ...props +}: ComponentPropsWithoutRef<"div">) => { + return ( +
    + {children} +
    + ) +} + +const Sidebar = ({ + children, + className, + ...props +}: ComponentPropsWithoutRef<"div">) => { + return ( +
    + {children} +
    + ) +} + +export const TwoColumnPage = Object.assign(Root, { Main, Sidebar }) diff --git a/packages/admin-next/dashboard/src/components/layout/pages/types.ts b/packages/admin-next/dashboard/src/components/layout/pages/types.ts new file mode 100644 index 0000000000..8cbaf9165e --- /dev/null +++ b/packages/admin-next/dashboard/src/components/layout/pages/types.ts @@ -0,0 +1,22 @@ +import { ReactNode } from "react" + +export type Widget = { + Component: React.ComponentType +} + +export type WidgetImport = { + widgets: Widget[] +} + +export interface WidgetProps { + before: WidgetImport + after: WidgetImport +} + +export interface PageProps { + children: ReactNode + widgets: WidgetProps + data?: TData + showJSON?: boolean + hasOutlet?: boolean +} diff --git a/packages/admin-next/dashboard/src/components/layout/settings-layout/settings-layout.tsx b/packages/admin-next/dashboard/src/components/layout/settings-layout/settings-layout.tsx index 563c3e54f9..05b37fb890 100644 --- a/packages/admin-next/dashboard/src/components/layout/settings-layout/settings-layout.tsx +++ b/packages/admin-next/dashboard/src/components/layout/settings-layout/settings-layout.tsx @@ -42,8 +42,8 @@ const useSettingRoutes = (): NavItemProps[] => { to: "/settings/regions", }, { - label: "Taxes", - to: "/settings/taxes", + label: t("taxRegions.domain"), + to: "/settings/tax-regions", }, { label: t("salesChannels.domain"), diff --git a/packages/admin-next/dashboard/src/components/layout/shell/shell.tsx b/packages/admin-next/dashboard/src/components/layout/shell/shell.tsx index b7c6ec277c..fed94dee7d 100644 --- a/packages/admin-next/dashboard/src/components/layout/shell/shell.tsx +++ b/packages/admin-next/dashboard/src/components/layout/shell/shell.tsx @@ -10,6 +10,7 @@ import { Keyboard, MagnifyingGlass, SidebarLeft, + TriangleRightMini, User as UserIcon, } from "@medusajs/icons" import { @@ -92,18 +93,17 @@ const Breadcrumbs = () => { }) return ( -
      +
        {crumbs.map((crumb, index) => { const isLast = index === crumbs.length - 1 const isSingle = crumbs.length === 1 return ( -
      1. +
      2. {!isLast ? ( {
    )} - {!isLast && } + {!isLast && ( + + + + )}
  • ) })} diff --git a/packages/admin-next/dashboard/src/components/modals/stacked-focus-modal/stacked-focus-modal.tsx b/packages/admin-next/dashboard/src/components/modals/stacked-focus-modal/stacked-focus-modal.tsx index 9ab5f35190..599b14861d 100644 --- a/packages/admin-next/dashboard/src/components/modals/stacked-focus-modal/stacked-focus-modal.tsx +++ b/packages/admin-next/dashboard/src/components/modals/stacked-focus-modal/stacked-focus-modal.tsx @@ -66,7 +66,7 @@ const Content = forwardRef< return ( ({ - + diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/common/date-cell/date-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/common/date-cell/date-cell.tsx index 8537c3e3c6..e3ef58ec09 100644 --- a/packages/admin-next/dashboard/src/components/table/table-cells/common/date-cell/date-cell.tsx +++ b/packages/admin-next/dashboard/src/components/table/table-cells/common/date-cell/date-cell.tsx @@ -1,10 +1,10 @@ import { Tooltip } from "@medusajs/ui" -import format from "date-fns/format" +import { format } from "date-fns/format" import { useTranslation } from "react-i18next" import { PlaceholderCell } from "../placeholder-cell" type DateCellProps = { - date: Date | string | null + date?: Date | string | null } export const DateCell = ({ date }: DateCellProps) => { diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/code-cell/code-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/discount/code-cell/code-cell.tsx deleted file mode 100644 index 76bdea5e8b..0000000000 --- a/packages/admin-next/dashboard/src/components/table/table-cells/discount/code-cell/code-cell.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import type { Discount } from "@medusajs/medusa" - -import { useTranslation } from "react-i18next" - -type DiscountCellProps = { - discount: Discount -} - -export const CodeCell = ({ discount }: DiscountCellProps) => { - return ( -
    - {/* // TODO: border color inversion*/} - - {discount.code} - -
    - ) -} - -export const CodeHeader = () => { - const { t } = useTranslation() - - return ( -
    - {t("fields.code")} -
    - ) -} diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/code-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/discount/code-cell/index.ts deleted file mode 100644 index a2eb7174de..0000000000 --- a/packages/admin-next/dashboard/src/components/table/table-cells/discount/code-cell/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./code-cell" diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/description-cell/description-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/discount/description-cell/description-cell.tsx deleted file mode 100644 index b625626782..0000000000 --- a/packages/admin-next/dashboard/src/components/table/table-cells/discount/description-cell/description-cell.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type { Discount } from "@medusajs/medusa" - -import { useTranslation } from "react-i18next" - -type DiscountCellProps = { - discount: Discount -} - -export const DescriptionCell = ({ discount }: DiscountCellProps) => { - return ( -
    - {discount.rule.description} -
    - ) -} - -export const DescriptionHeader = () => { - const { t } = useTranslation() - - return ( -
    - {t("fields.description")} -
    - ) -} diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/description-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/discount/description-cell/index.ts deleted file mode 100644 index 83760e2da6..0000000000 --- a/packages/admin-next/dashboard/src/components/table/table-cells/discount/description-cell/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./description-cell" diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/redemption-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/discount/redemption-cell/index.ts deleted file mode 100644 index faac33e975..0000000000 --- a/packages/admin-next/dashboard/src/components/table/table-cells/discount/redemption-cell/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./redemption-cell.tsx" diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/redemption-cell/redemption-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/discount/redemption-cell/redemption-cell.tsx deleted file mode 100644 index 00c76fd738..0000000000 --- a/packages/admin-next/dashboard/src/components/table/table-cells/discount/redemption-cell/redemption-cell.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useTranslation } from "react-i18next" - -type DiscountCellProps = { - redemptions: number -} - -export const RedemptionCell = ({ redemptions }: DiscountCellProps) => { - return ( -
    - {redemptions} -
    - ) -} - -export const RedemptionHeader = () => { - const { t } = useTranslation() - - return ( -
    - {t("fields.totalRedemptions")} -
    - ) -} diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/status-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/discount/status-cell/index.ts deleted file mode 100644 index 8cc5d6026b..0000000000 --- a/packages/admin-next/dashboard/src/components/table/table-cells/discount/status-cell/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./status-cell" diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/status-cell/status-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/discount/status-cell/status-cell.tsx deleted file mode 100644 index 6d841f0ccf..0000000000 --- a/packages/admin-next/dashboard/src/components/table/table-cells/discount/status-cell/status-cell.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { Discount } from "@medusajs/medusa" - -import { StatusCell as StatusCell_ } from "../../common/status-cell" - -import { useTranslation } from "react-i18next" -import { - getDiscountStatus, - PromotionStatus, -} from "../../../../../lib/discounts.ts" - -type DiscountCellProps = { - discount: Discount -} - -export const StatusCell = ({ discount }: DiscountCellProps) => { - const { t } = useTranslation() - - const [color, text] = { - [PromotionStatus.DISABLED]: [ - "grey", - t("discounts.discountStatus.disabled"), - ], - [PromotionStatus.ACTIVE]: ["green", t("discounts.discountStatus.active")], - [PromotionStatus.SCHEDULED]: [ - "orange", - t("discounts.discountStatus.scheduled"), - ], - [PromotionStatus.EXPIRED]: ["red", t("discounts.discountStatus.expired")], - }[getDiscountStatus(discount)] as [ - "grey" | "orange" | "green" | "red", - string - ] - - return {text} -} - -export const StatusHeader = () => { - const { t } = useTranslation() - - return ( -
    - {t("fields.status")} -
    - ) -} diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/value-cell/index.ts b/packages/admin-next/dashboard/src/components/table/table-cells/discount/value-cell/index.ts deleted file mode 100644 index 91c37e81b2..0000000000 --- a/packages/admin-next/dashboard/src/components/table/table-cells/discount/value-cell/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./value-cell.tsx" diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/discount/value-cell/value-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/discount/value-cell/value-cell.tsx deleted file mode 100644 index 6ce4b0c86e..0000000000 --- a/packages/admin-next/dashboard/src/components/table/table-cells/discount/value-cell/value-cell.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { DiscountRule } from "@medusajs/medusa" - -import { useTranslation } from "react-i18next" -import { MoneyAmountCell } from "../../common/money-amount-cell" - -type DiscountCellProps = { - rule: DiscountRule - currencyCode: string -} - -export const ValueCell = ({ currencyCode, rule }: DiscountCellProps) => { - const isFixed = rule.type === "fixed" - const isPercentage = rule.type === "percentage" - const isFreeShipping = rule.type === "free_shipping" - - return ( -
    - {isFreeShipping && Free shipping} - {isPercentage && {rule.value}%} - {isFixed && ( - - )} -
    - ) -} - -export const ValueHeader = () => { - const { t } = useTranslation() - - return ( -
    - {t("fields.value")} -
    - ) -} diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/region/countries-cell/countries-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/region/countries-cell/countries-cell.tsx index 85ec0e0141..22727be485 100644 --- a/packages/admin-next/dashboard/src/components/table/table-cells/region/countries-cell/countries-cell.tsx +++ b/packages/admin-next/dashboard/src/components/table/table-cells/region/countries-cell/countries-cell.tsx @@ -1,9 +1,9 @@ -import { useTranslation } from "react-i18next" import { RegionCountryDTO } from "@medusajs/types" +import { useTranslation } from "react-i18next" -import { PlaceholderCell } from "../../common/placeholder-cell" +import { countries as COUNTRIES } from "../../../../../lib/data/countries" import { ListSummary } from "../../../../common/list-summary" -import { countries as COUNTRIES } from "../../../../../lib/countries" +import { PlaceholderCell } from "../../common/placeholder-cell" type CountriesCellProps = { countries?: RegionCountryDTO[] | null diff --git a/packages/admin-next/dashboard/src/components/utilities/i18n/i18n.tsx b/packages/admin-next/dashboard/src/components/utilities/i18n/i18n.tsx index 608a320e05..7ac31af6b6 100644 --- a/packages/admin-next/dashboard/src/components/utilities/i18n/i18n.tsx +++ b/packages/admin-next/dashboard/src/components/utilities/i18n/i18n.tsx @@ -21,3 +21,5 @@ export const I18n = () => { return null } + +export { i18n } diff --git a/packages/admin-next/dashboard/src/hooks/api/customer-groups.tsx b/packages/admin-next/dashboard/src/hooks/api/customer-groups.tsx index 1fe62ff874..cbd697e98b 100644 --- a/packages/admin-next/dashboard/src/hooks/api/customer-groups.tsx +++ b/packages/admin-next/dashboard/src/hooks/api/customer-groups.tsx @@ -1,3 +1,4 @@ +import { FetchError } from "@medusajs/js-sdk" import { HttpTypes, PaginatedResponse } from "@medusajs/types" import { QueryKey, @@ -25,7 +26,7 @@ export const useCustomerGroup = ( options?: Omit< UseQueryOptions< { customer_group: HttpTypes.AdminCustomerGroup }, - Error, + FetchError, { customer_group: HttpTypes.AdminCustomerGroup }, QueryKey >, @@ -46,7 +47,7 @@ export const useCustomerGroups = ( options?: Omit< UseQueryOptions< PaginatedResponse<{ customer_groups: HttpTypes.AdminCustomerGroup[] }>, - Error, + FetchError, PaginatedResponse<{ customer_groups: HttpTypes.AdminCustomerGroup[] }>, QueryKey >, @@ -65,7 +66,7 @@ export const useCustomerGroups = ( export const useCreateCustomerGroup = ( options?: UseMutationOptions< { customer_group: HttpTypes.AdminCustomerGroup }, - Error, + FetchError, z.infer > ) => { @@ -85,7 +86,7 @@ export const useUpdateCustomerGroup = ( id: string, options?: UseMutationOptions< { customer_group: HttpTypes.AdminCustomerGroup }, - Error, + FetchError, z.infer > ) => { @@ -109,7 +110,7 @@ export const useDeleteCustomerGroup = ( id: string, options?: UseMutationOptions< { id: string; object: "customer-group"; deleted: boolean }, - Error, + FetchError, void > ) => { @@ -133,7 +134,7 @@ export const useAddCustomersToGroup = ( id: string, options?: UseMutationOptions< { customer_group: HttpTypes.AdminCustomerGroup }, - Error, + FetchError, { customer_ids: string[] } > ) => { @@ -160,7 +161,7 @@ export const useRemoveCustomersFromGroup = ( id: string, options?: UseMutationOptions< { customer_group: HttpTypes.AdminCustomerGroup }, - Error, + FetchError, { customer_ids: string[] } > ) => { diff --git a/packages/admin-next/dashboard/src/hooks/api/index.ts b/packages/admin-next/dashboard/src/hooks/api/index.ts new file mode 100644 index 0000000000..782d130c2c --- /dev/null +++ b/packages/admin-next/dashboard/src/hooks/api/index.ts @@ -0,0 +1,31 @@ +export * from "./api-keys" +export * from "./auth" +export * from "./campaigns" +export * from "./categories" +export * from "./collections" +export * from "./currencies" +export * from "./customer-groups" +export * from "./customers" +export * from "./fulfillment" +export * from "./fulfillment-providers" +export * from "./fulfillment-sets" +export * from "./inventory" +export * from "./invites" +export * from "./orders" +export * from "./payments" +export * from "./price-lists" +export * from "./product-types" +export * from "./products" +export * from "./promotions" +export * from "./regions" +export * from "./reservations" +export * from "./sales-channels" +export * from "./shipping-options" +export * from "./shipping-profiles" +export * from "./stock-locations" +export * from "./store" +export * from "./tags" +export * from "./tax-rates" +export * from "./tax-regions" +export * from "./users" +export * from "./workflow-executions" diff --git a/packages/admin-next/dashboard/src/hooks/api/tags.tsx b/packages/admin-next/dashboard/src/hooks/api/tags.tsx index 988ff531f4..0bc7f9bdae 100644 --- a/packages/admin-next/dashboard/src/hooks/api/tags.tsx +++ b/packages/admin-next/dashboard/src/hooks/api/tags.tsx @@ -1,7 +1,8 @@ +import { FetchError } from "@medusajs/js-sdk" +import { HttpTypes } from "@medusajs/types" import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query" -import { client } from "../../lib/client" +import { sdk } from "../../lib/client" import { queryKeysFactory } from "../../lib/query-key-factory" -import { TagsListRes, TagRes } from "../../types/api-responses" const TAGS_QUERY_KEY = "tags" as const export const tagsQueryKeys = queryKeysFactory(TAGS_QUERY_KEY) @@ -9,13 +10,18 @@ export const tagsQueryKeys = queryKeysFactory(TAGS_QUERY_KEY) export const useTag = ( id: string, options?: Omit< - UseQueryOptions, + UseQueryOptions< + HttpTypes.AdminProductTagResponse, + FetchError, + HttpTypes.AdminProductTagResponse, + QueryKey + >, "queryFn" | "queryKey" > ) => { const { data, ...rest } = useQuery({ queryKey: tagsQueryKeys.detail(id), - queryFn: async () => client.tags.retrieve(id), + queryFn: async () => sdk.admin.productTag.retrieve(id), ...options, }) @@ -23,15 +29,20 @@ export const useTag = ( } export const useTags = ( - query?: Record, + query?: HttpTypes.AdminProductTagListParams, options?: Omit< - UseQueryOptions, + UseQueryOptions< + HttpTypes.AdminProductTagListResponse, + FetchError, + HttpTypes.AdminProductTagListResponse, + QueryKey + >, "queryFn" | "queryKey" > ) => { const { data, ...rest } = useQuery({ queryKey: tagsQueryKeys.list(query), - queryFn: async () => client.tags.list(query), + queryFn: async () => sdk.admin.productTag.list(query), ...options, }) diff --git a/packages/admin-next/dashboard/src/hooks/api/tax-rates.tsx b/packages/admin-next/dashboard/src/hooks/api/tax-rates.tsx index 9866a98cb7..823775c267 100644 --- a/packages/admin-next/dashboard/src/hooks/api/tax-rates.tsx +++ b/packages/admin-next/dashboard/src/hooks/api/tax-rates.tsx @@ -1,3 +1,4 @@ +import { FetchError } from "@medusajs/js-sdk" import { HttpTypes } from "@medusajs/types" import { QueryKey, @@ -9,6 +10,7 @@ import { import { sdk } from "../../lib/client" import { queryClient } from "../../lib/query-client" import { queryKeysFactory } from "../../lib/query-key-factory" +import { taxRegionsQueryKeys } from "./tax-regions" const TAX_RATES_QUERY_KEY = "tax_rates" as const export const taxRatesQueryKeys = queryKeysFactory(TAX_RATES_QUERY_KEY) @@ -19,7 +21,7 @@ export const useTaxRate = ( options?: Omit< UseQueryOptions< HttpTypes.AdminTaxRateResponse, - Error, + FetchError, HttpTypes.AdminTaxRateResponse, QueryKey >, @@ -72,6 +74,8 @@ export const useUpdateTaxRate = ( queryKey: taxRatesQueryKeys.detail(id), }) + queryClient.invalidateQueries({ queryKey: taxRegionsQueryKeys.details() }) + options?.onSuccess?.(data, variables, context) }, ...options, @@ -81,7 +85,7 @@ export const useUpdateTaxRate = ( export const useCreateTaxRate = ( options?: UseMutationOptions< HttpTypes.AdminTaxRateResponse, - Error, + FetchError, HttpTypes.AdminCreateTaxRate > ) => { @@ -89,6 +93,9 @@ export const useCreateTaxRate = ( mutationFn: (payload) => sdk.admin.taxRate.create(payload), onSuccess: (data, variables, context) => { queryClient.invalidateQueries({ queryKey: taxRatesQueryKeys.lists() }) + + queryClient.invalidateQueries({ queryKey: taxRegionsQueryKeys.details() }) + options?.onSuccess?.(data, variables, context) }, ...options, @@ -111,6 +118,8 @@ export const useDeleteTaxRate = ( queryKey: taxRatesQueryKeys.detail(id), }) + queryClient.invalidateQueries({ queryKey: taxRegionsQueryKeys.details() }) + options?.onSuccess?.(data, variables, context) }, ...options, diff --git a/packages/admin-next/dashboard/src/hooks/api/tax-regions.tsx b/packages/admin-next/dashboard/src/hooks/api/tax-regions.tsx index 2991e8ca57..74eb7a73df 100644 --- a/packages/admin-next/dashboard/src/hooks/api/tax-regions.tsx +++ b/packages/admin-next/dashboard/src/hooks/api/tax-regions.tsx @@ -1,3 +1,4 @@ +import { FetchError } from "@medusajs/js-sdk" import { HttpTypes } from "@medusajs/types" import { QueryKey, @@ -15,11 +16,11 @@ export const taxRegionsQueryKeys = queryKeysFactory(TAX_REGIONS_QUERY_KEY) export const useTaxRegion = ( id: string, - query?: Record, + query?: HttpTypes.AdminTaxRegionParams, options?: Omit< UseQueryOptions< HttpTypes.AdminTaxRegionResponse, - Error, + FetchError, HttpTypes.AdminTaxRegionResponse, QueryKey >, @@ -36,11 +37,11 @@ export const useTaxRegion = ( } export const useTaxRegions = ( - query?: Record, + query?: HttpTypes.AdminTaxRegionListParams, options?: Omit< UseQueryOptions< HttpTypes.AdminTaxRegionListResponse, - Error, + FetchError, HttpTypes.AdminTaxRegionListResponse, QueryKey >, @@ -59,7 +60,7 @@ export const useTaxRegions = ( export const useCreateTaxRegion = ( options?: UseMutationOptions< HttpTypes.AdminTaxRegionResponse, - Error, + FetchError, HttpTypes.AdminCreateTaxRegion > ) => { @@ -77,7 +78,7 @@ export const useDeleteTaxRegion = ( id: string, options?: UseMutationOptions< HttpTypes.AdminTaxRegionDeleteResponse, - Error, + FetchError, void > ) => { @@ -89,6 +90,9 @@ export const useDeleteTaxRegion = ( queryKey: taxRegionsQueryKeys.detail(id), }) + // Invalidate all detail queries, as the deleted tax region may have been a sublevel region + queryClient.invalidateQueries({ queryKey: taxRegionsQueryKeys.details() }) + options?.onSuccess?.(data, variables, context) }, ...options, diff --git a/packages/admin-next/dashboard/src/hooks/table/columns/index.ts b/packages/admin-next/dashboard/src/hooks/table/columns/index.ts new file mode 100644 index 0000000000..b8ef3befc2 --- /dev/null +++ b/packages/admin-next/dashboard/src/hooks/table/columns/index.ts @@ -0,0 +1,12 @@ +export * from "./use-campaign-table-columns" +export * from "./use-collection-table-columns" +export * from "./use-customer-group-table-columns" +export * from "./use-customer-table-columns" +export * from "./use-order-table-columns" +export * from "./use-product-table-columns" +export * from "./use-product-tag-table-columns" +export * from "./use-product-type-table-columns" +export * from "./use-region-table-columns" +export * from "./use-sales-channel-table-columns" +export * from "./use-shipping-option-table-columns" +export * from "./use-tax-rates-table-columns" diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-columns.tsx b/packages/admin-next/dashboard/src/hooks/table/columns/use-collection-table-columns.tsx similarity index 66% rename from packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-columns.tsx rename to packages/admin-next/dashboard/src/hooks/table/columns/use-collection-table-columns.tsx index 2c531c4425..cbe46d582d 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-columns.tsx +++ b/packages/admin-next/dashboard/src/hooks/table/columns/use-collection-table-columns.tsx @@ -2,8 +2,7 @@ import { HttpTypes } from "@medusajs/types" import { createColumnHelper } from "@tanstack/react-table" import { useMemo } from "react" import { useTranslation } from "react-i18next" - -import { CollectionRowActions } from "./collection-row-actions" +import { TextCell } from "../../../components/table/table-cells/common/text-cell" const columnHelper = createColumnHelper() @@ -14,23 +13,20 @@ export const useCollectionTableColumns = () => { () => [ columnHelper.accessor("title", { header: t("fields.title"), + cell: ({ getValue }) => , }), columnHelper.accessor("handle", { header: t("fields.handle"), - cell: ({ getValue }) => `/${getValue()}`, + cell: ({ getValue }) => , }), columnHelper.accessor("products", { header: t("fields.products"), cell: ({ getValue }) => { - const count = getValue()?.length + const count = getValue()?.length || undefined - return {count || "-"} + return }, }), - columnHelper.display({ - id: "actions", - cell: ({ row }) => , - }), ], [t] ) diff --git a/packages/admin-next/dashboard/src/hooks/table/columns/use-discount-table-columns.tsx b/packages/admin-next/dashboard/src/hooks/table/columns/use-discount-table-columns.tsx deleted file mode 100644 index 3c2ed34960..0000000000 --- a/packages/admin-next/dashboard/src/hooks/table/columns/use-discount-table-columns.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useMemo } from "react" -import { ColumnDef, createColumnHelper } from "@tanstack/react-table" - -import type { Discount } from "@medusajs/medusa" - -import { - CodeCell, - CodeHeader, -} from "../../../components/table/table-cells/discount/code-cell" -import { - DescriptionHeader, - DescriptionCell, -} from "../../../components/table/table-cells/discount/description-cell" -import { - ValueCell, - ValueHeader, -} from "../../../components/table/table-cells/discount/value-cell" -import { - RedemptionCell, - RedemptionHeader, -} from "../../../components/table/table-cells/discount/redemption-cell" -import { - StatusHeader, - StatusCell, -} from "../../../components/table/table-cells/discount/status-cell" - -const columnHelper = createColumnHelper() - -export const useDiscountTableColumns = () => { - return useMemo( - () => [ - columnHelper.display({ - id: "discount", - header: () => , - cell: ({ row }) => , - }), - columnHelper.accessor("rule.description", { - header: () => , - cell: ({ row }) => , - }), - - columnHelper.accessor("rule.value", { - header: () => , - cell: ({ row }) => ( - - ), - }), - columnHelper.display({ - id: "status", - header: () => , - cell: ({ row }) => , - }), - columnHelper.accessor("usage_count", { - header: () => , - cell: ({ row }) => ( - - ), - }), - ], - [] - ) as ColumnDef[] -} diff --git a/packages/admin-next/dashboard/src/hooks/table/columns/use-product-tag-table-columns.tsx b/packages/admin-next/dashboard/src/hooks/table/columns/use-product-tag-table-columns.tsx new file mode 100644 index 0000000000..05cd038b76 --- /dev/null +++ b/packages/admin-next/dashboard/src/hooks/table/columns/use-product-tag-table-columns.tsx @@ -0,0 +1,35 @@ +import { HttpTypes } from "@medusajs/types" +import { createColumnHelper } from "@tanstack/react-table" +import { useMemo } from "react" +import { useTranslation } from "react-i18next" +import { DateCell } from "../../../components/table/table-cells/common/date-cell" +import { TextCell } from "../../../components/table/table-cells/common/text-cell" + +const columnHelper = createColumnHelper() + +export const useProductTagTableColumns = () => { + const { t } = useTranslation() + + return useMemo( + () => [ + columnHelper.accessor("value", { + header: () => t("fields.value"), + cell: ({ getValue }) => , + }), + columnHelper.accessor("created_at", { + header: () => t("fields.createdAt"), + + cell: ({ getValue }) => { + return + }, + }), + columnHelper.accessor("updated_at", { + header: () => t("fields.updatedAt"), + cell: ({ getValue }) => { + return + }, + }), + ], + [t] + ) +} diff --git a/packages/admin-next/dashboard/src/routes/product-types/product-type-list/components/product-type-list-table/use-product-type-table-columns.tsx b/packages/admin-next/dashboard/src/hooks/table/columns/use-product-type-table-columns.tsx similarity index 54% rename from packages/admin-next/dashboard/src/routes/product-types/product-type-list/components/product-type-list-table/use-product-type-table-columns.tsx rename to packages/admin-next/dashboard/src/hooks/table/columns/use-product-type-table-columns.tsx index b4d8b5cef2..87a29a134d 100644 --- a/packages/admin-next/dashboard/src/routes/product-types/product-type-list/components/product-type-list-table/use-product-type-table-columns.tsx +++ b/packages/admin-next/dashboard/src/hooks/table/columns/use-product-type-table-columns.tsx @@ -2,8 +2,9 @@ import { HttpTypes } from "@medusajs/types" import { createColumnHelper } from "@tanstack/react-table" import { useMemo } from "react" import { useTranslation } from "react-i18next" -import { ProductTypeRowActions } from "./product-table-row-actions" -import { DateCell } from "../../../../../components/table/table-cells/common/date-cell" + +import { DateCell } from "../../../components/table/table-cells/common/date-cell" +import { TextCell } from "../../../components/table/table-cells/common/text-cell" const columnHelper = createColumnHelper() @@ -13,28 +14,20 @@ export const useProductTypeTableColumns = () => { return useMemo( () => [ columnHelper.accessor("value", { - header: () => t("productTypes.fields.value"), - cell: ({ getValue }) => getValue(), + header: () => t("fields.value"), + cell: ({ getValue }) => , }), columnHelper.accessor("created_at", { header: () => t("fields.createdAt"), cell: ({ getValue }) => { - const date = new Date(getValue()) - return + return }, }), columnHelper.accessor("updated_at", { header: () => t("fields.updatedAt"), cell: ({ getValue }) => { - const date = new Date(getValue()) - return - }, - }), - columnHelper.display({ - id: "actions", - cell: ({ row }) => { - return + return }, }), ], diff --git a/packages/admin-next/dashboard/src/hooks/table/filters/index.ts b/packages/admin-next/dashboard/src/hooks/table/filters/index.ts new file mode 100644 index 0000000000..7301d6f347 --- /dev/null +++ b/packages/admin-next/dashboard/src/hooks/table/filters/index.ts @@ -0,0 +1,13 @@ +export * from "./use-collection-table-filters" +export * from "./use-customer-group-table-filters" +export * from "./use-customer-table-filters" +export * from "./use-date-table-filters" +export * from "./use-order-table-filters" +export * from "./use-product-table-filters" +export * from "./use-product-tag-table-filters" +export * from "./use-product-type-table-filters" +export * from "./use-promotion-table-filters" +export * from "./use-region-table-filters" +export * from "./use-sales-channel-table-filters" +export * from "./use-shipping-option-table-filters" +export * from "./use-tax-rate-table-filters" diff --git a/packages/admin-next/dashboard/src/hooks/table/filters/use-collection-table-filters.tsx b/packages/admin-next/dashboard/src/hooks/table/filters/use-collection-table-filters.tsx new file mode 100644 index 0000000000..3f93c3a14b --- /dev/null +++ b/packages/admin-next/dashboard/src/hooks/table/filters/use-collection-table-filters.tsx @@ -0,0 +1,7 @@ +import { useDateTableFilters } from "./use-date-table-filters" + +export const useCollectionTableFilters = () => { + const dateFilters = useDateTableFilters() + + return dateFilters +} diff --git a/packages/admin-next/dashboard/src/hooks/table/filters/use-discount-table-filters.tsx b/packages/admin-next/dashboard/src/hooks/table/filters/use-discount-table-filters.tsx deleted file mode 100644 index 8158b0a3d8..0000000000 --- a/packages/admin-next/dashboard/src/hooks/table/filters/use-discount-table-filters.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { useTranslation } from "react-i18next" -import { Filter } from "../../../components/table/data-table" - -export const useDiscountTableFilters = () => { - const { t } = useTranslation() - - let filters: Filter[] = [ - { label: t("fields.createdAt"), key: "created_at" }, - { label: t("fields.updatedAt"), key: "updated_at" }, - ].map((f) => ({ - key: f.key, - label: f.label, - type: "date", - })) - - const isDisabledFilter: Filter = { - key: "is_disabled", - label: t("fields.disabled"), - type: "select", - options: [ - { - label: t("fields.true"), - value: "true", - }, - { - label: t("fields.false"), - value: "false", - }, - ], - } - - const isDynamicFilter: Filter = { - key: "is_dynamic", - label: t("fields.type"), - type: "select", - options: [ - { - label: t("fields.dynamic"), - value: "true", - }, - { - label: t("fields.normal"), - value: "false", - }, - ], - } - - filters = [...filters, isDisabledFilter, isDynamicFilter] - - return filters -} diff --git a/packages/admin-next/dashboard/src/hooks/table/filters/use-product-tag-table-filters.tsx b/packages/admin-next/dashboard/src/hooks/table/filters/use-product-tag-table-filters.tsx new file mode 100644 index 0000000000..de2499cb97 --- /dev/null +++ b/packages/admin-next/dashboard/src/hooks/table/filters/use-product-tag-table-filters.tsx @@ -0,0 +1,7 @@ +import { useDateTableFilters } from "./use-date-table-filters" + +export const useProductTagTableFilters = () => { + const dateFilters = useDateTableFilters() + + return dateFilters +} diff --git a/packages/admin-next/dashboard/src/routes/product-types/product-type-list/components/product-type-list-table/use-product-type-table-filters.tsx b/packages/admin-next/dashboard/src/hooks/table/filters/use-product-type-table-filters.tsx similarity index 55% rename from packages/admin-next/dashboard/src/routes/product-types/product-type-list/components/product-type-list-table/use-product-type-table-filters.tsx rename to packages/admin-next/dashboard/src/hooks/table/filters/use-product-type-table-filters.tsx index 25b7118cdc..4a0feb5000 100644 --- a/packages/admin-next/dashboard/src/routes/product-types/product-type-list/components/product-type-list-table/use-product-type-table-filters.tsx +++ b/packages/admin-next/dashboard/src/hooks/table/filters/use-product-type-table-filters.tsx @@ -1,4 +1,4 @@ -import { useDateTableFilters } from "../../../../../hooks/table/filters/use-date-table-filters" +import { useDateTableFilters } from "./use-date-table-filters" export const useProductTypeTableFilters = () => { const dateFilters = useDateTableFilters() diff --git a/packages/admin-next/dashboard/src/hooks/table/query/index.ts b/packages/admin-next/dashboard/src/hooks/table/query/index.ts new file mode 100644 index 0000000000..1ced60c78f --- /dev/null +++ b/packages/admin-next/dashboard/src/hooks/table/query/index.ts @@ -0,0 +1,14 @@ +export * from "./use-campaign-table-query" +export * from "./use-collection-table-query" +export * from "./use-customer-group-table-query" +export * from "./use-customer-table-query" +export * from "./use-order-table-query" +export * from "./use-product-table-query" +export * from "./use-product-tag-table-query" +export * from "./use-product-type-table-query" +export * from "./use-region-table-query" +export * from "./use-sales-channel-table-query" +export * from "./use-shipping-option-table-query" +export * from "./use-tax-rate-table-query" +export * from "./use-tax-region-table-query" +export * from "./use-user-invite-table-query" diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-query.tsx b/packages/admin-next/dashboard/src/hooks/table/query/use-collection-table-query.tsx similarity index 75% rename from packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-query.tsx rename to packages/admin-next/dashboard/src/hooks/table/query/use-collection-table-query.tsx index 620c92ad3b..9359335722 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-query.tsx +++ b/packages/admin-next/dashboard/src/hooks/table/query/use-collection-table-query.tsx @@ -1,5 +1,5 @@ -import { AdminCollectionFilters, FindParams } from "@medusajs/types" -import { useQueryParams } from "../../../../../hooks/use-query-params" +import { HttpTypes } from "@medusajs/types" +import { useQueryParams } from "../../use-query-params" type UseCollectionTableQueryProps = { prefix?: string @@ -17,7 +17,7 @@ export const useCollectionTableQuery = ({ const { offset, created_at, updated_at, q, order } = queryObject - const searchParams: FindParams & AdminCollectionFilters = { + const searchParams: HttpTypes.AdminCollectionListParams = { limit: pageSize, offset: offset ? Number(offset) : 0, order, diff --git a/packages/admin-next/dashboard/src/hooks/table/query/use-discount-table-query.tsx b/packages/admin-next/dashboard/src/hooks/table/query/use-discount-table-query.tsx deleted file mode 100644 index a3f9f0e6fb..0000000000 --- a/packages/admin-next/dashboard/src/hooks/table/query/use-discount-table-query.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { AdminGetDiscountsParams } from "@medusajs/medusa" - -import { useQueryParams } from "../../use-query-params" - -type UseDiscountTableQueryProps = { - prefix?: string - pageSize?: number -} - -export const useDiscountTableQuery = ({ - prefix, - pageSize = 20, -}: UseDiscountTableQueryProps) => { - const queryObject = useQueryParams( - [ - "offset", - "order", - "q", - "is_dynamic", - "is_disabled", - "created_at", - "updated_at", - ], - prefix - ) - - const { offset, order, q, is_dynamic, is_disabled, created_at, updated_at } = - queryObject - - const searchParams: AdminGetDiscountsParams = { - limit: pageSize, - is_disabled: is_disabled ? is_disabled === "true" : undefined, - is_dynamic: is_dynamic ? is_dynamic === "true" : undefined, - created_at: created_at ? JSON.parse(created_at) : undefined, - updated_at: updated_at ? JSON.parse(updated_at) : undefined, - offset: offset ? Number(offset) : 0, - order, - q, - } - - return { - searchParams, - raw: queryObject, - } -} diff --git a/packages/admin-next/dashboard/src/hooks/table/query/use-product-tag-table-query.tsx b/packages/admin-next/dashboard/src/hooks/table/query/use-product-tag-table-query.tsx new file mode 100644 index 0000000000..68a1222f00 --- /dev/null +++ b/packages/admin-next/dashboard/src/hooks/table/query/use-product-tag-table-query.tsx @@ -0,0 +1,32 @@ +import { HttpTypes } from "@medusajs/types" +import { useQueryParams } from "../../use-query-params" + +type UseProductTagTableQueryProps = { + prefix?: string + pageSize?: number +} + +export const useProductTagTableQuery = ({ + prefix, + pageSize = 20, +}: UseProductTagTableQueryProps) => { + const queryObject = useQueryParams( + ["offset", "q", "order", "created_at", "updated_at"], + prefix + ) + + const { offset, q, order, created_at, updated_at } = queryObject + const searchParams: HttpTypes.AdminProductTagListParams = { + limit: pageSize, + offset: offset ? Number(offset) : 0, + order, + created_at: created_at ? JSON.parse(created_at) : undefined, + updated_at: updated_at ? JSON.parse(updated_at) : undefined, + q, + } + + return { + searchParams, + raw: queryObject, + } +} diff --git a/packages/admin-next/dashboard/src/routes/product-types/product-type-list/components/product-type-list-table/use-product-type-table-query.tsx b/packages/admin-next/dashboard/src/hooks/table/query/use-product-type-table-query.tsx similarity index 79% rename from packages/admin-next/dashboard/src/routes/product-types/product-type-list/components/product-type-list-table/use-product-type-table-query.tsx rename to packages/admin-next/dashboard/src/hooks/table/query/use-product-type-table-query.tsx index 1957f57a56..779d662483 100644 --- a/packages/admin-next/dashboard/src/routes/product-types/product-type-list/components/product-type-list-table/use-product-type-table-query.tsx +++ b/packages/admin-next/dashboard/src/hooks/table/query/use-product-type-table-query.tsx @@ -1,4 +1,5 @@ -import { useQueryParams } from "../../../../../hooks/use-query-params" +import { HttpTypes } from "@medusajs/types" +import { useQueryParams } from "../../use-query-params" type UseProductTypeTableQueryProps = { prefix?: string @@ -15,7 +16,7 @@ export const useProductTypeTableQuery = ({ ) const { offset, q, order, created_at, updated_at } = queryObject - const searchParams = { + const searchParams: HttpTypes.AdminProductTypeListParams = { limit: pageSize, offset: offset ? Number(offset) : 0, order, diff --git a/packages/admin-next/dashboard/src/hooks/table/query/use-tax-region-table-query copy.tsx b/packages/admin-next/dashboard/src/hooks/table/query/use-tax-region-table-query.tsx similarity index 100% rename from packages/admin-next/dashboard/src/hooks/table/query/use-tax-region-table-query copy.tsx rename to packages/admin-next/dashboard/src/hooks/table/query/use-tax-region-table-query.tsx diff --git a/packages/admin-next/dashboard/src/i18n/translations/en.json b/packages/admin-next/dashboard/src/i18n/translations/en.json index 2ebbfd6585..2a62353e5a 100644 --- a/packages/admin-next/dashboard/src/i18n/translations/en.json +++ b/packages/admin-next/dashboard/src/i18n/translations/en.json @@ -98,7 +98,8 @@ "add": "Add", "select": "Select", "browse": "Browse", - "logout": "Logout" + "logout": "Logout", + "hide": "Hide" }, "operators": { "in": "In" @@ -1049,127 +1050,239 @@ "type": "Enter shipping profile type, for example: Heavy, Oversized, Freight-only, etc." } }, - "discounts": { - "domain": "Discounts", - "startDate": "Start date", - "createDiscountTitle": "Create Discount", - "validDuration": "Duration of the discount", - "redemptionsLimit": "Redemptions limit", - "endDate": "End date", - "type": "Discount type", - "percentageDiscount": "Percentage discount", - "freeShipping": "Free shipping", - "fixedDiscount": "Fixed discount", - "fixedAmount": "Fixed amount", - "validRegions": "Valid regions", - "deleteWarning": "You are about to delete the discount {{code}}. This action cannot be undone.", - "editDiscountDetails": "Edit Discount Details", - "editDiscountConfiguration": "Edit Discount Configuration", - "hasStartDate": "Discount has a start date", - "hasEndDate": "Discount has an expiry date", - "startDateHint": "Schedule the discount to activate in the future.", - "endDateHint": "Schedule the discount to deactivate in the future.", - "codeHint": "Discount code applies from when you hit the publish button and forever if left untouched.", - "hasUsageLimit": "Limit the number of redemptions?", - "usageLimitHint": "Limit applies across all customers, not per customer.", - "titleHint": "The code your customers will enter during checkout. This will appear on your customer's invoice.\nUppercase letters and numbers only.", - "hasDurationLimit": "Availability duration", - "durationHint": "Set the duration of the discount", - "chooseValidRegions": "Choose valid regions", - "conditionsHint": "Create conditions to apply on the discount", - "isTemplateDiscount": "Is this a template discount?", - "percentageDescription": "Discount applied in %", - "fixedDescription": "Amount discount", - "shippingDescription": "Override delivery amount", - "selectRegionFirst": "Select a region first", - "templateHint": "Template discounts allow you to define a set of rules that can be used across a group of discounts. This is useful in campaigns that should generate unique codes for each user, but where the rules for all unique codes should be the same.", - "conditions": { - "editHeader": "Edit Discount Conditions", - "editHint": "Specify conditions for when the discount can be applied to a cart.", - "manageTypesAction": "Manage condition types", - "including": { - "products_one": "Discount applies to <0/> product", - "products_other": "Discount applies to <0/> products", - "customer_groups_one": "Discount applies to <0/> customer group", - "customer_groups_other": "Discount applies to <0/> customer groups", - "product_tags_one": "Discount applies to <0/> tag", - "product_tags_other": "Discount applies to <0/> tags", - "product_collections_one": "Discount applies to <0/> product collection", - "product_collections_other": "Discount applies to <0/> product collections", - "product_types_one": "Discount applies to <0/> product type", - "product_types_other": "Discount applies to <0/> product types" - }, - "excluding": { - "products": "Discount applies to <1>all products except <0/>", - "customer_groups": "Discount applies to <1>all customer groups except <0/>", - "product_tags": "Discount applies to <1>all product tags except <0/>", - "product_collections": "Discount applies to <1>all product collections except <0/>", - "product_types": "Discount applies to <1>all product types except <0/>" - }, - "edit": { - "appliesTo": "Discount applies to", - "except": { - "products_one": "product, except", - "products_other": "products, except", - "product_tags_one": "product tag, except", - "product_tags_other": "product tags, except", - "product_types_one": "product type, except", - "product_types_other": "product types, except", - "product_collections_one": "product collection, except", - "product_collections_other": "product collections, except", - "customer_groups_one": "customer group, except", - "customer_groups_other": "customer groups, except" - } - } - }, - "discountStatus": { - "scheduled": "Scheduled", - "expired": "Expired", - "active": "Active", - "disabled": "Disabled" - } - }, - "taxRates": { - "domain": "Tax Rates", - "fields": { - "isCombinable": "Is combinable?", - "appliesTo": "Tax Rate applies to", - "customer_groups": "Customer Group", - "product_collections": "Product Collection", - "product_tags": "Product Tag", - "product_types": "Product Type", - "products": "Product" - }, - "edit": { - "title": "Edit Tax Rate", - "description": "Edits the tax rate of a tax region" - }, - "create": { - "title": "Create Tax Rate Override", - "description": "Create a tax rate that overrides the default tax rates for selected conditions." - } - }, "taxRegions": { "domain": "Tax Regions", - "subtitle": "Manage tax rates and settings for each region.", - "description": "Manage your region's tax structure", + "list": { + "hint": "Manage what you charge your customers when they shop from different countries and regions." + }, + "delete": { + "confirmation": "You are about to delete a tax region. This action cannot be undone.", + "successToast": "The tax region was successfully deleted." + }, "create": { - "title": "Create Tax Region", - "description": "Creates a tax region with a default tax rate." - }, - "create-child": { - "title": "Create Default Rate for Province", - "description": "Creates a tax region for a province with default tax rate" - }, - "removeWarning": "You are about to remove {{tax_region_name}}. This action cannot be undone.", - "fields": { - "rate": { - "name": "Rate", - "hint": "Tax rate to apply for a region or province" + "header": "Create Tax Region", + "hint": "Create a new tax region to define tax rates for a specific country.", + "errors": { + "rateIsRequired": "Tax rate is required when creating a default tax rate.", + "nameIsRequired": "Name is required when creating a default tax rate." }, - "is_combinable": { - "name": "Is combinable", - "hint": "Whether this tax rate can be combined with the default rate from province or parent tax region." + "successToast": "The tax region was successfully created." + }, + "province": { + "header": "Provinces", + "create": { + "header": "Create Province Tax Region", + "hint": "Create a new tax region to define tax rates for a specific province." + } + }, + "state": { + "header": "States", + "create": { + "header": "Create State Tax Region", + "hint": "Create a new tax region to define tax rates for a specific state." + } + }, + "stateOrTerritory": { + "header": "States or Territories", + "create": { + "header": "Create State/Territory Tax Region", + "hint": "Create a new tax region to define tax rates for a specific state/territory." + } + }, + "county": { + "header": "Counties", + "create": { + "header": "Create County Tax Region", + "hint": "Create a new tax region to define tax rates for a specific county." + } + }, + "region": { + "header": "Regions", + "create": { + "header": "Create Region Tax Region", + "hint": "Create a new tax region to define tax rates for a specific region." + } + }, + "department": { + "header": "Departments", + "create": { + "header": "Create Department Tax Region", + "hint": "Create a new tax region to define tax rates for a specific department." + } + }, + "territory": { + "header": "Territories", + "create": { + "header": "Create Territory Tax Region", + "hint": "Create a new tax region to define tax rates for a specific territory." + } + }, + "prefecture": { + "header": "Prefectures", + "create": { + "header": "Create Prefecture Tax Region", + "hint": "Create a new tax region to define tax rates for a specific prefecture." + } + }, + "district": { + "header": "Districts", + "create": { + "header": "Create District Tax Region", + "hint": "Create a new tax region to define tax rates for a specific district." + } + }, + "governorate": { + "header": "Governorates", + "create": { + "header": "Create Governorate Tax Region", + "hint": "Create a new tax region to define tax rates for a specific governorate." + } + }, + "canton": { + "header": "Cantons", + "create": { + "header": "Create Canton Tax Region", + "hint": "Create a new tax region to define tax rates for a specific canton." + } + }, + "emirate": { + "header": "Emirates", + "create": { + "header": "Create Emirate Tax Region", + "hint": "Create a new tax region to define tax rates for a specific emirate." + } + }, + "sublevel": { + "header": "Sublevels", + "create": { + "header": "Create Sublevel Tax Region", + "hint": "Create a new tax region to define tax rates for a specific sublevel." + } + }, + "taxOverrides": { + "header": "Overrides", + "create": { + "header": "Create Override", + "hint": "Create a tax rate that overrides the default tax rates for selected conditions." + }, + "edit": { + "header": "Edit Override", + "hint": "Edit the tax rate that overrides the default tax rates for selected conditions." + } + }, + "taxRates": { + "create": { + "header": "Create Tax Rate", + "hint": "Create a new tax rate to define the tax rate for a region.", + "successToast": "Tax rate was successfully created." + }, + "edit": { + "header": "Edit Tax Rate", + "hint": "Edit the tax rate to define the tax rate for a region.", + "successToast": "Tax rate was successfully updated." + }, + "delete": { + "confirmation": "You are about to delete the tax rate {{name}}. This action cannot be undone.", + "successToast": "Tax rate was successfully deleted." + } + }, + "fields": { + "isCombinable": { + "label": "Combinable", + "hint": "Whether this tax rate can be combined with the default rate from the tax region.", + "true": "Combinable", + "false": "Not combinable" + }, + "defaultTaxRate": { + "label": "Default tax rate", + "tooltip": "The default tax rate for this region. An example is the standard VAT rate for a country or region.", + "action": "Create default tax rate" + }, + "taxRate": "Tax rate", + "taxCode": "Tax code", + "targets": { + "label": "Targets", + "hint": "Select the targets that this tax rate will apply to.", + "options": { + "product": "Products", + "productCollection": "Product collections", + "productTag": "Product tags", + "productType": "Product types", + "customerGroup": "Customer groups" + }, + "operators": { + "in": "in", + "on": "on", + "and": "and" + }, + "placeholders": { + "product": "Search for products", + "productCollection": "Search for product collections", + "productTag": "Search for product tags", + "productType": "Search for product types", + "customerGroup": "Search for customer groups" + }, + "tags": { + "product": "Product", + "productCollection": "Product collection", + "productTag": "Product tag", + "productType": "Product type", + "customerGroup": "Customer group" + }, + "modal": { + "header": "Add targets" + }, + "values_one": "{{count}} value", + "values_other": "{{count}} values", + "numberOfTargets_one": "{{count}} target", + "numberOfTargets_other": "{{count}} targets", + "additionalValues_one": "and {{count}} more value", + "additionalValues_other": "and {{count}} more values", + "action": "Add target" + }, + "sublevels": { + "labels": { + "province": "Province", + "state": "State", + "region": "Region", + "stateOrTerritory": "State/Territory", + "department": "Department", + "county": "County", + "territory": "Territory", + "prefecture": "Prefecture", + "district": "District", + "governorate": "Governorate", + "emirate": "Emirate", + "canton": "Canton", + "sublevel": "Sublevel code" + }, + "placeholders": { + "province": "Select province", + "state": "Select state", + "region": "Select region", + "stateOrTerritory": "Select state/territory", + "department": "Select department", + "county": "Select county", + "territory": "Select territory", + "prefecture": "Select prefecture", + "district": "Select district", + "governorate": "Select governorate", + "emirate": "Select emirate", + "canton": "Select canton" + }, + "tooltips": { + "sublevel": "Enter the ISO 3166-2 code for the sublevel tax region.", + "notPartOfCountry": "{{province}} does not appear to be part of {{country}}. Please double-check if this is correct." + }, + "alert": { + "header": "Sublevel regions are disabled for this tax region", + "description": "Sublevel regions are disabled for this region by default. You can enable them to create sublevel regions like provinces, states, or territories.", + "action": "Enable sublevel regions" + } + }, + "noDefaultRate": { + "label": "No default rate", + "tooltip": "This tax region does not have a default tax rate. If there is a standard rate, such as a country's VAT, please add it to this region." } } }, @@ -1985,8 +2098,6 @@ "note": "Note", "automaticTaxes": "Automatic Taxes", "taxInclusivePricing": "Tax inclusive pricing", - "taxRate": "Tax Rate", - "taxCode": "Tax Code", "currency": "Currency", "address": "Address", "address2": "Apartment, suite, etc.", diff --git a/packages/admin-next/dashboard/src/lib/addresses.ts b/packages/admin-next/dashboard/src/lib/addresses.ts index 7008c2c480..24b18bea98 100644 --- a/packages/admin-next/dashboard/src/lib/addresses.ts +++ b/packages/admin-next/dashboard/src/lib/addresses.ts @@ -1,6 +1,6 @@ import { AddressDTO } from "@medusajs/types" -import { countries } from "./countries" +import { countries } from "./data/countries" export const isSameAddress = (a: AddressDTO | null, b: AddressDTO | null) => { if (!a || !b) { diff --git a/packages/admin-next/dashboard/src/lib/countries.ts b/packages/admin-next/dashboard/src/lib/data/countries.ts similarity index 100% rename from packages/admin-next/dashboard/src/lib/countries.ts rename to packages/admin-next/dashboard/src/lib/data/countries.ts diff --git a/packages/admin-next/dashboard/src/lib/data/country-states.ts b/packages/admin-next/dashboard/src/lib/data/country-states.ts new file mode 100644 index 0000000000..0329673ba6 --- /dev/null +++ b/packages/admin-next/dashboard/src/lib/data/country-states.ts @@ -0,0 +1,1306 @@ +export type CountryProvinceObject = { + type: + | "province" + | "stateOrTerritory" + | "state" + | "territory" + | "governorate" + | "prefecture" + | "region" + | "county" + | "department" + | "emirate" + | "district" + | "canton" + options: Record +} + +export function getCountryProvinceObjectByIso2( + iso2: string | null | undefined +): CountryProvinceObject | null { + if (!iso2) { + return null + } + + const code = iso2.toUpperCase() + + return countryProvinceMap[code] || null +} + +/** + * Find the name of a province by its ISO 3166-2 alpha-1 sub-division code + * @param iso2 - The ISO 3166-2 alpha-1 sub-division code + */ +export function getProvinceByIso2(iso2: string | null | undefined) { + if (!iso2) { + return null + } + + const key = iso2.toUpperCase() + + for (const country in countryProvinceMap) { + if (countryProvinceMap[country].options[key]) { + return countryProvinceMap[country].options[key] + } + } + + return null +} + +export function isProvinceInCountry( + countryCode?: string | null, + provinceCode?: string | null +) { + if (!countryCode || !provinceCode) { + return false + } + + const code = provinceCode.toUpperCase() + + const country = countryProvinceMap[countryCode.toUpperCase()] + + return country?.options[code] !== undefined +} + +const countryProvinceMap: Record = { + AR: { + type: "province", + options: { + "AR-B": "Buenos Aires", + "AR-C": "Ciudad Autónoma de Buenos Aires", + "AR-K": "Catamarca", + "AR-H": "Chaco", + "AR-U": "Chubut", + "AR-X": "Córdoba", + "AR-W": "Corrientes", + "AR-E": "Entre Ríos", + "AR-P": "Formosa", + "AR-Y": "Jujuy", + "AR-L": "La Pampa", + "AR-F": "La Rioja", + "AR-M": "Mendoza", + "AR-N": "Misiones", + "AR-Q": "Neuquén", + "AR-R": "Río Negro", + "AR-A": "Salta", + "AR-J": "San Juan", + "AR-D": "San Luis", + "AR-Z": "Santa Cruz", + "AR-S": "Santa Fe", + "AR-G": "Santiago del Estero", + "AR-V": "Tierra del Fuego", + "AR-T": "Tucumán", + }, + }, + AU: { + type: "stateOrTerritory", + options: { + "AU-ACT": "Australian Capital Territory", + "AU-NSW": "New South Wales", + "AU-NT": "Northern Territory", + "AU-QLD": "Queensland", + "AU-SA": "South Australia", + "AU-TAS": "Tasmania", + "AU-VIC": "Victoria", + "AU-WA": "Western Australia", + }, + }, + BR: { + type: "state", + options: { + "BR-AC": "Acre", + "BR-AL": "Alagoas", + "BR-AP": "Amapá", + "BR-AM": "Amazonas", + "BR-BA": "Bahia", + "BR-CE": "Ceará", + "BR-DF": "Distrito Federal", + "BR-ES": "Espírito Santo", + "BR-GO": "Goiás", + "BR-MA": "Maranhão", + "BR-MT": "Mato Grosso", + "BR-MS": "Mato Grosso do Sul", + "BR-MG": "Minas Gerais", + "BR-PA": "Pará", + "BR-PB": "Paraíba", + "BR-PR": "Paraná", + "BR-PE": "Pernambuco", + "BR-PI": "Piauí", + "BR-RJ": "Rio de Janeiro", + "BR-RN": "Rio Grande do Norte", + "BR-RS": "Rio Grande do Sul", + "BR-RO": "Rondônia", + "BR-RR": "Roraima", + "BR-SC": "Santa Catarina", + "BR-SP": "São Paulo", + "BR-SE": "Sergipe", + "BR-TO": "Tocantins", + }, + }, + CA: { + type: "province", + options: { + "CA-AB": "Alberta", + "CA-BC": "British Columbia", + "CA-MB": "Manitoba", + "CA-NB": "New Brunswick", + "CA-NL": "Newfoundland and Labrador", + "CA-NS": "Nova Scotia", + "CA-NT": "Northwest Territories", + "CA-NU": "Nunavut", + "CA-ON": "Ontario", + "CA-PE": "Prince Edward Island", + "CA-QC": "Quebec", + "CA-SK": "Saskatchewan", + "CA-YT": "Yukon", + }, + }, + CL: { + type: "region", + options: { + "CL-AI": "Aisén del General Carlos Ibáñez del Campo", + "CL-AN": "Antofagasta", + "CL-AR": "Araucanía", + "CL-AP": "Arica y Parinacota", + "CL-AT": "Atacama", + "CL-BI": "Bío Bío", + "CL-CO": "Coquimbo", + "CL-LI": "Libertador General Bernardo O'Higgins", + "CL-LL": "Los Lagos", + "CL-LR": "Los Ríos", + "CL-MA": "Magallanes y de la Antártica Chilena", + "CL-ML": "Maule", + "CL-RM": "Región Metropolitana de Santiago", + "CL-TA": "Tarapacá", + "CL-VS": "Valparaíso", + }, + }, + CN: { + type: "province", + options: { + "CN-AH": "Anhui", + "CN-BJ": "Beijing", + "CN-CQ": "Chongqing", + "CN-FJ": "Fujian", + "CN-GD": "Guangdong", + "CN-GS": "Gansu", + "CN-GX": "Guangxi Zhuang", + "CN-GZ": "Guizhou", + "CN-HA": "Henan", + "CN-HB": "Hubei", + "CN-HE": "Hebei", + "CN-HI": "Hainan", + "CN-HK": "Hong Kong", + "CN-HL": "Heilongjiang", + "CN-HN": "Hunan", + "CN-JL": "Jilin", + "CN-JS": "Jiangsu", + "CN-JX": "Jiangxi", + "CN-LN": "Liaoning", + "CN-NM": "Inner Mongolia", + "CN-NX": "Ningxia Hui", + "CN-QH": "Qinghai", + "CN-SA": "Shaanxi", + "CN-SC": "Sichuan", + "CN-SD": "Shandong", + "CN-SH": "Shanghai", + "CN-SN": "Shanxi", + "CN-TJ": "Tianjin", + "CN-XJ": "Xinjiang Uygur", + "CN-XZ": "Tibet", + "CN-YN": "Yunnan", + "CN-ZJ": "Zhejiang", + }, + }, + CO: { + type: "province", + options: { + "CO-AMA": "Amazonas", + "CO-ANT": "Antioquia", + "CO-ARA": "Arauca", + "CO-ATL": "Atlántico", + "CO-BOL": "Bolívar", + "CO-BOY": "Boyacá", + "CO-CAL": "Caldas", + "CO-CAQ": "Caquetá", + "CO-CAS": "Casanare", + "CO-CAU": "Cauca", + "CO-CES": "Cesar", + "CO-COR": "Córdoba", + "CO-CUN": "Cundinamarca", + "CO-DC": "Bogotá D.C.", + "CO-GUA": "Guainía", + "CO-GUV": "Guaviare", + "CO-HUI": "Huila", + "CO-LAG": "La Guajira", + "CO-MAG": "Magdalena", + "CO-MET": "Meta", + "CO-NAR": "Nariño", + "CO-NSA": "Norte de Santander", + "CO-PUT": "Putumayo", + "CO-QUI": "Quindío", + "CO-RIS": "Risaralda", + "CO-SAP": "San Andrés y Providencia", + "CO-SAN": "Santander", + "CO-SUC": "Sucre", + "CO-TOL": "Tolima", + "CO-VAC": "Valle del Cauca", + "CO-VAU": "Vaupés", + "CO-VID": "Vichada", + }, + }, + CR: { + type: "province", + options: { + "CR-A": "Alajuela", + "CR-C": "Cartago", + "CR-G": "Guanacaste", + "CR-H": "Heredia", + "CR-L": "Limón", + "CR-P": "Puntarenas", + "CR-SJ": "San José", + }, + }, + CH: { + type: "canton", + options: { + "CH-AG": "Aargau", + "CH-AR": "Appenzell Ausserrhoden", + "CH-AI": "Appenzell Innerrhoden", + "CH-BL": "Basel-Landschaft", + "CH-BS": "Basel-Stadt", + "CH-BE": "Bern", + "CH-FR": "Fribourg", + "CH-GE": "Geneva", + "CH-GL": "Glarus", + "CH-GR": "Graubünden", + "CH-JU": "Jura", + "CH-LU": "Lucerne", + "CH-NE": "Neuchâtel", + "CH-NW": "Nidwalden", + "CH-OW": "Obwalden", + "CH-SG": "St. Gallen", + "CH-SH": "Schaffhausen", + "CH-SZ": "Schwyz", + "CH-SO": "Solothurn", + "CH-TG": "Thurgau", + "CH-TI": "Ticino", + "CH-UR": "Uri", + "CH-VS": "Valais", + "CH-VD": "Vaud", + "CH-ZG": "Zug", + "CH-ZH": "Zurich", + }, + }, + EG: { + type: "governorate", + options: { + "EG-ALX": "Alexandria", + "EG-ASN": "Aswan", + "EG-AST": "Asyut", + "EG-BA": "Red Sea", + "EG-BH": "Beheira", + "EG-BNS": "Beni Suef", + "EG-C": "Cairo", + "EG-DK": "Dakahlia", + "EG-DT": "Damietta", + "EG-FYM": "Faiyum", + "EG-GH": "Gharbia", + "EG-GZ": "Giza", + "EG-IS": "Ismailia", + "EG-JS": "South Sinai", + "EG-KB": "Qalyubia", + "EG-KFS": "Kafr el-Sheikh", + "EG-KN": "Qena", + "EG-LX": "Luxor", + "EG-MNF": "Monufia", + "EG-MT": "Matrouh", + "EG-PTS": "Port Said", + "EG-SHG": "Sohag", + "EG-SHR": "Al Sharqia", + "EG-SIN": "North Sinai", + "EG-SUZ": "Suez", + }, + }, + GT: { + type: "department", + options: { + "GT-AV": "Alta Verapaz", + "GT-BV": "Baja Verapaz", + "GT-CM": "Chimaltenango", + "GT-CQ": "Chiquimula", + "GT-PR": "El Progreso", + "GT-ES": "Escuintla", + "GT-GU": "Guatemala", + "GT-HU": "Huehuetenango", + "GT-IZ": "Izabal", + "GT-JA": "Jalapa", + "GT-JU": "Jutiapa", + "GT-PE": "Petén", + "GT-QZ": "Quetzaltenango", + "GT-QC": "Quiché", + "GT-RE": "Retalhuleu", + "GT-SA": "Sacatepéquez", + "GT-SM": "San Marcos", + "GT-SR": "Santa Rosa", + "GT-SO": "Sololá", + "GT-SU": "Suchitepéquez", + "GT-TO": "Totonicapán", + "GT-ZA": "Zacapa", + }, + }, + HK: { + type: "region", + options: { + "HK-HKI": "Hong Kong Island", + "HK-KLN": "Kowloon", + "HK-NT": "New Territories", + }, + }, + IN: { + type: "state", + options: { + "IN-AN": "Andaman and Nicobar Islands", + "IN-AP": "Andhra Pradesh", + "IN-AR": "Arunachal Pradesh", + "IN-AS": "Assam", + "IN-BR": "Bihar", + "IN-CH": "Chandigarh", + "IN-CT": "Chhattisgarh", + "IN-DN": "Dadra and Nagar Haveli and Daman and Diu", + "IN-DL": "Delhi", + "IN-GA": "Goa", + "IN-GJ": "Gujarat", + "IN-HR": "Haryana", + "IN-HP": "Himachal Pradesh", + "IN-JH": "Jharkhand", + "IN-KA": "Karnataka", + "IN-KL": "Kerala", + "IN-LD": "Lakshadweep", + "IN-MP": "Madhya Pradesh", + "IN-MH": "Maharashtra", + "IN-MN": "Manipur", + "IN-ML": "Meghalaya", + "IN-MZ": "Mizoram", + "IN-NL": "Nagaland", + "IN-OR": "Odisha", + "IN-PY": "Puducherry", + "IN-PB": "Punjab", + "IN-RJ": "Rajasthan", + "IN-SK": "Sikkim", + "IN-TN": "Tamil Nadu", + "IN-TG": "Telangana", + "IN-TR": "Tripura", + "IN-UP": "Uttar Pradesh", + "IN-UT": "Uttarakhand", + "IN-WB": "West Bengal", + }, + }, + IE: { + type: "county", + options: { + "IE-CW": "Carlow", + "IE-CN": "Cavan", + "IE-CE": "Clare", + "IE-C": "Cork", + "IE-D": "Dublin", + "IE-DL": "Donegal", + "IE-G": "Galway", + "IE-KY": "Kerry", + "IE-KE": "Kildare", + "IE-KK": "Kilkenny", + "IE-LS": "Laois", + "IE-LM": "Leitrim", + "IE-LK": "Limerick", + "IE-LD": "Longford", + "IE-LH": "Louth", + "IE-MO": "Mayo", + "IE-MH": "Meath", + "IE-MN": "Monaghan", + "IE-OY": "Offaly", + "IE-RN": "Roscommon", + "IE-SO": "Sligo", + "IE-TA": "Tipperary", + "IE-WD": "Waterford", + "IE-WH": "Westmeath", + "IE-WX": "Wexford", + "IE-WW": "Wicklow", + }, + }, + IT: { + type: "province", + options: { + "IT-AG": "Agrigento", + "IT-AL": "Alessandria", + "IT-AN": "Ancona", + "IT-AO": "Aosta", + "IT-AR": "Arezzo", + "IT-AP": "Ascoli Piceno", + "IT-AT": "Asti", + "IT-AV": "Avellino", + "IT-BA": "Bari", + "IT-BT": "Barletta-Andria-Trani", + "IT-BL": "Belluno", + "IT-BN": "Benevento", + "IT-BG": "Bergamo", + "IT-BI": "Biella", + "IT-BO": "Bologna", + "IT-BZ": "Bolzano", + "IT-BS": "Brescia", + "IT-BR": "Brindisi", + "IT-CA": "Cagliari", + "IT-CL": "Caltanissetta", + "IT-CB": "Campobasso", + "IT-CI": "Carbonia-Iglesias", + "IT-CE": "Caserta", + "IT-CT": "Catania", + "IT-CZ": "Catanzaro", + "IT-CH": "Chieti", + "IT-CO": "Como", + "IT-CS": "Cosenza", + "IT-CR": "Cremona", + "IT-KR": "Crotone", + "IT-CN": "Cuneo", + "IT-EN": "Enna", + "IT-FM": "Fermo", + "IT-FE": "Ferrara", + "IT-FI": "Firenze", + "IT-FG": "Foggia", + "IT-FC": "Forlì-Cesena", + "IT-FR": "Frosinone", + "IT-GE": "Genova", + "IT-GO": "Gorizia", + "IT-GR": "Grosseto", + "IT-IM": "Imperia", + "IT-IS": "Isernia", + "IT-SP": "La Spezia", + "IT-AQ": "L'Aquila", + "IT-LT": "Latina", + "IT-LE": "Lecce", + "IT-LC": "Lecco", + "IT-LI": "Livorno", + "IT-LO": "Lodi", + "IT-LU": "Lucca", + "IT-MC": "Macerata", + "IT-MN": "Mantova", + "IT-MS": "Massa-Carrara", + "IT-MT": "Matera", + "IT-VS": "Medio Campidano", + "IT-ME": "Messina", + "IT-MI": "Milano", + "IT-MO": "Modena", + "IT-MB": "Monza e Brianza", + "IT-NA": "Napoli", + "IT-NO": "Novara", + "IT-NU": "Nuoro", + "IT-OG": "Ogliastra", + "IT-OT": "Olbia-Tempio", + "IT-OR": "Oristano", + "IT-PD": "Padova", + "IT-PA": "Palermo", + "IT-PR": "Parma", + "IT-PV": "Pavia", + "IT-PG": "Perugia", + "IT-PU": "Pesaro e Urbino", + "IT-PE": "Pescara", + "IT-PC": "Piacenza", + "IT-PI": "Pisa", + "IT-PT": "Pistoia", + "IT-PN": "Pordenone", + "IT-PZ": "Potenza", + "IT-PO": "Prato", + "IT-RG": "Ragusa", + "IT-RA": "Ravenna", + "IT-RC": "Reggio Calabria", + "IT-RE": "Reggio Emilia", + "IT-RI": "Rieti", + "IT-RN": "Rimini", + "IT-RM": "Roma", + "IT-RO": "Rovigo", + "IT-SA": "Salerno", + "IT-SS": "Sassari", + "IT-SV": "Savona", + "IT-SI": "Siena", + "IT-SR": "Siracusa", + "IT-SO": "Sondrio", + "IT-TA": "Taranto", + "IT-TE": "Teramo", + "IT-TR": "Terni", + "IT-TO": "Torino", + "IT-TP": "Trapani", + "IT-TN": "Trento", + "IT-TV": "Treviso", + "IT-TS": "Trieste", + "IT-UD": "Udine", + "IT-VA": "Varese", + "IT-VE": "Venezia", + "IT-VB": "Verbano-Cusio-Ossola", + "IT-VC": "Vercelli", + "IT-VR": "Verona", + "IT-VV": "Vibo Valentia", + "IT-VI": "Vicenza", + "IT-VT": "Viterbo", + }, + }, + JP: { + type: "prefecture", + options: { + "JP-01": "Hokkaido", + "JP-02": "Aomori", + "JP-03": "Iwate", + "JP-04": "Miyagi", + "JP-05": "Akita", + "JP-06": "Yamagata", + "JP-07": "Fukushima", + "JP-08": "Ibaraki", + "JP-09": "Tochigi", + "JP-10": "Gunma", + "JP-11": "Saitama", + "JP-12": "Chiba", + "JP-13": "Tokyo", + "JP-14": "Kanagawa", + "JP-15": "Niigata", + "JP-16": "Toyama", + "JP-17": "Ishikawa", + "JP-18": "Fukui", + "JP-19": "Yamanashi", + "JP-20": "Nagano", + "JP-21": "Gifu", + "JP-22": "Shizuoka", + "JP-23": "Aichi", + "JP-24": "Mie", + "JP-25": "Shiga", + "JP-26": "Kyoto", + "JP-27": "Osaka", + "JP-28": "Hyogo", + "JP-29": "Nara", + "JP-30": "Wakayama", + "JP-31": "Tottori", + "JP-32": "Shimane", + "JP-33": "Okayama", + "JP-34": "Hiroshima", + "JP-35": "Yamaguchi", + "JP-36": "Tokushima", + "JP-37": "Kagawa", + "JP-38": "Ehime", + "JP-39": "Kochi", + "JP-40": "Fukuoka", + "JP-41": "Saga", + "JP-42": "Nagasaki", + "JP-43": "Kumamoto", + "JP-44": "Oita", + "JP-45": "Miyazaki", + "JP-46": "Kagoshima", + "JP-47": "Okinawa", + }, + }, + KW: { + type: "governorate", + options: { + "KW-AH": "Al Ahmadi", + "KW-FA": "Al Farwaniyah", + "KW-HA": "Hawalli", + "KW-JA": "Al Jahra", + "KW-KU": "Capital", + "KW-MU": "Mubarak Al-Kabeer", + }, + }, + MY: { + type: "stateOrTerritory", + options: { + "MY-01": "Johor", + "MY-02": "Kedah", + "MY-03": "Kelantan", + "MY-04": "Melaka", + "MY-05": "Negeri Sembilan", + "MY-06": "Pahang", + "MY-08": "Perak", + "MY-09": "Perlis", + "MY-07": "Pulau Pinang", + "MY-12": "Sabah", + "MY-13": "Sarawak", + "MY-10": "Selangor", + "MY-11": "Terengganu", + "MY-14": "Wilayah Persekutuan Kuala Lumpur", + "MY-15": "Wilayah Persekutuan Labuan", + "MY-16": "Wilayah Persekutuan Putrajaya", + }, + }, + MX: { + type: "state", + options: { + "MX-AGU": "Aguascalientes", + "MX-BCN": "Baja California", + "MX-BCS": "Baja California Sur", + "MX-CAM": "Campeche", + "MX-CMX": "Ciudad de México", + "MX-COA": "Coahuila de Zaragoza", + "MX-COL": "Colima", + "MX-CHP": "Chiapas", + "MX-CHH": "Chihuahua", + "MX-DUR": "Durango", + "MX-GUA": "Guanajuato", + "MX-GRO": "Guerrero", + "MX-HID": "Hidalgo", + "MX-JAL": "Jalisco", + "MX-MEX": "México", + "MX-MIC": "Michoacán de Ocampo", + "MX-MOR": "Morelos", + "MX-NAY": "Nayarit", + "MX-NLE": "Nuevo León", + "MX-OAX": "Oaxaca", + "MX-PUE": "Puebla", + "MX-QUE": "Querétaro", + "MX-ROO": "Quintana Roo", + "MX-SLP": "San Luis Potosí", + "MX-SIN": "Sinaloa", + "MX-SON": "Sonora", + "MX-TAB": "Tabasco", + "MX-TAM": "Tamaulipas", + "MX-TLA": "Tlaxcala", + "MX-VER": "Veracruz de Ignacio de la Llave", + "MX-YUC": "Yucatán", + "MX-ZAC": "Zacatecas", + }, + }, + NZ: { + type: "region", + options: { + "NZ-AUK": "Auckland", + "NZ-BOP": "Bay of Plenty", + "NZ-CAN": "Canterbury", + "NZ-CIT": "Chatham Islands Territory", + "NZ-GIS": "Gisborne", + "NZ-WGN": "Greater Wellington", + "NZ-HKB": "Hawke's Bay", + "NZ-MWT": "Manawatū-Whanganui", + "NZ-MBH": "Marlborough", + "NZ-NSN": "Nelson", + "NZ-NTL": "Northland", + "NZ-OTA": "Otago", + "NZ-STL": "Southland", + "NZ-TKI": "Taranaki", + "NZ-TAS": "Tasman", + "NZ-WKO": "Waikato", + "NZ-WTC": "West Coast", + }, + }, + NG: { + type: "state", + options: { + "NG-AB": "Abia", + "NG-AD": "Adamawa", + "NG-AK": "Akwa Ibom", + "NG-AN": "Anambra", + "NG-BA": "Bauchi", + "NG-BY": "Bayelsa", + "NG-BE": "Benue", + "NG-BO": "Borno", + "NG-CR": "Cross River", + "NG-DE": "Delta", + "NG-EB": "Ebonyi", + "NG-ED": "Edo", + "NG-EK": "Ekiti", + "NG-EN": "Enugu", + "NG-FC": "Federal Capital Territory", + "NG-GO": "Gombe", + "NG-IM": "Imo", + "NG-JI": "Jigawa", + "NG-KD": "Kaduna", + "NG-KN": "Kano", + "NG-KT": "Katsina", + "NG-KE": "Kebbi", + "NG-KO": "Kogi", + "NG-KW": "Kwara", + "NG-LA": "Lagos", + "NG-NA": "Nasarawa", + "NG-NI": "Niger", + "NG-OG": "Ogun", + "NG-ON": "Ondo", + "NG-OS": "Osun", + "NG-OY": "Oyo", + "NG-PL": "Plateau", + "NG-RI": "Rivers", + "NG-SO": "Sokoto", + "NG-TA": "Taraba", + "NG-YO": "Yobe", + "NG-ZA": "Zamfara", + }, + }, + PA: { + type: "province", + options: { + "PA-1": "Bocas del Toro", + "PA-4": "Chiriquí", + "PA-2": "Coclé", + "PA-3": "Colón", + "PA-5": "Darién", + "PA-6": "Herrera", + "PA-7": "Los Santos", + "PA-8": "Panamá", + "PA-9": "Panamá Oeste", + "PA-10": "Veraguas", + "PA-EM": "Emberá", + "PA-KY": "Guna Yala", + "PA-NB": "Ngäbe-Buglé", + }, + }, + PE: { + type: "province", + options: { + "PE-AMA": "Amazonas", + "PE-ANC": "Ancash", + "PE-APU": "Apurímac", + "PE-ARE": "Arequipa", + "PE-AYA": "Ayacucho", + "PE-CAJ": "Cajamarca", + "PE-CAL": "El Callao", + "PE-CUS": "Cusco", + "PE-HUV": "Huancavelica", + "PE-HUC": "Huánuco", + "PE-ICA": "Ica", + "PE-JUN": "Junín", + "PE-LAL": "La Libertad", + "PE-LAM": "Lambayeque", + "PE-LIMD": "Lima (Department)", + "PE-LIM": "Lima (Metropolitan)", + "PE-LOR": "Loreto", + "PE-MDD": "Madre de Dios", + "PE-MOQ": "Moquegua", + "PE-PAS": "Pasco", + "PE-PIU": "Piura", + "PE-PUN": "Puno", + "PE-SAM": "San Martín", + "PE-TAC": "Tacna", + "PE-TUM": "Tumbes", + "PE-UCA": "Ucayali", + }, + }, + + PH: { + type: "region", + options: { + "PH-ABR": "Abra", + "PH-AGN": "Agusan del Norte", + "PH-AGS": "Agusan del Sur", + "PH-AKL": "Aklan", + "PH-ALB": "Albay", + "PH-ANT": "Antique", + "PH-APA": "Apayao", + "PH-AUR": "Aurora", + "PH-BAS": "Basilan", + "PH-BAN": "Bataan", + "PH-BTG": "Batangas", + "PH-BEN": "Benguet", + "PH-BOH": "Bohol", + "PH-BUK": "Bukidnon", + "PH-BUL": "Bulacan", + "PH-CAG": "Cagayan", + "PH-CAM": "Camiguin", + "PH-CAN": "Camarines Norte", + "PH-CAS": "Camarines Sur", + "PH-CAP": "Capiz", + "PH-CAT": "Catanduanes", + "PH-CAV": "Cavite", + "PH-CEB": "Cebu", + "PH-COM": "Compostela Valley", + "PH-COT": "Cotabato", + "PH-DAV": "Davao del Norte", + "PH-DAS": "Davao del Sur", + "PH-DAO": "Davao Oriental", + "PH-EAS": "Eastern Samar", + "PH-GUI": "Guimaras", + "PH-IFU": "Ifugao", + "PH-ILN": "Ilocos Norte", + "PH-ILS": "Ilocos Sur", + "PH-ILI": "Iloilo", + "PH-ISA": "Isabela", + "PH-KAL": "Kalinga", + "PH-LUN": "La Union", + "PH-LAG": "Laguna", + "PH-LAN": "Lanao del Norte", + "PH-LAS": "Lanao del Sur", + "PH-LEY": "Leyte", + "PH-MAG": "Maguindanao", + "PH-MAD": "Marinduque", + "PH-MAS": "Masbate", + "PH-MDC": "Misamis Occidental", + "PH-MDR": "Misamis Oriental", + "PH-MOU": "Mountain Province", + "PH-NEC": "Negros Occidental", + "PH-NER": "Negros Oriental", + "PH-NCO": "North Cotabato", + "PH-NSA": "Northern Samar", + "PH-NUE": "Nueva Ecija", + "PH-NUV": "Nueva Vizcaya", + "PH-PLW": "Palawan", + "PH-PAM": "Pampanga", + "PH-PAN": "Pangasinan", + "PH-QUE": "Quezon", + "PH-QUI": "Quirino", + "PH-RIZ": "Rizal", + "PH-ROM": "Romblon", + "PH-WSA": "Samar", + "PH-SAR": "Sarangani", + "PH-SIG": "Siquijor", + "PH-SOR": "Sorsogon", + "PH-SCO": "South Cotabato", + "PH-SLE": "Southern Leyte", + "PH-SUK": "Sultan Kudarat", + "PH-SLU": "Sulu", + "PH-SUN": "Surigao del Norte", + "PH-SUR": "Surigao del Sur", + "PH-TAR": "Tarlac", + "PH-TAW": "Tawi-Tawi", + "PH-ZMB": "Zambales", + "PH-ZAN": "Zamboanga del Norte", + "PH-ZAS": "Zamboanga del Sur", + "PH-ZSI": "Zamboanga Sibugay", + "PH-00": "Metro Manila", + }, + }, + PT: { + type: "district", + options: { + "PT-01": "Aveiro", + "PT-02": "Beja", + "PT-03": "Braga", + "PT-04": "Bragança", + "PT-05": "Castelo Branco", + "PT-06": "Coimbra", + "PT-07": "Évora", + "PT-08": "Faro", + "PT-09": "Guarda", + "PT-10": "Leiria", + "PT-11": "Lisboa", + "PT-12": "Portalegre", + "PT-13": "Porto", + "PT-14": "Santarém", + "PT-15": "Setúbal", + "PT-16": "Viana do Castelo", + "PT-17": "Vila Real", + "PT-18": "Viseu", + "PT-20": "Azores", + "PT-30": "Madeira", + }, + }, + RO: { + type: "county", + options: { + "RO-AB": "Alba", + "RO-AR": "Arad", + "RO-AG": "Argeș", + "RO-BC": "Bacău", + "RO-BH": "Bihor", + "RO-BN": "Bistrița-Năsăud", + "RO-BT": "Botoșani", + "RO-BV": "Brașov", + "RO-BR": "Brăila", + "RO-BZ": "Buzău", + "RO-CS": "Caraș-Severin", + "RO-CL": "Călărași", + "RO-CJ": "Cluj", + "RO-CT": "Constanța", + "RO-CV": "Covasna", + "RO-DB": "Dâmbovița", + "RO-DJ": "Dolj", + "RO-GL": "Galați", + "RO-GR": "Giurgiu", + "RO-GJ": "Gorj", + "RO-HR": "Harghita", + "RO-HD": "Hunedoara", + "RO-IL": "Ialomița", + "RO-IS": "Iași", + "RO-MM": "Maramureș", + "RO-MH": "Mehedinți", + "RO-MS": "Mureș", + "RO-NT": "Neamț", + "RO-OT": "Olt", + "RO-PH": "Prahova", + "RO-SJ": "Sălaj", + "RO-SM": "Satu Mare", + "RO-SB": "Sibiu", + "RO-SV": "Suceava", + "RO-TR": "Teleorman", + "RO-TM": "Timiș", + "RO-TL": "Tulcea", + "RO-VS": "Vaslui", + "RO-VL": "Vâlcea", + "RO-VN": "Vrancea", + }, + }, + RU: { + type: "region", + options: { + "RU-AD": "Adygea", + "RU-AL": "Altai", + "RU-ALT": "Altai Krai", + "RU-AMU": "Amur", + "RU-ARK": "Arkhangelsk", + "RU-AST": "Astrakhan", + "RU-BA": "Bashkortostan", + "RU-BEL": "Belgorod", + "RU-BRY": "Bryansk", + "RU-BU": "Buryat", + "RU-CE": "Chechen", + "RU-CHE": "Chelyabinsk", + "RU-CHU": "Chukotka Okrug", + "RU-CU": "Chuvash", + "RU-DA": "Dagestan", + "RU-IRK": "Irkutsk", + "RU-IVA": "Ivanovo", + "RU-YEV": "Jewish", + "RU-KB": "Kabardino-Balkar", + "RU-KGD": "Kaliningrad", + "RU-KLU": "Kaluga", + "RU-KAM": "Kamchatka Krai", + "RU-KC": "Karachay-Cherkess", + "RU-KEM": "Kemerovo", + "RU-KHA": "Khabarovsk Krai", + "RU-KHM": "Khanty-Mansi", + "RU-KIR": "Kirov", + "RU-KO": "Komi", + "RU-KOS": "Kostroma", + "RU-KDA": "Krasnodar Krai", + "RU-KYA": "Krasnoyarsk Krai", + "RU-KGN": "Kurgan", + "RU-KRS": "Kursk", + "RU-LEN": "Leningrad", + "RU-LIP": "Lipetsk", + "RU-MAG": "Magadan", + "RU-ME": "Mari El", + "RU-MOW": "Moscow", + "RU-MOS": "Moscow Province", + "RU-MUR": "Murmansk", + "RU-NIZ": "Nizhny Novgorod", + "RU-NGR": "Novgorod", + "RU-NVS": "Novosibirsk", + "RU-OMS": "Omsk", + "RU-ORE": "Orenburg", + "RU-ORL": "Oryol", + "RU-PNZ": "Penza", + "RU-PER": "Perm Krai", + "RU-PRI": "Primorsky Krai", + "RU-PSK": "Pskov", + "RU-SA": "Sakha", + "RU-SAK": "Sakhalin", + "RU-SAM": "Samara", + "RU-SAR": "Saratov", + "RU-SMO": "Smolensk", + "RU-SE": "North Ossetia-Alania", + "RU-STA": "Stavropol Krai", + "RU-SVE": "Sverdlovsk", + "RU-TAM": "Tambov", + "RU-TOM": "Tomsk", + "RU-TUL": "Tula", + "RU-TVE": "Tver", + "RU-TYU": "Tyumen", + "RU-TY": "Tuva", + "RU-UD": "Udmurt", + "RU-ULY": "Ulyanovsk", + "RU-VLA": "Vladimir", + "RU-VGG": "Volgograd", + "RU-VLG": "Vologda", + "RU-VOR": "Voronezh", + "RU-YAN": "Yamalo-Nenets Okrug", + "RU-YAR": "Yaroslavl", + "RU-ZAB": "Zabaykalsky Krai", + }, + }, + ZA: { + type: "province", + options: { + "ZA-EC": "Eastern Cape", + "ZA-FS": "Free State", + "ZA-GT": "Gauteng", + "ZA-NL": "KwaZulu-Natal", + "ZA-LP": "Limpopo", + "ZA-MP": "Mpumalanga", + "ZA-NC": "Northern Cape", + "ZA-NW": "North West", + "ZA-WC": "Western Cape", + }, + }, + KR: { + type: "province", + options: { + "KR-11": "Seoul", + "KR-26": "Busan", + "KR-27": "Daegu", + "KR-28": "Incheon", + "KR-29": "Gwangju", + "KR-30": "Daejeon", + "KR-31": "Ulsan", + "KR-41": "Gyeonggi", + "KR-42": "Gangwon", + "KR-43": "North Chungcheong", + "KR-44": "South Chungcheong", + "KR-45": "North Jeolla", + "KR-46": "South Jeolla", + "KR-47": "North Gyeongsang", + "KR-48": "South Gyeongsang", + "KR-49": "Jeju", + }, + }, + ES: { + type: "province", + options: { + "ES-A": "A Coruña", + "ES-AB": "Albacete", + "ES-AL": "Almería", + "ES-AN": "Andalucía", + "ES-AR": "Aragón", + "ES-AS": "Asturias", + "ES-AV": "Ávila", + "ES-B": "Barcelona", + "ES-BA": "Badajoz", + "ES-BI": "Bizkaia", + "ES-BU": "Burgos", + "ES-C": "Cáceres", + "ES-CA": "Cádiz", + "ES-CM": "Castilla-La Mancha", + "ES-CL": "Castilla y León", + "ES-CN": "Canarias", + "ES-CT": "Catalunya", + "ES-CE": "Ceuta", + "ES-CR": "Ciudad Real", + "ES-CO": "Córdoba", + "ES-CU": "Cuenca", + "ES-EX": "Extremadura", + "ES-GA": "Galicia", + "ES-GI": "Girona", + "ES-GR": "Granada", + "ES-GU": "Guadalajara", + "ES-H": "Huelva", + "ES-HU": "Huesca", + "ES-IB": "Illes Balears", + "ES-J": "Jaén", + "ES-JA": "Jerez", + "ES-LE": "León", + "ES-L": "Lleida", + "ES-LO": "La Rioja", + "ES-LU": "Lugo", + "ES-M": "Madrid", + "ES-MA": "Málaga", + "ES-MU": "Murcia", + "ES-NA": "Navarra", + "ES-NC": "Nouvelle-Calédonie", + "ES-O": "Ourense", + "ES-P": "Palencia", + "ES-PM": "Palma", + "ES-PO": "Pontevedra", + "ES-SA": "Salamanca", + "ES-SG": "Segovia", + "ES-SE": "Sevilla", + "ES-SO": "Soria", + "ES-T": "Tarragona", + "ES-TE": "Teruel", + "ES-TO": "Toledo", + "ES-V": "Valencia", + "ES-VA": "Valladolid", + "ES-VI": "Vizcaya", + "ES-Z": "Zaragoza", + "ES-ZA": "Zamora", + }, + }, + TH: { + type: "province", + options: { + "TH-10": "Bangkok", + "TH-11": "Samut Prakan", + "TH-12": "Nonthaburi", + "TH-13": "Pathum Thani", + "TH-14": "Phra Nakhon Si Ayutthaya", + "TH-15": "Ang Thong", + "TH-16": "Lop Buri", + "TH-17": "Sing Buri", + "TH-18": "Chai Nat", + "TH-19": "Saraburi", + "TH-20": "Chon Buri", + "TH-21": "Rayong", + "TH-22": "Chanthaburi", + "TH-23": "Trat", + "TH-24": "Chachoengsao", + "TH-25": "Prachin Buri", + "TH-26": "Nakhon Nayok", + "TH-27": "Sa Kaeo", + "TH-30": "Nakhon Ratchasima", + "TH-31": "Buri Ram", + "TH-32": "Surin", + "TH-33": "Si Sa Ket", + "TH-34": "Ubon Ratchathani", + "TH-35": "Yasothon", + "TH-36": "Chaiyaphum", + "TH-37": "Amnat Charoen", + "TH-38": "Bueng Kan", + "TH-39": "Nong Bua Lam Phu", + "TH-40": "Khon Kaen", + "TH-41": "Udon Thani", + "TH-42": "Loei", + "TH-43": "Nong Khai", + "TH-44": "Maha Sarakham", + "TH-45": "Roi Et", + "TH-46": "Kalasin", + "TH-47": "Sakon Nakhon", + "TH-48": "Nakhon Phanom", + "TH-49": "Mukdahan", + "TH-50": "Chiang Mai", + "TH-51": "Lamphun", + "TH-52": "Lampang", + "TH-53": "Uttaradit", + "TH-54": "Phrae", + "TH-55": "Nan", + "TH-56": "Phayao", + "TH-57": "Chiang Rai", + "TH-58": "Mae Hong Son", + "TH-60": "Nakhon Sawan", + "TH-61": "Uthai Thani", + "TH-62": "Kamphaeng Phet", + "TH-63": "Tak", + "TH-64": "Sukhothai", + "TH-65": "Phitsanulok", + "TH-66": "Phichit", + "TH-67": "Phetchabun", + "TH-70": "Ratchaburi", + "TH-71": "Kanchanaburi", + "TH-72": "Suphan Buri", + "TH-73": "Nakhon Pathom", + "TH-74": "Samut Sakhon", + "TH-75": "Samut Songkhram", + "TH-76": "Phetchaburi", + "TH-77": "Prachuap Khiri Khan", + "TH-80": "Nakhon Si Thammarat", + "TH-81": "Krabi", + "TH-82": "Phang Nga", + "TH-83": "Phuket", + "TH-84": "Surat Thani", + "TH-85": "Ranong", + "TH-86": "Chumphon", + "TH-90": "Songkhla", + "TH-91": "Satun", + "TH-92": "Trang", + "TH-93": "Phatthalung", + "TH-94": "Pattani", + "TH-95": "Yala", + "TH-96": "Narathiwat", + }, + }, + AE: { + type: "emirate", + options: { + "AE-AJ": "Ajman", + "AE-AZ": "Abu Dhabi", + "AE-DU": "Dubai", + "AE-FU": "Fujairah", + "AE-RK": "Ras al-Khaimah", + "AE-SH": "Sharjah", + "AE-UQ": "Umm al-Quwain", + }, + }, + UY: { + type: "department", + options: { + "UY-AR": "Artigas", + "UY-CA": "Canelones", + "UY-CL": "Cerro Largo", + "UY-CO": "Colonia", + "UY-DU": "Durazno", + "UY-FD": "Flores", + "UY-FS": "Florida", + "UY-LA": "Lavalleja", + "UY-MA": "Maldonado", + "UY-MO": "Montevideo", + "UY-PA": "Paysandú", + "UY-RN": "Río Negro", + "UY-RV": "Rivera", + "UY-RO": "Rocha", + "UY-SA": "Salto", + "UY-SJ": "San José", + "UY-SO": "Soriano", + "UY-TA": "Tacuarembó", + "UY-TT": "Treinta y Tres", + }, + }, + US: { + type: "state", + options: { + "US-AL": "Alabama", + "US-AK": "Alaska", + "US-AZ": "Arizona", + "US-AR": "Arkansas", + "US-CA": "California", + "US-CO": "Colorado", + "US-CT": "Connecticut", + "US-FL": "Florida", + "US-GA": "Georgia", + "US-HI": "Hawaii", + "US-ID": "Idaho", + "US-IL": "Illinois", + "US-IN": "Indiana", + "US-IA": "Iowa", + "US-KS": "Kansas", + "US-KY": "Kentucky", + "US-LA": "Louisiana", + "US-ME": "Maine", + "US-MD": "Maryland", + "US-MA": "Massachusetts", + "US-MI": "Michigan", + "US-MN": "Minnesota", + "US-MS": "Mississippi", + "US-MO": "Missouri", + "US-NE": "Nebraska", + "US-NV": "Nevada", + "US-NJ": "New Jersey", + "US-NM": "New Mexico", + "US-NY": "New York", + "US-NC": "North Carolina", + "US-ND": "North Dakota", + "US-OH": "Ohio", + "US-OK": "Oklahoma", + "US-PA": "Pennsylvania", + "US-PR": "Puerto Rico", + "US-RI": "Rhode Island", + "US-SC": "South Carolina", + "US-SD": "South Dakota", + "US-TN": "Tennessee", + "US-TX": "Texas", + "US-UT": "Utah", + "US-VT": "Vermont", + "US-VA": "Virginia", + "US-WA": "Washington", + "US-DC": "Washington DC", + "US-WV": "West Virginia", + "US-WI": "Wisconsin", + "US-WY": "Wyoming", + }, + }, + VE: { + type: "state", + options: { + "VE-A": "Distrito Capital", + "VE-B": "Anzoátegui", + "VE-C": "Apure", + "VE-D": "Aragua", + "VE-E": "Barinas", + "VE-F": "Bolívar", + "VE-G": "Carabobo", + "VE-H": "Cojedes", + "VE-I": "Falcón", + "VE-J": "Guárico", + "VE-K": "Lara", + "VE-L": "Mérida", + "VE-M": "Miranda", + "VE-N": "Monagas", + "VE-O": "Nueva Esparta", + "VE-P": "Portuguesa", + "VE-R": "Sucre", + "VE-S": "Táchira", + "VE-T": "Trujillo", + "VE-U": "La Guaira", + "VE-V": "Yaracuy", + "VE-W": "Zulia", + "VE-X": "Dependencias Federales", + "VE-Y": "Vargas", + }, + }, +} diff --git a/packages/admin-next/dashboard/src/lib/currencies.ts b/packages/admin-next/dashboard/src/lib/data/currencies.ts similarity index 100% rename from packages/admin-next/dashboard/src/lib/currencies.ts rename to packages/admin-next/dashboard/src/lib/data/currencies.ts diff --git a/packages/admin-next/dashboard/src/lib/discounts.ts b/packages/admin-next/dashboard/src/lib/discounts.ts deleted file mode 100644 index 2fa7856f11..0000000000 --- a/packages/admin-next/dashboard/src/lib/discounts.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Discount } from "@medusajs/medusa" -import { end, parse } from "iso8601-duration" - -export enum PromotionStatus { - SCHEDULED = "SCHEDULED", - EXPIRED = "EXPIRED", - ACTIVE = "ACTIVE", - DISABLED = "DISABLED", -} - -export const getDiscountStatus = (discount: Discount) => { - if (discount.is_disabled) { - return PromotionStatus.DISABLED - } - - const date = new Date() - if (new Date(discount.starts_at) > date) { - return PromotionStatus.SCHEDULED - } - - if ( - (discount.ends_at && new Date(discount.ends_at) < date) || - (discount.valid_duration && - date > - end(parse(discount.valid_duration), new Date(discount.starts_at))) || - discount.usage_count === discount.usage_limit - ) { - return PromotionStatus.EXPIRED - } - - return PromotionStatus.ACTIVE -} diff --git a/packages/admin-next/dashboard/src/lib/money-amount-helpers.ts b/packages/admin-next/dashboard/src/lib/money-amount-helpers.ts index 62ef29b211..128104b454 100644 --- a/packages/admin-next/dashboard/src/lib/money-amount-helpers.ts +++ b/packages/admin-next/dashboard/src/lib/money-amount-helpers.ts @@ -1,4 +1,4 @@ -import { currencies } from "./currencies" +import { currencies } from "./data/currencies" export const getDecimalDigits = (currency: string) => { return currencies[currency.toUpperCase()]?.decimal_digits ?? 0 @@ -15,7 +15,7 @@ export const getDecimalDigits = (currency: string) => { * getFormattedAmount(10, "usd") // '10,00 $' if the browser's locale is fr-FR */ export const getLocaleAmount = (amount: number, currencyCode: string) => { - const formatter = new Intl.NumberFormat(undefined, { + const formatter = new Intl.NumberFormat([], { style: "currency", currencyDisplay: "narrowSymbol", currency: currencyCode, @@ -25,7 +25,7 @@ export const getLocaleAmount = (amount: number, currencyCode: string) => { } export const getNativeSymbol = (currencyCode: string) => { - const formatted = new Intl.NumberFormat(undefined, { + const formatted = new Intl.NumberFormat([], { style: "currency", currency: currencyCode, currencyDisplay: "narrowSymbol", diff --git a/packages/admin-next/dashboard/src/lib/percentage-helpers.ts b/packages/admin-next/dashboard/src/lib/percentage-helpers.ts index 6f7ba54a01..20af15db63 100644 --- a/packages/admin-next/dashboard/src/lib/percentage-helpers.ts +++ b/packages/admin-next/dashboard/src/lib/percentage-helpers.ts @@ -1,4 +1,4 @@ -const formatter = new Intl.NumberFormat(undefined, { +const formatter = new Intl.NumberFormat([], { style: "percent", minimumFractionDigits: 2, }) diff --git a/packages/admin-next/dashboard/src/providers/keybind-provider/hooks.tsx b/packages/admin-next/dashboard/src/providers/keybind-provider/hooks.tsx index c6da202fa1..e5984fbed3 100644 --- a/packages/admin-next/dashboard/src/providers/keybind-provider/hooks.tsx +++ b/packages/admin-next/dashboard/src/providers/keybind-provider/hooks.tsx @@ -220,7 +220,7 @@ export const useGlobalShortcuts = () => { }, label: t("app.keyboardShortcuts.goToTaxRegions"), type: "settingShortcut", - callback: () => navigate("/settings/taxes"), + callback: () => navigate("/settings/tax-regions"), }, { keys: { diff --git a/packages/admin-next/dashboard/src/providers/router-provider/route-map.tsx b/packages/admin-next/dashboard/src/providers/router-provider/route-map.tsx index 295a81bf2c..0dac6873f8 100644 --- a/packages/admin-next/dashboard/src/providers/router-provider/route-map.tsx +++ b/packages/admin-next/dashboard/src/providers/router-provider/route-map.tsx @@ -1,11 +1,9 @@ import { AdminApiKeyResponse, AdminProductCategoryResponse, - AdminTaxRateResponse, AdminTaxRegionResponse, HttpTypes, SalesChannelDTO, - UserDTO, } from "@medusajs/types" import { Outlet, RouteObject } from "react-router-dom" @@ -15,6 +13,12 @@ import { SettingsLayout } from "../../components/layout/settings-layout" import { ErrorBoundary } from "../../components/utilities/error-boundary" import { PriceListRes } from "../../types/api-responses" +import { getCountryByIso2 } from "../../lib/data/countries" +import { + getProvinceByIso2, + isProvinceInCountry, +} from "../../lib/data/country-states" +import { taxRegionLoader } from "../../routes/tax-regions/tax-region-detail/loader" import { RouteExtensions } from "./route-extensions" import { SettingsExtensions } from "./settings-extensions" @@ -1074,60 +1078,116 @@ export const RouteMap: RouteObject[] = [ ], }, { - path: "taxes", + path: "tax-regions", element: , handle: { - crumb: () => "Taxes", + crumb: () => "Tax Regions", }, children: [ { path: "", - lazy: () => import("../../routes/taxes/tax-region-list"), + lazy: () => import("../../routes/tax-regions/tax-region-list"), children: [ { path: "create", - lazy: () => import("../../routes/taxes/tax-region-create"), - children: [], + lazy: () => + import("../../routes/tax-regions/tax-region-create"), }, ], }, { path: ":id", - lazy: () => import("../../routes/taxes/tax-region-detail"), + Component: Outlet, + loader: taxRegionLoader, handle: { crumb: (data: AdminTaxRegionResponse) => { - return data.tax_region.country_code + return ( + getCountryByIso2(data.tax_region.country_code) + ?.display_name || + data.tax_region.country_code?.toUpperCase() + ) }, }, children: [ { - path: "create-default", + path: "", lazy: () => - import("../../routes/taxes/tax-province-create"), - children: [], - }, - { - path: "create-override", - lazy: () => import("../../routes/taxes/tax-rate-create"), - children: [], - }, - { - path: "tax-rates", + import("../../routes/tax-regions/tax-region-detail"), children: [ { - path: ":taxRateId", - children: [ - { - path: "edit", - lazy: () => - import("../../routes/taxes/tax-rate-edit"), - handle: { - crumb: (data: AdminTaxRateResponse) => { - return data.tax_rate.code - }, - }, - }, - ], + path: "provinces/create", + lazy: () => + import( + "../../routes/tax-regions/tax-region-province-create" + ), + }, + { + path: "overrides/create", + lazy: () => + import( + "../../routes/tax-regions/tax-region-tax-override-create" + ), + }, + { + path: "overrides/:tax_rate_id/edit", + lazy: () => + import( + "../../routes/tax-regions/tax-region-tax-override-edit" + ), + }, + { + path: "tax-rates/create", + lazy: () => + import( + "../../routes/tax-regions/tax-region-tax-rate-create" + ), + }, + { + path: "tax-rates/:tax_rate_id/edit", + lazy: () => + import( + "../../routes/tax-regions/tax-region-tax-rate-edit" + ), + }, + ], + }, + { + path: "provinces/:province_id", + lazy: () => + import( + "../../routes/tax-regions/tax-region-province-detail" + ), + handle: { + crumb: (data: AdminTaxRegionResponse) => { + const countryCode = + data.tax_region.country_code?.toUpperCase() + const provinceCode = + data.tax_region.province_code?.toUpperCase() + + const isValid = isProvinceInCountry( + countryCode, + provinceCode + ) + + return isValid + ? getProvinceByIso2(provinceCode) + : provinceCode + }, + }, + children: [ + { + path: "tax-rates/create", + lazy: () => + import( + "../../routes/tax-regions/tax-region-tax-rate-create" + ), + }, + { + path: "tax-rates/:tax_rate_id/edit", + lazy: () => + import( + "../../routes/tax-regions/tax-region-tax-rate-edit" + ), }, ], }, diff --git a/packages/admin-next/dashboard/src/routes/campaigns/campaign-budget-edit/components/edit-campaign-budget-form/edit-campaign-budget-form.tsx b/packages/admin-next/dashboard/src/routes/campaigns/campaign-budget-edit/components/edit-campaign-budget-form/edit-campaign-budget-form.tsx index ae8cf34208..6cd77ca8d5 100644 --- a/packages/admin-next/dashboard/src/routes/campaigns/campaign-budget-edit/components/edit-campaign-budget-form/edit-campaign-budget-form.tsx +++ b/packages/admin-next/dashboard/src/routes/campaigns/campaign-budget-edit/components/edit-campaign-budget-form/edit-campaign-budget-form.tsx @@ -7,7 +7,7 @@ import * as zod from "zod" import { Form } from "../../../../../components/common/form" import { RouteDrawer, useRouteModal } from "../../../../../components/modals" import { useUpdateCampaign } from "../../../../../hooks/api/campaigns" -import { getCurrencySymbol } from "../../../../../lib/currencies" +import { getCurrencySymbol } from "../../../../../lib/data/currencies" type EditCampaignBudgetFormProps = { campaign: CampaignResponse diff --git a/packages/admin-next/dashboard/src/routes/campaigns/campaign-detail/components/campaign-general-section/campaign-general-section.tsx b/packages/admin-next/dashboard/src/routes/campaigns/campaign-detail/components/campaign-general-section/campaign-general-section.tsx index 49f9629463..4dbe49cb6d 100644 --- a/packages/admin-next/dashboard/src/routes/campaigns/campaign-detail/components/campaign-general-section/campaign-general-section.tsx +++ b/packages/admin-next/dashboard/src/routes/campaigns/campaign-detail/components/campaign-general-section/campaign-general-section.tsx @@ -14,7 +14,7 @@ import { useNavigate } from "react-router-dom" import { ActionMenu } from "../../../../../components/common/action-menu" import { formatDate } from "../../../../../components/common/date" import { useDeleteCampaign } from "../../../../../hooks/api/campaigns" -import { currencies } from "../../../../../lib/currencies" +import { currencies } from "../../../../../lib/data/currencies" import { campaignStatus, statusColor, diff --git a/packages/admin-next/dashboard/src/routes/campaigns/common/components/create-campaign-form-fields/create-campaign-form-fields.tsx b/packages/admin-next/dashboard/src/routes/campaigns/common/components/create-campaign-form-fields/create-campaign-form-fields.tsx index 6f0ce38cbc..f630974da3 100644 --- a/packages/admin-next/dashboard/src/routes/campaigns/common/components/create-campaign-form-fields/create-campaign-form-fields.tsx +++ b/packages/admin-next/dashboard/src/routes/campaigns/common/components/create-campaign-form-fields/create-campaign-form-fields.tsx @@ -14,7 +14,7 @@ import { useTranslation } from "react-i18next" import { Form } from "../../../../../components/common/form" import { useStore } from "../../../../../hooks/api/store" -import { currencies, getCurrencySymbol } from "../../../../../lib/currencies" +import { currencies, getCurrencySymbol } from "../../../../../lib/data/currencies" export const CreateCampaignFormFields = ({ form, fieldScope = "" }) => { const { t } = useTranslation() diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/collection-list-table.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/collection-list-table.tsx index 6e19bfec43..225acee56a 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/collection-list-table.tsx +++ b/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/collection-list-table.tsx @@ -2,13 +2,17 @@ import { Button, Container, Heading, Text } from "@medusajs/ui" import { useTranslation } from "react-i18next" import { Link } from "react-router-dom" +import { HttpTypes } from "@medusajs/types" import { keepPreviousData } from "@tanstack/react-query" +import { createColumnHelper } from "@tanstack/react-table" +import { useMemo } from "react" import { DataTable } from "../../../../../components/table/data-table" import { useCollections } from "../../../../../hooks/api/collections" +import { useCollectionTableColumns } from "../../../../../hooks/table/columns/use-collection-table-columns" +import { useCollectionTableFilters } from "../../../../../hooks/table/filters" +import { useCollectionTableQuery } from "../../../../../hooks/table/query" import { useDataTable } from "../../../../../hooks/use-data-table" -import { useCollectionTableColumns } from "./use-collection-table-columns" -import { useCollectionTableFilters } from "./use-collection-table-filters" -import { useCollectionTableQuery } from "./use-collection-table-query" +import { CollectionRowActions } from "./collection-row-actions" const PAGE_SIZE = 20 @@ -26,7 +30,7 @@ export const CollectionListTable = () => { ) const filters = useCollectionTableFilters() - const columns = useCollectionTableColumns() + const columns = useColumns() const { table } = useDataTable({ data: collections ?? [], @@ -71,3 +75,20 @@ export const CollectionListTable = () => { ) } + +const columnHelper = createColumnHelper() + +const useColumns = () => { + const base = useCollectionTableColumns() + + return useMemo( + () => [ + ...base, + columnHelper.display({ + id: "actions", + cell: ({ row }) => , + }), + ], + [base] + ) +} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-filters.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-filters.tsx deleted file mode 100644 index 8dbb2d168c..0000000000 --- a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-filters.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useTranslation } from "react-i18next" -import { Filter } from "../../../../../components/table/data-table" - -export const useCollectionTableFilters = () => { - const { t } = useTranslation() - - let filters: Filter[] = [] - - const dateFilters: Filter[] = [ - { label: t("fields.createdAt"), key: "created_at" }, - { label: t("fields.updatedAt"), key: "updated_at" }, - ].map((f) => ({ - key: f.key, - label: f.label, - type: "date", - })) - - filters = [...filters, ...dateFilters] - - return filters -} diff --git a/packages/admin-next/dashboard/src/routes/locations/common/components/geo-zone-form/geo-zone-form.tsx b/packages/admin-next/dashboard/src/routes/locations/common/components/geo-zone-form/geo-zone-form.tsx index 07653eb628..6aa351a3b8 100644 --- a/packages/admin-next/dashboard/src/routes/locations/common/components/geo-zone-form/geo-zone-form.tsx +++ b/packages/admin-next/dashboard/src/routes/locations/common/components/geo-zone-form/geo-zone-form.tsx @@ -17,7 +17,7 @@ import { useDataTable } from "../../../../../hooks/use-data-table" import { StaticCountry, countries as staticCountries, -} from "../../../../../lib/countries" +} from "../../../../../lib/data/countries" import { useCountries } from "../../../../regions/common/hooks/use-countries" import { useCountryTableColumns } from "../../../../regions/common/hooks/use-country-table-columns" import { useCountryTableQuery } from "../../../../regions/common/hooks/use-country-table-query" diff --git a/packages/admin-next/dashboard/src/routes/locations/location-detail/components/location-general-section/location-general-section.tsx b/packages/admin-next/dashboard/src/routes/locations/location-detail/components/location-general-section/location-general-section.tsx index 0187d9ec49..8ce5d7d13f 100644 --- a/packages/admin-next/dashboard/src/routes/locations/location-detail/components/location-general-section/location-general-section.tsx +++ b/packages/admin-next/dashboard/src/routes/locations/location-detail/components/location-general-section/location-general-section.tsx @@ -41,7 +41,7 @@ import { getFormattedAddress } from "../../../../../lib/addresses" import { StaticCountry, countries as staticCountries, -} from "../../../../../lib/countries" +} from "../../../../../lib/data/countries" import { formatProvider } from "../../../../../lib/format-provider" import { isOptionEnabledInStore, diff --git a/packages/admin-next/dashboard/src/routes/locations/location-service-zone-manage-areas/components/edit-region-areas-form/edit-service-zone-areas-form.tsx b/packages/admin-next/dashboard/src/routes/locations/location-service-zone-manage-areas/components/edit-region-areas-form/edit-service-zone-areas-form.tsx index 37c9b13f52..31e2933cdb 100644 --- a/packages/admin-next/dashboard/src/routes/locations/location-service-zone-manage-areas/components/edit-region-areas-form/edit-service-zone-areas-form.tsx +++ b/packages/admin-next/dashboard/src/routes/locations/location-service-zone-manage-areas/components/edit-region-areas-form/edit-service-zone-areas-form.tsx @@ -12,7 +12,7 @@ import { useRouteModal, } from "../../../../../components/modals" import { useUpdateFulfillmentSetServiceZone } from "../../../../../hooks/api/fulfillment-sets" -import { countries } from "../../../../../lib/countries" +import { countries } from "../../../../../lib/data/countries" import { GeoZoneForm } from "../../../common/components/geo-zone-form" const EditeServiceZoneSchema = z.object({ diff --git a/packages/admin-next/dashboard/src/routes/price-lists/price-list-edit/components/price-list-edit-form/edit-price-list-form.tsx b/packages/admin-next/dashboard/src/routes/price-lists/price-list-edit/components/price-list-edit-form/edit-price-list-form.tsx index d04fe31495..8a56e1fc8f 100644 --- a/packages/admin-next/dashboard/src/routes/price-lists/price-list-edit/components/price-list-edit-form/edit-price-list-form.tsx +++ b/packages/admin-next/dashboard/src/routes/price-lists/price-list-edit/components/price-list-edit-form/edit-price-list-form.tsx @@ -42,7 +42,7 @@ export const PriceListEditForm = ({ priceList }: PriceListEditFormProps) => { resolver: zodResolver(PriceListEditSchema), }) - const { mutateAsync } = useUpdatePriceList(priceList.id) + const { mutateAsync, isPending } = useUpdatePriceList(priceList.id) const handleSubmit = form.handleSubmit(async (values) => { await mutateAsync(values, { @@ -172,7 +172,7 @@ export const PriceListEditForm = ({ priceList }: PriceListEditFormProps) => { {t("actions.cancel")} - diff --git a/packages/admin-next/dashboard/src/routes/product-types/product-type-list/components/product-type-list-table/product-type-list-table.tsx b/packages/admin-next/dashboard/src/routes/product-types/product-type-list/components/product-type-list-table/product-type-list-table.tsx index 462efd2ab5..d0323d0208 100644 --- a/packages/admin-next/dashboard/src/routes/product-types/product-type-list/components/product-type-list-table/product-type-list-table.tsx +++ b/packages/admin-next/dashboard/src/routes/product-types/product-type-list/components/product-type-list-table/product-type-list-table.tsx @@ -1,13 +1,18 @@ +import { HttpTypes } from "@medusajs/types" import { Button, Container, Heading, Text } from "@medusajs/ui" import { keepPreviousData } from "@tanstack/react-query" +import { createColumnHelper } from "@tanstack/react-table" +import { useMemo } from "react" import { useTranslation } from "react-i18next" import { Link } from "react-router-dom" + import { DataTable } from "../../../../../components/table/data-table" import { useProductTypes } from "../../../../../hooks/api/product-types" +import { useProductTypeTableColumns } from "../../../../../hooks/table/columns/use-product-type-table-columns" +import { useProductTypeTableFilters } from "../../../../../hooks/table/filters/use-product-type-table-filters" +import { useProductTypeTableQuery } from "../../../../../hooks/table/query/use-product-type-table-query" import { useDataTable } from "../../../../../hooks/use-data-table" -import { useProductTypeTableColumns } from "./use-product-type-table-columns" -import { useProductTypeTableFilters } from "./use-product-type-table-filters" -import { useProductTypeTableQuery } from "./use-product-type-table-query" +import { ProductTypeRowActions } from "./product-table-row-actions" const PAGE_SIZE = 20 @@ -25,7 +30,7 @@ export const ProductTypeListTable = () => { ) const filters = useProductTypeTableFilters() - const columns = useProductTypeTableColumns() + const columns = useColumns() const { table } = useDataTable({ columns, @@ -68,3 +73,22 @@ export const ProductTypeListTable = () => { ) } + +const columnHelper = createColumnHelper() + +const useColumns = () => { + const base = useProductTypeTableColumns() + + return useMemo( + () => [ + ...base, + columnHelper.display({ + id: "actions", + cell: ({ row }) => { + return + }, + }), + ], + [base] + ) +} diff --git a/packages/admin-next/dashboard/src/routes/promotions/promotion-create/components/create-promotion-form/create-promotion-form.tsx b/packages/admin-next/dashboard/src/routes/promotions/promotion-create/components/create-promotion-form/create-promotion-form.tsx index e221e239e7..9223c7ba1f 100644 --- a/packages/admin-next/dashboard/src/routes/promotions/promotion-create/components/create-promotion-form/create-promotion-form.tsx +++ b/packages/admin-next/dashboard/src/routes/promotions/promotion-create/components/create-promotion-form/create-promotion-form.tsx @@ -26,14 +26,14 @@ import { } from "@medusajs/types" import { Divider } from "../../../../../components/common/divider" import { Form } from "../../../../../components/common/form" -import { PercentageInput } from "../../../../../components/inputs/percentage-input" +import { DeprecatedPercentageInput } from "../../../../../components/inputs/percentage-input" import { RouteFocusModal, useRouteModal, } from "../../../../../components/modals" import { useCampaigns } from "../../../../../hooks/api/campaigns" import { useCreatePromotion } from "../../../../../hooks/api/promotions" -import { getCurrencySymbol } from "../../../../../lib/currencies" +import { getCurrencySymbol } from "../../../../../lib/data/currencies" import { defaultCampaignValues } from "../../../../campaigns/campaign-create/components/create-campaign-form" import { RulesFormField } from "../../../common/edit-rules/components/rules-form-field" import { AddCampaignPromotionFields } from "../../../promotion-add-campaign/components/add-campaign-promotion-form" @@ -661,7 +661,7 @@ export const CreatePromotionForm = () => { disabled={!currencyCode} /> ) : ( - ) : ( - () diff --git a/packages/admin-next/dashboard/src/routes/regions/region-add-countries/components/add-countries-form/add-countries-form.tsx b/packages/admin-next/dashboard/src/routes/regions/region-add-countries/components/add-countries-form/add-countries-form.tsx index 3c8efd8950..94bad4e138 100644 --- a/packages/admin-next/dashboard/src/routes/regions/region-add-countries/components/add-countries-form/add-countries-form.tsx +++ b/packages/admin-next/dashboard/src/routes/regions/region-add-countries/components/add-countries-form/add-countries-form.tsx @@ -18,7 +18,7 @@ import { import { DataTable } from "../../../../../components/table/data-table" import { useUpdateRegion } from "../../../../../hooks/api/regions" import { useDataTable } from "../../../../../hooks/use-data-table" -import { countries as staticCountries } from "../../../../../lib/countries" +import { countries as staticCountries } from "../../../../../lib/data/countries" import { useCountries } from "../../../common/hooks/use-countries" import { useCountryTableColumns } from "../../../common/hooks/use-country-table-columns" import { useCountryTableQuery } from "../../../common/hooks/use-country-table-query" diff --git a/packages/admin-next/dashboard/src/routes/regions/region-create/components/create-region-form/create-region-form.tsx b/packages/admin-next/dashboard/src/routes/regions/region-create/components/create-region-form/create-region-form.tsx index 4863280cf1..c5ece7e20b 100644 --- a/packages/admin-next/dashboard/src/routes/regions/region-create/components/create-region-form/create-region-form.tsx +++ b/packages/admin-next/dashboard/src/routes/regions/region-create/components/create-region-form/create-region-form.tsx @@ -29,8 +29,8 @@ import { import { DataTable } from "../../../../../components/table/data-table" import { useCreateRegion } from "../../../../../hooks/api/regions" import { useDataTable } from "../../../../../hooks/use-data-table" -import { countries as staticCountries } from "../../../../../lib/countries" -import { CurrencyInfo } from "../../../../../lib/currencies" +import { countries as staticCountries } from "../../../../../lib/data/countries" +import { CurrencyInfo } from "../../../../../lib/data/currencies" import { formatProvider } from "../../../../../lib/format-provider" import { useCountries } from "../../../common/hooks/use-countries" import { useCountryTableColumns } from "../../../common/hooks/use-country-table-columns" diff --git a/packages/admin-next/dashboard/src/routes/regions/region-create/region-create.tsx b/packages/admin-next/dashboard/src/routes/regions/region-create/region-create.tsx index 551f9ca500..04616a2ebb 100644 --- a/packages/admin-next/dashboard/src/routes/regions/region-create/region-create.tsx +++ b/packages/admin-next/dashboard/src/routes/regions/region-create/region-create.tsx @@ -1,7 +1,7 @@ import { RouteFocusModal } from "../../../components/modals/route-focus-modal" import { usePaymentProviders } from "../../../hooks/api/payments" import { useStore } from "../../../hooks/api/store" -import { currencies } from "../../../lib/currencies" +import { currencies } from "../../../lib/data/currencies" import { CreateRegionForm } from "./components/create-region-form" export const RegionCreate = () => { diff --git a/packages/admin-next/dashboard/src/routes/regions/region-detail/components/region-general-section/region-general-section.tsx b/packages/admin-next/dashboard/src/routes/regions/region-detail/components/region-general-section/region-general-section.tsx index 12eee57d60..8fc1c1d85e 100644 --- a/packages/admin-next/dashboard/src/routes/regions/region-detail/components/region-general-section/region-general-section.tsx +++ b/packages/admin-next/dashboard/src/routes/regions/region-detail/components/region-general-section/region-general-section.tsx @@ -7,7 +7,7 @@ import { useNavigate } from "react-router-dom" import { ActionMenu } from "../../../../../components/common/action-menu/index.ts" import { ListSummary } from "../../../../../components/common/list-summary/index.ts" import { useDeleteRegion } from "../../../../../hooks/api/regions.tsx" -import { currencies } from "../../../../../lib/currencies.ts" +import { currencies } from "../../../../../lib/data/currencies.ts" import { formatProvider } from "../../../../../lib/format-provider.ts" import { SectionRow } from "../../../../../components/common/section/section-row.tsx" diff --git a/packages/admin-next/dashboard/src/routes/regions/region-edit/components/edit-region-form/edit-region-form.tsx b/packages/admin-next/dashboard/src/routes/regions/region-edit/components/edit-region-form/edit-region-form.tsx index 5c42c1dbe3..c2cdc88625 100644 --- a/packages/admin-next/dashboard/src/routes/regions/region-edit/components/edit-region-form/edit-region-form.tsx +++ b/packages/admin-next/dashboard/src/routes/regions/region-edit/components/edit-region-form/edit-region-form.tsx @@ -11,7 +11,7 @@ import { useRouteModal, } from "../../../../../components/modals/index.ts" import { useUpdateRegion } from "../../../../../hooks/api/regions.tsx" -import { CurrencyInfo } from "../../../../../lib/currencies.ts" +import { CurrencyInfo } from "../../../../../lib/data/currencies.ts" import { formatProvider } from "../../../../../lib/format-provider.ts" type EditRegionFormProps = { diff --git a/packages/admin-next/dashboard/src/routes/regions/region-edit/region-edit.tsx b/packages/admin-next/dashboard/src/routes/regions/region-edit/region-edit.tsx index 96353416a5..70ea76dd7e 100644 --- a/packages/admin-next/dashboard/src/routes/regions/region-edit/region-edit.tsx +++ b/packages/admin-next/dashboard/src/routes/regions/region-edit/region-edit.tsx @@ -6,7 +6,7 @@ import { RouteDrawer } from "../../../components/modals" import { usePaymentProviders } from "../../../hooks/api/payments" import { useRegion } from "../../../hooks/api/regions" import { useStore } from "../../../hooks/api/store" -import { currencies } from "../../../lib/currencies" +import { currencies } from "../../../lib/data/currencies" import { EditRegionForm } from "./components/edit-region-form" import { usePricePreferences } from "../../../hooks/api/price-preferences" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/components/target-form/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/target-form/index.ts new file mode 100644 index 0000000000..d93105fbd4 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/target-form/index.ts @@ -0,0 +1 @@ +export * from "./target-form" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/components/target-form/target-form.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/target-form/target-form.tsx new file mode 100644 index 0000000000..61702c32dd --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/target-form/target-form.tsx @@ -0,0 +1,789 @@ +import { HttpTypes } from "@medusajs/types" +import { Button, Checkbox } from "@medusajs/ui" +import { keepPreviousData } from "@tanstack/react-query" +import { + OnChangeFn, + RowSelectionState, + createColumnHelper, +} from "@tanstack/react-table" +import { useEffect, useMemo, useState } from "react" +import { useTranslation } from "react-i18next" + +import { useSearchParams } from "react-router-dom" +import { + StackedDrawer, + StackedFocusModal, +} from "../../../../../components/modals" +import { DataTable } from "../../../../../components/table/data-table" +import { + useCollections, + useCustomerGroups, + useProductTypes, + useProducts, + useTags, +} from "../../../../../hooks/api" +import { + useCollectionTableColumns, + useCustomerGroupTableColumns, + useProductTableColumns, + useProductTagTableColumns, + useProductTypeTableColumns, +} from "../../../../../hooks/table/columns" +import { + useCollectionTableFilters, + useCustomerGroupTableFilters, + useProductTableFilters, + useProductTagTableFilters, + useProductTypeTableFilters, +} from "../../../../../hooks/table/filters" +import { + useCollectionTableQuery, + useCustomerGroupTableQuery, + useProductTableQuery, + useProductTagTableQuery, + useProductTypeTableQuery, +} from "../../../../../hooks/table/query" +import { useDataTable } from "../../../../../hooks/use-data-table" +import { TaxRateRuleReferenceType } from "../../constants" +import { TaxRateRuleReference } from "../../schemas" + +type TargetFormProps = { + referenceType: TaxRateRuleReferenceType + type: "focus" | "drawer" + state: TaxRateRuleReference[] + setState: (state: TaxRateRuleReference[]) => void +} + +function initRowSelection(state: TaxRateRuleReference[]) { + return state.reduce((acc, reference) => { + acc[reference.value] = true + return acc + }, {} as RowSelectionState) +} + +export const TargetForm = ({ + referenceType, + type, + setState, + state, +}: TargetFormProps) => { + const { t } = useTranslation() + const Component = type === "focus" ? StackedFocusModal : StackedDrawer + + const [intermediate, setIntermediate] = + useState(state) + + const handleSave = () => { + setState(intermediate) + } + + return ( +
    + + + + + + + + + + + ) +} + +type TableProps = { + referenceType: TaxRateRuleReferenceType + initialRowState: RowSelectionState + intermediate: TaxRateRuleReference[] + setIntermediate: (state: TaxRateRuleReference[]) => void +} + +const Table = ({ referenceType, ...props }: TableProps) => { + switch (referenceType) { + case TaxRateRuleReferenceType.CUSTOMER_GROUP: + return + case TaxRateRuleReferenceType.PRODUCT: + return + case TaxRateRuleReferenceType.PRODUCT_COLLECTION: + return + case TaxRateRuleReferenceType.PRODUCT_TYPE: + return + case TaxRateRuleReferenceType.PRODUCT_TAG: + return + default: + return null + } +} + +type TableImplementationProps = { + initialRowState: RowSelectionState + intermediate: TaxRateRuleReference[] + setIntermediate: (state: TaxRateRuleReference[]) => void +} + +const PAGE_SIZE = 50 + +const PREFIX_CUSTOMER_GROUP = "cg" + +const CustomerGroupTable = ({ + initialRowState, + intermediate, + setIntermediate, +}: TableImplementationProps) => { + const [rowSelection, setRowSelection] = + useState(initialRowState) + + useCleanupSearchParams() + + const { searchParams, raw } = useCustomerGroupTableQuery({ + pageSize: PAGE_SIZE, + prefix: PREFIX_CUSTOMER_GROUP, + }) + const { customer_groups, count, isLoading, isError, error } = + useCustomerGroups(searchParams, { + placeholderData: keepPreviousData, + }) + + const updater: OnChangeFn = (value) => { + const state = typeof value === "function" ? value(rowSelection) : value + const currentIds = Object.keys(rowSelection) + + const ids = Object.keys(state) + + const newIds = ids.filter((id) => !currentIds.includes(id)) + const removedIds = currentIds.filter((id) => !ids.includes(id)) + + const newCustomerGroups = + customer_groups + ?.filter((cg) => newIds.includes(cg.id)) + .map((cg) => ({ value: cg.id, label: cg.name! })) || [] + + const filteredIntermediate = intermediate.filter( + (cg) => !removedIds.includes(cg.value) + ) + + setIntermediate([...filteredIntermediate, ...newCustomerGroups]) + setRowSelection(state) + } + + const filters = useCustomerGroupTableFilters() + const columns = useGroupColumns() + + const { table } = useDataTable({ + data: customer_groups || [], + columns, + count, + enablePagination: true, + enableRowSelection: true, + getRowId: (row) => row.id, + rowSelection: { + state: rowSelection, + updater, + }, + pageSize: PAGE_SIZE, + prefix: PREFIX_CUSTOMER_GROUP, + }) + + if (isError) { + throw error + } + + return ( + + ) +} + +const cgColumnHelper = createColumnHelper() + +const useGroupColumns = () => { + const base = useCustomerGroupTableColumns() + + return useMemo( + () => [ + cgColumnHelper.display({ + id: "select", + header: ({ table }) => { + return ( + + table.toggleAllPageRowsSelected(!!value) + } + /> + ) + }, + cell: ({ row }) => { + return ( + row.toggleSelected(!!value)} + onClick={(e) => { + e.stopPropagation() + }} + /> + ) + }, + }), + ...base, + ], + [base] + ) +} + +const PREFIX_PRODUCT = "p" + +const ProductTable = ({ + initialRowState, + intermediate, + setIntermediate, +}: TableImplementationProps) => { + const [rowSelection, setRowSelection] = + useState(initialRowState) + + useCleanupSearchParams() + + const { searchParams, raw } = useProductTableQuery({ + pageSize: PAGE_SIZE, + prefix: PREFIX_PRODUCT, + }) + + const { products, count, isLoading, isError, error } = useProducts( + searchParams, + { + placeholderData: keepPreviousData, + } + ) + + const updater: OnChangeFn = (value) => { + const state = typeof value === "function" ? value(rowSelection) : value + const currentIds = Object.keys(rowSelection) + + const ids = Object.keys(state) + + const newIds = ids.filter((id) => !currentIds.includes(id)) + const removedIds = currentIds.filter((id) => !ids.includes(id)) + + const newProducts = + products + ?.filter((p) => newIds.includes(p.id)) + .map((p) => ({ + value: p.id, + label: p.title!, + })) || [] + + const filteredIntermediate = intermediate.filter( + (p) => !removedIds.includes(p.value) + ) + + setIntermediate([...filteredIntermediate, ...newProducts]) + setRowSelection(state) + } + + const filters = useProductTableFilters() + const columns = useProductColumns() + + const { table } = useDataTable({ + data: products || [], + columns, + count, + enablePagination: true, + enableRowSelection: true, + getRowId: (row) => row.id, + rowSelection: { + state: rowSelection, + updater, + }, + pageSize: PAGE_SIZE, + prefix: PREFIX_PRODUCT, + }) + + if (isError) { + throw error + } + + return ( + + ) +} + +const pColumnHelper = createColumnHelper() + +const useProductColumns = () => { + const base = useProductTableColumns() + + return useMemo( + () => [ + pColumnHelper.display({ + id: "select", + header: ({ table }) => { + return ( + + table.toggleAllPageRowsSelected(!!value) + } + /> + ) + }, + cell: ({ row }) => { + return ( + row.toggleSelected(!!value)} + onClick={(e) => { + e.stopPropagation() + }} + /> + ) + }, + }), + ...base, + ], + [base] + ) +} + +const PREFIX_PRODUCT_COLLECTION = "pc" + +const ProductCollectionTable = ({ + initialRowState, + intermediate, + setIntermediate, +}: TableImplementationProps) => { + const [rowSelection, setRowSelection] = + useState(initialRowState) + + useCleanupSearchParams() + + const { searchParams, raw } = useCollectionTableQuery({ + pageSize: PAGE_SIZE, + prefix: PREFIX_PRODUCT_COLLECTION, + }) + + const { collections, count, isLoading, isError, error } = useCollections( + searchParams, + { + placeholderData: keepPreviousData, + } + ) + + const updater: OnChangeFn = (value) => { + const state = typeof value === "function" ? value(rowSelection) : value + const currentIds = Object.keys(rowSelection) + + const ids = Object.keys(state) + + const newIds = ids.filter((id) => !currentIds.includes(id)) + const removedIds = currentIds.filter((id) => !ids.includes(id)) + + const newCollections = + collections + ?.filter((p) => newIds.includes(p.id)) + .map((p) => ({ + value: p.id, + label: p.title, + })) || [] + + const filteredIntermediate = intermediate.filter( + (p) => !removedIds.includes(p.value) + ) + + setIntermediate([...filteredIntermediate, ...newCollections]) + setRowSelection(state) + } + + const filters = useCollectionTableFilters() + const columns = useCollectionColumns() + + const { table } = useDataTable({ + data: collections || [], + columns, + count, + enablePagination: true, + enableRowSelection: true, + getRowId: (row) => row.id, + rowSelection: { + state: rowSelection, + updater, + }, + pageSize: PAGE_SIZE, + prefix: PREFIX_PRODUCT_COLLECTION, + }) + + if (isError) { + throw error + } + + return ( + + ) +} + +const pcColumnHelper = createColumnHelper() + +const useCollectionColumns = () => { + const base = useCollectionTableColumns() + + return useMemo( + () => [ + pcColumnHelper.display({ + id: "select", + header: ({ table }) => { + return ( + + table.toggleAllPageRowsSelected(!!value) + } + /> + ) + }, + cell: ({ row }) => { + return ( + row.toggleSelected(!!value)} + onClick={(e) => { + e.stopPropagation() + }} + /> + ) + }, + }), + ...base, + ], + [base] + ) +} + +const PREFIX_PRODUCT_TYPE = "pt" + +const ProductTypeTable = ({ + initialRowState, + intermediate, + setIntermediate, +}: TableImplementationProps) => { + const [rowSelection, setRowSelection] = + useState(initialRowState) + + useCleanupSearchParams() + + const { searchParams, raw } = useProductTypeTableQuery({ + pageSize: PAGE_SIZE, + prefix: PREFIX_PRODUCT_TYPE, + }) + + const { product_types, count, isLoading, isError, error } = useProductTypes( + searchParams, + { + placeholderData: keepPreviousData, + } + ) + + const updater: OnChangeFn = (value) => { + const state = typeof value === "function" ? value(rowSelection) : value + const currentIds = Object.keys(rowSelection) + + const ids = Object.keys(state) + + const newIds = ids.filter((id) => !currentIds.includes(id)) + const removedIds = currentIds.filter((id) => !ids.includes(id)) + + const newTypes = + product_types + ?.filter((p) => newIds.includes(p.id)) + .map((p) => ({ + value: p.id, + label: p.value, + })) || [] + + const filteredIntermediate = intermediate.filter( + (p) => !removedIds.includes(p.value) + ) + + setIntermediate([...filteredIntermediate, ...newTypes]) + setRowSelection(state) + } + + const filters = useProductTypeTableFilters() + const columns = useProductTypeColumns() + + const { table } = useDataTable({ + data: product_types || [], + columns, + count, + enablePagination: true, + enableRowSelection: true, + getRowId: (row) => row.id, + rowSelection: { + state: rowSelection, + updater, + }, + pageSize: PAGE_SIZE, + prefix: PREFIX_PRODUCT_TYPE, + }) + + if (isError) { + throw error + } + + return ( + + ) +} + +const ptColumnHelper = createColumnHelper() + +const useProductTypeColumns = () => { + const base = useProductTypeTableColumns() + + return useMemo( + () => [ + ptColumnHelper.display({ + id: "select", + header: ({ table }) => { + return ( + + table.toggleAllPageRowsSelected(!!value) + } + /> + ) + }, + cell: ({ row }) => { + return ( + row.toggleSelected(!!value)} + onClick={(e) => { + e.stopPropagation() + }} + /> + ) + }, + }), + ...base, + ], + [base] + ) +} + +const PREFIX_PRODUCT_TAG = "ptag" + +const ProductTagTable = ({ + initialRowState, + intermediate, + setIntermediate, +}: TableImplementationProps) => { + const [rowSelection, setRowSelection] = + useState(initialRowState) + + useCleanupSearchParams() + + const { searchParams, raw } = useProductTagTableQuery({ + pageSize: PAGE_SIZE, + prefix: PREFIX_PRODUCT_TAG, + }) + + const { product_tags, count, isLoading, isError, error } = useTags( + searchParams, + { + placeholderData: keepPreviousData, + } + ) + + const updater: OnChangeFn = (value) => { + const state = typeof value === "function" ? value(rowSelection) : value + const currentIds = Object.keys(rowSelection) + + const ids = Object.keys(state) + + const newIds = ids.filter((id) => !currentIds.includes(id)) + const removedIds = currentIds.filter((id) => !ids.includes(id)) + + const newTags = + product_tags + ?.filter((p) => newIds.includes(p.id)) + .map((p) => ({ + value: p.id, + label: p.value, + })) || [] + + const filteredIntermediate = intermediate.filter( + (p) => !removedIds.includes(p.value) + ) + + setIntermediate([...filteredIntermediate, ...newTags]) + setRowSelection(state) + } + + const filters = useProductTagTableFilters() + const columns = useProductTagColumns() + + const { table } = useDataTable({ + data: product_tags || [], + columns, + count, + enablePagination: true, + enableRowSelection: true, + getRowId: (row) => row.id, + rowSelection: { + state: rowSelection, + updater, + }, + pageSize: PAGE_SIZE, + prefix: PREFIX_PRODUCT_TAG, + }) + + if (isError) { + throw error + } + + return ( + + ) +} + +const ptagColumnHelper = createColumnHelper() + +const useProductTagColumns = () => { + const base = useProductTagTableColumns() + + return useMemo( + () => [ + ptagColumnHelper.display({ + id: "select", + header: ({ table }) => { + return ( + + table.toggleAllPageRowsSelected(!!value) + } + /> + ) + }, + cell: ({ row }) => { + return ( + row.toggleSelected(!!value)} + onClick={(e) => { + e.stopPropagation() + }} + /> + ) + }, + }), + ...base, + ], + [base] + ) +} + +const useCleanupSearchParams = () => { + const [_, setSearchParams] = useSearchParams() + + useEffect(() => { + return () => { + setSearchParams({}) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/components/target-item/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/target-item/index.ts new file mode 100644 index 0000000000..447d8662d7 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/target-item/index.ts @@ -0,0 +1 @@ +export * from "./target-item" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/components/target-item/target-item.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/target-item/target-item.tsx new file mode 100644 index 0000000000..4f30b0fd4b --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/target-item/target-item.tsx @@ -0,0 +1,26 @@ +import { XMarkMini } from "@medusajs/icons" +import { IconButton, Text } from "@medusajs/ui" + +type TargetItemProps = { + index: number + onRemove: (index: number) => void + label: string +} + +export const TargetItem = ({ index, label, onRemove }: TargetItemProps) => { + return ( +
    + + {label} + + onRemove(index)} + > + + +
    + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-override-card/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-override-card/index.ts new file mode 100644 index 0000000000..1e6f3237c5 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-override-card/index.ts @@ -0,0 +1 @@ +export * from "./tax-override-card" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-override-card/tax-override-card.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-override-card/tax-override-card.tsx new file mode 100644 index 0000000000..5d9a4557a9 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-override-card/tax-override-card.tsx @@ -0,0 +1,378 @@ +import { + ArrowDownRightMini, + PencilSquare, + Trash, + TriangleRightMini, +} from "@medusajs/icons" +import { HttpTypes } from "@medusajs/types" +import { Badge, IconButton, StatusBadge, Text, Tooltip } from "@medusajs/ui" +import * as Collapsible from "@radix-ui/react-collapsible" +import { ComponentPropsWithoutRef } from "react" +import { useTranslation } from "react-i18next" + +import { FetchError } from "@medusajs/js-sdk" +import { ActionMenu } from "../../../../../components/common/action-menu" +import { Divider } from "../../../../../components/common/divider" +import { useCollections } from "../../../../../hooks/api/collections" +import { useCustomerGroups } from "../../../../../hooks/api/customer-groups" +import { useProductTypes } from "../../../../../hooks/api/product-types" +import { useProducts } from "../../../../../hooks/api/products" +import { useTags } from "../../../../../hooks/api/tags" +import { formatPercentage } from "../../../../../lib/percentage-helpers" +import { TaxRateRuleReferenceType } from "../../constants" +import { useDeleteTaxRateAction } from "../../hooks" + +interface TaxOverrideCardProps extends ComponentPropsWithoutRef<"div"> { + taxRate: HttpTypes.AdminTaxRate +} + +export const TaxOverrideCard = ({ taxRate }: TaxOverrideCardProps) => { + const { t } = useTranslation() + const handleDelete = useDeleteTaxRateAction(taxRate) + + if (taxRate.is_default) { + return null + } + + const groupedRules = taxRate.rules.reduce((acc, rule) => { + if (!acc[rule.reference]) { + acc[rule.reference] = [] + } + + acc[rule.reference].push(rule.reference_id) + + return acc + }, {} as Record) + + const validKeys = Object.values(TaxRateRuleReferenceType) + const numberOfTargets = Object.keys(groupedRules).map((key) => + validKeys.includes(key as TaxRateRuleReferenceType) + ).length + + return ( + +
    +
    + + + + + +
    + + {taxRate.name} + + {taxRate.code && ( +
    + + · + + + {taxRate.code} + +
    + )} +
    +
    +
    + + {t("taxRegions.fields.targets.numberOfTargets", { + count: numberOfTargets, + })} + +
    + + {taxRate.is_combinable + ? t("taxRegions.fields.isCombinable.true") + : t("taxRegions.fields.isCombinable.false")} + + , + to: `overrides/${taxRate.id}/edit`, + }, + ], + }, + { + actions: [ + { + label: t("actions.delete"), + icon: , + onClick: handleDelete, + }, + ], + }, + ]} + /> +
    +
    + +
    + +
    +
    +
    + +
    +
    + {formatPercentage(taxRate.rate)} + + {t("taxRegions.fields.targets.operators.on")} + + {Object.entries(groupedRules).map(([reference, ids], index) => { + return ( +
    + + {index < Object.keys(groupedRules).length - 1 && ( + + {t("taxRegions.fields.targets.operators.and")} + + )} +
    + ) + })} +
    +
    +
    +
    +
    + + ) +} + +const Reference = ({ + reference, + ids, +}: { + reference: TaxRateRuleReferenceType + ids: string[] +}) => { + return ( +
    + + +
    + ) +} + +const ReferenceBadge = ({ + reference, +}: { + reference: TaxRateRuleReferenceType +}) => { + const { t } = useTranslation() + let label: string | null = null + + switch (reference) { + case TaxRateRuleReferenceType.PRODUCT: + label = t("taxRegions.fields.targets.tags.product") + break + case TaxRateRuleReferenceType.PRODUCT_COLLECTION: + label = t("taxRegions.fields.targets.tags.productCollection") + break + case TaxRateRuleReferenceType.PRODUCT_TAG: + label = t("taxRegions.fields.targets.tags.productTag") + break + case TaxRateRuleReferenceType.PRODUCT_TYPE: + label = t("taxRegions.fields.targets.tags.productType") + break + case TaxRateRuleReferenceType.CUSTOMER_GROUP: + label = t("taxRegions.fields.targets.tags.customerGroup") + break + } + + if (!label) { + return null + } + + return {label} +} + +const ReferenceValues = ({ + type, + ids, +}: { + type: TaxRateRuleReferenceType + ids: string[] +}) => { + const { t } = useTranslation() + + const { isPending, additional, labels, isError, error } = useReferenceValues( + type, + ids + ) + + if (isError) { + throw error + } + + if (isPending) { + return ( +
    + ) + } + + return ( + + {labels?.map((label: string, index) => ( +
  • {label}
  • + ))} + {additional > 0 && ( +
  • + {t("taxRegions.fields.targets.additionalValues", { + count: additional, + })} +
  • + )} + + } + > + + {t("taxRegions.fields.targets.values", { + count: ids.length, + })} + +
    + ) +} + +const useReferenceValues = ( + type: TaxRateRuleReferenceType, + ids: string[] +): { + labels: string[] | undefined + isPending: boolean + additional: number + isError: boolean + error: FetchError | null +} => { + const products = useProducts( + { + id: ids, + limit: 10, + }, + { + enabled: !!ids.length && type === TaxRateRuleReferenceType.PRODUCT, + } + ) + + const tags = useTags( + { + id: ids, + limit: 10, + }, + { + enabled: !!ids.length && type === TaxRateRuleReferenceType.PRODUCT_TAG, + } + ) + + const productTypes = useProductTypes( + { + id: ids, + limit: 10, + }, + { + enabled: !!ids.length && type === TaxRateRuleReferenceType.PRODUCT_TYPE, + } + ) + + const collections = useCollections( + { + id: ids, + limit: 10, + }, + { + enabled: + !!ids.length && type === TaxRateRuleReferenceType.PRODUCT_COLLECTION, + } + ) + + const customerGroups = useCustomerGroups( + { + id: ids, + limit: 10, + }, + { + enabled: !!ids.length && type === TaxRateRuleReferenceType.CUSTOMER_GROUP, + } + ) + + switch (type) { + case TaxRateRuleReferenceType.PRODUCT: + return { + labels: products.products?.map((product) => product.title), + isPending: products.isPending, + additional: + products.products && products.count + ? products.count - products.products.length + : 0, + isError: products.isError, + error: products.error, + } + case TaxRateRuleReferenceType.PRODUCT_TAG: + return { + labels: tags.product_tags?.map((tag: any) => tag.value), + isPending: tags.isPending, + additional: + tags.product_tags && tags.count + ? tags.count - tags.product_tags.length + : 0, + isError: tags.isError, + error: tags.error, + } + case TaxRateRuleReferenceType.PRODUCT_TYPE: + return { + labels: productTypes.product_types?.map((type) => type.value), + isPending: productTypes.isPending, + additional: + productTypes.product_types && productTypes.count + ? productTypes.count - productTypes.product_types.length + : 0, + isError: productTypes.isError, + error: productTypes.error, + } + case TaxRateRuleReferenceType.PRODUCT_COLLECTION: + return { + labels: collections.collections?.map((collection) => collection.title!), + isPending: collections.isPending, + additional: + collections.collections && collections.count + ? collections.count - collections.collections.length + : 0, + isError: collections.isError, + error: collections.error, + } + case TaxRateRuleReferenceType.CUSTOMER_GROUP: + return { + labels: customerGroups.customer_groups?.map((group) => group.name!), + isPending: customerGroups.isPending, + additional: + customerGroups.customer_groups && customerGroups.count + ? customerGroups.count - customerGroups.customer_groups.length + : 0, + isError: customerGroups.isError, + error: customerGroups.error, + } + } +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-override-table/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-override-table/index.ts new file mode 100644 index 0000000000..0d359a1f34 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-override-table/index.ts @@ -0,0 +1 @@ +export * from "./tax-override-table" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-override-table/tax-override-table.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-override-table/tax-override-table.tsx new file mode 100644 index 0000000000..e8e81182c4 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-override-table/tax-override-table.tsx @@ -0,0 +1,119 @@ +import { HttpTypes } from "@medusajs/types" +import { Button } from "@medusajs/ui" +import { Table } from "@tanstack/react-table" +import { ReactNode } from "react" +import { Link } from "react-router-dom" +import { + NoRecords, + NoResults, +} from "../../../../../components/common/empty-table-content" +import { TableFooterSkeleton } from "../../../../../components/common/skeleton" +import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" +import { DataTableOrderBy } from "../../../../../components/table/data-table/data-table-order-by" +import { DataTableSearch } from "../../../../../components/table/data-table/data-table-search" +import { TaxOverrideCard } from "../tax-override-card" + +type TaxOverrideTableProps = { + isPending: boolean + queryObject: Record + count?: number + table: Table + action: { label: string; to: string } + prefix?: string + children?: ReactNode +} + +export const TaxOverrideTable = ({ + isPending, + action, + count = 0, + table, + queryObject, + prefix, + children, +}: TaxOverrideTableProps) => { + if (isPending) { + return ( +
    + {Array.from({ length: 3 }).map((_, index) => { + return ( +
    + ) + })} + +
    + ) + } + + const noQuery = + Object.values(queryObject).filter((v) => Boolean(v)).length === 0 + const noResults = !isPending && count === 0 && !noQuery + const noRecords = !isPending && count === 0 && noQuery + + const { pageIndex, pageSize } = table.getState().pagination + + return ( +
    +
    +
    {children}
    +
    + {!noRecords && ( +
    +
    + +
    + +
    + )} + + + +
    +
    + {noResults && } + {noRecords && } + {!noRecords && !noResults + ? !isPending + ? table.getRowModel().rows.map((row) => { + return ( + + ) + }) + : Array.from({ length: 3 }).map((_, index) => { + return ( +
    + ) + }) + : null} + {!noRecords && ( + + )} +
    + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-rate-line/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-rate-line/index.ts new file mode 100644 index 0000000000..1035ec66e6 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-rate-line/index.ts @@ -0,0 +1 @@ +export * from "./tax-rate-line" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-rate-line/tax-rate-line.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-rate-line/tax-rate-line.tsx new file mode 100644 index 0000000000..d3a7e9ebd8 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-rate-line/tax-rate-line.tsx @@ -0,0 +1,82 @@ +import { PencilSquare, Trash } from "@medusajs/icons" +import { HttpTypes } from "@medusajs/types" +import { StatusBadge, Text } from "@medusajs/ui" +import { useTranslation } from "react-i18next" +import { ActionMenu } from "../../../../../components/common/action-menu" +import { formatPercentage } from "../../../../../lib/percentage-helpers" +import { useDeleteTaxRateAction } from "../../hooks" + +type TaxRateLineProps = { + taxRate: HttpTypes.AdminTaxRate + isSublevelTaxRate?: boolean +} + +export const TaxRateLine = ({ + taxRate, + isSublevelTaxRate, +}: TaxRateLineProps) => { + const { t } = useTranslation() + + return ( +
    +
    + + {taxRate.name} + + {taxRate.code && ( +
    + + · + + + {taxRate.code} + +
    + )} +
    + + {formatPercentage(taxRate.rate)} + +
    + {isSublevelTaxRate && ( + + {taxRate.is_combinable + ? t("taxRegions.fields.isCombinable.true") + : t("taxRegions.fields.isCombinable.false")} + + )} + +
    +
    + ) +} + +const TaxRateActions = ({ taxRate }: { taxRate: HttpTypes.AdminTaxRate }) => { + const { t } = useTranslation() + const handleDelete = useDeleteTaxRateAction(taxRate) + + return ( + , + to: `tax-rates/${taxRate.id}/edit`, + }, + ], + }, + { + actions: [ + { + label: t("actions.delete"), + icon: , + onClick: handleDelete, + }, + ], + }, + ]} + /> + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-region-card/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-region-card/index.ts new file mode 100644 index 0000000000..fe3cb8c761 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-region-card/index.ts @@ -0,0 +1 @@ +export * from "./tax-region-card" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-region-card/tax-region-card.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-region-card/tax-region-card.tsx new file mode 100644 index 0000000000..b4041e7edf --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-region-card/tax-region-card.tsx @@ -0,0 +1,200 @@ +import { HttpTypes } from "@medusajs/types" +import { Heading, Text, Tooltip, clx } from "@medusajs/ui" +import ReactCountryFlag from "react-country-flag" + +import { ExclamationCircle, MapPin, Plus, Trash } from "@medusajs/icons" +import { ComponentPropsWithoutRef, ReactNode } from "react" +import { useTranslation } from "react-i18next" +import { Link } from "react-router-dom" +import { ActionMenu } from "../../../../../components/common/action-menu" +import { IconAvatar } from "../../../../../components/common/icon-avatar" +import { getCountryByIso2 } from "../../../../../lib/data/countries" +import { + getProvinceByIso2, + isProvinceInCountry, +} from "../../../../../lib/data/country-states" +import { useDeleteTaxRegionAction } from "../../hooks" + +interface TaxRegionCardProps extends ComponentPropsWithoutRef<"div"> { + taxRegion: HttpTypes.AdminTaxRegion + type?: "header" | "list" + variant?: "country" | "province" + asLink?: boolean + badge?: ReactNode +} + +export const TaxRegionCard = ({ + taxRegion, + type = "list", + variant = "country", + asLink = true, + badge, +}: TaxRegionCardProps) => { + const { t } = useTranslation() + const { id, country_code, province_code } = taxRegion + + const country = getCountryByIso2(country_code) + const province = getProvinceByIso2(province_code) + + let name = "N/A" + let misconfiguredSublevelTooltip: string | null = null + + if (province || province_code) { + name = province ? province : province_code!.toUpperCase() + } else if (country || country_code) { + name = country ? country.display_name : country_code!.toUpperCase() + } + + if ( + country_code && + province_code && + !isProvinceInCountry(country_code, province_code) + ) { + name = province_code.toUpperCase() + misconfiguredSublevelTooltip = t( + "taxRegions.fields.sublevels.tooltips.notPartOfCountry", + { + country: country?.display_name, + province: province_code.toUpperCase(), + } + ) + } + + const showCreateDefaultTaxRate = + !taxRegion.tax_rates.filter((tr) => tr.is_default).length && + type === "header" + + const Component = ( +
    +
    +
    + + {country_code && !province_code ? ( +
    + +
    + ) : ( + + )} +
    +
    + {type === "list" ? ( + + {name} + + ) : ( + {name} + )} +
    +
    +
    + {misconfiguredSublevelTooltip && ( + + + + )} + {badge} + +
    +
    + +
    + {misconfiguredSublevelTooltip && ( + + + + )} + {badge} + +
    +
    + ) + + if (asLink) { + return ( + + {Component} + + ) + } + + return Component +} + +const TaxRegionCardActions = ({ + taxRegion, + showCreateDefaultTaxRate, +}: { + taxRegion: HttpTypes.AdminTaxRegion + showCreateDefaultTaxRate?: boolean +}) => { + const { t } = useTranslation() + + const to = taxRegion.parent_id + ? `/settings/tax-regions/${taxRegion.parent_id}` + : undefined + const handleDelete = useDeleteTaxRegionAction({ taxRegion, to }) + + return ( + , + label: t("taxRegions.fields.defaultTaxRate.action"), + to: `tax-rates/create`, + }, + ], + }, + ] + : []), + { + actions: [ + { + icon: , + label: t("actions.delete"), + onClick: handleDelete, + }, + ], + }, + ]} + /> + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-region-table/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-region-table/index.ts new file mode 100644 index 0000000000..3189ac08f8 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-region-table/index.ts @@ -0,0 +1 @@ +export * from "./tax-region-table" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-region-table/tax-region-table.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-region-table/tax-region-table.tsx new file mode 100644 index 0000000000..cf5056ad03 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/components/tax-region-table/tax-region-table.tsx @@ -0,0 +1,122 @@ +import { HttpTypes } from "@medusajs/types" +import { Button } from "@medusajs/ui" +import { Table } from "@tanstack/react-table" +import { ReactNode } from "react" +import { Link } from "react-router-dom" +import { + NoRecords, + NoResults, +} from "../../../../../components/common/empty-table-content" +import { TableFooterSkeleton } from "../../../../../components/common/skeleton" +import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" +import { DataTableOrderBy } from "../../../../../components/table/data-table/data-table-order-by" +import { TaxRegionCard } from "../tax-region-card" + +type TaxRegionTableProps = { + variant?: "country" | "province" + isPending: boolean + queryObject: Record + count?: number + table: Table + action: { label: string; to: string } + prefix?: string + children?: ReactNode +} + +export const TaxRegionTable = ({ + variant = "country", + isPending, + action, + count = 0, + table, + queryObject, + prefix, + children, +}: TaxRegionTableProps) => { + if (isPending) { + return ( +
    + {Array.from({ length: 3 }).map((_, index) => { + return ( +
    + ) + })} + +
    + ) + } + + const noQuery = + Object.values(queryObject).filter((v) => Boolean(v)).length === 0 + const noResults = !isPending && count === 0 && !noQuery + const noRecords = !isPending && count === 0 && noQuery + + const { pageIndex, pageSize } = table.getState().pagination + + return ( +
    +
    +
    {children}
    +
    + {!noRecords && ( +
    + {/* Re-enable when we allow searching tax regions by country name rather than country_code */} + {/*
    + +
    */} + +
    + )} + + + +
    +
    + {noResults && } + {noRecords && } + {!noRecords && !noResults + ? !isPending + ? table.getRowModel().rows.map((row) => { + return ( + + ) + }) + : Array.from({ length: 3 }).map((_, index) => { + return ( +
    + ) + }) + : null} + {!noRecords && ( + + )} +
    + ) +} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/constants.ts b/packages/admin-next/dashboard/src/routes/tax-regions/common/constants.ts similarity index 70% rename from packages/admin-next/dashboard/src/routes/taxes/common/constants.ts rename to packages/admin-next/dashboard/src/routes/tax-regions/common/constants.ts index 2541fdb756..676d2f1479 100644 --- a/packages/admin-next/dashboard/src/routes/taxes/common/constants.ts +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/constants.ts @@ -1,11 +1,7 @@ -export enum ConditionEntities { +export enum TaxRateRuleReferenceType { PRODUCT = "products", - PRODUCT_TYPE = "product_types", PRODUCT_COLLECTION = "product_collections", PRODUCT_TAG = "product_tags", + PRODUCT_TYPE = "product_types", CUSTOMER_GROUP = "customer_groups", } - -export enum Operators { - IN = "in", -} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/hooks.ts b/packages/admin-next/dashboard/src/routes/tax-regions/common/hooks.ts new file mode 100644 index 0000000000..0f519a0904 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/hooks.ts @@ -0,0 +1,87 @@ +import { HttpTypes } from "@medusajs/types" +import { toast, usePrompt } from "@medusajs/ui" +import { useTranslation } from "react-i18next" +import { useNavigate } from "react-router-dom" +import { useDeleteTaxRate } from "../../../hooks/api/tax-rates" +import { useDeleteTaxRegion } from "../../../hooks/api/tax-regions" + +export const useDeleteTaxRegionAction = ({ + taxRegion, + to = "/settings/tax-regions", +}: { + taxRegion: HttpTypes.AdminTaxRegion + to?: string +}) => { + const { t } = useTranslation() + const navigate = useNavigate() + const prompt = usePrompt() + + const { mutateAsync } = useDeleteTaxRegion(taxRegion.id) + + const handleDelete = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("taxRegions.delete.confirmation"), + confirmText: t("actions.delete"), + cancelText: t("actions.cancel"), + }) + + if (!res) { + return + } + + await mutateAsync(undefined, { + onSuccess: () => { + toast.success(t("general.success"), { + description: t("taxRegions.delete.successToast"), + dismissable: true, + dismissLabel: t("actions.close"), + }) + + navigate(to, { replace: true }) + }, + onError: (e) => { + toast.error(t("general.error"), { + description: e.message, + dismissable: true, + dismissLabel: t("actions.close"), + }) + }, + }) + } + + return handleDelete +} + +export const useDeleteTaxRateAction = (taxRate: HttpTypes.AdminTaxRate) => { + const { t } = useTranslation() + const prompt = usePrompt() + + const { mutateAsync } = useDeleteTaxRate(taxRate.id) + + const handleDelete = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("taxRegions.taxRates.delete.confirmation", { + name: taxRate.name, + }), + confirmText: t("actions.delete"), + cancelText: t("actions.cancel"), + }) + + if (!res) { + return + } + + await mutateAsync(undefined, { + onSuccess: () => { + toast.success(t("taxRegions.taxRates.delete.successToast")) + }, + onError: (e) => { + toast.error(e.message) + }, + }) + } + + return handleDelete +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/hooks/use-tax-override-table.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/common/hooks/use-tax-override-table.tsx new file mode 100644 index 0000000000..0a346f72cd --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/hooks/use-tax-override-table.tsx @@ -0,0 +1,92 @@ +import { HttpTypes } from "@medusajs/types" +import { + OnChangeFn, + PaginationState, + getCoreRowModel, + getPaginationRowModel, + useReactTable, +} from "@tanstack/react-table" +import { useEffect, useMemo, useState } from "react" +import { useSearchParams } from "react-router-dom" + +type UseTaxRegionTableProps = { + data?: HttpTypes.AdminTaxRate[] + count?: number + pageSize?: number + prefix?: string +} + +export const useTaxOverrideTable = ({ + data = [], + count = 0, + pageSize: _pageSize = 10, + prefix, +}: UseTaxRegionTableProps) => { + const [searchParams, setSearchParams] = useSearchParams() + const offsetKey = `${prefix ? `${prefix}_` : ""}offset` + const offset = searchParams.get(offsetKey) + + const [{ pageIndex, pageSize }, setPagination] = useState({ + pageIndex: offset ? Math.ceil(Number(offset) / _pageSize) : 0, + pageSize: _pageSize, + }) + const pagination = useMemo( + () => ({ + pageIndex, + pageSize, + }), + [pageIndex, pageSize] + ) + + useEffect(() => { + const index = offset ? Math.ceil(Number(offset) / _pageSize) : 0 + + if (index === pageIndex) { + return + } + + setPagination((prev) => ({ + ...prev, + pageIndex: index, + })) + }, [offset, _pageSize, pageIndex]) + + const onPaginationChange = ( + updater: (old: PaginationState) => PaginationState + ) => { + const state = updater(pagination) + const { pageIndex, pageSize } = state + + setSearchParams((prev) => { + if (!pageIndex) { + prev.delete(offsetKey) + return prev + } + + const newSearch = new URLSearchParams(prev) + newSearch.set(offsetKey, String(pageIndex * pageSize)) + + return newSearch + }) + + setPagination(state) + return state + } + + const table = useReactTable({ + data, + columns: [], // We don't actually want to render any columns + pageCount: Math.ceil(count / pageSize), + state: { + pagination, + }, + getCoreRowModel: getCoreRowModel(), + onPaginationChange: onPaginationChange as OnChangeFn, + getPaginationRowModel: getPaginationRowModel(), + manualPagination: true, + }) + + return { + table, + } +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/hooks/use-tax-region-table.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/common/hooks/use-tax-region-table.tsx new file mode 100644 index 0000000000..12c9827625 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/hooks/use-tax-region-table.tsx @@ -0,0 +1,92 @@ +import { HttpTypes } from "@medusajs/types" +import { + OnChangeFn, + PaginationState, + getCoreRowModel, + getPaginationRowModel, + useReactTable, +} from "@tanstack/react-table" +import { useEffect, useMemo, useState } from "react" +import { useSearchParams } from "react-router-dom" + +type UseTaxRegionTableProps = { + data?: HttpTypes.AdminTaxRegion[] + count?: number + pageSize?: number + prefix?: string +} + +export const useTaxRegionTable = ({ + data = [], + count = 0, + pageSize: _pageSize = 10, + prefix, +}: UseTaxRegionTableProps) => { + const [searchParams, setSearchParams] = useSearchParams() + const offsetKey = `${prefix ? `${prefix}_` : ""}offset` + const offset = searchParams.get(offsetKey) + + const [{ pageIndex, pageSize }, setPagination] = useState({ + pageIndex: offset ? Math.ceil(Number(offset) / _pageSize) : 0, + pageSize: _pageSize, + }) + const pagination = useMemo( + () => ({ + pageIndex, + pageSize, + }), + [pageIndex, pageSize] + ) + + useEffect(() => { + const index = offset ? Math.ceil(Number(offset) / _pageSize) : 0 + + if (index === pageIndex) { + return + } + + setPagination((prev) => ({ + ...prev, + pageIndex: index, + })) + }, [offset, _pageSize, pageIndex]) + + const onPaginationChange = ( + updater: (old: PaginationState) => PaginationState + ) => { + const state = updater(pagination) + const { pageIndex, pageSize } = state + + setSearchParams((prev) => { + if (!pageIndex) { + prev.delete(offsetKey) + return prev + } + + const newSearch = new URLSearchParams(prev) + newSearch.set(offsetKey, String(pageIndex * pageSize)) + + return newSearch + }) + + setPagination(state) + return state + } + + const table = useReactTable({ + data, + columns: [], // We don't actually want to render any columns + pageCount: Math.ceil(count / pageSize), + state: { + pagination, + }, + getCoreRowModel: getCoreRowModel(), + onPaginationChange: onPaginationChange as OnChangeFn, + getPaginationRowModel: getPaginationRowModel(), + manualPagination: true, + }) + + return { + table, + } +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/schemas.ts b/packages/admin-next/dashboard/src/routes/tax-regions/common/schemas.ts new file mode 100644 index 0000000000..b705c70ee1 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/schemas.ts @@ -0,0 +1,16 @@ +import { z } from "zod" +import { TaxRateRuleReferenceType } from "./constants" + +export const TaxRateRuleReferenceSchema = z.object({ + value: z.string(), + label: z.string(), +}) + +export type TaxRateRuleReference = z.infer + +export const TaxRateRuleTargetSchema = z.object({ + reference_type: z.nativeEnum(TaxRateRuleReferenceType), + references: z.array(TaxRateRuleReferenceSchema), +}) + +export type TaxRateRuleTarget = z.infer diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/common/utils.ts b/packages/admin-next/dashboard/src/routes/tax-regions/common/utils.ts new file mode 100644 index 0000000000..7f0535e121 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/common/utils.ts @@ -0,0 +1,12 @@ +import { HttpTypes } from "@medusajs/types" + +import { TaxRateRuleTarget } from "./schemas" + +export const createTaxRulePayload = ( + target: TaxRateRuleTarget +): HttpTypes.AdminCreateTaxRate["rules"] => { + return target.references.map((reference) => ({ + reference: target.reference_type, + reference_id: reference.value, + })) +} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/components/tax-region-create-form/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-create/components/tax-region-create-form/index.ts similarity index 100% rename from packages/admin-next/dashboard/src/routes/taxes/common/components/tax-region-create-form/index.ts rename to packages/admin-next/dashboard/src/routes/tax-regions/tax-region-create/components/tax-region-create-form/index.ts diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-create/components/tax-region-create-form/tax-region-create-form.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-create/components/tax-region-create-form/tax-region-create-form.tsx new file mode 100644 index 0000000000..4d603ea375 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-create/components/tax-region-create-form/tax-region-create-form.tsx @@ -0,0 +1,215 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import { z } from "zod" + +import { InformationCircleSolid } from "@medusajs/icons" +import { Button, Heading, Input, Text, Tooltip, toast } from "@medusajs/ui" +import { useTranslation } from "react-i18next" +import { Form } from "../../../../../components/common/form" +import { CountrySelect } from "../../../../../components/inputs/country-select" +import { PercentageInput } from "../../../../../components/inputs/percentage-input" +import { + RouteFocusModal, + useRouteModal, +} from "../../../../../components/modals" +import { useCreateTaxRegion } from "../../../../../hooks/api" + +type TaxRegionCreateFormProps = { + parentId?: string +} + +const TaxRegionCreateSchema = z.object({ + name: z.string().optional(), + code: z.string().optional(), + rate: z + .object({ + float: z.number().optional(), + value: z.string().optional(), + }) + .optional(), + country_code: z.string().min(1), +}) + +export const TaxRegionCreateForm = ({ parentId }: TaxRegionCreateFormProps) => { + const { t } = useTranslation() + const { handleSuccess } = useRouteModal() + + const form = useForm>({ + defaultValues: { + name: "", + rate: { + value: "", + }, + code: "", + country_code: "", + }, + resolver: zodResolver(TaxRegionCreateSchema), + }) + + const { mutateAsync, isPending } = useCreateTaxRegion() + + const handleSubmit = form.handleSubmit(async (values) => { + const defaultRate = + values.name && values.rate?.float + ? { + name: values.name, + rate: values.rate.float, + code: values.code, + } + : undefined + + await mutateAsync( + { + country_code: values.country_code, + parent_id: parentId, + default_tax_rate: defaultRate, + }, + { + onSuccess: ({ tax_region }) => { + toast.success(t("taxRegions.create.successToast")) + handleSuccess(`../${tax_region.id}`) + }, + onError: (error) => { + toast.error(error.message) + }, + } + ) + }) + + return ( + +
    + + +
    +
    +
    + + {t("salesChannels.createSalesChannel")} + + + {t("salesChannels.createSalesChannelHint")} + +
    +
    +
    + { + return ( + + {t("fields.country")} + + + + + + ) + }} + /> +
    +
    +
    +
    + + {t("taxRegions.fields.defaultTaxRate.label")} + + + ({t("fields.optional")}) + + + + +
    +
    +
    + { + return ( + + {t("fields.name")} + + + + + + ) + }} + /> + { + return ( + + + {t("taxRegions.fields.taxRate")} + + + + onChange({ + value: value, + float: values?.float, + }) + } + /> + + + + ) + }} + /> + { + return ( + + + {t("taxRegions.fields.taxCode")} + + + + + + + ) + }} + /> +
    +
    +
    +
    +
    +
    + +
    + + + + +
    +
    + +
    + ) +} diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-region-create/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-create/index.ts similarity index 100% rename from packages/admin-next/dashboard/src/routes/taxes/tax-region-create/index.ts rename to packages/admin-next/dashboard/src/routes/tax-regions/tax-region-create/index.ts diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-create/tax-region-create.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-create/tax-region-create.tsx new file mode 100644 index 0000000000..680cca3578 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-create/tax-region-create.tsx @@ -0,0 +1,10 @@ +import { RouteFocusModal } from "../../../components/modals" +import { TaxRegionCreateForm } from "./components/tax-region-create-form" + +export const TaxRegionCreate = () => { + return ( + + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/index.ts new file mode 100644 index 0000000000..63c5d1595d --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/index.ts @@ -0,0 +1,2 @@ +export * from "./tax-region-general-detail" +export * from "./tax-region-province-section" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-detail-section/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-detail-section/index.ts new file mode 100644 index 0000000000..69adb253b8 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-detail-section/index.ts @@ -0,0 +1 @@ +export * from "./tax-region-detail-section" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-detail-section/tax-region-detail-section.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-detail-section/tax-region-detail-section.tsx new file mode 100644 index 0000000000..ba9bd6d52a --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-detail-section/tax-region-detail-section.tsx @@ -0,0 +1,41 @@ +import { HttpTypes } from "@medusajs/types" +import { Badge, Container, Tooltip } from "@medusajs/ui" +import { useTranslation } from "react-i18next" + +import { TaxRateLine } from "../../../common/components/tax-rate-line" +import { TaxRegionCard } from "../../../common/components/tax-region-card" + +type TaxRegionDetailSectionProps = { + taxRegion: HttpTypes.AdminTaxRegion +} + +export const TaxRegionDetailSection = ({ + taxRegion, +}: TaxRegionDetailSectionProps) => { + const { t } = useTranslation() + + const defaultRates = taxRegion.tax_rates.filter((r) => r.is_default === true) + const showBage = defaultRates.length === 0 + + return ( + + + + {t("taxRegions.fields.noDefaultRate.label")} + + + ) + } + /> + {defaultRates.map((rate) => { + return + })} + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-override-section/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-override-section/index.ts new file mode 100644 index 0000000000..1198163d0b --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-override-section/index.ts @@ -0,0 +1 @@ +export * from "./tax-region-override-section" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-override-section/tax-region-override-section.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-override-section/tax-region-override-section.tsx new file mode 100644 index 0000000000..7b375886ba --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-override-section/tax-region-override-section.tsx @@ -0,0 +1,66 @@ +import { HttpTypes } from "@medusajs/types" +import { Container, Heading } from "@medusajs/ui" +import { keepPreviousData } from "@tanstack/react-query" +import { useTranslation } from "react-i18next" + +import { useTaxRates } from "../../../../../hooks/api/tax-rates" +import { useTaxRateTableQuery } from "../../../../../hooks/table/query/use-tax-rate-table-query" +import { TaxOverrideTable } from "../../../common/components/tax-override-table" +import { useTaxOverrideTable } from "../../../common/hooks/use-tax-override-table" + +type TaxRegionOverrideSectionProps = { + taxRegion: HttpTypes.AdminTaxRegion +} + +const PAGE_SIZE = 10 +const PREFIX = "o" + +export const TaxRegionOverrideSection = ({ + taxRegion, +}: TaxRegionOverrideSectionProps) => { + const { t } = useTranslation() + + const { searchParams, raw } = useTaxRateTableQuery({ + pageSize: PAGE_SIZE, + prefix: PREFIX, + }) + const { tax_rates, count, isPending, isError, error } = useTaxRates( + { + ...searchParams, + tax_region_id: taxRegion.id, + is_default: false, + }, + { + placeholderData: keepPreviousData, + } + ) + + const { table } = useTaxOverrideTable({ + count, + data: tax_rates, + pageSize: PAGE_SIZE, + prefix: PREFIX, + }) + + if (isError) { + throw error + } + + return ( + + + {t("taxRegions.taxOverrides.header")} + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-province-section/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-province-section/index.ts new file mode 100644 index 0000000000..f82dd83116 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-province-section/index.ts @@ -0,0 +1 @@ +export * from "./tax-region-province-section" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-province-section/tax-region-province-section.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-province-section/tax-region-province-section.tsx new file mode 100644 index 0000000000..2c6fcfcbf0 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-province-section/tax-region-province-section.tsx @@ -0,0 +1,72 @@ +import { HttpTypes } from "@medusajs/types" +import { Container, Heading } from "@medusajs/ui" +import { keepPreviousData } from "@tanstack/react-query" +import { useTranslation } from "react-i18next" +import { useTaxRegions } from "../../../../../hooks/api/tax-regions" +import { useTaxRegionTableQuery } from "../../../../../hooks/table/query/use-tax-region-table-query" +import { getCountryProvinceObjectByIso2 } from "../../../../../lib/data/country-states" +import { TaxRegionTable } from "../../../common/components/tax-region-table" +import { useTaxRegionTable } from "../../../common/hooks/use-tax-region-table" + +const PAGE_SIZE = 10 +const PREFIX = "p" + +type TaxRateListProps = { + taxRegion: HttpTypes.AdminTaxRegion + showSublevelRegions: boolean +} + +export const TaxRegionProvinceSection = ({ + taxRegion, + showSublevelRegions, +}: TaxRateListProps) => { + const { t } = useTranslation() + + const { searchParams, raw } = useTaxRegionTableQuery({ + pageSize: PAGE_SIZE, + prefix: PREFIX, + }) + const { tax_regions, count, isPending, isError, error } = useTaxRegions( + { + ...searchParams, + parent_id: taxRegion.id, + }, + { + placeholderData: keepPreviousData, + } + ) + + const { table } = useTaxRegionTable({ + count, + data: tax_regions, + pageSize: PAGE_SIZE, + prefix: PREFIX, + }) + + const provinceObject = getCountryProvinceObjectByIso2(taxRegion.country_code!) + + if (!provinceObject && !showSublevelRegions && !taxRegion.children.length) { + return null + } + + const type = provinceObject?.type || "sublevel" + + if (isError) { + throw error + } + + return ( + + + {t(`taxRegions.${type}.header`)} + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-sublevel-alert/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-sublevel-alert/index.ts new file mode 100644 index 0000000000..062f7d50d5 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-sublevel-alert/index.ts @@ -0,0 +1 @@ +export * from "./tax-region-sublevel-alert" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-sublevel-alert/tax-region-sublevel-alert.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-sublevel-alert/tax-region-sublevel-alert.tsx new file mode 100644 index 0000000000..7486022c6a --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/components/tax-region-sublevel-alert/tax-region-sublevel-alert.tsx @@ -0,0 +1,62 @@ +import { HttpTypes } from "@medusajs/types" +import { Alert, Button, Text } from "@medusajs/ui" +import { useState } from "react" +import { useTranslation } from "react-i18next" +import { getCountryProvinceObjectByIso2 } from "../../../../../lib/data/country-states" + +type TaxRegionSublevelAlertProps = { + taxRegion: HttpTypes.AdminTaxRegion + showSublevelRegions: boolean + setShowSublevelRegions: (state: boolean) => void +} + +export const TaxRegionSublevelAlert = ({ + taxRegion, + showSublevelRegions, + setShowSublevelRegions, +}: TaxRegionSublevelAlertProps) => { + const { t } = useTranslation() + + const [dismissed, setDismissed] = useState(false) + const provinceObject = getCountryProvinceObjectByIso2(taxRegion.country_code!) + + if ( + provinceObject || + showSublevelRegions || + dismissed || + taxRegion.children.length + ) { + return null + } + + return ( + +
    +
    + +

    {t("taxRegions.fields.sublevels.alert.header")}

    +
    + + {t("taxRegions.fields.sublevels.alert.description")} + +
    +
    + + +
    +
    +
    + ) +} diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/index.ts similarity index 100% rename from packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/index.ts rename to packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/index.ts diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/loader.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/loader.ts similarity index 100% rename from packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/loader.ts rename to packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/loader.ts diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/tax-region-detail.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/tax-region-detail.tsx new file mode 100644 index 0000000000..f89fc04682 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-detail/tax-region-detail.tsx @@ -0,0 +1,61 @@ +import { useLoaderData, useParams } from "react-router-dom" + +import { SingleColumnPage } from "../../../components/layout/pages" +import { useTaxRegion } from "../../../hooks/api/tax-regions" +import { TaxRegionDetailSection } from "./components/tax-region-detail-section" +import { TaxRegionProvinceSection } from "./components/tax-region-province-section" + +import { useState } from "react" +import after from "virtual:medusa/widgets/tax/details/after" +import before from "virtual:medusa/widgets/tax/details/before" +import { TaxRegionOverrideSection } from "./components/tax-region-override-section" +import { TaxRegionSublevelAlert } from "./components/tax-region-sublevel-alert" +import { taxRegionLoader } from "./loader" + +export const TaxRegionDetail = () => { + const { id } = useParams() + const [showSublevelRegions, setShowSublevelRegions] = useState(false) + + const initialData = useLoaderData() as Awaited< + ReturnType + > + + const { + tax_region: taxRegion, + isLoading, + isError, + error, + } = useTaxRegion(id!, undefined, { initialData }) + + if (isLoading || !taxRegion) { + return
    Loading...
    + } + + if (isError) { + throw error + } + + return ( + + + + + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-list/components/tax-region-list-view/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-list/components/tax-region-list-view/index.ts new file mode 100644 index 0000000000..2e242b9077 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-list/components/tax-region-list-view/index.ts @@ -0,0 +1 @@ +export * from "./tax-region-list-view" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-list/components/tax-region-list-view/tax-region-list-view.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-list/components/tax-region-list-view/tax-region-list-view.tsx new file mode 100644 index 0000000000..bf95a1eb02 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-list/components/tax-region-list-view/tax-region-list-view.tsx @@ -0,0 +1,58 @@ +import { Container, Heading, Text } from "@medusajs/ui" +import { keepPreviousData } from "@tanstack/react-query" +import { useTranslation } from "react-i18next" + +import { useTaxRegions } from "../../../../../hooks/api/tax-regions" +import { useTaxRegionTableQuery } from "../../../../../hooks/table/query/use-tax-region-table-query" +import { TaxRegionTable } from "../../../common/components/tax-region-table" +import { useTaxRegionTable } from "../../../common/hooks/use-tax-region-table" + +const PAGE_SIZE = 20 + +export const TaxRegionListView = () => { + const { t } = useTranslation() + + const { searchParams, raw } = useTaxRegionTableQuery({ + pageSize: PAGE_SIZE, + }) + const { tax_regions, count, isPending, isError, error } = useTaxRegions( + { + ...searchParams, + order: "country_code", + parent_id: "null", + }, + { + placeholderData: keepPreviousData, + } + ) + + const { table } = useTaxRegionTable({ + count, + data: tax_regions, + pageSize: PAGE_SIZE, + }) + + if (isError) { + throw error + } + + return ( + + + {t("taxes.domain")} + + {t("taxRegions.list.hint")} + + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-region-list/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-list/index.ts similarity index 100% rename from packages/admin-next/dashboard/src/routes/taxes/tax-region-list/index.ts rename to packages/admin-next/dashboard/src/routes/tax-regions/tax-region-list/index.ts diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-list/tax-region-list.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-list/tax-region-list.tsx new file mode 100644 index 0000000000..9c4aadb5ce --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-list/tax-region-list.tsx @@ -0,0 +1,19 @@ +import { SingleColumnPage } from "../../../components/layout/pages" +import { TaxRegionListView } from "./components/tax-region-list-view" + +import after from "virtual:medusa/widgets/tax/list/after" +import before from "virtual:medusa/widgets/tax/list/before" + +export const TaxRegionsList = () => { + return ( + + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-create/components/tax-region-province-create-form/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-create/components/tax-region-province-create-form/index.ts new file mode 100644 index 0000000000..01d4f8413a --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-create/components/tax-region-province-create-form/index.ts @@ -0,0 +1 @@ +export * from "./tax-region-province-create-form" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-create/components/tax-region-province-create-form/tax-region-province-create-form.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-create/components/tax-region-province-create-form/tax-region-province-create-form.tsx new file mode 100644 index 0000000000..f357d05e3a --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-create/components/tax-region-province-create-form/tax-region-province-create-form.tsx @@ -0,0 +1,247 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { InformationCircleSolid } from "@medusajs/icons" +import { HttpTypes } from "@medusajs/types" +import { Button, Heading, Input, Text, Tooltip, toast } from "@medusajs/ui" +import { useForm } from "react-hook-form" +import { useTranslation } from "react-i18next" +import { z } from "zod" + +import { Form } from "../../../../../components/common/form" +import { SwitchBox } from "../../../../../components/common/switch-box" +import { PercentageInput } from "../../../../../components/inputs/percentage-input" +import { ProvinceSelect } from "../../../../../components/inputs/province-select" +import { + RouteFocusModal, + useRouteModal, +} from "../../../../../components/modals" +import { useCreateTaxRegion } from "../../../../../hooks/api/tax-regions" +import { getCountryProvinceObjectByIso2 } from "../../../../../lib/data/country-states" + +type TaxRegionProvinceCreateFormProps = { + parent: HttpTypes.AdminTaxRegion +} + +const CreateTaxRegionProvinceSchema = z.object({ + province_code: z.string().min(1), + name: z.string().optional(), + code: z.string().optional(), + rate: z + .object({ + float: z.number().optional(), + value: z.string().optional(), + }) + .optional(), + is_combinable: z.boolean().optional(), +}) + +export const TaxRegionProvinceCreateForm = ({ + parent, +}: TaxRegionProvinceCreateFormProps) => { + const { t } = useTranslation() + const { handleSuccess } = useRouteModal() + + const form = useForm>({ + defaultValues: { + province_code: "", + code: "", + is_combinable: false, + name: "", + rate: { + value: "", + }, + }, + resolver: zodResolver(CreateTaxRegionProvinceSchema), + }) + + const { mutateAsync, isPending } = useCreateTaxRegion() + + const handleSubmit = form.handleSubmit(async (values) => { + const defaultRate = + values.name && values.rate?.float + ? { + name: values.name, + rate: values.rate.float, + code: values.code, + is_combinable: values.is_combinable, + } + : undefined + + await mutateAsync( + { + country_code: parent.country_code!, + province_code: values.province_code, + parent_id: parent.id, + default_tax_rate: defaultRate, + }, + { + onSuccess: ({ tax_region }) => { + toast.success(t("taxRegions.create.successToast")) + handleSuccess( + `/settings/tax-regions/${parent.id}/provinces/${tax_region.id}` + ) + }, + onError: (error) => { + toast.error(error.message) + }, + } + ) + }) + + const countryProvinceObject = getCountryProvinceObjectByIso2( + parent.country_code! + ) + + const type = countryProvinceObject?.type || "sublevel" + const label = t(`taxRegions.fields.sublevels.labels.${type}`) + + return ( + +
    + + +
    +
    +
    + {t(`taxRegions.${type}.create.header`)} + + {t(`taxRegions.${type}.create.hint`)} + +
    +
    + { + return ( + + + {label} + + + {countryProvinceObject ? ( + + ) : ( + + )} + + + + ) + }} + /> +
    +
    +
    + + {t("taxRegions.fields.defaultTaxRate.label")} + + + ({t("fields.optional")}) + + + + +
    +
    + { + return ( + + {t("fields.name")} + + + + + + ) + }} + /> + { + return ( + + + {t("taxRegions.fields.taxRate")} + + + + onChange({ + value: value, + float: values?.float, + }) + } + /> + + + + ) + }} + /> + { + return ( + + + {t("taxRegions.fields.taxCode")} + + + + + + + ) + }} + /> +
    +
    + +
    +
    +
    + +
    + + + + +
    +
    + +
    + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-create/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-create/index.ts new file mode 100644 index 0000000000..ef1938b86c --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-create/index.ts @@ -0,0 +1,3 @@ +export * from "./tax-region-province-create" + +export { TaxProvinceCreate as Component } from "./tax-region-province-create" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-create/tax-region-province-create.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-create/tax-region-province-create.tsx new file mode 100644 index 0000000000..25a88a625d --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-create/tax-region-province-create.tsx @@ -0,0 +1,22 @@ +import { useParams } from "react-router-dom" +import { RouteFocusModal } from "../../../components/modals" +import { useTaxRegion } from "../../../hooks/api/tax-regions" +import { TaxRegionProvinceCreateForm } from "./components/tax-region-province-create-form" + +export const TaxProvinceCreate = () => { + const { id } = useParams() + + const { tax_region, isPending, isError, error } = useTaxRegion(id!) + + const ready = !isPending && !!tax_region + + if (isError) { + throw error + } + + return ( + + {ready && } + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/components/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/components/index.ts new file mode 100644 index 0000000000..63c5d1595d --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/components/index.ts @@ -0,0 +1,2 @@ +export * from "./tax-region-general-detail" +export * from "./tax-region-province-section" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/components/tax-region-province-detail-section/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/components/tax-region-province-detail-section/index.ts new file mode 100644 index 0000000000..69adb253b8 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/components/tax-region-province-detail-section/index.ts @@ -0,0 +1 @@ +export * from "./tax-region-detail-section" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/components/tax-region-province-detail-section/tax-region-detail-section.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/components/tax-region-province-detail-section/tax-region-detail-section.tsx new file mode 100644 index 0000000000..84a2eb640c --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/components/tax-region-province-detail-section/tax-region-detail-section.tsx @@ -0,0 +1,41 @@ +import { HttpTypes } from "@medusajs/types" +import { Badge, Container, Tooltip } from "@medusajs/ui" +import { useTranslation } from "react-i18next" + +import { TaxRateLine } from "../../../common/components/tax-rate-line" +import { TaxRegionCard } from "../../../common/components/tax-region-card" + +type TaxRegionProvinceDetailSectionProps = { + taxRegion: HttpTypes.AdminTaxRegion +} + +export const TaxRegionProvinceDetailSection = ({ + taxRegion, +}: TaxRegionProvinceDetailSectionProps) => { + const { t } = useTranslation() + + const defaultRates = taxRegion.tax_rates.filter((r) => r.is_default === true) + const showBage = defaultRates.length === 0 + + return ( + + + + {t("taxRegions.fields.noDefaultRate.label")} + + + ) + } + /> + {defaultRates.map((rate) => { + return + })} + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/components/tax-region-province-override-section/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/components/tax-region-province-override-section/index.ts new file mode 100644 index 0000000000..3e61e14eb4 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/components/tax-region-province-override-section/index.ts @@ -0,0 +1 @@ +export * from "./tax-region-province-override-section" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/components/tax-region-province-override-section/tax-region-province-override-section.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/components/tax-region-province-override-section/tax-region-province-override-section.tsx new file mode 100644 index 0000000000..0f3be26fca --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/components/tax-region-province-override-section/tax-region-province-override-section.tsx @@ -0,0 +1,65 @@ +import { HttpTypes } from "@medusajs/types" +import { Container, Heading } from "@medusajs/ui" +import { keepPreviousData } from "@tanstack/react-query" +import { useTranslation } from "react-i18next" +import { useTaxRates } from "../../../../../hooks/api/tax-rates" +import { useTaxRateTableQuery } from "../../../../../hooks/table/query/use-tax-rate-table-query" +import { TaxOverrideTable } from "../../../common/components/tax-override-table" +import { useTaxOverrideTable } from "../../../common/hooks/use-tax-override-table" + +type TaxRegionProvinceOverrideSectionProps = { + taxRegion: HttpTypes.AdminTaxRegion +} + +const PAGE_SIZE = 10 +const PREFIX = "o" + +export const TaxRegionProvinceOverrideSection = ({ + taxRegion, +}: TaxRegionProvinceOverrideSectionProps) => { + const { t } = useTranslation() + + const { searchParams, raw } = useTaxRateTableQuery({ + pageSize: PAGE_SIZE, + prefix: PREFIX, + }) + const { tax_rates, count, isPending, isError, error } = useTaxRates( + { + ...searchParams, + tax_region_id: taxRegion.id, + is_default: false, + }, + { + placeholderData: keepPreviousData, + } + ) + + const { table } = useTaxOverrideTable({ + count, + data: tax_rates, + pageSize: PAGE_SIZE, + prefix: PREFIX, + }) + + if (isError) { + throw error + } + + return ( + + + {t("taxRegions.taxOverrides.header")} + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/index.ts new file mode 100644 index 0000000000..38f10d7514 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/index.ts @@ -0,0 +1,4 @@ +export * from "./tax-region-detail" + +export { taxRegionLoader as loader } from "./loader" +export { TaxRegionDetail as Component } from "./tax-region-detail" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/loader.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/loader.ts new file mode 100644 index 0000000000..5d8a3155e8 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/loader.ts @@ -0,0 +1,20 @@ +import { AdminTaxRegionResponse } from "@medusajs/types" +import { LoaderFunctionArgs } from "react-router-dom" +import { taxRegionsQueryKeys } from "../../../hooks/api/tax-regions" +import { sdk } from "../../../lib/client" +import { queryClient } from "../../../lib/query-client" + +const taxRegionDetailQuery = (id: string) => ({ + queryKey: taxRegionsQueryKeys.detail(id), + queryFn: async () => sdk.admin.taxRegion.retrieve(id), +}) + +export const taxRegionLoader = async ({ params }: LoaderFunctionArgs) => { + const id = params.province_id + const query = taxRegionDetailQuery(id!) + + return ( + queryClient.getQueryData(query.queryKey) ?? + (await queryClient.fetchQuery(query)) + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/tax-region-detail.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/tax-region-detail.tsx new file mode 100644 index 0000000000..44b14f2a87 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-province-detail/tax-region-detail.tsx @@ -0,0 +1,48 @@ +import { useLoaderData, useParams } from "react-router-dom" + +import { SingleColumnPage } from "../../../components/layout/pages" +import { useTaxRegion } from "../../../hooks/api/tax-regions" +import { TaxRegionProvinceDetailSection } from "./components/tax-region-province-detail-section" + +import after from "virtual:medusa/widgets/tax/details/after" +import before from "virtual:medusa/widgets/tax/details/before" +import { TaxRegionProvinceOverrideSection } from "./components/tax-region-province-override-section" +import { taxRegionLoader } from "./loader" + +export const TaxRegionDetail = () => { + const { province_id } = useParams() + + const initialData = useLoaderData() as Awaited< + ReturnType + > + + const { + tax_region: taxRegion, + isLoading, + isError, + error, + } = useTaxRegion(province_id!, undefined, { initialData }) + + if (isLoading || !taxRegion) { + return
    Loading...
    + } + + if (isError) { + throw error + } + + return ( + + + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-create/components/tax-region-override-create-form/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-create/components/tax-region-override-create-form/index.ts new file mode 100644 index 0000000000..27d428b930 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-create/components/tax-region-override-create-form/index.ts @@ -0,0 +1 @@ +export * from "./tax-region-tax-override-create" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-create/components/tax-region-override-create-form/tax-region-tax-override-create.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-create/components/tax-region-override-create-form/tax-region-tax-override-create.tsx new file mode 100644 index 0000000000..1bd96d9538 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-create/components/tax-region-override-create-form/tax-region-tax-override-create.tsx @@ -0,0 +1,646 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { + Button, + Heading, + Hint, + Input, + Label, + Select, + Text, + clx, + toast, +} from "@medusajs/ui" +import { useFieldArray, useForm, useWatch } from "react-hook-form" +import { z } from "zod" + +import { MagnifyingGlass } from "@medusajs/icons" +import { HttpTypes } from "@medusajs/types" +import { useTranslation } from "react-i18next" +import { Divider } from "../../../../../components/common/divider" +import { Form } from "../../../../../components/common/form" +import { SwitchBox } from "../../../../../components/common/switch-box" +import { PercentageInput } from "../../../../../components/inputs/percentage-input" +import { + RouteFocusModal, + StackedFocusModal, + useRouteModal, + useStackedModal, +} from "../../../../../components/modals" +import { useCreateTaxRate } from "../../../../../hooks/api/tax-rates" +import { TargetForm } from "../../../common/components/target-form/target-form" +import { TargetItem } from "../../../common/components/target-item/target-item" +import { TaxRateRuleReferenceType } from "../../../common/constants" +import { + TaxRateRuleReference, + TaxRateRuleReferenceSchema, +} from "../../../common/schemas" +import { createTaxRulePayload } from "../../../common/utils" + +const TaxRegionCreateTaxOverrideSchema = z.object({ + name: z.string().min(1), + code: z.string().optional(), + rate: z + .object({ + float: z.number().optional(), + value: z.string().optional(), + }) + .optional(), + is_combinable: z.boolean().optional(), + enabled_rules: z.object({ + products: z.boolean(), + product_collections: z.boolean(), + product_tags: z.boolean(), + product_types: z.boolean(), + customer_groups: z.boolean(), + }), + products: z.array(TaxRateRuleReferenceSchema).optional(), + product_collections: z.array(TaxRateRuleReferenceSchema).optional(), + product_tags: z.array(TaxRateRuleReferenceSchema).optional(), + product_types: z.array(TaxRateRuleReferenceSchema).optional(), + customer_groups: z.array(TaxRateRuleReferenceSchema).optional(), +}) + +type TaxRegionCreateTaxOverrideFormProps = { + taxRegion: HttpTypes.AdminTaxRegion +} + +const STACKED_MODAL_ID = "tr" +const getStackedModalId = (type: TaxRateRuleReferenceType) => + `${STACKED_MODAL_ID}-${type}` + +export const TaxRegionCreateTaxOverrideForm = ({ + taxRegion, +}: TaxRegionCreateTaxOverrideFormProps) => { + const { t } = useTranslation() + const { handleSuccess } = useRouteModal() + const { setIsOpen } = useStackedModal() + + const form = useForm>({ + defaultValues: { + name: "", + code: "", + is_combinable: false, + rate: { + value: "", + }, + enabled_rules: { + products: true, + product_collections: false, + product_tags: false, + product_types: false, + customer_groups: false, + }, + products: [], + product_collections: [], + product_tags: [], + product_types: [], + customer_groups: [], + }, + resolver: zodResolver(TaxRegionCreateTaxOverrideSchema), + }) + + const { mutateAsync, isPending } = useCreateTaxRate() + + const handleSubmit = form.handleSubmit(async (values) => { + const { + products, + customer_groups, + product_collections, + product_tags, + product_types, + } = values + + const productRules = createTaxRulePayload({ + reference_type: TaxRateRuleReferenceType.PRODUCT, + references: products || [], + }) + const customerGroupRules = createTaxRulePayload({ + reference_type: TaxRateRuleReferenceType.CUSTOMER_GROUP, + references: customer_groups || [], + }) + const productCollectionRules = createTaxRulePayload({ + reference_type: TaxRateRuleReferenceType.PRODUCT_COLLECTION, + references: product_collections || [], + }) + const productTagRules = createTaxRulePayload({ + reference_type: TaxRateRuleReferenceType.PRODUCT_TAG, + references: product_tags || [], + }) + const productTypeRules = createTaxRulePayload({ + reference_type: TaxRateRuleReferenceType.PRODUCT_TYPE, + references: product_types || [], + }) + + const rules = [ + productRules, + customerGroupRules, + productCollectionRules, + productTagRules, + productTypeRules, + ] + .filter((rule) => Boolean(rule)) + .flatMap((r) => r) as HttpTypes.AdminCreateTaxRate["rules"] + + mutateAsync( + { + name: values.name, + tax_region_id: taxRegion.id, + rate: values.rate?.float, + code: values.code, + is_combinable: values.is_combinable, + rules: rules, + is_default: false, + }, + { + onSuccess: () => { + handleSuccess() + }, + onError: (error) => { + toast.error(error.message) + }, + } + ) + }) + + const products = useFieldArray({ + control: form.control, + name: "products", + }) + + const productCollections = useFieldArray({ + control: form.control, + name: "product_collections", + }) + + const productTags = useFieldArray({ + control: form.control, + name: "product_tags", + }) + + const productTypes = useFieldArray({ + control: form.control, + name: "product_types", + }) + + const customerGroups = useFieldArray({ + control: form.control, + name: "customer_groups", + }) + + const getControls = (type: TaxRateRuleReferenceType) => { + switch (type) { + case TaxRateRuleReferenceType.PRODUCT: + return products + case TaxRateRuleReferenceType.PRODUCT_COLLECTION: + return productCollections + case TaxRateRuleReferenceType.PRODUCT_TAG: + return productTags + case TaxRateRuleReferenceType.PRODUCT_TYPE: + return productTypes + case TaxRateRuleReferenceType.CUSTOMER_GROUP: + return customerGroups + } + } + + const referenceTypeOptions = [ + { + value: TaxRateRuleReferenceType.PRODUCT, + label: t("taxRegions.fields.targets.options.product"), + }, + { + value: TaxRateRuleReferenceType.PRODUCT_COLLECTION, + label: t("taxRegions.fields.targets.options.productCollection"), + }, + { + value: TaxRateRuleReferenceType.PRODUCT_TAG, + label: t("taxRegions.fields.targets.options.productTag"), + }, + { + value: TaxRateRuleReferenceType.PRODUCT_TYPE, + label: t("taxRegions.fields.targets.options.productType"), + }, + { + value: TaxRateRuleReferenceType.CUSTOMER_GROUP, + label: t("taxRegions.fields.targets.options.customerGroup"), + }, + ] + + const searchPlaceholders = { + [TaxRateRuleReferenceType.PRODUCT]: t( + "taxRegions.fields.targets.placeholders.product" + ), + [TaxRateRuleReferenceType.PRODUCT_COLLECTION]: t( + "taxRegions.fields.targets.placeholders.productCollection" + ), + [TaxRateRuleReferenceType.PRODUCT_TAG]: t( + "taxRegions.fields.targets.placeholders.productTag" + ), + [TaxRateRuleReferenceType.PRODUCT_TYPE]: t( + "taxRegions.fields.targets.placeholders.productType" + ), + [TaxRateRuleReferenceType.CUSTOMER_GROUP]: t( + "taxRegions.fields.targets.placeholders.customerGroup" + ), + } + + const getFieldHandler = (type: TaxRateRuleReferenceType) => { + const { fields, remove, append } = getControls(type) + const modalId = getStackedModalId(type) + + return (references: TaxRateRuleReference[]) => { + if (!references.length) { + form.setValue(type, [], { + shouldDirty: true, + }) + setIsOpen(modalId, false) + return + } + + const newIds = references.map((reference) => reference.value) + + const fieldsToAdd = references.filter( + (reference) => !fields.some((field) => field.value === reference.value) + ) + + for (const field of fields) { + if (!newIds.includes(field.value)) { + remove(fields.indexOf(field)) + } + } + + append(fieldsToAdd) + setIsOpen(modalId, false) + } + } + + const displayOrder = new Set([ + TaxRateRuleReferenceType.PRODUCT, + ]) + + const disableRule = (type: TaxRateRuleReferenceType) => { + form.setValue(type, [], { + shouldDirty: true, + }) + form.setValue(`enabled_rules.${type}`, false, { + shouldDirty: true, + }) + + displayOrder.delete(type) + } + + const enableRule = (type: TaxRateRuleReferenceType) => { + form.setValue(`enabled_rules.${type}`, true, { + shouldDirty: true, + }) + form.setValue(type, [], { + shouldDirty: true, + }) + + displayOrder.add(type) + } + + const watchedEnabledRules = useWatch({ + control: form.control, + name: "enabled_rules", + }) + + const addRule = () => { + const firstDisabledRule = Object.keys(watchedEnabledRules).find( + (key) => !watchedEnabledRules[key as TaxRateRuleReferenceType] + ) + + if (firstDisabledRule) { + enableRule(firstDisabledRule as TaxRateRuleReferenceType) + } + } + + const visibleRuleTypes = referenceTypeOptions + .filter((option) => watchedEnabledRules[option.value]) + .sort((a, b) => { + const orderArray = Array.from(displayOrder) + return orderArray.indexOf(b.value) - orderArray.indexOf(a.value) + }) + + const getAvailableRuleTypes = (type: TaxRateRuleReferenceType) => { + return referenceTypeOptions.filter((option) => { + return ( + !visibleRuleTypes.some( + (visibleOption) => visibleOption.value === option.value + ) || option.value === type + ) + }) + } + + const showAddButton = Object.values(watchedEnabledRules).some( + (value) => !value + ) + + return ( + +
    + + +
    +
    +
    + + + {t("taxRegions.taxOverrides.create.header")} + + + + + {t("taxRegions.taxOverrides.create.hint")} + + +
    +
    +
    +
    + { + return ( + + {t("fields.name")} + + + + + + ) + }} + /> + { + return ( + + + {t("taxRegions.fields.taxRate")} + + + + onChange({ + value: value, + float: values?.float, + }) + } + /> + + + + ) + }} + /> + { + return ( + + + {t("taxRegions.fields.taxCode")} + + + + + + + ) + }} + /> +
    +
    +
    + +
    +
    +
    +
    + + + ({t("fields.optional")}) + +
    + + {t("taxRegions.fields.targets.hint")} + +
    + {showAddButton && ( + + )} +
    +
    + {visibleRuleTypes.map((ruleType, index) => { + const type = ruleType.value + const label = ruleType.label + const isLast = index === visibleRuleTypes.length - 1 + const searchPlaceholder = searchPlaceholders[type] + + const options = getAvailableRuleTypes(type) + + const { fields, remove } = getControls(type) + const handler = getFieldHandler(type) + + const modalId = getStackedModalId(type) + + const handleChangeType = ( + value: TaxRateRuleReferenceType + ) => { + disableRule(type) + enableRule(value) + } + + return ( +
    + { + return ( + + + {label} + +
    +
    + {isLast ? ( + + ) : ( +
    + {label} +
    + )} +
    + {t( + "taxRegions.fields.targets.operators.in" + )} +
    +
    +
    + + + + + + + + + + + + {t( + "taxRegions.fields.targets.modal.header" + )} + + + + {t( + "taxRegions.fields.targets.hint" + )} + + + + + + +
    + {fields.length > 0 ? ( +
    + +
    + {fields.map((field, index) => { + return ( + + ) + })} +
    +
    + ) : null} +
    + +
    + ) + }} + /> +
    + ) + })} +
    +
    +
    +
    +
    + +
    + + + + +
    +
    + +
    + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-create/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-create/index.ts new file mode 100644 index 0000000000..6fa8714dc6 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-create/index.ts @@ -0,0 +1 @@ +export { TaxRegionCreateTaxOverride as Component } from "./tax-region-tax-override-create" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-create/tax-region-tax-override-create.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-create/tax-region-tax-override-create.tsx new file mode 100644 index 0000000000..dde788de11 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-create/tax-region-tax-override-create.tsx @@ -0,0 +1,22 @@ +import { useParams } from "react-router-dom" +import { RouteFocusModal } from "../../../components/modals" +import { useTaxRegion } from "../../../hooks/api/tax-regions" +import { TaxRegionCreateTaxOverrideForm } from "./components/tax-region-override-create-form" + +export const TaxRegionCreateTaxOverride = () => { + const { id } = useParams() + + const { tax_region, isPending, isError, error } = useTaxRegion(id!) + + const ready = !isPending && !!tax_region + + if (isError) { + throw error + } + + return ( + + {ready && } + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-edit/components/tax-region-tax-override-edit-form/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-edit/components/tax-region-tax-override-edit-form/index.ts new file mode 100644 index 0000000000..0c41ecb0c9 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-edit/components/tax-region-tax-override-edit-form/index.ts @@ -0,0 +1 @@ +export * from "./tax-region-tax-override-edit-form" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-edit/components/tax-region-tax-override-edit-form/tax-region-tax-override-edit-form.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-edit/components/tax-region-tax-override-edit-form/tax-region-tax-override-edit-form.tsx new file mode 100644 index 0000000000..9e830f137a --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-edit/components/tax-region-tax-override-edit-form/tax-region-tax-override-edit-form.tsx @@ -0,0 +1,604 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { MagnifyingGlass } from "@medusajs/icons" +import { HttpTypes } from "@medusajs/types" +import { + Button, + Heading, + Hint, + Input, + Label, + Select, + Text, + clx, + toast, +} from "@medusajs/ui" +import { useFieldArray, useForm, useWatch } from "react-hook-form" +import { useTranslation } from "react-i18next" +import { z } from "zod" + +import { Divider } from "../../../../../components/common/divider" +import { Form } from "../../../../../components/common/form" +import { SwitchBox } from "../../../../../components/common/switch-box" +import { PercentageInput } from "../../../../../components/inputs/percentage-input" +import { + RouteDrawer, + StackedDrawer, + useRouteModal, + useStackedModal, +} from "../../../../../components/modals" +import { useUpdateTaxRate } from "../../../../../hooks/api/tax-rates" +import { TargetForm } from "../../../common/components/target-form/target-form" +import { TargetItem } from "../../../common/components/target-item/target-item" +import { TaxRateRuleReferenceType } from "../../../common/constants" +import { + TaxRateRuleReference, + TaxRateRuleReferenceSchema, +} from "../../../common/schemas" +import { createTaxRulePayload } from "../../../common/utils" +import { InitialRuleValues } from "../../types" + +type TaxRegionTaxOverrideEditFormProps = { + taxRate: HttpTypes.AdminTaxRate + initialValues: InitialRuleValues + isCombinable?: boolean +} +const STACKED_MODAL_ID = "tr" +const getStackedModalId = (type: TaxRateRuleReferenceType) => + `${STACKED_MODAL_ID}-${type}` + +const TaxRegionTaxRateEditSchema = z.object({ + name: z.string().min(1), + code: z.string().optional(), + rate: z.object({ + float: z.number().optional(), + value: z.string().optional(), + }), + is_combinable: z.boolean().optional(), + enabled_rules: z.object({ + products: z.boolean(), + product_collections: z.boolean(), + product_tags: z.boolean(), + product_types: z.boolean(), + customer_groups: z.boolean(), + }), + products: z.array(TaxRateRuleReferenceSchema).optional(), + product_collections: z.array(TaxRateRuleReferenceSchema).optional(), + product_tags: z.array(TaxRateRuleReferenceSchema).optional(), + product_types: z.array(TaxRateRuleReferenceSchema).optional(), + customer_groups: z.array(TaxRateRuleReferenceSchema).optional(), +}) + +export const TaxRegionTaxOverrideEditForm = ({ + taxRate, + isCombinable = false, + initialValues, +}: TaxRegionTaxOverrideEditFormProps) => { + const { t } = useTranslation() + const { handleSuccess } = useRouteModal() + const { setIsOpen } = useStackedModal() + + const form = useForm>({ + defaultValues: { + name: taxRate.name, + code: taxRate.code || "", + rate: { + value: taxRate.rate?.toString() || "", + }, + is_combinable: taxRate.is_combinable, + enabled_rules: { + products: initialValues.products.length > 0, + product_collections: initialValues.product_collections.length > 0, + product_tags: initialValues.product_tags.length > 0, + product_types: initialValues.product_types.length > 0, + customer_groups: initialValues.customer_groups.length > 0, + }, + products: initialValues.products, + product_collections: initialValues.product_collections, + product_tags: initialValues.product_tags, + product_types: initialValues.product_types, + customer_groups: initialValues.customer_groups, + }, + resolver: zodResolver(TaxRegionTaxRateEditSchema), + }) + + const { mutateAsync, isPending } = useUpdateTaxRate(taxRate.id) + + const handleSubmit = form.handleSubmit(async (values) => { + const { + products, + customer_groups, + product_collections, + product_tags, + product_types, + } = values + + const productRules = createTaxRulePayload({ + reference_type: TaxRateRuleReferenceType.PRODUCT, + references: products || [], + }) + const customerGroupRules = createTaxRulePayload({ + reference_type: TaxRateRuleReferenceType.CUSTOMER_GROUP, + references: customer_groups || [], + }) + const productCollectionRules = createTaxRulePayload({ + reference_type: TaxRateRuleReferenceType.PRODUCT_COLLECTION, + references: product_collections || [], + }) + const productTagRules = createTaxRulePayload({ + reference_type: TaxRateRuleReferenceType.PRODUCT_TAG, + references: product_tags || [], + }) + const productTypeRules = createTaxRulePayload({ + reference_type: TaxRateRuleReferenceType.PRODUCT_TYPE, + references: product_types || [], + }) + + const rules = [ + productRules, + customerGroupRules, + productCollectionRules, + productTagRules, + productTypeRules, + ] + .filter((rule) => Boolean(rule)) + .flatMap((r) => r) as HttpTypes.AdminCreateTaxRate["rules"] + + await mutateAsync( + { + name: values.name, + code: values.code || null, + rate: values.rate?.float, + is_combinable: values.is_combinable, + rules, + }, + { + onSuccess: () => { + toast.success(t("taxRegions.taxRates.edit.successToast")) + handleSuccess() + }, + onError: (error) => { + toast.error(error.message) + }, + } + ) + }) + + const products = useFieldArray({ + control: form.control, + name: "products", + }) + + const productCollections = useFieldArray({ + control: form.control, + name: "product_collections", + }) + + const productTags = useFieldArray({ + control: form.control, + name: "product_tags", + }) + + const productTypes = useFieldArray({ + control: form.control, + name: "product_types", + }) + + const customerGroups = useFieldArray({ + control: form.control, + name: "customer_groups", + }) + + const getControls = (type: TaxRateRuleReferenceType) => { + switch (type) { + case TaxRateRuleReferenceType.PRODUCT: + return products + case TaxRateRuleReferenceType.PRODUCT_COLLECTION: + return productCollections + case TaxRateRuleReferenceType.PRODUCT_TAG: + return productTags + case TaxRateRuleReferenceType.PRODUCT_TYPE: + return productTypes + case TaxRateRuleReferenceType.CUSTOMER_GROUP: + return customerGroups + } + } + + const referenceTypeOptions = [ + { + value: TaxRateRuleReferenceType.PRODUCT, + label: t("taxRegions.fields.targets.options.product"), + }, + { + value: TaxRateRuleReferenceType.PRODUCT_COLLECTION, + label: t("taxRegions.fields.targets.options.productCollection"), + }, + { + value: TaxRateRuleReferenceType.PRODUCT_TAG, + label: t("taxRegions.fields.targets.options.productTag"), + }, + { + value: TaxRateRuleReferenceType.PRODUCT_TYPE, + label: t("taxRegions.fields.targets.options.productType"), + }, + { + value: TaxRateRuleReferenceType.CUSTOMER_GROUP, + label: t("taxRegions.fields.targets.options.customerGroup"), + }, + ] + + const searchPlaceholders = { + [TaxRateRuleReferenceType.PRODUCT]: t( + "taxRegions.fields.targets.placeholders.product" + ), + [TaxRateRuleReferenceType.PRODUCT_COLLECTION]: t( + "taxRegions.fields.targets.placeholders.productCollection" + ), + [TaxRateRuleReferenceType.PRODUCT_TAG]: t( + "taxRegions.fields.targets.placeholders.productTag" + ), + [TaxRateRuleReferenceType.PRODUCT_TYPE]: t( + "taxRegions.fields.targets.placeholders.productType" + ), + [TaxRateRuleReferenceType.CUSTOMER_GROUP]: t( + "taxRegions.fields.targets.placeholders.customerGroup" + ), + } + + const getFieldHandler = (type: TaxRateRuleReferenceType) => { + const { fields, remove, append } = getControls(type) + const modalId = getStackedModalId(type) + + return (references: TaxRateRuleReference[]) => { + if (!references.length) { + form.setValue(type, [], { + shouldDirty: true, + }) + setIsOpen(modalId, false) + return + } + + const newIds = references.map((reference) => reference.value) + + const fieldsToAdd = references.filter( + (reference) => !fields.some((field) => field.value === reference.value) + ) + + for (const field of fields) { + if (!newIds.includes(field.value)) { + remove(fields.indexOf(field)) + } + } + + append(fieldsToAdd) + setIsOpen(modalId, false) + } + } + + const displayOrder = new Set([ + TaxRateRuleReferenceType.PRODUCT, + ]) + + const disableRule = (type: TaxRateRuleReferenceType) => { + form.setValue(type, [], { + shouldDirty: true, + }) + form.setValue(`enabled_rules.${type}`, false, { + shouldDirty: true, + }) + + displayOrder.delete(type) + } + + const enableRule = (type: TaxRateRuleReferenceType) => { + form.setValue(`enabled_rules.${type}`, true, { + shouldDirty: true, + }) + form.setValue(type, [], { + shouldDirty: true, + }) + + displayOrder.add(type) + } + + const watchedEnabledRules = useWatch({ + control: form.control, + name: "enabled_rules", + }) + + const addRule = () => { + const firstDisabledRule = Object.keys(watchedEnabledRules).find( + (key) => !watchedEnabledRules[key as TaxRateRuleReferenceType] + ) + + if (firstDisabledRule) { + enableRule(firstDisabledRule as TaxRateRuleReferenceType) + } + } + + const visibleRuleTypes = referenceTypeOptions + .filter((option) => watchedEnabledRules[option.value]) + .sort((a, b) => { + const orderArray = Array.from(displayOrder) + return orderArray.indexOf(a.value) - orderArray.indexOf(b.value) + }) + + const getAvailableRuleTypes = (type: TaxRateRuleReferenceType) => { + return referenceTypeOptions.filter((option) => { + return ( + !visibleRuleTypes.some( + (visibleOption) => visibleOption.value === option.value + ) || option.value === type + ) + }) + } + + const showAddButton = Object.values(watchedEnabledRules).some( + (value) => !value + ) + + return ( + +
    + +
    + { + return ( + + {t("fields.name")} + + + + + + ) + }} + /> + { + return ( + + {t("taxRegions.fields.taxCode")} + + + + + + ) + }} + /> + { + return ( + + {t("taxRegions.fields.taxRate")} + + + onChange({ + value: value, + float: values?.float, + }) + } + /> + + + + ) + }} + /> +
    + {isCombinable && ( + + )} +
    +
    +
    +
    + + + ({t("fields.optional")}) + +
    + + {t("taxRegions.fields.targets.hint")} + +
    + {showAddButton && ( + + )} +
    +
    + {visibleRuleTypes.map((ruleType, index) => { + const type = ruleType.value + const label = ruleType.label + const isLast = index === visibleRuleTypes.length - 1 + const searchPlaceholder = searchPlaceholders[type] + + const options = getAvailableRuleTypes(type) + const modalId = getStackedModalId(type) + + const { fields, remove } = getControls(type) + const handler = getFieldHandler(type) + + const handleChangeType = (value: TaxRateRuleReferenceType) => { + disableRule(type) + enableRule(value) + } + + return ( +
    + { + return ( + + {label} +
    +
    + {isLast ? ( + + ) : ( +
    + {label} +
    + )} +
    + {t("taxRegions.fields.targets.operators.in")} +
    +
    +
    + + + + + + + + + + + + {t( + "taxRegions.fields.targets.modal.header" + )} + + + + {t("taxRegions.fields.targets.hint")} + + + + + + +
    + {fields.length > 0 ? ( +
    + +
    + {fields.map((field, index) => { + return ( + + ) + })} +
    +
    + ) : null} +
    + +
    + ) + }} + /> +
    + ) + })} +
    +
    +
    + +
    + + + + +
    +
    + +
    + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-edit/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-edit/index.ts new file mode 100644 index 0000000000..16787c2ce4 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-edit/index.ts @@ -0,0 +1 @@ +export { TaxRegionTaxOverrideEdit as Component } from "./tax-region-tax-override-edit" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-edit/tax-region-tax-override-edit.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-edit/tax-region-tax-override-edit.tsx new file mode 100644 index 0000000000..429b7234f6 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-edit/tax-region-tax-override-edit.tsx @@ -0,0 +1,167 @@ +import { HttpTypes } from "@medusajs/types" +import { Heading } from "@medusajs/ui" +import { useTranslation } from "react-i18next" +import { useParams } from "react-router-dom" + +import { RouteDrawer } from "../../../components/modals" +import { useCollections } from "../../../hooks/api/collections" +import { useCustomerGroups } from "../../../hooks/api/customer-groups" +import { useProductTypes } from "../../../hooks/api/product-types" +import { useProducts } from "../../../hooks/api/products" +import { useTags } from "../../../hooks/api/tags" +import { useTaxRate } from "../../../hooks/api/tax-rates" +import { TaxRateRuleReferenceType } from "../common/constants" +import { TaxRegionTaxOverrideEditForm } from "./components/tax-region-tax-override-edit-form" +import { InitialRuleValues } from "./types" + +export const TaxRegionTaxOverrideEdit = () => { + const { t } = useTranslation() + const { tax_rate_id } = useParams() + + const { tax_rate, isPending, isError, error } = useTaxRate(tax_rate_id!) + + const { initialValues, isPending: isInitializing } = + useDefaultRulesValues(tax_rate) + + const ready = !isPending && !!tax_rate && !isInitializing && !!initialValues + + if (isError) { + throw error + } + + return ( + + + + {t("taxRegions.taxOverrides.edit.header")} + + + {t("taxRegions.taxOverrides.edit.hint")} + + + {ready && ( + + )} + + ) +} + +const useDefaultRulesValues = ( + taxRate?: HttpTypes.AdminTaxRate +): { initialValues?: InitialRuleValues; isPending: boolean } => { + const rules = taxRate?.rules || [] + + const idsByReferenceType: { + [key in TaxRateRuleReferenceType]: string[] + } = { + [TaxRateRuleReferenceType.PRODUCT]: [], + [TaxRateRuleReferenceType.PRODUCT_COLLECTION]: [], + [TaxRateRuleReferenceType.PRODUCT_TAG]: [], + [TaxRateRuleReferenceType.PRODUCT_TYPE]: [], + [TaxRateRuleReferenceType.CUSTOMER_GROUP]: [], + } + + rules.forEach((rule) => { + const reference = rule.reference as TaxRateRuleReferenceType + idsByReferenceType[reference]?.push(rule.reference_id) + }) + + const queries = [ + { + ids: idsByReferenceType[TaxRateRuleReferenceType.PRODUCT], + hook: useProducts, + key: TaxRateRuleReferenceType.PRODUCT, + getResult: (result: HttpTypes.AdminProductListResponse) => + result.products.map((product) => ({ + label: product.title, + value: product.id, + })), + }, + { + ids: idsByReferenceType[TaxRateRuleReferenceType.PRODUCT_COLLECTION], + hook: useCollections, + key: TaxRateRuleReferenceType.PRODUCT_COLLECTION, + getResult: (result: HttpTypes.AdminCollectionListResponse) => + result.collections.map((collection) => ({ + label: collection.title!, + value: collection.id!, + })), + }, + { + ids: idsByReferenceType[TaxRateRuleReferenceType.PRODUCT_TAG], + hook: useTags, + key: TaxRateRuleReferenceType.PRODUCT_TAG, + getResult: (result: any) => + result.tags.map((tag: any) => ({ + label: tag.value, + value: tag.id, + })), + }, + { + ids: idsByReferenceType[TaxRateRuleReferenceType.PRODUCT_TYPE], + hook: useProductTypes, + key: TaxRateRuleReferenceType.PRODUCT_TYPE, + getResult: (result: HttpTypes.AdminProductTypeListResponse) => + result.product_types.map((productType) => ({ + label: productType.value, + value: productType.id, + })), + }, + { + ids: idsByReferenceType[TaxRateRuleReferenceType.CUSTOMER_GROUP], + hook: useCustomerGroups, + key: TaxRateRuleReferenceType.CUSTOMER_GROUP, + getResult: ( + result: HttpTypes.PaginatedResponse<{ + customer_groups: HttpTypes.AdminCustomerGroup[] + }> + ) => + result.customer_groups.map((customerGroup) => ({ + label: customerGroup.name!, + value: customerGroup.id, + })), + }, + ] + + const queryResults = queries.map(({ ids, hook }) => { + const enabled = ids.length > 0 + return { + result: hook({ id: ids, limit: ids.length }, { enabled }), + enabled, + } + }) + + if (!taxRate) { + return { isPending: true } + } + + const isPending = queryResults.some( + ({ result, enabled }) => enabled && result.isPending + ) + + if (isPending) { + return { isPending } + } + + queryResults.forEach(({ result, enabled }) => { + if (enabled && result.isError) { + throw result.error + } + }) + + const initialRulesValues: InitialRuleValues = queries.reduce( + (acc, { key, getResult }, index) => ({ + ...acc, + [key]: queryResults[index].enabled + ? getResult(queryResults[index].result) + : [], + }), + {} as InitialRuleValues + ) + + return { initialValues: initialRulesValues, isPending: false } +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-edit/types.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-edit/types.ts new file mode 100644 index 0000000000..43b8c7c573 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-override-edit/types.ts @@ -0,0 +1,10 @@ +import { TaxRateRuleReferenceType } from "../common/constants" +import { TaxRateRuleReference } from "../common/schemas" + +export type InitialRuleValues = { + [TaxRateRuleReferenceType.PRODUCT]: TaxRateRuleReference[] + [TaxRateRuleReferenceType.PRODUCT_COLLECTION]: TaxRateRuleReference[] + [TaxRateRuleReferenceType.PRODUCT_TAG]: TaxRateRuleReference[] + [TaxRateRuleReferenceType.PRODUCT_TYPE]: TaxRateRuleReference[] + [TaxRateRuleReferenceType.CUSTOMER_GROUP]: TaxRateRuleReference[] +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-create/components/tax-region-tax-rate-create-form/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-create/components/tax-region-tax-rate-create-form/index.ts new file mode 100644 index 0000000000..238c4e2d8e --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-create/components/tax-region-tax-rate-create-form/index.ts @@ -0,0 +1 @@ +export * from "./tax-region-tax-rate-create-form" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-create/components/tax-region-tax-rate-create-form/tax-region-tax-rate-create-form.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-create/components/tax-region-tax-rate-create-form/tax-region-tax-rate-create-form.tsx new file mode 100644 index 0000000000..d643fdf422 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-create/components/tax-region-tax-rate-create-form/tax-region-tax-rate-create-form.tsx @@ -0,0 +1,178 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { HttpTypes } from "@medusajs/types" +import { Button, Heading, Input, Text, toast } from "@medusajs/ui" +import { useForm } from "react-hook-form" +import { useTranslation } from "react-i18next" +import { z } from "zod" +import { Form } from "../../../../../components/common/form" +import { SwitchBox } from "../../../../../components/common/switch-box" +import { PercentageInput } from "../../../../../components/inputs/percentage-input" +import { + RouteFocusModal, + useRouteModal, +} from "../../../../../components/modals" +import { useCreateTaxRate } from "../../../../../hooks/api/tax-rates" + +type TaxRegionTaxRateCreateFormProps = { + taxRegion: HttpTypes.AdminTaxRegion + isSublevel?: boolean +} + +const TaxRegionTaxRateCreateSchema = z.object({ + name: z.string().min(1), + code: z.string().optional(), + rate: z + .object({ + float: z.number().optional(), + value: z.string().optional(), + }) + .optional(), + is_combinable: z.boolean().optional(), +}) + +export const TaxRegionTaxRateCreateForm = ({ + taxRegion, + isSublevel = false, +}: TaxRegionTaxRateCreateFormProps) => { + const { t } = useTranslation() + const { handleSuccess } = useRouteModal() + + const form = useForm>({ + defaultValues: { + name: "", + code: "", + rate: { + value: "", + }, + is_combinable: false, + }, + resolver: zodResolver(TaxRegionTaxRateCreateSchema), + }) + + const { mutateAsync, isPending } = useCreateTaxRate() + + const handleSubmit = form.handleSubmit(async (values) => { + await mutateAsync( + { + tax_region_id: taxRegion.id, + is_default: true, + name: values.name, + code: values.code || undefined, + rate: values.rate?.float, + is_combinable: values.is_combinable, + }, + { + onSuccess: () => { + toast.success(t("taxRegions.taxRates.create.successToast")) + handleSuccess() + }, + onError: (error) => { + toast.error(error.message) + }, + } + ) + }) + + return ( + +
    + + +
    +
    +
    + {t(`taxRegions.taxRates.create.header`)} + + {t(`taxRegions.taxRates.create.hint`)} + +
    +
    + { + return ( + + {t("fields.name")} + + + + + + ) + }} + /> + { + return ( + + + {t("taxRegions.fields.taxRate")} + + + + onChange({ + value: value, + float: values?.float, + }) + } + /> + + + + ) + }} + /> + { + return ( + + + {t("taxRegions.fields.taxCode")} + + + + + + + ) + }} + /> +
    + {isSublevel && ( + + )} +
    +
    +
    + +
    + + + + +
    +
    + +
    + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-create/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-create/index.ts new file mode 100644 index 0000000000..3b3b963fa1 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-create/index.ts @@ -0,0 +1 @@ +export { TaxRegionTaxRateCreate as Component } from "./tax-region-tax-rate-create" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-create/tax-region-tax-rate-create.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-create/tax-region-tax-rate-create.tsx new file mode 100644 index 0000000000..447337cc88 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-create/tax-region-tax-rate-create.tsx @@ -0,0 +1,29 @@ +import { useParams } from "react-router-dom" +import { RouteFocusModal } from "../../../components/modals" +import { useTaxRegion } from "../../../hooks/api/tax-regions" +import { TaxRegionTaxRateCreateForm } from "./components/tax-region-tax-rate-create-form" + +export const TaxRegionTaxRateCreate = () => { + const { id, province_id } = useParams() + + const { tax_region, isPending, isError, error } = useTaxRegion( + province_id || id! + ) + + const ready = !isPending && !!tax_region + + if (isError) { + throw error + } + + return ( + + {ready && ( + + )} + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-edit/components/tax-region-tax-rate-edit-form/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-edit/components/tax-region-tax-rate-edit-form/index.ts new file mode 100644 index 0000000000..45a8e940e0 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-edit/components/tax-region-tax-rate-edit-form/index.ts @@ -0,0 +1 @@ +export * from "./tax-region-tax-rate-edit-form" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-edit/components/tax-region-tax-rate-edit-form/tax-region-tax-rate-edit-form.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-edit/components/tax-region-tax-rate-edit-form/tax-region-tax-rate-edit-form.tsx new file mode 100644 index 0000000000..813fdc8611 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-edit/components/tax-region-tax-rate-edit-form/tax-region-tax-rate-edit-form.tsx @@ -0,0 +1,157 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { HttpTypes } from "@medusajs/types" +import { Button, Input, toast } from "@medusajs/ui" +import { useForm } from "react-hook-form" +import { useTranslation } from "react-i18next" +import { z } from "zod" + +import { Form } from "../../../../../components/common/form" +import { SwitchBox } from "../../../../../components/common/switch-box" +import { PercentageInput } from "../../../../../components/inputs/percentage-input" +import { RouteDrawer, useRouteModal } from "../../../../../components/modals" +import { useUpdateTaxRate } from "../../../../../hooks/api/tax-rates" + +type TaxRegionTaxRateEditFormProps = { + taxRate: HttpTypes.AdminTaxRate + isSublevel?: boolean +} + +const TaxRegionTaxRateEditSchema = z.object({ + name: z.string().min(1), + code: z.string().optional(), + rate: z.object({ + float: z.number().optional(), + value: z.string().optional(), + }), + is_combinable: z.boolean().optional(), +}) + +export const TaxRegionTaxRateEditForm = ({ + taxRate, + isSublevel = false, +}: TaxRegionTaxRateEditFormProps) => { + const { t } = useTranslation() + const { handleSuccess } = useRouteModal() + + const form = useForm>({ + defaultValues: { + name: taxRate.name, + code: taxRate.code || "", + rate: { + value: taxRate.rate?.toString() || "", + }, + is_combinable: taxRate.is_combinable, + }, + resolver: zodResolver(TaxRegionTaxRateEditSchema), + }) + + const { mutateAsync, isPending } = useUpdateTaxRate(taxRate.id) + + const handleSubmit = form.handleSubmit(async (values) => { + await mutateAsync( + { + name: values.name, + code: values.code || null, + rate: values.rate?.float, + is_combinable: values.is_combinable, + }, + { + onSuccess: () => { + toast.success(t("taxRegions.taxRates.edit.successToast")) + handleSuccess() + }, + onError: (error) => { + toast.error(error.message) + }, + } + ) + }) + + return ( + +
    + +
    + { + return ( + + {t("fields.name")} + + + + + + ) + }} + /> + { + return ( + + {t("taxRegions.fields.taxCode")} + + + + + + ) + }} + /> + { + return ( + + {t("taxRegions.fields.taxRate")} + + + onChange({ + value: value, + float: values?.float, + }) + } + /> + + + + ) + }} + /> +
    + {isSublevel && ( + + )} +
    + +
    + + + + +
    +
    + +
    + ) +} diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-edit/index.ts b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-edit/index.ts new file mode 100644 index 0000000000..01a9ee8fdb --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-edit/index.ts @@ -0,0 +1 @@ +export { TaxRegionEdit as Component } from "./tax-region-edit" diff --git a/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-edit/tax-region-edit.tsx b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-edit/tax-region-edit.tsx new file mode 100644 index 0000000000..35ca15aade --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/tax-regions/tax-region-tax-rate-edit/tax-region-edit.tsx @@ -0,0 +1,38 @@ +import { Heading } from "@medusajs/ui" +import { useTranslation } from "react-i18next" +import { useParams } from "react-router-dom" +import { RouteDrawer } from "../../../components/modals" +import { useTaxRate } from "../../../hooks/api/tax-rates" +import { TaxRegionTaxRateEditForm } from "./components/tax-region-tax-rate-edit-form" + +export const TaxRegionEdit = () => { + const { t } = useTranslation() + const { province_id, tax_rate_id } = useParams() + + const { tax_rate, isPending, isError, error } = useTaxRate(tax_rate_id!) + + const ready = !isPending && !!tax_rate + + if (isError) { + throw error + } + + return ( + + + + {t("taxRegions.taxRates.edit.header")} + + + {t("taxRegions.taxRates.edit.hint")} + + + {ready && ( + + )} + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/components/conditions-drawer/conditions-drawer.tsx b/packages/admin-next/dashboard/src/routes/taxes/common/components/conditions-drawer/conditions-drawer.tsx deleted file mode 100644 index 926c8311ad..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/common/components/conditions-drawer/conditions-drawer.tsx +++ /dev/null @@ -1,655 +0,0 @@ -import { CustomerGroup } from "@medusajs/medusa" -import { Button } from "@medusajs/ui" -import { OnChangeFn, RowSelectionState } from "@tanstack/react-table" -import { useState } from "react" -import { useTranslation } from "react-i18next" - -import { SplitView } from "../../../../../components/layout/split-view" -import { DataTable } from "../../../../../components/table/data-table" - -import { useDataTable } from "../../../../../hooks/use-data-table" - -import { useProductTableFilters } from "../../../../../hooks/table/filters/use-product-table-filters" -import { useProductTableQuery } from "../../../../../hooks/table/query/use-product-table-query" -import { useProductConditionsTableColumns } from "../../hooks/columns/use-product-conditions-table-columns" - -import { useCustomerGroupTableQuery } from "../../../../../hooks/table/query/use-customer-group-table-query" -import { useCustomerGroupConditionsTableColumns } from "../../hooks/columns/use-customer-group-conditions-table-columns" -import { useCustomerGroupConditionsTableFilters } from "../../hooks/filters/use-customer-group-conditions-table-filters" - -import { useProductTypeConditionsTableColumns } from "../../hooks/columns/use-product-type-conditions-table-columns" -import { useProductTypeConditionsTableFilters } from "../../hooks/filters/use-product-type-conditions-table-filters" -import { useProductTypeConditionsTableQuery } from "../../hooks/query/use-product-type-conditions-table-query" - -import { useProductCollectionConditionsTableColumns } from "../../hooks/columns/use-product-collection-conditions-table-columns" -import { useProductCollectionConditionsTableFilters } from "../../hooks/filters/use-product-collection-conditions-table-filters" -import { useProductCollectionConditionsTableQuery } from "../../hooks/query/use-product-collection-conditions-table-query" - -import { keepPreviousData } from "@tanstack/react-query" -import { useCollections } from "../../../../../hooks/api/collections" -import { useCustomerGroups } from "../../../../../hooks/api/customer-groups" -import { useProductTypes } from "../../../../../hooks/api/product-types" -import { useProducts } from "../../../../../hooks/api/products" -import { ConditionEntities } from "../../constants" -import { ConditionsOption } from "../../types" -import { HttpTypes } from "@medusajs/types" - -const PAGE_SIZE = 50 - -const PRODUCT_PREFIX = "product" -const PRODUCT_TYPE_PREFIX = "product_type" -const PRODUCT_COLLECTION_PREFIX = "product_collection" -const CUSTOMER_GROUP_PREFIX = "customer_group" -const PRODUCT_TAG_PREFIX = "customer_group" - -type ConditionsProps = { - selected: ConditionsOption[] - onSave: (options: ConditionsOption[]) => void -} - -const initRowState = (selected: ConditionsOption[] = []): RowSelectionState => { - return selected.reduce((acc, { value }) => { - acc[value] = true - return acc - }, {} as RowSelectionState) -} - -const ConditionsFooter = ({ onSave }: { onSave: () => void }) => { - const { t } = useTranslation() - - return ( -
    - - - - - -
    - ) -} - -const ProductConditionsTable = ({ selected = [], onSave }: ConditionsProps) => { - const [rowSelection, setRowSelection] = useState( - initRowState(selected) - ) - - const [intermediate, setIntermediate] = useState(selected) - - const { searchParams, raw } = useProductTableQuery({ - pageSize: PAGE_SIZE, - prefix: PRODUCT_PREFIX, - }) - const { products, count, isLoading, isError, error } = useProducts( - { - ...searchParams, - }, - { - placeholderData: keepPreviousData, - } - ) - - const updater: OnChangeFn = (fn) => { - const newState: RowSelectionState = - typeof fn === "function" ? fn(rowSelection) : fn - - const added = Object.keys(newState).filter( - (k) => newState[k] !== rowSelection[k] - ) - - if (added.length) { - const addedProducts = (products?.filter((p) => added.includes(p.id!)) ?? - []) as HttpTypes.AdminProduct[] - - if (addedProducts.length > 0) { - const newConditions = addedProducts.map((p) => ({ - label: p.title, - value: p.id!, - })) - - setIntermediate((prev) => { - const filteredPrev = prev.filter((p) => p.value in newState) - return Array.from(new Set([...filteredPrev, ...newConditions])) - }) - } - - setRowSelection(newState) - } - - const removed = Object.keys(rowSelection).filter( - (k) => newState[k] !== rowSelection[k] - ) - - if (removed.length) { - setIntermediate((prev) => { - return prev.filter((p) => !removed.includes(p.value)) - }) - - setRowSelection(newState) - } - } - - const handleSave = () => { - onSave(intermediate) - } - - const columns = useProductConditionsTableColumns() - const filters = useProductTableFilters() - - const { table } = useDataTable({ - data: products ?? [], - columns: columns, - count, - enablePagination: true, - getRowId: (row) => row.id, - pageSize: PAGE_SIZE, - enableRowSelection: true, - rowSelection: { - state: rowSelection, - updater, - }, - prefix: PRODUCT_PREFIX, - }) - - if (isError) { - throw error - } - - return ( -
    - - -
    - ) -} - -const CustomerGroupConditionsTable = ({ - selected = [], - onSave, -}: ConditionsProps) => { - const [rowSelection, setRowSelection] = useState( - initRowState(selected) - ) - - const [intermediate, setIntermediate] = useState(selected) - - const { searchParams, raw } = useCustomerGroupTableQuery({ - pageSize: PAGE_SIZE, - prefix: PRODUCT_PREFIX, - }) - const { customer_groups, count, isLoading, isError, error } = - useCustomerGroups( - { - ...searchParams, - }, - { - placeholderData: keepPreviousData, - } - ) - - const updater: OnChangeFn = (fn) => { - const newState: RowSelectionState = - typeof fn === "function" ? fn(rowSelection) : fn - - const added = Object.keys(newState).filter( - (k) => newState[k] !== rowSelection[k] - ) - - if (added.length) { - const addedGroups = - customer_groups?.filter((p) => added.includes(p.id!)) ?? [] - - if (addedGroups.length > 0) { - const newConditions = addedGroups.map((p) => ({ - label: p.name, - value: p.id!, - })) - - setIntermediate((prev) => { - const filteredPrev = prev.filter((p) => p.value in newState) - return Array.from(new Set([...filteredPrev, ...newConditions])) - }) - } - - setRowSelection(newState) - } - - const removed = Object.keys(rowSelection).filter( - (k) => newState[k] !== rowSelection[k] - ) - - if (removed.length) { - setIntermediate((prev) => { - return prev.filter((p) => !removed.includes(p.value)) - }) - - setRowSelection(newState) - } - } - - const handleSave = () => { - onSave(intermediate) - } - - const columns = useCustomerGroupConditionsTableColumns() - const filters = useCustomerGroupConditionsTableFilters() - - const { table } = useDataTable({ - data: (customer_groups ?? []) as CustomerGroup[], - columns: columns, - count, - enablePagination: true, - getRowId: (row) => row.id, - pageSize: PAGE_SIZE, - enableRowSelection: true, - rowSelection: { - state: rowSelection, - updater, - }, - prefix: CUSTOMER_GROUP_PREFIX, - }) - - if (isError) { - throw error - } - - return ( -
    - - -
    - ) -} - -const ProductTypeConditionsTable = ({ - onSave, - selected = [], -}: ConditionsProps) => { - const [rowSelection, setRowSelection] = useState( - initRowState(selected) - ) - const [intermediate, setIntermediate] = useState(selected) - - const { searchParams, raw } = useProductTypeConditionsTableQuery({ - pageSize: PAGE_SIZE, - prefix: PRODUCT_TYPE_PREFIX, - }) - const { product_types, count, isLoading, isError, error } = useProductTypes( - { - ...searchParams, - }, - { - placeholderData: keepPreviousData, - } - ) - - const updater: OnChangeFn = (fn) => { - const newState: RowSelectionState = - typeof fn === "function" ? fn(rowSelection) : fn - - const added = Object.keys(newState).filter( - (k) => newState[k] !== rowSelection[k] - ) - - if (added.length) { - const addedTypes = (product_types?.filter((p) => added.includes(p.id!)) ?? - []) as HttpTypes.AdminProductType[] - - if (addedTypes.length > 0) { - const newConditions = addedTypes.map((p) => ({ - label: p.value, - value: p.id!, - })) - - setIntermediate((prev) => { - const filteredPrev = prev.filter((p) => p.value in newState) - return Array.from(new Set([...filteredPrev, ...newConditions])) - }) - } - - setRowSelection(newState) - } - const removed = Object.keys(rowSelection).filter( - (k) => newState[k] !== rowSelection[k] - ) - - if (removed.length) { - setIntermediate((prev) => { - return prev.filter((p) => !removed.includes(p.value)) - }) - - setRowSelection(newState) - } - } - - const handleSave = () => { - onSave(intermediate) - } - - const columns = useProductTypeConditionsTableColumns() - const filters = useProductTypeConditionsTableFilters() - - const { table } = useDataTable({ - data: product_types ?? [], - columns: columns, - count, - enablePagination: true, - getRowId: (row) => row.id, - pageSize: PAGE_SIZE, - enableRowSelection: true, - rowSelection: { - state: rowSelection, - updater, - }, - prefix: PRODUCT_TYPE_PREFIX, - }) - - if (isError) { - throw error - } - - return ( -
    - - -
    - ) -} - -const ProductCollectionConditionsTable = ({ - onSave, - selected = [], -}: ConditionsProps) => { - const [rowSelection, setRowSelection] = useState( - initRowState(selected) - ) - const [intermediate, setIntermediate] = useState(selected) - - const { searchParams, raw } = useProductCollectionConditionsTableQuery({ - pageSize: PAGE_SIZE, - prefix: PRODUCT_COLLECTION_PREFIX, - }) - const { collections, count, isPending, isError, error } = useCollections( - { - ...searchParams, - }, - { - placeholderData: keepPreviousData, - } - ) - - const updater: OnChangeFn = (fn) => { - const newState: RowSelectionState = - typeof fn === "function" ? fn(rowSelection) : fn - - const added = Object.keys(newState).filter( - (k) => newState[k] !== rowSelection[k] - ) - - if (added.length) { - const addedCollections = (collections?.filter((p) => - added.includes(p.id!) - ) ?? []) as HttpTypes.AdminCollection[] - - if (addedCollections.length > 0) { - const newConditions = addedCollections.map((p) => ({ - label: p.title, - value: p.id!, - })) - - setIntermediate((prev) => { - const filteredPrev = prev.filter((p) => p.value in newState) - return Array.from(new Set([...filteredPrev, ...newConditions])) - }) - } - - setRowSelection(newState) - } - - const removed = Object.keys(rowSelection).filter( - (k) => newState[k] !== rowSelection[k] - ) - - if (removed.length) { - setIntermediate((prev) => { - return prev.filter((p) => !removed.includes(p.value)) - }) - - setRowSelection(newState) - } - } - - const handleSave = () => { - onSave(intermediate) - } - - const columns = useProductCollectionConditionsTableColumns() - const filters = useProductCollectionConditionsTableFilters() - - const { table } = useDataTable({ - data: collections ?? [], - columns: columns, - count, - enablePagination: true, - getRowId: (row) => row.id, - pageSize: PAGE_SIZE, - enableRowSelection: true, - rowSelection: { - state: rowSelection, - updater, - }, - prefix: PRODUCT_COLLECTION_PREFIX, - }) - - if (isError) { - throw error - } - - return ( -
    - - -
    - ) -} - -// const ProductTagConditionsTable = ({ -// onSave, -// selected = [], -// }: ConditionsProps) => { -// const [rowSelection, setRowSelection] = useState( -// initRowState(selected) -// ) -// const [intermediate, setIntermediate] = useState(selected) - -// const { searchParams, raw } = useProductTagConditionsTableQuery({ -// pageSize: PAGE_SIZE, -// prefix: PRODUCT_TAG_PREFIX, -// }) - -// // TODO: replace this with useProductTags when its available -// const { product_tags, count, isLoading, isError, error } = -// useAdminProductTags( -// { -// ...searchParams, -// }, -// { -// keepPreviousData: true, -// } -// ) - -// const updater: OnChangeFn = (fn) => { -// const newState: RowSelectionState = -// typeof fn === "function" ? fn(rowSelection) : fn - -// const added = Object.keys(newState).filter( -// (k) => newState[k] !== rowSelection[k] -// ) - -// if (added.length) { -// const addedTags = (product_tags?.filter((p) => added.includes(p.id!)) ?? -// []) as ProductTag[] - -// if (addedTags.length > 0) { -// const newConditions = addedTags.map((p) => ({ -// label: p.value, -// value: p.id!, -// })) - -// setIntermediate((prev) => { -// const filteredPrev = prev.filter((p) => p.value in newState) -// return Array.from(new Set([...filteredPrev, ...newConditions])) -// }) -// } - -// setRowSelection(newState) -// } - -// const removed = Object.keys(rowSelection).filter( -// (k) => newState[k] !== rowSelection[k] -// ) - -// if (removed.length) { -// setIntermediate((prev) => { -// return prev.filter((p) => !removed.includes(p.value)) -// }) - -// setRowSelection(newState) -// } -// } - -// const handleSave = () => { -// onSave(intermediate) -// } - -// const columns = useProductTagConditionsTableColumns() -// const filters = useProductTagConditionsTableFilters() - -// const { table } = useDataTable({ -// data: product_tags ?? [], -// columns: columns, -// count, -// enablePagination: true, -// getRowId: (row) => row.id, -// pageSize: PAGE_SIZE, -// enableRowSelection: true, -// rowSelection: { -// state: rowSelection, -// updater, -// }, -// prefix: PRODUCT_TAG_PREFIX, -// }) - -// if (isError) { -// throw error -// } - -// return ( -//
    -// -// -//
    -// ) -// } - -type ConditionsTableProps = { - product: ConditionsProps - productType: ConditionsProps - productTag: ConditionsProps - productCollection: ConditionsProps - customerGroup: ConditionsProps - selected: ConditionEntities | null -} - -export const ConditionsDrawer = ({ - product, - productType, - customerGroup, - productCollection, - productTag, - selected, -}: ConditionsTableProps) => { - switch (selected) { - case ConditionEntities.PRODUCT: - return - case ConditionEntities.PRODUCT_TYPE: - return - case ConditionEntities.PRODUCT_COLLECTION: - return - case ConditionEntities.PRODUCT_TAG: - // return - return
    Not implemented
    - case ConditionEntities.CUSTOMER_GROUP: - return - default: - return null - } -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/components/conditions-drawer/index.ts b/packages/admin-next/dashboard/src/routes/taxes/common/components/conditions-drawer/index.ts deleted file mode 100644 index b65a27e54d..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/common/components/conditions-drawer/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./conditions-drawer" diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/components/tax-region-create-form/tax-region-create-form.tsx b/packages/admin-next/dashboard/src/routes/taxes/common/components/tax-region-create-form/tax-region-create-form.tsx deleted file mode 100644 index ba3b3434b1..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/common/components/tax-region-create-form/tax-region-create-form.tsx +++ /dev/null @@ -1,250 +0,0 @@ -import { zodResolver } from "@hookform/resolvers/zod" -import { Button, Heading, Input, Select, Switch, Text } from "@medusajs/ui" -import { useForm } from "react-hook-form" -import { useTranslation } from "react-i18next" -import * as zod from "zod" - -import { TaxRegionResponse } from "@medusajs/types" -import { Form } from "../../../../../components/common/form" -import { PercentageInput } from "../../../../../components/inputs/percentage-input" -import { - RouteFocusModal, - useRouteModal, -} from "../../../../../components/modals" -import { useCreateTaxRegion } from "../../../../../hooks/api/tax-regions" -import { countries } from "../../../../../lib/countries" - -export const TaxRegionCreateForm = ({ - taxRegion, - formSchema, -}: { - taxRegion?: TaxRegionResponse - formSchema: zod.ZodObject<{ - province_code: any - country_code: any - parent_id: any - name: any - code: any - rate: any - is_combinable: any - }> -}) => { - const { t } = useTranslation() - const { handleSuccess } = useRouteModal() - - const form = useForm>({ - defaultValues: { - country_code: taxRegion?.country_code || undefined, - parent_id: taxRegion?.id || undefined, - }, - resolver: zodResolver(formSchema), - }) - - const { mutateAsync, isPending } = useCreateTaxRegion() - - const handleSubmit = form.handleSubmit(async (data) => { - await mutateAsync( - { - parent_id: taxRegion?.id, - province_code: data.province_code, - country_code: data.country_code, - default_tax_rate: { - name: data.name, - code: data.code, - rate: data.rate, - }, - }, - { - onSuccess: () => { - taxRegion?.id - ? handleSuccess(`/settings/taxes/${taxRegion.id}`) - : handleSuccess(`/settings/taxes`) - }, - } - ) - }) - - return ( - -
    - -
    - - - - - -
    -
    - - -
    -
    - - {taxRegion - ? t("taxRegions.create-child.title") - : t("taxRegions.create.title")} - - - - {taxRegion - ? t("taxRegions.create-child.description") - : t("taxRegions.create.description")} - -
    - - {!taxRegion && ( - { - return ( - - {t("fields.country")} - - - - - - - - ) - }} - /> - )} - - {taxRegion && ( - { - return ( - - {t("fields.province")} - - - - - - ) - }} - /> - )} - - { - return ( - - Tax Rate Name - - - - - - ) - }} - /> - - { - return ( - - {t("fields.rate")} - - - { - if (e.target.value) { - field.onChange(parseInt(e.target.value)) - } - }} - /> - - - - {t("taxRegions.fields.rate.hint")} - - - ) - }} - /> - - { - return ( - - {t("fields.code")} - - - - - - ) - }} - /> - - {!taxRegion?.parent_id && ( - { - return ( - - - {t("taxRates.fields.isCombinable")} - - - - - - - - {t("taxRegions.fields.is_combinable.hint")} - - - ) - }} - /> - )} -
    -
    - -
    - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/columns/use-customer-group-conditions-table-columns.tsx b/packages/admin-next/dashboard/src/routes/taxes/common/hooks/columns/use-customer-group-conditions-table-columns.tsx deleted file mode 100644 index 5ed653dad6..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/columns/use-customer-group-conditions-table-columns.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { CustomerGroup } from "@medusajs/medusa" -import { Checkbox } from "@medusajs/ui" -import { createColumnHelper } from "@tanstack/react-table" -import { useMemo } from "react" -import { useCustomerGroupTableColumns } from "../../../../../hooks/table/columns/use-customer-group-table-columns" - -const columnHelper = createColumnHelper() - -export const useCustomerGroupConditionsTableColumns = () => { - const base = useCustomerGroupTableColumns() - - return useMemo( - () => [ - columnHelper.display({ - id: "select", - header: ({ table }) => { - return ( - - table.toggleAllPageRowsSelected(!!value) - } - /> - ) - }, - cell: ({ row }) => { - return ( - row.toggleSelected(!!value)} - onClick={(e) => { - e.stopPropagation() - }} - /> - ) - }, - }), - ...base, - ], - [base] - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/columns/use-product-collection-conditions-table-columns.tsx b/packages/admin-next/dashboard/src/routes/taxes/common/hooks/columns/use-product-collection-conditions-table-columns.tsx deleted file mode 100644 index 034aae321e..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/columns/use-product-collection-conditions-table-columns.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { HttpTypes } from "@medusajs/types" -import { Checkbox } from "@medusajs/ui" -import { createColumnHelper } from "@tanstack/react-table" -import { useMemo } from "react" -import { useTranslation } from "react-i18next" - -const columnHelper = createColumnHelper() - -export const useProductCollectionConditionsTableColumns = () => { - const { t } = useTranslation() - - return useMemo( - () => [ - columnHelper.display({ - id: "select", - header: ({ table }) => { - return ( - - table.toggleAllPageRowsSelected(!!value) - } - /> - ) - }, - cell: ({ row }) => { - return ( - row.toggleSelected(!!value)} - onClick={(e) => { - e.stopPropagation() - }} - /> - ) - }, - }), - columnHelper.accessor("title", { - header: t("fields.title"), - }), - columnHelper.accessor("handle", { - header: t("fields.handle"), - cell: ({ getValue }) => `/${getValue()}`, - }), - columnHelper.accessor("products", { - header: t("fields.products"), - cell: ({ getValue }) => { - const count = getValue()?.length - - return {count || "-"} - }, - }), - ], - [t] - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/columns/use-product-conditions-table-columns.tsx b/packages/admin-next/dashboard/src/routes/taxes/common/hooks/columns/use-product-conditions-table-columns.tsx deleted file mode 100644 index 174412cd31..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/columns/use-product-conditions-table-columns.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Checkbox } from "@medusajs/ui" -import { createColumnHelper } from "@tanstack/react-table" -import { useMemo } from "react" -import { useProductTableColumns } from "../../../../../hooks/table/columns/use-product-table-columns" -import { HttpTypes } from "@medusajs/types" - -const columnHelper = createColumnHelper() - -export const useProductConditionsTableColumns = () => { - const base = useProductTableColumns() - - return useMemo( - () => [ - columnHelper.display({ - id: "select", - header: ({ table }) => { - return ( - - table.toggleAllPageRowsSelected(!!value) - } - /> - ) - }, - cell: ({ row }) => { - return ( - row.toggleSelected(!!value)} - onClick={(e) => { - e.stopPropagation() - }} - /> - ) - }, - }), - ...base, - ], - [base] - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/columns/use-product-tag-conditions-table-columns.tsx b/packages/admin-next/dashboard/src/routes/taxes/common/hooks/columns/use-product-tag-conditions-table-columns.tsx deleted file mode 100644 index 540c8d1354..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/columns/use-product-tag-conditions-table-columns.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useTranslation } from "react-i18next" -import { useMemo } from "react" -import { createColumnHelper } from "@tanstack/react-table" -import { ProductTag } from "@medusajs/medusa" -import { Checkbox } from "@medusajs/ui" - -const columnHelper = createColumnHelper() - -export const useProductTagConditionsTableColumns = () => { - const { t } = useTranslation() - - return useMemo( - () => [ - columnHelper.display({ - id: "select", - header: ({ table }) => { - return ( - - table.toggleAllPageRowsSelected(!!value) - } - /> - ) - }, - cell: ({ row }) => { - return ( - row.toggleSelected(!!value)} - onClick={(e) => { - e.stopPropagation() - }} - /> - ) - }, - }), - columnHelper.accessor("value", { - header: t("fields.value"), - }), - ], - [t] - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/columns/use-product-type-conditions-table-columns.tsx b/packages/admin-next/dashboard/src/routes/taxes/common/hooks/columns/use-product-type-conditions-table-columns.tsx deleted file mode 100644 index 9a9fdfd5d0..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/columns/use-product-type-conditions-table-columns.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { HttpTypes } from "@medusajs/types" -import { Checkbox } from "@medusajs/ui" -import { createColumnHelper } from "@tanstack/react-table" -import { useMemo } from "react" -import { useTranslation } from "react-i18next" - -const columnHelper = createColumnHelper() - -export const useProductTypeConditionsTableColumns = () => { - const { t } = useTranslation() - - return useMemo( - () => [ - columnHelper.display({ - id: "select", - header: ({ table }) => { - return ( - - table.toggleAllPageRowsSelected(!!value) - } - /> - ) - }, - cell: ({ row }) => { - return ( - row.toggleSelected(!!value)} - onClick={(e) => { - e.stopPropagation() - }} - /> - ) - }, - }), - columnHelper.accessor("value", { - header: t("fields.value"), - cell: ({ getValue }) => getValue(), - }), - ], - [t] - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/filters/use-customer-group-conditions-table-filters.tsx b/packages/admin-next/dashboard/src/routes/taxes/common/hooks/filters/use-customer-group-conditions-table-filters.tsx deleted file mode 100644 index 50859c8e24..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/filters/use-customer-group-conditions-table-filters.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useTranslation } from "react-i18next" -import { Filter } from "../../../../../components/table/data-table" - -export const useCustomerGroupConditionsTableFilters = () => { - const { t } = useTranslation() - - const filters: Filter[] = [ - { label: t("fields.createdAt"), key: "created_at" }, - { label: t("fields.updatedAt"), key: "updated_at" }, - ].map((f) => ({ - key: f.key, - label: f.label, - type: "date", - })) - - return filters -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/filters/use-product-collection-conditions-table-filters.tsx b/packages/admin-next/dashboard/src/routes/taxes/common/hooks/filters/use-product-collection-conditions-table-filters.tsx deleted file mode 100644 index 45de4cc975..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/filters/use-product-collection-conditions-table-filters.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useTranslation } from "react-i18next" -import { Filter } from "../../../../../components/table/data-table" - -export const useProductCollectionConditionsTableFilters = () => { - const { t } = useTranslation() - - const filters: Filter[] = [ - { label: t("fields.createdAt"), key: "created_at" }, - { label: t("fields.updatedAt"), key: "updated_at" }, - ].map((f) => ({ - key: f.key, - label: f.label, - type: "date", - })) - - return filters -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/filters/use-product-tag-conditions-table-filters.tsx b/packages/admin-next/dashboard/src/routes/taxes/common/hooks/filters/use-product-tag-conditions-table-filters.tsx deleted file mode 100644 index b79dad59a0..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/filters/use-product-tag-conditions-table-filters.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useTranslation } from "react-i18next" -import { Filter } from "../../../../../components/table/data-table" - -export const useProductTagConditionsTableFilters = () => { - const { t } = useTranslation() - - const filters: Filter[] = [ - { label: t("fields.createdAt"), key: "created_at" }, - { label: t("fields.updatedAt"), key: "updated_at" }, - ].map((f) => ({ - key: f.key, - label: f.label, - type: "date", - })) - - return filters -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/filters/use-product-type-conditions-table-filters.tsx b/packages/admin-next/dashboard/src/routes/taxes/common/hooks/filters/use-product-type-conditions-table-filters.tsx deleted file mode 100644 index 7c9a74018c..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/filters/use-product-type-conditions-table-filters.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useTranslation } from "react-i18next" -import { Filter } from "../../../../../components/table/data-table" - -export const useProductTypeConditionsTableFilters = () => { - const { t } = useTranslation() - - const filters: Filter[] = [ - { label: t("fields.createdAt"), key: "created_at" }, - { label: t("fields.updatedAt"), key: "updated_at" }, - ].map((f) => ({ - key: f.key, - label: f.label, - type: "date", - })) - - return filters -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/query/use-product-collection-conditions-table-query.tsx b/packages/admin-next/dashboard/src/routes/taxes/common/hooks/query/use-product-collection-conditions-table-query.tsx deleted file mode 100644 index 159ab0a8bd..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/query/use-product-collection-conditions-table-query.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { AdminGetCollectionsParams } from "@medusajs/medusa" - -import { useQueryParams } from "../../../../../hooks/use-query-params" - -export const useProductCollectionConditionsTableQuery = ({ - pageSize = 50, - prefix, -}: { - pageSize?: number - prefix: string -}) => { - const raw = useQueryParams( - [ - "offset", - "q", - "order", - "title", - "handle", - "discount_condition_id", - "created_at", - "updated_at", - ], - prefix - ) - - const searchParams: AdminGetCollectionsParams = { - limit: pageSize, - offset: raw.offset ? Number(raw.offset) : 0, - q: raw.q, - title: raw.title, - handle: raw.handle, - discount_condition_id: raw.discount_condition_id, - created_at: raw.created_at ? JSON.parse(raw.created_at) : undefined, - updated_at: raw.updated_at ? JSON.parse(raw.updated_at) : undefined, - order: raw.order, - } - - return { - searchParams, - raw, - } -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/query/use-product-tag-conditions-table-query.tsx b/packages/admin-next/dashboard/src/routes/taxes/common/hooks/query/use-product-tag-conditions-table-query.tsx deleted file mode 100644 index 717cab17f6..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/query/use-product-tag-conditions-table-query.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { AdminGetProductTagsParams } from "@medusajs/medusa" - -import { useQueryParams } from "../../../../../hooks/use-query-params" - -export const useProductTagConditionsTableQuery = ({ - pageSize = 50, - prefix, -}: { - pageSize?: number - prefix: string -}) => { - const raw = useQueryParams( - [ - "offset", - "q", - "order", - "value", - "discount_condition_id", - "created_at", - "updated_at", - ], - prefix - ) - - const searchParams: AdminGetProductTagsParams = { - limit: pageSize, - offset: raw.offset ? Number(raw.offset) : 0, - q: raw.q, - discount_condition_id: raw.discount_condition_id, - created_at: raw.created_at ? JSON.parse(raw.created_at) : undefined, - updated_at: raw.updated_at ? JSON.parse(raw.updated_at) : undefined, - order: raw.order, - } - - return { - searchParams, - raw, - } -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/query/use-product-type-conditions-table-query.tsx b/packages/admin-next/dashboard/src/routes/taxes/common/hooks/query/use-product-type-conditions-table-query.tsx deleted file mode 100644 index 737d00d143..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/common/hooks/query/use-product-type-conditions-table-query.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { AdminGetProductTypesParams } from "@medusajs/medusa" -import { useQueryParams } from "../../../../../hooks/use-query-params" - -export const useProductTypeConditionsTableQuery = ({ - pageSize = 50, - prefix, -}: { - pageSize?: number - prefix: string -}) => { - const raw = useQueryParams( - ["offset", "q", "order", "created_at", "updated_at"], - prefix - ) - - const searchParams: AdminGetProductTypesParams = { - limit: pageSize, - offset: raw.offset ? Number(raw.offset) : 0, - q: raw.q, - created_at: raw.created_at ? JSON.parse(raw.created_at) : undefined, - updated_at: raw.updated_at ? JSON.parse(raw.updated_at) : undefined, - order: raw.order, - } - - return { - searchParams, - raw, - } -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/common/types.ts b/packages/admin-next/dashboard/src/routes/taxes/common/types.ts deleted file mode 100644 index 59b303c420..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/common/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ConditionEntities } from "./constants" - -export type ConditionsOption = { - value: string - label: string -} - -export type ConditionsState = { - [K in ConditionEntities]: boolean -} - -export type ConditionEntitiesValues = `${ConditionEntities}` diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-province-create/index.ts b/packages/admin-next/dashboard/src/routes/taxes/tax-province-create/index.ts deleted file mode 100644 index ace2775212..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-province-create/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./tax-province-create" - -export { TaxProvinceCreate as Component } from "./tax-province-create" diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-province-create/tax-province-create.tsx b/packages/admin-next/dashboard/src/routes/taxes/tax-province-create/tax-province-create.tsx deleted file mode 100644 index 70dbe22abc..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-province-create/tax-province-create.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useParams } from "react-router-dom" -import * as zod from "zod" -import { RouteFocusModal } from "../../../components/modals" -import { useTaxRegion } from "../../../hooks/api/tax-regions" -import { TaxRegionCreateForm } from "../common/components/tax-region-create-form" - -const CreateTaxProvinceForm = zod.object({ - province_code: zod.string(), - country_code: zod.string(), - parent_id: zod.string(), - name: zod.string(), - code: zod.string().optional(), - rate: zod.number(), - is_combinable: zod.boolean().default(false), -}) - -export const TaxProvinceCreate = () => { - const { id } = useParams() - - const { tax_region: taxRegion } = useTaxRegion( - id!, - {}, - { - enabled: !!id, - } - ) - - return ( - - - - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/components/condition/condition.tsx b/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/components/condition/condition.tsx deleted file mode 100644 index d3a836601e..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/components/condition/condition.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Button, Text } from "@medusajs/ui" -import { useTranslation } from "react-i18next" -import { ListSummary } from "../../../../../components/common/list-summary" -import { ConditionEntitiesValues } from "../../../common/types" - -const N = 2 - -type ConditionProps = { - labels: string[] - type: ConditionEntitiesValues - onClick: () => void -} - -export function Condition({ labels, type, onClick }: ConditionProps) { - const { t } = useTranslation() - const isInButtonDisabled = !!labels.length - - return ( -
    -
    - - {t("taxRates.fields.appliesTo")} {t(`taxRates.fields.${type}`)} - - -
    - -
    -
    -
    - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/components/condition/index.ts b/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/components/condition/index.ts deleted file mode 100644 index 1e9884b3b3..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/components/condition/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./condition" diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/components/index.ts b/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/components/index.ts deleted file mode 100644 index 3cf124f9d6..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./condition" -export * from "./tax-rate-create-form" diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/components/tax-rate-create-form/index.ts b/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/components/tax-rate-create-form/index.ts deleted file mode 100644 index 69b098c24a..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/components/tax-rate-create-form/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./tax-rate-create-form" diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/components/tax-rate-create-form/tax-rate-create-form.tsx b/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/components/tax-rate-create-form/tax-rate-create-form.tsx deleted file mode 100644 index c0342705c0..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/components/tax-rate-create-form/tax-rate-create-form.tsx +++ /dev/null @@ -1,481 +0,0 @@ -import { zodResolver } from "@hookform/resolvers/zod" -import { - Button, - clx, - DropdownMenu, - Heading, - Input, - Select, - Switch, - Text, -} from "@medusajs/ui" -import { useForm, useWatch } from "react-hook-form" -import { useTranslation } from "react-i18next" -import * as zod from "zod" - -import { TaxRegionResponse } from "@medusajs/types" -import { useState } from "react" -import { useSearchParams } from "react-router-dom" -import { Form } from "../../../../../components/common/form" -import { PercentageInput } from "../../../../../components/inputs/percentage-input" -import { SplitView } from "../../../../../components/layout/split-view" -import { - RouteFocusModal, - useRouteModal, -} from "../../../../../components/modals" -import { useCreateTaxRate } from "../../../../../hooks/api/tax-rates" -import { useTaxRegions } from "../../../../../hooks/api/tax-regions" -import { ConditionsDrawer } from "../../../common/components/conditions-drawer" -import { ConditionEntities } from "../../../common/constants" -import { - ConditionEntitiesValues, - ConditionsOption, -} from "../../../common/types" -import { Condition } from "../condition" - -const SelectedConditionTypesSchema = zod.object({ - [ConditionEntities.PRODUCT]: zod.boolean(), - [ConditionEntities.PRODUCT_COLLECTION]: zod.boolean(), - [ConditionEntities.PRODUCT_TAG]: zod.boolean(), - [ConditionEntities.PRODUCT_TYPE]: zod.boolean(), - [ConditionEntities.CUSTOMER_GROUP]: zod.boolean(), -}) - -const ConditionSchema = zod.array( - zod.object({ - label: zod.string(), - value: zod.string(), - }) -) - -const CreateTaxRateSchema = zod.object({ - tax_region_id: zod.string(), - name: zod.string(), - code: zod.string(), - rate: zod.number(), - is_combinable: zod.boolean().default(false), - selected_condition_types: SelectedConditionTypesSchema, - products: ConditionSchema, - product_types: ConditionSchema, - product_collections: ConditionSchema, - product_tags: ConditionSchema, - customer_groups: ConditionSchema, -}) - -export const TaxRateCreateForm = ({ - taxRegion, -}: { - taxRegion: TaxRegionResponse -}) => { - const { t } = useTranslation() - const { handleSuccess } = useRouteModal() - - const form = useForm>({ - defaultValues: { - tax_region_id: taxRegion.id, - selected_condition_types: { - [ConditionEntities.PRODUCT]: true, - [ConditionEntities.PRODUCT_TYPE]: false, - [ConditionEntities.PRODUCT_COLLECTION]: false, - [ConditionEntities.PRODUCT_TAG]: false, - [ConditionEntities.CUSTOMER_GROUP]: false, - }, - products: [], - product_types: [], - product_collections: [], - product_tags: [], - customer_groups: [], - }, - resolver: zodResolver(CreateTaxRateSchema), - }) - - const { tax_regions: taxRegions } = useTaxRegions({ - parent_id: taxRegion.id, - province_code: { $ne: "null" }, - }) - - const { mutateAsync, isPending } = useCreateTaxRate() - - const handleSubmit = form.handleSubmit(async (data) => { - await mutateAsync( - { - name: data.name, - code: data.code, - rate: data.rate, - is_combinable: data.is_combinable, - tax_region_id: data.tax_region_id || taxRegion.id, - rules: - data.products?.map((product) => ({ - reference: "product", - reference_id: product.value, - })) || [], - }, - { - onSuccess: () => handleSuccess(`/settings/taxes/${taxRegion.id}`), - } - ) - }) - - const [open, setOpen] = useState(false) - const [isDropdownOpen, setIsDropdownOpen] = useState(false) - const [conditionType, setConditionType] = useState( - null - ) - const selectedConditionTypes = useWatch({ - name: "selected_condition_types", - control: form.control, - }) - - const selectedProducts = useWatch({ - control: form.control, - name: "products", - }) - - const selectedProductCollections = useWatch({ - control: form.control, - name: "product_collections", - }) - - const selectedProductTypes = useWatch({ - control: form.control, - name: "product_types", - }) - - const selectedProductTags = useWatch({ - control: form.control, - name: "product_tags", - }) - - const selectedCustomerGroups = useWatch({ - control: form.control, - name: "customer_groups", - }) - - const handleSaveConditions = (type: ConditionEntitiesValues) => { - return (options: ConditionsOption[]) => { - form.setValue(type, options, { - shouldDirty: true, - shouldTouch: true, - }) - - setOpen(false) - } - } - - const selectedTypes = Object.keys(selectedConditionTypes || {}) - .filter( - (k) => selectedConditionTypes[k as keyof typeof selectedConditionTypes] - ) - .sort() as ConditionEntities[] - - const toggleSelectedConditionTypes = (type: ConditionEntities) => { - const state = { ...form.getValues().selected_condition_types } - - if (state[type]) { - delete state[type] - } else { - state[type] = true - } - - form.setValue("selected_condition_types", state, { - shouldDirty: true, - shouldTouch: true, - }) - } - - const clearAllSelectedConditions = () => { - form.setValue( - "selected_condition_types", - { - [ConditionEntities.PRODUCT]: false, - [ConditionEntities.PRODUCT_TYPE]: false, - [ConditionEntities.PRODUCT_COLLECTION]: false, - [ConditionEntities.PRODUCT_TAG]: false, - [ConditionEntities.CUSTOMER_GROUP]: false, - }, - { - shouldDirty: true, - shouldTouch: true, - } - ) - } - - const [, setSearchParams] = useSearchParams() - const handleOpenChange = (open: boolean) => { - if (!open) { - setConditionType(null) - setSearchParams( - {}, - { - replace: true, - } - ) - } - - setOpen(open) - } - - return ( - -
    - -
    - - - - - -
    -
    - - - - -
    -
    -
    - - {t("taxRates.create.title")} - - - - {t("taxRates.create.description")} - -
    - - { - return ( - - {t("fields.province")} - - - - - - - ) - }} - /> - - { - return ( - - {t("fields.name")} - - - - - - ) - }} - /> - - { - return ( - - {t("fields.rate")} - - - { - if (e.target.value) { - field.onChange(parseInt(e.target.value)) - } - }} - /> - - - ) - }} - /> - - { - return ( - - {t("fields.code")} - - - - - - ) - }} - /> - - {taxRegion.parent_id && ( - { - return ( - - - {t("taxRates.fields.isCombinable")} - - - - - - - ) - }} - /> - )} - -
    - {selectedTypes.length > 0 && ( -
    - {selectedTypes.map((selectedType) => { - if (selectedType in (selectedConditionTypes || {})) { - const field = form.getValues(selectedType) || [] - - return ( - f.label)} - onClick={() => { - setConditionType(selectedType) - setOpen(true) - }} - /> - ) - } - })} -
    - )} - -
    - { - v && setIsDropdownOpen(v) - }} - > - - - - - { - setIsDropdownOpen(true) - }} - > - {Object.values(ConditionEntities).map((type) => ( - - toggleSelectedConditionTypes(type) - } - > - - {t(`fields.${type}`)} - - - ))} - - - - {selectedTypes.length > 0 && ( - - )} -
    -
    -
    -
    -
    - - - - -
    -
    - -
    - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/index.ts b/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/index.ts deleted file mode 100644 index 72ee1c8329..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./tax-rate-create" - -export { TaxRateCreate as Component } from "./tax-rate-create" diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/tax-rate-create.tsx b/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/tax-rate-create.tsx deleted file mode 100644 index 807584d506..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-create/tax-rate-create.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useParams } from "react-router-dom" -import { RouteFocusModal } from "../../../components/modals" -import { useTaxRegion } from "../../../hooks/api/tax-regions" -import { TaxRateCreateForm } from "./components" - -export const TaxRateCreate = () => { - const params = useParams() - const { tax_region: taxRegion, isError, error } = useTaxRegion(params.id!) - - if (isError) { - throw error - } - - return ( - taxRegion && ( - - - - ) - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/components/index.ts b/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/components/index.ts deleted file mode 100644 index 8f2ab9b263..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./tax-rate-edit-form" diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/components/tax-rate-edit-form/index.ts b/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/components/tax-rate-edit-form/index.ts deleted file mode 100644 index 8f2ab9b263..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/components/tax-rate-edit-form/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./tax-rate-edit-form" diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/components/tax-rate-edit-form/tax-rate-edit-form.tsx b/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/components/tax-rate-edit-form/tax-rate-edit-form.tsx deleted file mode 100644 index df2ab897f7..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/components/tax-rate-edit-form/tax-rate-edit-form.tsx +++ /dev/null @@ -1,488 +0,0 @@ -import { zodResolver } from "@hookform/resolvers/zod" -import { - Button, - clx, - DropdownMenu, - Heading, - Input, - Switch, - Text, -} from "@medusajs/ui" -import { useForm, useWatch } from "react-hook-form" -import { useTranslation } from "react-i18next" -import * as zod from "zod" - -import { TaxRateResponse, TaxRegionResponse } from "@medusajs/types" -import { useState } from "react" -import { useSearchParams } from "react-router-dom" -import { Form } from "../../../../../components/common/form" -import { PercentageInput } from "../../../../../components/inputs/percentage-input" -import { SplitView } from "../../../../../components/layout/split-view" -import { - RouteFocusModal, - useRouteModal, -} from "../../../../../components/modals" -import { useUpdateTaxRate } from "../../../../../hooks/api/tax-rates" -import { ConditionsDrawer } from "../../../common/components/conditions-drawer" -import { ConditionEntities, Operators } from "../../../common/constants" -import { ConditionsOption } from "../../../common/types" -import { Condition } from "../../../tax-rate-create/components" - -const SelectedConditionTypesSchema = zod.object({ - [ConditionEntities.PRODUCT]: zod.boolean(), - [ConditionEntities.PRODUCT_TYPE]: zod.boolean(), - [ConditionEntities.PRODUCT_COLLECTION]: zod.boolean(), - [ConditionEntities.PRODUCT_TAG]: zod.boolean(), - [ConditionEntities.CUSTOMER_GROUP]: zod.boolean(), -}) - -const ResourceSchema = zod.array( - zod.object({ - label: zod.string(), - value: zod.string(), - }) -) - -const UpdateTaxRateSchema = zod.object({ - name: zod.string().optional(), - code: zod.string().optional(), - rate: zod.number().optional(), - is_combinable: zod.boolean().optional(), - selected_condition_types: SelectedConditionTypesSchema, - products: ResourceSchema, - product_types: ResourceSchema, - product_collections: ResourceSchema, - product_tags: ResourceSchema, - customer_groups: ResourceSchema, -}) - -export const TaxRateEditForm = ({ - taxRegion, - taxRate, -}: { - taxRegion: TaxRegionResponse - taxRate: TaxRateResponse -}) => { - const { t } = useTranslation() - const { handleSuccess } = useRouteModal() - const productRules = taxRate.rules?.filter((r) => r.reference == "product") - const productTypeRules = taxRate.rules?.filter( - (r) => r.reference == "product_type" - ) - const productCollectionRules = taxRate.rules?.filter( - (r) => r.reference == "product_collection" - ) - const productTagRules = taxRate.rules?.filter( - (r) => r.reference == "product_tag" - ) - const customerGroupRules = taxRate.rules?.filter( - (r) => r.reference == "customer_group" - ) - - const form = useForm>({ - defaultValues: { - name: taxRate.name, - code: taxRate.code || undefined, - rate: taxRate.rate || undefined, - is_combinable: taxRate.is_combinable, - selected_condition_types: { - [ConditionEntities.PRODUCT]: !!productRules?.length, - [ConditionEntities.PRODUCT_TYPE]: !!productTypeRules?.length, - [ConditionEntities.PRODUCT_COLLECTION]: - !!productCollectionRules?.length, - [ConditionEntities.PRODUCT_TAG]: !!productTagRules?.length, - [ConditionEntities.CUSTOMER_GROUP]: !!customerGroupRules?.length, - }, - products: productRules.map((r) => ({ - label: r.reference, - value: r.reference_id, - })), - product_types: productTypeRules.map((r) => ({ - label: r.reference, - value: r.reference_id, - })), - product_collections: productCollectionRules.map((r) => ({ - label: r.reference, - value: r.reference_id, - })), - product_tags: productTagRules.map((r) => ({ - label: r.reference, - value: r.reference_id, - })), - customer_groups: customerGroupRules.map((r) => ({ - label: r.reference, - value: r.reference_id, - })), - }, - resolver: zodResolver(UpdateTaxRateSchema), - }) - - const { mutateAsync, isPending } = useUpdateTaxRate(taxRate.id) - - const buildRules = (key: string, data: { value: string }[]) => - data?.map((product) => ({ - reference: key, - reference_id: product.value, - })) || [] - - const handleSubmit = form.handleSubmit(async (data) => { - const rules = [ - ...buildRules("product", data.products), - ...buildRules("product_type", data.product_types), - ...buildRules("product_collection", data.product_collections), - ...buildRules("product_tag", data.product_tags), - ...buildRules("customer_group", data.customer_groups), - ] - - await mutateAsync( - { - name: data.name, - code: data.code || undefined, - rate: data.rate, - is_combinable: data.is_combinable, - rules, - }, - { - onSuccess: () => handleSuccess(`/settings/taxes/${taxRegion.id}`), - } - ) - }) - - const [open, setOpen] = useState(false) - const [isDropdownOpen, setIsDropdownOpen] = useState(false) - const [conditionType, setConditionType] = useState( - null - ) - const selectedConditionTypes = useWatch({ - name: "selected_condition_types", - control: form.control, - }) - - const selectedProducts = useWatch({ - control: form.control, - name: "products", - }) - - const selectedProductCollections = useWatch({ - control: form.control, - name: "product_collections", - }) - - const selectedProductTypes = useWatch({ - control: form.control, - name: "product_types", - }) - - const selectedProductTags = useWatch({ - control: form.control, - name: "product_tags", - }) - - const selectedCustomerGroups = useWatch({ - control: form.control, - name: "customer_groups", - }) - - const handleSaveConditions = (type: ConditionEntities) => { - return (options: ConditionsOption[]) => { - form.setValue(type, options, { - shouldDirty: true, - shouldTouch: true, - }) - - setOpen(false) - } - } - - const selectedTypes = Object.keys(selectedConditionTypes || {}) - .filter( - (k) => selectedConditionTypes[k as keyof typeof selectedConditionTypes] - ) - .sort() as ConditionEntities[] - - const handleOpenDrawer = (type: ConditionEntities, operator: Operators) => { - setConditionType(type) - setOpen(true) - } - - const toggleSelectedConditionTypes = (type: ConditionEntities) => { - const state = { ...form.getValues().selected_condition_types } - if (state[type]) { - delete state[type] - } else { - state[type] = true - } - - form.setValue("selected_condition_types", state, { - shouldDirty: true, - shouldTouch: true, - }) - } - - const clearAllSelectedConditions = () => { - form.setValue( - "selected_condition_types", - { - [ConditionEntities.PRODUCT]: false, - [ConditionEntities.PRODUCT_TYPE]: false, - [ConditionEntities.PRODUCT_COLLECTION]: false, - [ConditionEntities.PRODUCT_TAG]: false, - [ConditionEntities.CUSTOMER_GROUP]: false, - }, - { - shouldDirty: true, - shouldTouch: true, - } - ) - } - - const [, setSearchParams] = useSearchParams() - const handleOpenChange = (open: boolean) => { - if (!open) { - setConditionType(null) - setSearchParams( - {}, - { - replace: true, - } - ) - } - - setOpen(open) - } - - return ( - -
    - -
    - - - - -
    -
    - - - - -
    -
    -
    - - {t("taxRates.edit.title")} - - - - {t("taxRates.edit.description")} - -
    - - { - return ( - - {t("fields.name")} - - - - - - ) - }} - /> - - { - return ( - - {t("fields.rate")} - - - { - if (e.target.value) { - field.onChange(parseInt(e.target.value)) - } - }} - /> - - - ) - }} - /> - - { - return ( - - {t("fields.code")} - - - - - - ) - }} - /> - - {taxRate.tax_region?.parent_id && ( - { - return ( - - - {t("taxRates.fields.isCombinable")} - - - - - - - ) - }} - /> - )} - - {!taxRate.is_default && ( -
    - {selectedTypes.length > 0 && ( -
    - {selectedTypes.map((selectedType) => { - if ( - selectedType in (selectedConditionTypes || {}) - ) { - const field = form.getValues(selectedType) || [] - - return ( - f.value)} - onClick={() => { - setConditionType(selectedType) - setOpen(true) - }} - /> - ) - } - })} -
    - )} - -
    - { - v && setIsDropdownOpen(v) - }} - > - - - - - setIsDropdownOpen(false)} - > - {Object.values(ConditionEntities).map((type) => ( - - toggleSelectedConditionTypes(type) - } - > - - {t(`fields.${type}`)} - - - ))} - - - - {selectedTypes.length > 0 && ( - - )} -
    -
    - )} -
    -
    -
    - - - - -
    -
    - -
    - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/index.ts b/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/index.ts deleted file mode 100644 index ef8328fd8b..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./tax-rate-edit" - -export { taxRateLoader as loader } from "./loader" -export { TaxRateEdit as Component } from "./tax-rate-edit" diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/loader.ts b/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/loader.ts deleted file mode 100644 index 6cd39eb892..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/loader.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { AdminTaxRateResponse } from "@medusajs/types" -import { LoaderFunctionArgs } from "react-router-dom" -import { taxRatesQueryKeys } from "../../../hooks/api/tax-rates" -import { sdk } from "../../../lib/client" -import { queryClient } from "../../../lib/query-client" - -const taxRateDetailQuery = (id: string) => ({ - queryKey: taxRatesQueryKeys.detail(id), - queryFn: async () => sdk.admin.taxRate.retrieve(id), -}) - -export const taxRateLoader = async ({ params }: LoaderFunctionArgs) => { - const id = params.taxRateId - const query = taxRateDetailQuery(id!) - - return ( - queryClient.getQueryData(query.queryKey) ?? - (await queryClient.fetchQuery(query)) - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/tax-rate-edit.tsx b/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/tax-rate-edit.tsx deleted file mode 100644 index 92f9a52554..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-rate-edit/tax-rate-edit.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useParams } from "react-router-dom" -import { RouteFocusModal } from "../../../components/modals" -import { useTaxRate } from "../../../hooks/api/tax-rates" -import { useTaxRegion } from "../../../hooks/api/tax-regions" -import { TaxRateEditForm } from "./components" - -export const TaxRateEdit = () => { - const params = useParams() - - const { tax_region: taxRegion } = useTaxRegion(params.id!) - const { - tax_rate: taxRate, - isLoading, - isError, - error, - } = useTaxRate(params.taxRateId!) - - if (isLoading) { - return
    Loading...
    - } - - if (isError) { - throw error - } - - return ( - taxRegion && - taxRate && ( - - - - ) - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-region-create/tax-region-create.tsx b/packages/admin-next/dashboard/src/routes/taxes/tax-region-create/tax-region-create.tsx deleted file mode 100644 index 9809f89d4e..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-region-create/tax-region-create.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import * as zod from "zod" -import { RouteFocusModal } from "../../../components/modals" -import { TaxRegionCreateForm } from "../common/components/tax-region-create-form" - -const CreateTaxRegionForm = zod.object({ - province_code: zod.string().optional(), - country_code: zod.string(), - parent_id: zod.string().optional(), - name: zod.string(), - code: zod.string().optional(), - rate: zod.number(), - is_combinable: zod.boolean().default(false), -}) - -export const TaxRegionCreate = () => { - return ( - - - - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/components/index.ts b/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/components/index.ts deleted file mode 100644 index 7c45c4182c..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./tax-rate-list" -export * from "./tax-region-general-detail" diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/components/tax-rate-list/index.ts b/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/components/tax-rate-list/index.ts deleted file mode 100644 index 9e413d7d01..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/components/tax-rate-list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./tax-rate-list" diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/components/tax-rate-list/tax-rate-list.tsx b/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/components/tax-rate-list/tax-rate-list.tsx deleted file mode 100644 index ea96bea4a4..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/components/tax-rate-list/tax-rate-list.tsx +++ /dev/null @@ -1,214 +0,0 @@ -import { PencilSquare, Trash } from "@medusajs/icons" -import { TaxRateResponse, TaxRegionResponse } from "@medusajs/types" -import { Button, Container, Heading, usePrompt } from "@medusajs/ui" -import { keepPreviousData } from "@tanstack/react-query" -import { RowSelectionState, createColumnHelper } from "@tanstack/react-table" -import { useMemo, useState } from "react" -import { useTranslation } from "react-i18next" -import { Link } from "react-router-dom" -import { ActionMenu } from "../../../../../components/common/action-menu" -import { DataTable } from "../../../../../components/table/data-table" -import { - useDeleteTaxRate, - useTaxRates, -} from "../../../../../hooks/api/tax-rates" -import { useDeleteTaxRegion } from "../../../../../hooks/api/tax-regions" -import { useTaxRateTableColumns } from "../../../../../hooks/table/columns/use-tax-rates-table-columns" -import { useTaxRateTableFilters } from "../../../../../hooks/table/filters/use-tax-rate-table-filters" -import { useTaxRateTableQuery } from "../../../../../hooks/table/query/use-tax-rate-table-query" -import { useDataTable } from "../../../../../hooks/use-data-table" - -const PAGE_SIZE = 10 - -type TaxRateListProps = { - taxRegion: TaxRegionResponse - isDefault: boolean -} - -export const TaxRateList = ({ - taxRegion, - isDefault = false, -}: TaxRateListProps) => { - const [rowSelection, setRowSelection] = useState({}) - - const { searchParams, raw } = useTaxRateTableQuery({ pageSize: PAGE_SIZE }) - const childrenIds = taxRegion.children?.map((c) => c.id) || [] - const { - tax_rates: taxRates, - count, - isLoading, - isError, - error, - } = useTaxRates( - { - ...searchParams, - tax_region_id: [taxRegion.id, ...childrenIds], - is_default: isDefault, - }, - { - placeholderData: keepPreviousData, - } - ) - - const columns = useColumns() - const filters = useTaxRateTableFilters() - - const { table } = useDataTable({ - data: taxRates ?? [], - columns, - count, - enablePagination: true, - enableRowSelection: true, - pageSize: PAGE_SIZE, - getRowId: (row) => row.id, - rowSelection: { - state: rowSelection, - updater: setRowSelection, - }, - meta: { - taxRegionId: taxRegion.id, - }, - }) - - const { mutateAsync } = useDeleteTaxRegion(taxRegion.id) - - const prompt = usePrompt() - const { t } = useTranslation() - - const handleRemove = async () => { - const result = await prompt({ - title: t("general.areYouSure"), - description: t("taxRegions.removeWarning", { - tax_region_name: taxRegion.name, - }), - confirmText: t("actions.delete"), - cancelText: t("actions.cancel"), - }) - - if (!result) { - return - } - - await mutateAsync(undefined, { - onSuccess: () => { - setRowSelection({}) - }, - }) - } - - if (isError) { - throw error - } - - return ( - -
    - - {isDefault ? `Default ${t("taxRates.domain")}` : `Tax Rate Overrides`} - - - - - -
    - - - `/settings/taxes/${taxRegion.id}/tax-rates/${row.id}/edit` - } - isLoading={isLoading} - orderBy={["is_default", "rate", "created_at", "updated_at"]} - queryObject={raw} - /> -
    - ) -} - -const columnHelper = createColumnHelper() - -const useColumns = () => { - const base = useTaxRateTableColumns() - - return useMemo( - () => [ - ...base, - columnHelper.display({ - id: "actions", - cell: ({ row, table }) => { - const { taxRegionId } = table.options.meta as { - taxRegionId: string - } - - return ( - - ) - }, - }), - ], - [base] - ) -} - -const TaxRateListActions = ({ - taxRateId, - taxRegionId, -}: { - taxRateId: string - taxRegionId: string -}) => { - const { t } = useTranslation() - - const { mutateAsync } = useDeleteTaxRate(taxRateId) - - const onRemove = async () => await mutateAsync() - - return ( - , - label: t("actions.edit"), - to: `/settings/taxes/${taxRegionId}/tax-rates/${taxRateId}/edit`, - }, - ], - }, - { - actions: [ - { - icon: , - label: t("actions.remove"), - onClick: onRemove, - }, - ], - }, - ]} - /> - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/components/tax-region-general-detail/index.ts b/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/components/tax-region-general-detail/index.ts deleted file mode 100644 index c9f34fc23f..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/components/tax-region-general-detail/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./tax-region-general-detail" diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/components/tax-region-general-detail/tax-region-general-detail.tsx b/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/components/tax-region-general-detail/tax-region-general-detail.tsx deleted file mode 100644 index 3300174685..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/components/tax-region-general-detail/tax-region-general-detail.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { TaxRegionResponse } from "@medusajs/types" -import { Container, Heading, Text } from "@medusajs/ui" -import { useTranslation } from "react-i18next" -import { formatDate } from "../../../../../components/common/date" -import { getCountryByIso2 } from "../../../../../lib/countries" - -type TaxRegionGeneralDetailProps = { - taxRegion: TaxRegionResponse -} - -export const TaxRegionGeneralDetail = ({ - taxRegion, -}: TaxRegionGeneralDetailProps) => { - const { t } = useTranslation() - const countryCode = taxRegion.parent?.country_code || taxRegion.country_code - const displayName = getCountryByIso2(countryCode)?.display_name || countryCode - - return ( - -
    -
    - {displayName} - - - {t("taxRegions.description")} - -
    -
    - -
    - - {t("fields.created")} - - - - {formatDate(taxRegion.created_at)} - -
    -
    - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/tax-region-detail.tsx b/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/tax-region-detail.tsx deleted file mode 100644 index a08662d088..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-region-detail/tax-region-detail.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Outlet, useParams } from "react-router-dom" - -import { JsonViewSection } from "../../../components/common/json-view-section" -import { useTaxRegion } from "../../../hooks/api/tax-regions" -import { TaxRateList } from "./components/tax-rate-list" -import { TaxRegionGeneralDetail } from "./components/tax-region-general-detail" - -import after from "virtual:medusa/widgets/tax/details/after" -import before from "virtual:medusa/widgets/tax/details/before" - -export const TaxRegionDetail = () => { - const { id } = useParams() - const { tax_region: taxRegion, isLoading, isError, error } = useTaxRegion(id!) - - if (isLoading) { - return
    Loading...
    - } - - if (isError) { - throw error - } - - return ( - taxRegion && ( -
    - {before.widgets.map((w, i) => { - return ( -
    - -
    - ) - })} - - - - {after.widgets.map((w, i) => { - return ( -
    - -
    - ) - })} - - -
    - ) - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-region-list/components/region-list-table/index.ts b/packages/admin-next/dashboard/src/routes/taxes/tax-region-list/components/region-list-table/index.ts deleted file mode 100644 index 3be79c1b50..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-region-list/components/region-list-table/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./tax-region-list-table" diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-region-list/components/region-list-table/tax-region-list-table.tsx b/packages/admin-next/dashboard/src/routes/taxes/tax-region-list/components/region-list-table/tax-region-list-table.tsx deleted file mode 100644 index 8e3fd89487..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-region-list/components/region-list-table/tax-region-list-table.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { Trash } from "@medusajs/icons" -import { AdminTaxRegionResponse } from "@medusajs/types" -import { Button, Container, Heading, Text } from "@medusajs/ui" -import { createColumnHelper } from "@tanstack/react-table" -import { t } from "i18next" -import { useMemo } from "react" -import { useTranslation } from "react-i18next" -import { Link, useNavigate } from "react-router-dom" -import { ActionMenu } from "../../../../../components/common/action-menu" -import { DataTable } from "../../../../../components/table/data-table" -import { - useDeleteTaxRegion, - useTaxRegions, -} from "../../../../../hooks/api/tax-regions" -import { useTaxRegionTableQuery } from "../../../../../hooks/table/query/use-tax-region-table-query copy" -import { useDataTable } from "../../../../../hooks/use-data-table" -import { getCountryByIso2 } from "../../../../../lib/countries" - -const PAGE_SIZE = 20 - -export const TaxRegionListTable = () => { - const { t } = useTranslation() - - const { searchParams, raw } = useTaxRegionTableQuery({ - pageSize: PAGE_SIZE, - }) - const { tax_regions, count, isLoading, isError, error } = useTaxRegions({ - ...searchParams, - parent_id: "null", - }) - - const columns = useColumns() - - const { table } = useDataTable({ - data: tax_regions ?? [], - columns, - count, - enablePagination: true, - getRowId: (row) => row.id, - pageSize: PAGE_SIZE, - }) - - if (isError) { - throw error - } - - return ( - -
    -
    - {t("taxes.domain")} - - {t("taxRegions.subtitle")} - -
    - -
    - `${row.original.id}`} - pagination - queryObject={raw} - /> -
    - ) -} - -const TaxRegionActions = ({ - taxRegion, -}: { - taxRegion: AdminTaxRegionResponse["tax_region"] -}) => { - const { t } = useTranslation() - const navigate = useNavigate() - const { mutateAsync } = useDeleteTaxRegion(taxRegion.id) - - const handleDelete = async () => { - await mutateAsync(undefined, { - onSuccess: () => { - navigate("/settings/taxes", { replace: true }) - }, - }) - } - - return ( - , - label: t("actions.delete"), - onClick: handleDelete, - }, - ], - }, - ]} - /> - ) -} - -const columnHelper = createColumnHelper() - -const useColumns = () => { - return useMemo( - () => [ - columnHelper.accessor("country_code", { - header: t("fields.country"), - cell: ({ getValue }) => { - const countryCode = getValue() - const displayName = - getCountryByIso2(countryCode)?.display_name || countryCode - - return ( -
    - {displayName} -
    - ) - }, - }), - columnHelper.display({ - id: "actions", - cell: ({ row }) => { - return - }, - }), - ], - [t] - ) -} diff --git a/packages/admin-next/dashboard/src/routes/taxes/tax-region-list/tax-region-list.tsx b/packages/admin-next/dashboard/src/routes/taxes/tax-region-list/tax-region-list.tsx deleted file mode 100644 index 63f1efc7a8..0000000000 --- a/packages/admin-next/dashboard/src/routes/taxes/tax-region-list/tax-region-list.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Outlet } from "react-router-dom" -import { TaxRegionListTable } from "./components/region-list-table" - -import after from "virtual:medusa/widgets/tax/list/after" -import before from "virtual:medusa/widgets/tax/list/before" - -export const TaxRegionsList = () => { - return ( -
    - {before.widgets.map((w, i) => { - return ( -
    - -
    - ) - })} - - {after.widgets.map((w, i) => { - return ( -
    - -
    - ) - })} - -
    - ) -} diff --git a/packages/core/js-sdk/src/admin/customer.ts b/packages/core/js-sdk/src/admin/customer.ts index bc7da8b45f..6d4d37b0c5 100644 --- a/packages/core/js-sdk/src/admin/customer.ts +++ b/packages/core/js-sdk/src/admin/customer.ts @@ -47,7 +47,7 @@ export class Customer { } async list( - queryParams?: FindParams & HttpTypes.AdminCollectionFilters, + queryParams?: FindParams & HttpTypes.AdminCustomerFilters, headers?: ClientHeaders ) { return this.client.fetch< diff --git a/packages/core/js-sdk/src/admin/index.ts b/packages/core/js-sdk/src/admin/index.ts index 3f95ed5e1c..006eaa09ae 100644 --- a/packages/core/js-sdk/src/admin/index.ts +++ b/packages/core/js-sdk/src/admin/index.ts @@ -11,6 +11,7 @@ import { PricePreference } from "./price-preference" import { Product } from "./product" import { ProductCategory } from "./product-category" import { ProductCollection } from "./product-collection" +import { ProductTag } from "./product-tag" import { ProductType } from "./product-type" import { Region } from "./region" import { SalesChannel } from "./sales-channel" @@ -45,6 +46,7 @@ export class Admin { public taxRate: TaxRate public taxRegion: TaxRegion public store: Store + public productTag: ProductTag constructor(client: Client) { this.invite = new Invite(client) @@ -69,5 +71,6 @@ export class Admin { this.taxRate = new TaxRate(client) this.taxRegion = new TaxRegion(client) this.store = new Store(client) + this.productTag = new ProductTag(client) } } diff --git a/packages/core/js-sdk/src/admin/product-collection.ts b/packages/core/js-sdk/src/admin/product-collection.ts index 2c1bd9815d..dfd465460f 100644 --- a/packages/core/js-sdk/src/admin/product-collection.ts +++ b/packages/core/js-sdk/src/admin/product-collection.ts @@ -1,10 +1,4 @@ -import { - DeleteResponse, - FindParams, - HttpTypes, - PaginatedResponse, - SelectParams, -} from "@medusajs/types" +import { HttpTypes } from "@medusajs/types" import { Client } from "../client" import { ClientHeaders } from "../types" @@ -16,10 +10,10 @@ export class ProductCollection { async create( body: HttpTypes.AdminCreateCollection, - query?: SelectParams, + query?: HttpTypes.AdminCollectionParams, headers?: ClientHeaders ) { - return this.client.fetch<{ collection: HttpTypes.AdminCollection }>( + return this.client.fetch( `/admin/collections`, { method: "POST", @@ -32,10 +26,10 @@ export class ProductCollection { async update( id: string, body: HttpTypes.AdminUpdateCollection, - query?: SelectParams, + query?: HttpTypes.AdminCollectionParams, headers?: ClientHeaders ) { - return this.client.fetch<{ collection: HttpTypes.AdminCollection }>( + return this.client.fetch( `/admin/collections/${id}`, { method: "POST", @@ -46,17 +40,25 @@ export class ProductCollection { ) } - async list(queryParams?: FindParams, headers?: ClientHeaders) { - return this.client.fetch< - PaginatedResponse<{ collections: HttpTypes.AdminCollection[] }> - >(`/admin/collections`, { - headers, - query: queryParams, - }) + async list( + queryParams?: HttpTypes.AdminCollectionListParams, + headers?: ClientHeaders + ) { + return this.client.fetch( + `/admin/collections`, + { + headers, + query: queryParams, + } + ) } - async retrieve(id: string, query?: SelectParams, headers?: ClientHeaders) { - return this.client.fetch<{ collection: HttpTypes.AdminCollection }>( + async retrieve( + id: string, + query?: HttpTypes.AdminCollectionParams, + headers?: ClientHeaders + ) { + return this.client.fetch( `/admin/collections/${id}`, { query, @@ -66,7 +68,7 @@ export class ProductCollection { } async delete(id: string, headers?: ClientHeaders) { - return this.client.fetch>( + return this.client.fetch( `/admin/collections/${id}`, { method: "DELETE", @@ -80,7 +82,7 @@ export class ProductCollection { body: HttpTypes.AdminUpdateCollectionProducts, headers?: ClientHeaders ) { - return this.client.fetch<{ collection: HttpTypes.AdminCollection }>( + return this.client.fetch( `/admin/collections/${id}/products`, { method: "POST", diff --git a/packages/core/js-sdk/src/admin/product-tag.ts b/packages/core/js-sdk/src/admin/product-tag.ts new file mode 100644 index 0000000000..5ac3e23f30 --- /dev/null +++ b/packages/core/js-sdk/src/admin/product-tag.ts @@ -0,0 +1,80 @@ +import { HttpTypes } from "@medusajs/types" +import { Client } from "../client" +import { ClientHeaders } from "../types" + +export class ProductTag { + private client: Client + constructor(client: Client) { + this.client = client + } + + async create( + body: HttpTypes.AdminCreateProductTag, + query?: HttpTypes.AdminProductTagParams, + headers?: ClientHeaders + ) { + return this.client.fetch( + `/admin/product-tags`, + { + method: "POST", + headers, + body, + query, + } + ) + } + + async update( + id: string, + body: HttpTypes.AdminUpdateProductTag, + query?: HttpTypes.AdminProductTagParams, + headers?: ClientHeaders + ) { + return this.client.fetch( + `/admin/product-tags/${id}`, + { + method: "POST", + headers, + body, + query, + } + ) + } + + async list( + query?: HttpTypes.AdminProductTagListParams, + headers?: ClientHeaders + ) { + return this.client.fetch( + `/admin/product-tags`, + { + headers, + query: query, + } + ) + } + + async retrieve( + id: string, + query?: HttpTypes.AdminProductTagParams, + headers?: ClientHeaders + ) { + return this.client.fetch( + `/admin/product-tags/${id}`, + { + query, + headers, + } + ) + } + + async delete(id: string, headers?: ClientHeaders) { + return this.client.fetch( + `/admin/product-tags/${id}`, + { + method: "DELETE", + headers, + } + ) + } +} diff --git a/packages/core/types/src/http/collection/admin.ts b/packages/core/types/src/http/collection/admin.ts deleted file mode 100644 index b419fbd50b..0000000000 --- a/packages/core/types/src/http/collection/admin.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { BaseCollection, BaseCollectionFilters } from "./common" - -export interface AdminCollection extends BaseCollection {} -export interface AdminCollectionFilters extends BaseCollectionFilters {} - -export interface AdminCreateCollection { - title: string - handle?: string - metadata?: Record -} -export interface AdminUpdateCollection extends Partial {} - -export interface AdminUpdateCollectionProducts { - add?: string[] - remove?: string[] -} diff --git a/packages/core/types/src/http/collection/admin/entities.ts b/packages/core/types/src/http/collection/admin/entities.ts new file mode 100644 index 0000000000..cc58ba91e4 --- /dev/null +++ b/packages/core/types/src/http/collection/admin/entities.ts @@ -0,0 +1,3 @@ +import { BaseCollection } from "../common" + +export interface AdminCollection extends BaseCollection {} diff --git a/packages/core/types/src/http/collection/admin/index.ts b/packages/core/types/src/http/collection/admin/index.ts new file mode 100644 index 0000000000..1f82a2ead5 --- /dev/null +++ b/packages/core/types/src/http/collection/admin/index.ts @@ -0,0 +1,4 @@ +export * from "./entities" +export * from "./payloads" +export * from "./queries" +export * from "./responses" diff --git a/packages/core/types/src/http/collection/admin/payloads.ts b/packages/core/types/src/http/collection/admin/payloads.ts new file mode 100644 index 0000000000..06d47bd681 --- /dev/null +++ b/packages/core/types/src/http/collection/admin/payloads.ts @@ -0,0 +1,16 @@ +export interface AdminCreateCollection { + title: string + handle?: string + metadata?: Record +} + +export interface AdminUpdateCollection { + title?: string + handle?: string + metadata?: Record +} + +export interface AdminUpdateCollectionProducts { + add?: string[] + remove?: string[] +} diff --git a/packages/core/types/src/http/collection/admin/queries.ts b/packages/core/types/src/http/collection/admin/queries.ts new file mode 100644 index 0000000000..33660d5ec8 --- /dev/null +++ b/packages/core/types/src/http/collection/admin/queries.ts @@ -0,0 +1,7 @@ +import { OperatorMap } from "../../../dal" +import { BaseCollectionListParams, BaseCollectionParams } from "../common" + +export interface AdminCollectionListParams extends BaseCollectionListParams { + deleted_at?: OperatorMap +} +export interface AdminCollectionParams extends BaseCollectionParams {} diff --git a/packages/core/types/src/http/collection/admin/responses.ts b/packages/core/types/src/http/collection/admin/responses.ts new file mode 100644 index 0000000000..ab5fbc18b5 --- /dev/null +++ b/packages/core/types/src/http/collection/admin/responses.ts @@ -0,0 +1,14 @@ +import { DeleteResponse, PaginatedResponse } from "../../common" +import { AdminCollection } from "./entities" + +export interface AdminCollectionResponse { + collection: AdminCollection +} + +export interface AdminCollectionListResponse + extends PaginatedResponse<{ + collections: AdminCollection[] + }> {} + +export interface AdminCollectionDeleteResponse + extends DeleteResponse<"collection"> {} diff --git a/packages/core/types/src/http/collection/common.ts b/packages/core/types/src/http/collection/common.ts index e48ad13b65..3bc6e82ce3 100644 --- a/packages/core/types/src/http/collection/common.ts +++ b/packages/core/types/src/http/collection/common.ts @@ -1,24 +1,27 @@ import { BaseFilterable, OperatorMap } from "../../dal" +import { FindParams, SelectParams } from "../common" import { AdminProduct } from "../product" export interface BaseCollection { - id?: string - title?: string - handle?: string - created_at?: string - updated_at?: string - deleted_at?: string | null + id: string + title: string + handle: string + created_at: string + updated_at: string + deleted_at: string | null products?: AdminProduct[] - metadata?: Record | null + metadata: Record | null } -export interface BaseCollectionFilters - extends BaseFilterable { +export interface BaseCollectionParams extends SelectParams {} + +export interface BaseCollectionListParams + extends FindParams, + BaseFilterable { q?: string id?: string | string[] handle?: string | string[] title?: string | string[] created_at?: OperatorMap updated_at?: OperatorMap - deleted_at?: OperatorMap } diff --git a/packages/core/types/src/http/collection/store.ts b/packages/core/types/src/http/collection/store.ts index f653b0d91a..f9f1cd55c7 100644 --- a/packages/core/types/src/http/collection/store.ts +++ b/packages/core/types/src/http/collection/store.ts @@ -1,4 +1,4 @@ -import { BaseCollection, BaseCollectionFilters } from "./common" +import { BaseCollection, BaseCollectionListParams } from "./common" export interface StoreCollection extends BaseCollection {} -export interface StoreCollectionFilters extends BaseCollectionFilters {} +export interface StoreCollectionFilters extends BaseCollectionListParams {} diff --git a/packages/core/types/src/http/index.ts b/packages/core/types/src/http/index.ts index 894f7749ea..2ba9b976f5 100644 --- a/packages/core/types/src/http/index.ts +++ b/packages/core/types/src/http/index.ts @@ -18,6 +18,7 @@ export * from "./price-list" export * from "./pricing" export * from "./product" export * from "./product-category" +export * from "./product-tag" export * from "./product-type" export * from "./promotion" export * from "./region" diff --git a/packages/core/types/src/http/product-tag/admin/entities.ts b/packages/core/types/src/http/product-tag/admin/entities.ts new file mode 100644 index 0000000000..72c0c3393b --- /dev/null +++ b/packages/core/types/src/http/product-tag/admin/entities.ts @@ -0,0 +1,3 @@ +import { BaseProductTag } from "../common" + +export interface AdminProductTag extends BaseProductTag {} diff --git a/packages/core/types/src/http/product-tag/admin/index.ts b/packages/core/types/src/http/product-tag/admin/index.ts new file mode 100644 index 0000000000..1f82a2ead5 --- /dev/null +++ b/packages/core/types/src/http/product-tag/admin/index.ts @@ -0,0 +1,4 @@ +export * from "./entities" +export * from "./payloads" +export * from "./queries" +export * from "./responses" diff --git a/packages/core/types/src/http/product-tag/admin/payloads.ts b/packages/core/types/src/http/product-tag/admin/payloads.ts new file mode 100644 index 0000000000..a7d0d5587d --- /dev/null +++ b/packages/core/types/src/http/product-tag/admin/payloads.ts @@ -0,0 +1,9 @@ +export interface AdminCreateProductTag { + value: string + metadata?: Record | null +} + +export interface AdminUpdateProductTag { + value?: string + metadata?: Record | null +} diff --git a/packages/core/types/src/http/product-tag/admin/queries.ts b/packages/core/types/src/http/product-tag/admin/queries.ts new file mode 100644 index 0000000000..c10e7454fa --- /dev/null +++ b/packages/core/types/src/http/product-tag/admin/queries.ts @@ -0,0 +1,11 @@ +import { BaseFilterable, OperatorMap } from "../../../dal" +import { SelectParams } from "../../common" +import { BaseProductTagListParams } from "../common" + +export interface AdminProductTagListParams + extends BaseProductTagListParams, + BaseFilterable { + deleted_at?: OperatorMap +} + +export interface AdminProductTagParams extends SelectParams {} diff --git a/packages/core/types/src/http/product-tag/admin/responses.ts b/packages/core/types/src/http/product-tag/admin/responses.ts new file mode 100644 index 0000000000..2b629459d8 --- /dev/null +++ b/packages/core/types/src/http/product-tag/admin/responses.ts @@ -0,0 +1,14 @@ +import { DeleteResponse, PaginatedResponse } from "../../common" +import { AdminProductTag } from "./entities" + +export interface AdminProductTagResponse { + product_tag: AdminProductTag +} + +export interface AdminProductTagListResponse + extends PaginatedResponse<{ + product_tags: AdminProductTag[] + }> {} + +export interface AdminProductTagDeleteResponse + extends DeleteResponse<"product_tag"> {} diff --git a/packages/core/types/src/http/product-tag/common.ts b/packages/core/types/src/http/product-tag/common.ts new file mode 100644 index 0000000000..1fa294c88c --- /dev/null +++ b/packages/core/types/src/http/product-tag/common.ts @@ -0,0 +1,19 @@ +import { OperatorMap } from "../../dal" +import { FindParams } from "../common" + +export interface BaseProductTag { + id: string + value: string + created_at: string + updated_at: string + deleted_at?: string | null + metadata?: Record | null +} + +export interface BaseProductTagListParams extends FindParams { + q?: string + id?: string | string[] + value?: string | string[] + created_at?: OperatorMap + updated_at?: OperatorMap +} diff --git a/packages/core/types/src/http/product-tag/index.ts b/packages/core/types/src/http/product-tag/index.ts new file mode 100644 index 0000000000..3bd2bd2cc0 --- /dev/null +++ b/packages/core/types/src/http/product-tag/index.ts @@ -0,0 +1,2 @@ +export * from "./admin" +export * from "./store" diff --git a/packages/core/types/src/http/product-tag/store/entities.ts b/packages/core/types/src/http/product-tag/store/entities.ts new file mode 100644 index 0000000000..9a1a0266fe --- /dev/null +++ b/packages/core/types/src/http/product-tag/store/entities.ts @@ -0,0 +1,3 @@ +import { BaseProductTag } from "../common" + +export interface StoreProductTag extends BaseProductTag {} diff --git a/packages/core/types/src/http/product-tag/store/index.ts b/packages/core/types/src/http/product-tag/store/index.ts new file mode 100644 index 0000000000..29057d02ce --- /dev/null +++ b/packages/core/types/src/http/product-tag/store/index.ts @@ -0,0 +1,2 @@ +export * from "./entities" +export * from "./queries" diff --git a/packages/core/types/src/http/product-tag/store/queries.ts b/packages/core/types/src/http/product-tag/store/queries.ts new file mode 100644 index 0000000000..c2a25ccb70 --- /dev/null +++ b/packages/core/types/src/http/product-tag/store/queries.ts @@ -0,0 +1,9 @@ +import { BaseFilterable } from "../../../dal" +import { SelectParams } from "../../common" +import { BaseProductTagListParams } from "../common" + +export interface StoreProductTagListParams + extends BaseProductTagListParams, + BaseFilterable {} + +export interface StoreProductTagParams extends SelectParams {} diff --git a/packages/core/types/src/http/product-type/common.ts b/packages/core/types/src/http/product-type/common.ts index fc7eac0758..116ddd5b85 100644 --- a/packages/core/types/src/http/product-type/common.ts +++ b/packages/core/types/src/http/product-type/common.ts @@ -1,8 +1,8 @@ export interface BaseProductType { id: string value: string - created_at?: string - updated_at?: string + created_at: string + updated_at: string deleted_at?: string | null metadata?: Record | null } diff --git a/packages/core/types/src/http/product/admin/entitites.ts b/packages/core/types/src/http/product/admin/entitites.ts index 3d9eb491c8..e18694d9ac 100644 --- a/packages/core/types/src/http/product/admin/entitites.ts +++ b/packages/core/types/src/http/product/admin/entitites.ts @@ -1,6 +1,7 @@ import { AdminCollection } from "../../collection" import { AdminPrice } from "../../pricing" import { AdminProductCategory } from "../../product-category" +import { AdminProductTag } from "../../product-tag" import { AdminProductType } from "../../product-type" import { AdminSalesChannel } from "../../sales-channel" import { @@ -8,7 +9,6 @@ import { BaseProductImage, BaseProductOption, BaseProductOptionValue, - BaseProductTag, BaseProductVariant, ProductStatus, } from "../common" @@ -16,7 +16,6 @@ import { export interface AdminProductVariant extends BaseProductVariant { prices: AdminPrice[] | null } -export interface AdminProductTag extends BaseProductTag {} export interface AdminProductOption extends BaseProductOption {} export interface AdminProductImage extends BaseProductImage {} export interface AdminProductOptionValue extends BaseProductOptionValue {} @@ -27,5 +26,6 @@ export interface AdminProduct sales_channels?: AdminSalesChannel[] | null variants?: AdminProductVariant[] | null type: AdminProductType | null + tags?: AdminProductTag[] | null } export type AdminProductStatus = ProductStatus diff --git a/packages/core/types/src/http/product/admin/queries.ts b/packages/core/types/src/http/product/admin/queries.ts index 0f4c277bec..2b05df0b33 100644 --- a/packages/core/types/src/http/product/admin/queries.ts +++ b/packages/core/types/src/http/product/admin/queries.ts @@ -1,11 +1,9 @@ import { BaseProductListParams, BaseProductOptionParams, - BaseProductTagParams, BaseProductVariantParams, } from "../common" -export interface AdminProductTagParams extends BaseProductTagParams {} export interface AdminProductOptionParams extends BaseProductOptionParams {} export interface AdminProductVariantParams extends BaseProductVariantParams {} export interface AdminProductListParams extends BaseProductListParams { diff --git a/packages/core/types/src/http/product/common.ts b/packages/core/types/src/http/product/common.ts index 8727cbcca0..2506126fab 100644 --- a/packages/core/types/src/http/product/common.ts +++ b/packages/core/types/src/http/product/common.ts @@ -3,6 +3,7 @@ import { BaseCollection } from "../collection/common" import { FindParams } from "../common" import { BaseCalculatedPriceSet } from "../pricing/common" import { BaseProductCategory } from "../product-category/common" +import { BaseProductTag } from "../product-tag/common" import { BaseProductType } from "../product-type/common" export type ProductStatus = "draft" | "proposed" | "published" | "rejected" @@ -28,7 +29,7 @@ export interface BaseProduct { categories?: BaseProductCategory[] | null type?: BaseProductType | null type_id: string | null - tags: BaseProductTag[] | null + tags?: BaseProductTag[] | null variants: BaseProductVariant[] | null options: BaseProductOption[] | null images: BaseProductImage[] | null @@ -68,13 +69,6 @@ export interface BaseProductVariant { metadata?: Record | null } -export interface BaseProductTag { - id: string - value: string - products?: BaseProduct[] - metadata?: Record | null -} - export interface BaseProductOption { id: string title: string @@ -129,14 +123,6 @@ export interface BaseProductListParams deleted_at?: OperatorMap } -export interface BaseProductTagParams - extends FindParams, - BaseFilterable { - q?: string - id?: string | string[] - value?: string | string[] -} - export interface BaseProductOptionParams extends FindParams, BaseFilterable { diff --git a/packages/core/types/src/http/product/store/entitites.ts b/packages/core/types/src/http/product/store/entitites.ts index 1e27d066b8..0ba2f3f1bf 100644 --- a/packages/core/types/src/http/product/store/entitites.ts +++ b/packages/core/types/src/http/product/store/entitites.ts @@ -5,7 +5,6 @@ import { BaseProductImage, BaseProductOption, BaseProductOptionValue, - BaseProductTag, BaseProductVariant, ProductStatus, } from "../common" @@ -15,7 +14,6 @@ export interface StoreProduct extends Omit { type?: StoreProductType | null } export interface StoreProductVariant extends BaseProductVariant {} -export interface StoreProductTag extends BaseProductTag {} export interface StoreProductOption extends BaseProductOption {} export interface StoreProductImage extends BaseProductImage {} export interface StoreProductOptionValue extends BaseProductOptionValue {} diff --git a/packages/core/types/src/http/product/store/queries.ts b/packages/core/types/src/http/product/store/queries.ts index a2a866ac8c..ff3b429980 100644 --- a/packages/core/types/src/http/product/store/queries.ts +++ b/packages/core/types/src/http/product/store/queries.ts @@ -1,11 +1,9 @@ import { BaseProductListParams, BaseProductOptionParams, - BaseProductTagParams, BaseProductVariantParams, } from "../common" -export interface StoreProductTagParams extends BaseProductTagParams {} export interface StoreProductOptionParams extends BaseProductOptionParams {} export interface StoreProductVariantParams extends BaseProductVariantParams {} export interface StoreProductParams extends BaseProductListParams { diff --git a/packages/core/types/src/http/tax-rate/admin/payloads.ts b/packages/core/types/src/http/tax-rate/admin/payloads.ts index 7aa2ba61de..16685ac684 100644 --- a/packages/core/types/src/http/tax-rate/admin/payloads.ts +++ b/packages/core/types/src/http/tax-rate/admin/payloads.ts @@ -17,7 +17,7 @@ export interface AdminCreateTaxRate { export interface AdminUpdateTaxRate { name?: string rate?: number - code?: string + code?: string | null rules?: AdminCreateTaxRateRule[] is_default?: boolean is_combinable?: boolean diff --git a/packages/core/types/src/http/tax-region/admin/entities.ts b/packages/core/types/src/http/tax-region/admin/entities.ts index 84ba95fde5..58f25a4d0d 100644 --- a/packages/core/types/src/http/tax-region/admin/entities.ts +++ b/packages/core/types/src/http/tax-region/admin/entities.ts @@ -17,5 +17,6 @@ export interface AdminTaxRegion { deleted_at: string | null created_by: string | null tax_rates: AdminTaxRate[] - parent: AdminTaxRegion + parent: AdminTaxRegion | null + children: AdminTaxRegion[] } diff --git a/packages/core/types/src/http/tax-region/admin/queries.ts b/packages/core/types/src/http/tax-region/admin/queries.ts index 9cad0b807d..891b4bdac6 100644 --- a/packages/core/types/src/http/tax-region/admin/queries.ts +++ b/packages/core/types/src/http/tax-region/admin/queries.ts @@ -1,7 +1,9 @@ -import { OperatorMap } from "../../../dal" -import { FindParams } from "../../common" +import { BaseFilterable, OperatorMap } from "../../../dal" +import { FindParams, SelectParams } from "../../common" -export interface AdminTaxRegionListParams extends FindParams { +export interface AdminTaxRegionListParams + extends FindParams, + BaseFilterable { id?: string | string[] q?: string parent_id?: string | string[] | OperatorMap @@ -12,3 +14,5 @@ export interface AdminTaxRegionListParams extends FindParams { deleted_at?: string | OperatorMap created_by?: string | OperatorMap } + +export interface AdminTaxRegionParams extends SelectParams {} diff --git a/packages/design-system/ui/package.json b/packages/design-system/ui/package.json index 6b0ac8613a..fecc9a4c8b 100644 --- a/packages/design-system/ui/package.json +++ b/packages/design-system/ui/package.json @@ -99,7 +99,6 @@ "clsx": "^1.2.1", "copy-to-clipboard": "^3.3.3", "cva": "1.0.0-beta.1", - "date-fns": "^2.30.0", "prism-react-renderer": "^2.0.6", "prismjs": "^1.29.0", "react-aria": "^3.33.1", diff --git a/packages/design-system/ui/src/components/alert/alert.tsx b/packages/design-system/ui/src/components/alert/alert.tsx index 132368d4b9..9b6a270205 100644 --- a/packages/design-system/ui/src/components/alert/alert.tsx +++ b/packages/design-system/ui/src/components/alert/alert.tsx @@ -58,7 +58,7 @@ export const Alert = React.forwardRef(
    cva({ base: clx( - "bg-ui-bg-field shadow-borders-base txt-compact-small text-ui-fg-base transition-fg grid items-center gap-2 overflow-hidden rounded-md", + "bg-ui-bg-field shadow-borders-base txt-compact-small text-ui-fg-base transition-fg grid items-center gap-2 overflow-hidden rounded-md h-fit", "focus-within:shadow-borders-interactive-with-active focus-visible:shadow-borders-interactive-with-active", "aria-[invalid=true]:shadow-borders-error invalid:shadow-borders-error", { diff --git a/packages/medusa/src/api/admin/tax-rates/validators.ts b/packages/medusa/src/api/admin/tax-rates/validators.ts index 2ac7d869b6..8a81beca49 100644 --- a/packages/medusa/src/api/admin/tax-rates/validators.ts +++ b/packages/medusa/src/api/admin/tax-rates/validators.ts @@ -48,7 +48,7 @@ export const AdminCreateTaxRate = z.object({ export type AdminUpdateTaxRateType = z.infer export const AdminUpdateTaxRate = z.object({ rate: z.number().optional(), - code: z.string().optional(), + code: z.string().nullish(), rules: z.array(AdminCreateTaxRateRule).optional(), name: z.string().optional(), is_default: z.boolean().optional(), diff --git a/packages/medusa/src/api/admin/tax-regions/validators.ts b/packages/medusa/src/api/admin/tax-regions/validators.ts index 170f342e90..52602266f4 100644 --- a/packages/medusa/src/api/admin/tax-regions/validators.ts +++ b/packages/medusa/src/api/admin/tax-regions/validators.ts @@ -48,9 +48,7 @@ export const AdminCreateTaxRegion = z.object({ rate: z.number().optional(), code: z.string().optional(), name: z.string(), - is_combinable: z - .union([z.literal("true"), z.literal("false")]) - .optional(), + is_combinable: z.boolean().optional(), metadata: z.record(z.unknown()).nullish(), }) .optional(), diff --git a/yarn.lock b/yarn.lock index 149cd3cb31..0ca600cd4f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5257,7 +5257,6 @@ __metadata: clsx: ^1.2.1 copy-to-clipboard: ^3.3.3 cva: 1.0.0-beta.1 - date-fns: ^2.30.0 eslint: ^7.32.0 eslint-plugin-storybook: ^0.6.12 jsdom: ^22.1.0 @@ -15918,15 +15917,6 @@ __metadata: languageName: node linkType: hard -"date-fns@npm:^2.30.0": - version: 2.30.0 - resolution: "date-fns@npm:2.30.0" - dependencies: - "@babel/runtime": ^7.21.0 - checksum: e4b521fbf22bc8c3db332bbfb7b094fd3e7627de0259a9d17c7551e2d2702608a7307a449206065916538e384f37b181565447ce2637ae09828427aed9cb5581 - languageName: node - linkType: hard - "date-fns@npm:^3.6.0": version: 3.6.0 resolution: "date-fns@npm:3.6.0"