diff --git a/packages/admin-next/dashboard/public/locales/en-US/translation.json b/packages/admin-next/dashboard/public/locales/en-US/translation.json index 89eb9c41db18a..3fb5f382cad5e 100644 --- a/packages/admin-next/dashboard/public/locales/en-US/translation.json +++ b/packages/admin-next/dashboard/public/locales/en-US/translation.json @@ -62,6 +62,7 @@ }, "actions": { "save": "Save", + "select": "Select", "saveAsDraft": "Save as draft", "publish": "Publish", "create": "Create", @@ -653,8 +654,8 @@ } }, "shipping": { - "title": "Location & Shipping", - "domain": "Location & Shipping", + "title": "Locations & Shipping", + "domain": "Locations & Shipping", "description": "Choose where you ship and how much you charge for shipping at checkout. Define shipping options specific for your locations.", "createLocation": "Create location", "createLocationDetailsHint": "Specify the details of the location.", @@ -714,10 +715,12 @@ "edit": { "title": "Edit Service Zone" }, + "editAreasTitle": "Manage {{zone}} areas", "deleteWarning": "Are you sure you want to delete \"{{name}}\". This will also delete all assocciated shipping options.", "toast": { "delete": "Zone \"{{name}}\" deleted successfully." }, + "manageAreas": "Manage areas", "editPrices": "Edit prices", "editOption": "Edit option", "optionsLength_one": "shipping option", @@ -745,8 +748,8 @@ "allocation": "Shipping amount", "fixed": "Fixed", "fixedDescription": "Shipping option's price is always the same amount.", - "enable": "Enable in store", - "enableDescription": "Enable or disable the shipping option visiblity in store", + "enable": "Show publicly", + "enableDescription": "When disabled, the shipping option can only be applied by admins.", "calculated": "Calculated", "calculatedDescription": "Shipping option's price is calculated by the fulfillment provider.", "profile": "Shipping profile" diff --git a/packages/admin-next/dashboard/src/hooks/api/stock-locations.tsx b/packages/admin-next/dashboard/src/hooks/api/stock-locations.tsx index 555f94b40b27b..9683d301b5830 100644 --- a/packages/admin-next/dashboard/src/hooks/api/stock-locations.tsx +++ b/packages/admin-next/dashboard/src/hooks/api/stock-locations.tsx @@ -41,7 +41,7 @@ export const useStockLocation = ( ) => { const { data, ...rest } = useQuery({ queryFn: () => client.stockLocations.retrieve(id, query), - queryKey: stockLocationsQueryKeys.detail(id, query), + queryKey: stockLocationsQueryKeys.details(), ...options, }) 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 0ddf799cfcb9b..a365d8f5f19b0 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 @@ -744,6 +744,13 @@ export const RouteMap: RouteObject[] = [ "../../v2-routes/shipping/service-zone-edit" ), }, + { + path: "edit-areas", + lazy: () => + import( + "../../v2-routes/shipping/service-zone-areas-edit" + ), + }, { path: "shipping-option", children: [ @@ -764,6 +771,13 @@ export const RouteMap: RouteObject[] = [ "../../v2-routes/shipping/shipping-option-edit" ), }, + { + path: "edit-pricing", + lazy: () => + import( + "../../v2-routes/shipping/shipping-options-edit-pricing" + ), + }, ], }, ], diff --git a/packages/admin-next/dashboard/src/v2-routes/shipping/location-details/components/location-general-section/location-general-section.tsx b/packages/admin-next/dashboard/src/v2-routes/shipping/location-details/components/location-general-section/location-general-section.tsx index 10ad9d22cd88a..1d1827a234f27 100644 --- a/packages/admin-next/dashboard/src/v2-routes/shipping/location-details/components/location-general-section/location-general-section.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/shipping/location-details/components/location-general-section/location-general-section.tsx @@ -156,7 +156,7 @@ function ShippingOption({ { label: t("shipping.serviceZone.editPrices"), icon: , - disabled: true, + to: `/settings/shipping/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${option.service_zone_id}/shipping-option/${option.id}/edit-pricing`, }, { label: t("actions.delete"), @@ -199,7 +199,7 @@ function ServiceZoneOptions({ {t("shipping.serviceZone.shippingOptions")} + + + + ) +} + +const EditeServiceZoneSchema = zod.object({ + countries: zod.array(zod.string().length(2)).min(1), +}) + +type EditServiceZoneAreasFormProps = { + fulfillmentSetId: string + locationId: string + zone: ServiceZoneDTO +} + +export function EditServiceZoneAreasForm({ + fulfillmentSetId, + locationId, + zone, +}: EditServiceZoneAreasFormProps) { + const { t } = useTranslation() + const { handleSuccess } = useRouteModal() + const [open, setOpen] = useState(false) + const [rowSelection, setRowSelection] = useState( + zone.geo_zones + .map((z) => z.country_code) + .reduce((acc, v) => { + acc[v] = true + return acc + }, {}) + ) + + const form = useForm>({ + defaultValues: { + countries: zone.geo_zones.map((z) => z.country_code), + }, + resolver: zodResolver(EditeServiceZoneSchema), + }) + + const { mutateAsync: editServiceZone, isPending: isLoading } = + useUpdateServiceZone(fulfillmentSetId, zone.id, locationId) + + const handleSubmit = form.handleSubmit(async (data) => { + try { + await editServiceZone({ + geo_zones: data.countries.map((iso2) => ({ + country_code: iso2, + type: "country", + })), + }) + } catch (e) { + toast.error(t("general.error"), { + description: e.message, + dismissLabel: t("general.close"), + }) + } + + handleSuccess() + }) + + const handleOpenChange = (open: boolean) => { + setOpen(open) + } + + const { searchParams, raw } = useCountryTableQuery({ + pageSize: PAGE_SIZE, + prefix: PREFIX, + }) + const { countries, count } = useCountries({ + countries: staticCountries.map((c, i) => ({ + display_name: c.display_name, + name: c.name, + id: i as any, + iso_2: c.iso_2, + iso_3: c.iso_3, + num_code: c.num_code, + region_id: null, + region: {} as RegionDTO, + })), + ...searchParams, + }) + + const columns = useColumns() + + const { table } = useDataTable({ + data: countries || [], + columns, + count, + enablePagination: true, + enableRowSelection: true, + getRowId: (row) => row.iso_2, + pageSize: PAGE_SIZE, + rowSelection: { + state: rowSelection, + updater: setRowSelection, + }, + prefix: PREFIX, + }) + + const countriesWatch = form.watch("countries") + + const onCountriesSave = () => { + form.setValue("countries", Object.keys(rowSelection)) + setOpen(false) + } + + const removeCountry = (iso2: string) => { + const state = { ...rowSelection } + delete state[iso2] + setRowSelection(state) + + form.setValue( + "countries", + countriesWatch.filter((c) => c !== iso2) + ) + } + + const clearAll = () => { + setRowSelection({}) + form.setValue("countries", []) + } + + const selectedCountries = useMemo(() => { + return staticCountries.filter((c) => c.iso_2 in rowSelection) + }, [countriesWatch]) + + useEffect(() => { + // set selected rows from form state on open + if (open) { + setRowSelection( + countriesWatch.reduce((acc, c) => { + acc[c] = true + return acc + }, {}) + ) + } + }, [open]) + + const showAreasError = + form.formState.errors["countries"]?.type === "too_small" + + return ( + +
+ +
+ + + + +
+
+ + + + +
+ + {t("shipping.serviceZone.editAreasTitle", { + zone: zone.name, + })} + +
+ +
+
+ + {t("shipping.serviceZone.areas.title")} + + + {t("shipping.serviceZone.areas.description")} + +
+ +
+ {!!selectedCountries.length && ( +
+ {selectedCountries.map((c) => ( + + {c.display_name} + removeCountry(c.iso_2)} + className="text-ui-fg-subtle p-0 px-1 pt-[1px]" + variant="transparent" + > + + + + ))} + +
+ )} + {showAreasError && ( + + {t("shipping.serviceZone.areas.error")} + + )} +
+ +
+ + +
+
+
+
+
+
+ ) +} + +const columnHelper = createColumnHelper() + +const useColumns = () => { + const base = useCountryTableColumns() + + return useMemo( + () => [ + columnHelper.display({ + id: "select", + header: ({ table }) => { + return ( + + table.toggleAllPageRowsSelected(!!value) + } + /> + ) + }, + cell: ({ row }) => { + const isPreselected = !row.getCanSelect() + + return ( + row.toggleSelected(!!value)} + onClick={(e) => { + e.stopPropagation() + }} + /> + ) + }, + }), + ...base, + ], + [base] + ) as ColumnDef[] +} diff --git a/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-areas-edit/components/edit-region-areas-form/index.ts b/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-areas-edit/components/edit-region-areas-form/index.ts new file mode 100644 index 0000000000000..522cccf066fa8 --- /dev/null +++ b/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-areas-edit/components/edit-region-areas-form/index.ts @@ -0,0 +1 @@ +export * from "./edit-service-zone-areas-form" diff --git a/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-areas-edit/index.ts b/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-areas-edit/index.ts new file mode 100644 index 0000000000000..fc088b9f88267 --- /dev/null +++ b/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-areas-edit/index.ts @@ -0,0 +1 @@ +export { ServiceZoneAreasEdit as Component } from "./service-zone-areas-edit" diff --git a/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-areas-edit/service-zone-areas-edit.tsx b/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-areas-edit/service-zone-areas-edit.tsx new file mode 100644 index 0000000000000..0bccfa6b4a5b4 --- /dev/null +++ b/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-areas-edit/service-zone-areas-edit.tsx @@ -0,0 +1,45 @@ +import { json, useParams } from "react-router-dom" + +import { RouteFocusModal } from "../../../components/route-modal" +import { EditServiceZoneAreasForm } from "./components/edit-region-areas-form" +import { useStockLocation } from "../../../hooks/api/stock-locations" + +export const ServiceZoneAreasEdit = () => { + const { location_id, fset_id, zone_id } = useParams() + + const { stock_location, isPending, isError, error } = useStockLocation( + location_id!, + { + // NOTE: use same query for all details page subroutes & fetches + fields: + "name,*sales_channels,address.city,address.country_code,fulfillment_sets.type,fulfillment_sets.name,*fulfillment_sets.service_zones.geo_zones,*fulfillment_sets.service_zones,*fulfillment_sets.service_zones.shipping_options,*fulfillment_sets.service_zones.shipping_options.rules,*fulfillment_sets.service_zones.shipping_options.shipping_profile", + } + ) + + const zone = stock_location?.fulfillment_sets + .find((f) => f.id === fset_id) + ?.service_zones.find((z) => z.id === zone_id) + + if (isError) { + throw error + } + + if (!isPending && !zone) { + throw json( + { message: `Service zone with ID ${zone_id} was not found` }, + 404 + ) + } + + return ( + + {!isPending && zone && ( + + )} + + ) +} diff --git a/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-create/components/create-service-zone-form/create-service-zone-form.tsx b/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-create/components/create-service-zone-form/create-service-zone-form.tsx index a29b5e412b387..3263a0f0e41ca 100644 --- a/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-create/components/create-service-zone-form/create-service-zone-form.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-create/components/create-service-zone-form/create-service-zone-form.tsx @@ -16,10 +16,11 @@ import { IconButton, Input, Text, + toast, } from "@medusajs/ui" import { FulfillmentSetDTO, RegionCountryDTO, RegionDTO } from "@medusajs/types" import { useTranslation } from "react-i18next" -import { Map, XMark, XMarkMini } from "@medusajs/icons" +import { XMarkMini } from "@medusajs/icons" import { RouteFocusModal, @@ -87,13 +88,20 @@ export function CreateServiceZoneForm({ useCreateServiceZone(locationId, fulfillmentSet.id) const handleSubmit = form.handleSubmit(async (data) => { - await createServiceZone({ - name: data.name, - geo_zones: data.countries.map((iso2) => ({ - country_code: iso2, - type: "country", - })), - }) + try { + await createServiceZone({ + name: data.name, + geo_zones: data.countries.map((iso2) => ({ + country_code: iso2, + type: "country", + })), + }) + } catch (e) { + toast.error(t("general.error"), { + description: e.message, + dismissLabel: t("general.close"), + }) + } handleSuccess() }) @@ -201,7 +209,7 @@ export function CreateServiceZoneForm({ -
+
{t("shipping.fulfillmentSet.create.title", { fulfillmentSet: fulfillmentSet.name, diff --git a/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-create/loader.ts b/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-create/loader.ts index d390458e11220..5d04be15b1794 100644 --- a/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-create/loader.ts +++ b/packages/admin-next/dashboard/src/v2-routes/shipping/service-zone-create/loader.ts @@ -6,7 +6,9 @@ import { StockLocationRes } from "../../../types/api-responses" import { stockLocationsQueryKeys } from "../../../hooks/api/stock-locations" const fulfillmentSetCreateQuery = (id: string) => ({ - queryKey: stockLocationsQueryKeys.detail(id), + queryKey: stockLocationsQueryKeys.detail(id, { + fields: "*fulfillment_sets", + }), queryFn: async () => client.stockLocations.retrieve(id, { fields: "*fulfillment_sets", diff --git a/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-create/components/create-shipping-options-form/create-shipping-options-form.tsx b/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-create/components/create-shipping-options-form/create-shipping-options-form.tsx index 4698810e5104b..87283058ec99c 100644 --- a/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-create/components/create-shipping-options-form/create-shipping-options-form.tsx +++ b/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-create/components/create-shipping-options-form/create-shipping-options-form.tsx @@ -45,7 +45,7 @@ type StepStatus = { [key in Tab]: ProgressStatus } -const CreateServiceZoneSchema = zod.object({ +const CreateShippingOptionSchema = zod.object({ name: zod.string().min(1), price_type: zod.nativeEnum(ShippingAllocation), enabled_in_store: zod.boolean().optional(), @@ -55,7 +55,7 @@ const CreateServiceZoneSchema = zod.object({ currency_prices: zod.record(zod.string(), zod.string().optional()), }) -type CreateServiceZoneFormProps = { +type CreateShippingOptionFormProps = { zone: ServiceZoneDTO isReturn?: boolean } @@ -63,7 +63,7 @@ type CreateServiceZoneFormProps = { export function CreateShippingOptionsForm({ zone, isReturn, -}: CreateServiceZoneFormProps) { +}: CreateShippingOptionFormProps) { const { t } = useTranslation() const { handleSuccess } = useRouteModal() const [tab, setTab] = React.useState(Tab.DETAILS) @@ -77,7 +77,7 @@ export function CreateShippingOptionsForm({ fields: "id,currency_code", }) - const form = useForm>({ + const form = useForm>({ defaultValues: { name: "", price_type: ShippingAllocation.FlatRate, @@ -87,7 +87,7 @@ export function CreateShippingOptionsForm({ region_prices: {}, currency_prices: {}, }, - resolver: zodResolver(CreateServiceZoneSchema), + resolver: zodResolver(CreateShippingOptionSchema), }) const isCalculatedPriceType = diff --git a/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-create/loader.ts b/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-create/loader.ts index 234ea0cb14038..ed7d531f00869 100644 --- a/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-create/loader.ts +++ b/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-create/loader.ts @@ -6,7 +6,10 @@ import { StockLocationRes } from "../../../types/api-responses" import { stockLocationsQueryKeys } from "../../../hooks/api/stock-locations" const fulfillmentSetCreateQuery = (id: string) => ({ - queryKey: stockLocationsQueryKeys.list(), // Use the list cache key for now + queryKey: stockLocationsQueryKeys.detail(id, { + fields: + "*fulfillment_sets,*fulfillment_sets.service_zones,*fulfillment_sets.service_zones.shipping_options", + }), queryFn: async () => client.stockLocations.retrieve(id, { fields: diff --git a/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-edit-pricing/components/create-shipping-options-form/edit-shipping-options-pricing-form.tsx b/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-edit-pricing/components/create-shipping-options-form/edit-shipping-options-pricing-form.tsx new file mode 100644 index 0000000000000..c5d0dbcc68256 --- /dev/null +++ b/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-edit-pricing/components/create-shipping-options-form/edit-shipping-options-pricing-form.tsx @@ -0,0 +1,319 @@ +import { useForm } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" +import React, { useEffect, useMemo, useState } from "react" +import * as zod from "zod" + +import { Button, toast } from "@medusajs/ui" +import { + CurrencyDTO, + PriceDTO, + ProductVariantDTO, + RegionDTO, + ShippingOptionDTO, +} from "@medusajs/types" +import { useTranslation } from "react-i18next" + +import { + RouteFocusModal, + useRouteModal, +} from "../../../../../components/route-modal" +import { + getDbAmount, + getPresentationalAmount, +} from "../../../../../lib/money-amount-helpers" +import { useRegions } from "../../../../../hooks/api/regions" +import { useStore } from "../../../../../hooks/api/store.tsx" +import { useCurrencies } from "../../../../../hooks/api/currencies" +import { ColumnDef, createColumnHelper } from "@tanstack/react-table" +import { ExtendedProductDTO } from "../../../../../types/api-responses" +import { CurrencyCell } from "../../../../../components/grid/grid-cells/common/currency-cell" +import { DataGridMeta } from "../../../../../components/grid/types" +import { DataGrid } from "../../../../../components/grid/data-grid" +import { useUpdateShippingOptions } from "../../../../../hooks/api/shipping-options.ts" + +const getInitialCurrencyPrices = (prices: PriceDTO[]) => { + const ret: Record = {} + prices.forEach((p) => { + if (p.price_rules!.length) { + // this is a region price + return + } + ret[p.currency_code!] = getPresentationalAmount( + p.amount as number, + p.currency_code! + ) + }) + return ret +} + +const getInitialRegionPrices = (prices: PriceDTO[]) => { + const ret: Record = {} + prices.forEach((p) => { + if (p.price_rules!.length) { + const regionId = p.price_rules![0].value + ret[regionId] = getPresentationalAmount( + p.amount as number, + p.currency_code! + ) + } + }) + + return ret +} + +const EditShippingOptionPricingSchema = zod.object({ + region_prices: zod.record( + zod.string(), + zod.string().or(zod.number()).optional() + ), + currency_prices: zod.record( + zod.string(), + zod.string().or(zod.number()).optional() + ), +}) + +enum ColumnType { + REGION = "region", + CURRENCY = "currency", +} + +type EnabledColumnRecord = Record + +type EditShippingOptionPricingFormProps = { + shippingOption: ShippingOptionDTO & { prices: PriceDTO[] } +} + +export function EditShippingOptionsPricingForm({ + shippingOption, +}: EditShippingOptionPricingFormProps) { + const { t } = useTranslation() + const { handleSuccess } = useRouteModal() + + const form = useForm>({ + defaultValues: { + region_prices: getInitialRegionPrices(shippingOption.prices), + currency_prices: getInitialCurrencyPrices(shippingOption.prices), + }, + resolver: zodResolver(EditShippingOptionPricingSchema), + }) + + const { mutateAsync, isPending: isLoading } = useUpdateShippingOptions( + shippingOption.id + ) + + const { regions } = useRegions() + + const { store, isLoading: isStoreLoading } = useStore() + + const { currencies, isLoading: isCurrenciesLoading } = useCurrencies( + { + code: store?.supported_currency_codes, + }, + { + enabled: !!store, + } + ) + + const [enabledColumns, setEnabledColumns] = useState({}) + + useEffect(() => { + if ( + store?.default_currency_code && + Object.keys(enabledColumns).length === 0 + ) { + setEnabledColumns({ + ...enabledColumns, + [store.default_currency_code]: ColumnType.CURRENCY, + }) + } + }, [store, enabledColumns]) + + const columns = useColumns({ + currencies, + regions, + }) + + const data = useMemo( + () => [[...(currencies || []), ...(regions || [])]], + [currencies, regions] + ) + + const handleSubmit = form.handleSubmit(async (data) => { + const currencyPrices = Object.entries(data.currency_prices) + .map(([code, value]) => { + if (value === "") { + return undefined + } + + const amount = getDbAmount(Number(value), code) + + const priceRecord = { + currency_code: code, + amount: amount, + } + + const price = shippingOption.prices.find( + (p) => p.currency_code === code && !p.price_rules!.length + ) + + // if that currency price is already defined for the SO, we will do an update + if (price) { + priceRecord["id"] = price.id + } + + return priceRecord + }) + .filter((p) => !!p) + + const regionsMap = new Map(regions.map((r) => [r.id, r.currency_code])) + + const regionPrices = Object.entries(data.region_prices) + .map(([region_id, value]) => { + if (value === "") { + return undefined + } + + const code = regionsMap.get(region_id)! + + const amount = getDbAmount(Number(value), code) + + const priceRecord = { + region_id, + amount: amount, + } + + /** + * HACK - when trying to update prices which already have a region price + * we get error: `Price rule with price_id: , rule_type_id: already exist`, + * so for now, we recreate region prices. + */ + + // const price = shippingOption.prices.find( + // (p) => p.price_rules?.[0]?.value === region_id + // ) + // + // if (price) { + // priceRecord["id"] = price.id + // } + + return priceRecord + }) + .filter((p) => !!p) + + try { + await mutateAsync({ + prices: [...currencyPrices, ...regionPrices], + }) + toast.error(t("general.success"), { + dismissLabel: t("general.close"), + }) + handleSuccess() + } catch (e) { + toast.error(t("general.error"), { + description: e.message, + dismissLabel: t("general.close"), + }) + } + }) + + const initializing = + isStoreLoading || isCurrenciesLoading || !store || !currencies + + return ( + +
+ +
+ + + + +
+
+ + +
+ +
+
+
+
+ ) +} + +const columnHelper = createColumnHelper< + ExtendedProductDTO | ProductVariantDTO +>() + +const useColumns = ({ + currencies = [], + regions = [], +}: { + currencies?: CurrencyDTO[] + regions?: RegionDTO[] +}) => { + const { t } = useTranslation() + + const colDefs: ColumnDef[] = + useMemo(() => { + return [ + ...currencies.map((currency) => { + return columnHelper.display({ + header: t("fields.priceTemplate", { + regionOrCountry: currency.code.toUpperCase(), + }), + cell: ({ row, table }) => { + return ( + } + field={`currency_prices.${currency.code}`} + /> + ) + }, + }) + }), + ...regions.map((region) => { + return columnHelper.display({ + header: t("fields.priceTemplate", { + regionOrCountry: region.name, + }), + cell: ({ row, table }) => { + return ( + c.code === region.currency_code + )} + meta={table.options.meta as DataGridMeta} + field={`region_prices.${region.id}`} + /> + ) + }, + }) + }), + ] + }, [t, currencies, regions]) + + return colDefs +} diff --git a/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-edit-pricing/components/create-shipping-options-form/index.ts b/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-edit-pricing/components/create-shipping-options-form/index.ts new file mode 100644 index 0000000000000..9839576e71f48 --- /dev/null +++ b/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-edit-pricing/components/create-shipping-options-form/index.ts @@ -0,0 +1 @@ +export * from "./edit-shipping-options-pricing-form.tsx" diff --git a/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-edit-pricing/index.ts b/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-edit-pricing/index.ts new file mode 100644 index 0000000000000..d841b62cdbcd4 --- /dev/null +++ b/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-edit-pricing/index.ts @@ -0,0 +1 @@ +export { ShippingOptionsEditPricing as Component } from "./shipping-options-edit-pricing" diff --git a/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-edit-pricing/shipping-options-edit-pricing.tsx b/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-edit-pricing/shipping-options-edit-pricing.tsx new file mode 100644 index 0000000000000..9c4c70fa84b5b --- /dev/null +++ b/packages/admin-next/dashboard/src/v2-routes/shipping/shipping-options-edit-pricing/shipping-options-edit-pricing.tsx @@ -0,0 +1,30 @@ +import { useParams } from "react-router-dom" + +import { RouteFocusModal } from "../../../components/route-modal" +import { useShippingOptions } from "../../../hooks/api/shipping-options" +import { EditShippingOptionsPricingForm } from "./components/create-shipping-options-form" + +export function ShippingOptionsEditPricing() { + const { so_id } = useParams() + + const { shipping_options, isPending } = useShippingOptions({ + // TODO: change this when GET option by id endpoint is implemented + id: [so_id], + fields: "*prices,*prices.price_rules", + limit: 999, + }) + + const shippingOption = shipping_options?.find((so) => so.id === so_id) + + if (!isPending && !shippingOption) { + throw new Error(`Shipping option with id: ${so_id} not found`) + } + + return ( + + {shippingOption && ( + + )} + + ) +} diff --git a/packages/core/types/src/fulfillment/mutations/shipping-option.ts b/packages/core/types/src/fulfillment/mutations/shipping-option.ts index a4931eb8fbd3e..06ab9c209c6f6 100644 --- a/packages/core/types/src/fulfillment/mutations/shipping-option.ts +++ b/packages/core/types/src/fulfillment/mutations/shipping-option.ts @@ -120,6 +120,14 @@ export interface UpdateShippingOptionDTO { id: string } )[] + + /** + * The shipping option pricing + */ + prices: ( + | { currency_code: string; amount: number; id?: string } + | { region_id: string; amount: number; id?: string } + )[] } /**