diff --git a/.changeset/thirty-moons-doubt.md b/.changeset/thirty-moons-doubt.md new file mode 100644 index 0000000000..daf07eeb08 --- /dev/null +++ b/.changeset/thirty-moons-doubt.md @@ -0,0 +1,11 @@ +--- +"@medusajs/ui-preset": patch +"@medusajs/ui": patch +"@medusajs/medusa": patch +"@medusajs/client-types": patch +--- + +feat(ui-preset): Pull latest styles from Figma. +fix(ui): Fix invalid state styling of Select, so it correctly shows when aria-invalid is true. +fix(medusa): Align query params between `/admin/products/:id/variants` and `/admin/variants`. +chore(client-types): Update `medusa` client types to reflect changes to the API. 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 d80810f47e..ca9d6f8756 100644 --- a/packages/admin-next/dashboard/public/locales/en-US/translation.json +++ b/packages/admin-next/dashboard/public/locales/en-US/translation.json @@ -62,7 +62,8 @@ "edit": "Edit", "download": "Download", "clearAll": "Clear all", - "apply": "Apply" + "apply": "Apply", + "add": "Add" }, "filters": { "date": { @@ -391,6 +392,34 @@ "status": { "open": "Open", "completed": "Completed" + }, + "create": { + "createDraftOrder": "Create Draft Order", + "createDraftOrderHint": "Create a new draft order to manage the details of an order before it is placed.", + "chooseRegionHint": "Choose region", + "existingItemsLabel": "Existing items", + "existingItemsHint": "Add existing products to the draft order.", + "customItemsLabel": "Custom items", + "customItemsHint": "Add custom items to the draft order.", + "addExistingItemsAction": "Add existing items", + "addCustomItemAction": "Add custom item", + "noCustomItemsAddedLabel": "No custom items added yet", + "noExistingItemsAddedLabel": "No existing items added yet", + "chooseRegionTooltip": "Choose a region first", + "useExistingCustomerLabel": "Use existing customer", + "addShippingMethodsAction": "Add shipping methods", + "unitPriceOverrideLabel": "Unit price override", + "shippingOptionLabel": "Shipping option", + "shippingOptionHint": "Choose the shipping option for the draft order.", + "shippingPriceOverrideLabel": "Shipping price override", + "shippingPriceOverrideHint": "Override the shipping price for the draft order.", + "sendNotificationLabel": "Send notification", + "sendNotificationHint": "Send a notification to the customer when the draft order is created." + }, + "validation": { + "requiredEmailOrCustomer": "Email or customer is required.", + "requiredItems": "At least one item is required.", + "invalidEmail": "Email must be a valid email address." } }, "discounts": { @@ -846,6 +875,7 @@ "orders": "Orders", "account": "Account", "total": "Total", + "totalExclTax": "Total excl. tax", "subtotal": "Subtotal", "shipping": "Shipping", "tax": "Tax", @@ -887,6 +917,7 @@ "thumbnail": "Thumbnail", "sku": "SKU", "managedInventory": "Managed inventory", + "allowBackorder": "Allow backorder", "inStock": "In stock", "location": "Location", "quantity": "Quantity", @@ -896,9 +927,11 @@ "maxSubtotal": "Max. Subtotal", "shippingProfile": "Shipping Profile", "summary": "Summary", + "details": "Details", "label": "Label", "rate": "Rate", "requiresShipping": "Requires shipping", + "unitPrice": "Unit price", "startDate": "Start date", "endDate": "End date", "draft": "Draft" diff --git a/packages/admin-next/dashboard/src/components/common/combobox/combobox.tsx b/packages/admin-next/dashboard/src/components/common/combobox/combobox.tsx index 7fca6fcea2..21378b270b 100644 --- a/packages/admin-next/dashboard/src/components/common/combobox/combobox.tsx +++ b/packages/admin-next/dashboard/src/components/common/combobox/combobox.tsx @@ -180,7 +180,7 @@ const ComboboxImpl = ( const results = isSearchControlled ? options : matches return ( - + ( "bg-ui-bg-field transition-fg shadow-borders-base", "hover:bg-ui-bg-field-hover", "has-[input:focus]:shadow-borders-interactive-with-active", - "has-[:invalid]:shadow-borders-error", + "has-[:invalid]:shadow-borders-error has-[[aria-invalid=true]]:shadow-borders-error", "has-[:disabled]:bg-ui-bg-disabled has-[:disabled]:text-ui-fg-disabled has-[:disabled]:cursor-not-allowed", { "pl-0.5": hasValue && isArrayValue, diff --git a/packages/admin-next/dashboard/src/components/common/conditional-tooltip/conditional-tooltip.tsx b/packages/admin-next/dashboard/src/components/common/conditional-tooltip/conditional-tooltip.tsx new file mode 100644 index 0000000000..2045800ae2 --- /dev/null +++ b/packages/admin-next/dashboard/src/components/common/conditional-tooltip/conditional-tooltip.tsx @@ -0,0 +1,19 @@ +import { Tooltip } from "@medusajs/ui" +import { PropsWithChildren, ReactNode } from "react" + +type ConditionalTooltipProps = PropsWithChildren<{ + content: ReactNode + showTooltip?: boolean +}> + +export const ConditionalTooltip = ({ + children, + content, + showTooltip = false, +}: ConditionalTooltipProps) => { + if (showTooltip) { + return {children} + } + + return children +} diff --git a/packages/admin-next/dashboard/src/components/common/conditional-tooltip/index.ts b/packages/admin-next/dashboard/src/components/common/conditional-tooltip/index.ts new file mode 100644 index 0000000000..a5a9ef3760 --- /dev/null +++ b/packages/admin-next/dashboard/src/components/common/conditional-tooltip/index.ts @@ -0,0 +1 @@ +export * from "./conditional-tooltip" diff --git a/packages/admin-next/dashboard/src/components/common/customer-info/customer-info.tsx b/packages/admin-next/dashboard/src/components/common/customer-info/customer-info.tsx index 2a031778a8..d51be3704a 100644 --- a/packages/admin-next/dashboard/src/components/common/customer-info/customer-info.tsx +++ b/packages/admin-next/dashboard/src/components/common/customer-info/customer-info.tsx @@ -2,6 +2,7 @@ import { Address, Cart, Order } from "@medusajs/medusa" import { Avatar, Copy, Text } from "@medusajs/ui" import { useTranslation } from "react-i18next" import { Link } from "react-router-dom" +import { getFormattedAddress, isSameAddress } from "../../../lib/addresses" const ID = ({ data }: { data: Cart | Order }) => { const { t } = useTranslation() @@ -182,74 +183,6 @@ export const CustomerInfo = Object.assign( } ) -const isSameAddress = (a: Address | null, b: Address | null) => { - if (!a || !b) { - return false - } - - return ( - a.first_name === b.first_name && - a.last_name === b.last_name && - a.address_1 === b.address_1 && - a.address_2 === b.address_2 && - a.city === b.city && - a.postal_code === b.postal_code && - a.province === b.province && - a.country_code === b.country_code - ) -} - -const getFormattedAddress = ({ address }: { address: Address }) => { - const { - first_name, - last_name, - company, - address_1, - address_2, - city, - postal_code, - province, - country, - country_code, - } = address - - const name = [first_name, last_name].filter(Boolean).join(" ") - - const formattedAddress = [] - - if (name) { - formattedAddress.push(name) - } - - if (company) { - formattedAddress.push(company) - } - - if (address_1) { - formattedAddress.push(address_1) - } - - if (address_2) { - formattedAddress.push(address_2) - } - - const cityProvincePostal = [city, province, postal_code] - .filter(Boolean) - .join(" ") - - if (cityProvincePostal) { - formattedAddress.push(cityProvincePostal) - } - - if (country) { - formattedAddress.push(country.display_name) - } else if (country_code) { - formattedAddress.push(country_code.toUpperCase()) - } - - return formattedAddress -} - const getCartOrOrderCustomer = (obj: Cart | Order) => { const { first_name: sFirstName, last_name: sLastName } = obj.shipping_address || {} diff --git a/packages/admin-next/dashboard/src/components/common/divider/divider.tsx b/packages/admin-next/dashboard/src/components/common/divider/divider.tsx new file mode 100644 index 0000000000..4474797ae6 --- /dev/null +++ b/packages/admin-next/dashboard/src/components/common/divider/divider.tsx @@ -0,0 +1,31 @@ +import { clx } from "@medusajs/ui" +import { ComponentPropsWithoutRef } from "react" + +interface DividerProps + extends Omit, "children"> { + orientation?: "horizontal" | "vertical" + variant?: "dashed" | "solid" +} + +export const Divider = ({ + orientation = "horizontal", + variant = "solid", + className, + ...props +}: DividerProps) => { + return ( +
+ ) +} diff --git a/packages/admin-next/dashboard/src/components/common/divider/index.ts b/packages/admin-next/dashboard/src/components/common/divider/index.ts new file mode 100644 index 0000000000..7fcd4962ee --- /dev/null +++ b/packages/admin-next/dashboard/src/components/common/divider/index.ts @@ -0,0 +1 @@ +export * from "./divider" diff --git a/packages/admin-next/dashboard/src/components/common/thumbnail/thumbnail.tsx b/packages/admin-next/dashboard/src/components/common/thumbnail/thumbnail.tsx index f63583de3a..f439a2cd38 100644 --- a/packages/admin-next/dashboard/src/components/common/thumbnail/thumbnail.tsx +++ b/packages/admin-next/dashboard/src/components/common/thumbnail/thumbnail.tsx @@ -1,9 +1,9 @@ -import { Photo } from "@medusajs/icons"; +import { Photo } from "@medusajs/icons" type ThumbnailProps = { - src?: string | null; - alt?: string; -}; + src?: string | null + alt?: string +} export const Thumbnail = ({ src, alt }: ThumbnailProps) => { return ( @@ -15,8 +15,8 @@ export const Thumbnail = ({ src, alt }: ThumbnailProps) => { className="h-full w-full object-cover object-center" /> ) : ( - + )}
- ); -}; + ) +} diff --git a/packages/admin-next/dashboard/src/components/layout/split-view/split-view.tsx b/packages/admin-next/dashboard/src/components/layout/split-view/split-view.tsx index decc9dd612..decade8003 100644 --- a/packages/admin-next/dashboard/src/components/layout/split-view/split-view.tsx +++ b/packages/admin-next/dashboard/src/components/layout/split-view/split-view.tsx @@ -1,16 +1,12 @@ import { Button, clx } from "@medusajs/ui" -import { AnimatePresence, motion } from "framer-motion" +import * as Dialog from "@radix-ui/react-dialog" import { ComponentPropsWithoutRef, PropsWithChildren, createContext, useContext, useRef, - useState, } from "react" -import FocusLock from "react-focus-lock" - -import { useMediaQuery } from "../../../hooks/use-media-query" type SplitViewContextValue = { open: boolean @@ -34,136 +30,66 @@ type SplitViewProps = PropsWithChildren<{ onOpenChange?: (open: boolean) => void }> -const Root = ({ - open: controlledOpen, - onOpenChange, - children, -}: SplitViewProps) => { +const Root = ({ open, onOpenChange, children }: SplitViewProps) => { const containerRef = useRef(null) - const isControlled = controlledOpen !== undefined - const [uncontrolledOpen, setUncontrolledOpen] = useState(false) - - const open = isControlled ? controlledOpen : uncontrolledOpen - - const handleOpenChange = (newOpen: boolean) => { - if (!isControlled) { - setUncontrolledOpen(newOpen) - } - - if (onOpenChange) { - onOpenChange(newOpen) - } - } - return ( - -
+ +
{children}
- +
) } -const Content = ({ children }: PropsWithChildren) => { - const { open, onOpenChange } = useSplitViewContext() - const isLargeScreenSize = useMediaQuery("(min-width: 1024px)") - - const contentWidth = !isLargeScreenSize ? "100%" : open ? "50%" : "100%" - +const Content = ({ + children, + className, + ...props +}: ComponentPropsWithoutRef<"div">) => { return ( - - - {open && ( - onOpenChange(false)} - /> - )} - - {children} - +
) } -const MotionFocusLock = motion(FocusLock) - const Drawer = ({ children }: PropsWithChildren) => { - const { open } = useSplitViewContext() - const isLargeScreenSize = useMediaQuery("(min-width: 1024px)") - return ( - - {open && ( - - {children} - - )} - +
+ + + {children} + +
) } const Close = ({ variant = "secondary", size = "small", - onClick, children, ...props }: ComponentPropsWithoutRef) => { - const { onOpenChange } = useSplitViewContext() - const handleClick = onClick ?? (() => onOpenChange(false)) - return ( - - ) -} - -const Open = ({ - variant = "secondary", - size = "small", - onClick, - children, - ...props -}: ComponentPropsWithoutRef) => { - const { onOpenChange } = useSplitViewContext() - const handleClick = onClick ?? (() => onOpenChange(true)) - - return ( - + + + ) } @@ -174,5 +100,4 @@ export const SplitView = Object.assign(Root, { Content, Drawer, Close, - Open, }) diff --git a/packages/admin-next/dashboard/src/lib/addresses.ts b/packages/admin-next/dashboard/src/lib/addresses.ts new file mode 100644 index 0000000000..ce82464f01 --- /dev/null +++ b/packages/admin-next/dashboard/src/lib/addresses.ts @@ -0,0 +1,77 @@ +import { Address } from "@medusajs/medusa" + +export const isSameAddress = (a: Address | null, b: Address | null) => { + if (!a || !b) { + return false + } + + return ( + a.first_name === b.first_name && + a.last_name === b.last_name && + a.address_1 === b.address_1 && + a.address_2 === b.address_2 && + a.city === b.city && + a.postal_code === b.postal_code && + a.province === b.province && + a.country_code === b.country_code + ) +} + +export const getFormattedAddress = ({ + address, +}: { + address?: Partial
| null +}) => { + if (!address) { + return [] + } + + const { + first_name, + last_name, + company, + address_1, + address_2, + city, + postal_code, + province, + country, + country_code, + } = address + + const name = [first_name, last_name].filter(Boolean).join(" ") + + const formattedAddress: string[] = [] + + if (name) { + formattedAddress.push(name) + } + + if (company) { + formattedAddress.push(company) + } + + if (address_1) { + formattedAddress.push(address_1) + } + + if (address_2) { + formattedAddress.push(address_2) + } + + const cityProvincePostal = [city, province, postal_code] + .filter(Boolean) + .join(" ") + + if (cityProvincePostal) { + formattedAddress.push(cityProvincePostal) + } + + if (country) { + formattedAddress.push(country.display_name) + } else if (country_code) { + formattedAddress.push(country_code.toUpperCase()) + } + + return formattedAddress +} diff --git a/packages/admin-next/dashboard/src/providers/router-provider/v1.tsx b/packages/admin-next/dashboard/src/providers/router-provider/v1.tsx index 8938eed7c3..0774208109 100644 --- a/packages/admin-next/dashboard/src/providers/router-provider/v1.tsx +++ b/packages/admin-next/dashboard/src/providers/router-provider/v1.tsx @@ -143,6 +143,13 @@ export const v1Routes: RouteObject[] = [ path: "", lazy: () => import("../../routes/draft-orders/draft-order-list"), + children: [ + { + path: "create", + lazy: () => + import("../../routes/draft-orders/draft-order-create"), + }, + ], }, { path: ":id", diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/constants.ts b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/constants.ts new file mode 100644 index 0000000000..12adca2927 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/constants.ts @@ -0,0 +1,130 @@ +import i18n from "i18next" +import { z } from "zod" +import { castNumber } from "../../../../../lib/cast-number" + +export const AddressPayload = z.object({ + first_name: z.string().min(1), + last_name: z.string().min(1), + address_1: z.string().min(1), + address_2: z.string().optional(), + city: z.string().min(1), + province: z.string().optional(), + postal_code: z.string().min(1), + country_code: z.string().min(1), + phone: z.string().optional(), + company: z.string().optional(), +}) + +export const ExistingItemSchema = z + .object({ + product_title: z.string().optional(), + thumbnail: z.string().optional(), + variant_title: z.string().optional(), + variant_id: z.string().min(1), + sku: z.string().optional(), + quantity: z.number().min(1), + unit_price: z.number().min(0), + custom_unit_price: z.union([z.number(), z.string()]).optional(), + }) + .superRefine((data, ctx) => { + if (data.custom_unit_price && isNaN(castNumber(data.custom_unit_price))) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Invalid custom unit price", + path: ["custom_unit_price"], + }) + } + }) + +export const CustomItemSchema = z + .object({ + title: z.string().min(1), + quantity: z.number().min(1), + unit_price: z.union([z.number(), z.string()]), + }) + .superRefine((data, ctx) => { + if ( + typeof data.unit_price === "string" && + isNaN(castNumber(data.unit_price)) + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Invalid unit price", + path: ["unit_price"], + }) + } + }) + +export const ShippingMethodSchema = z + .object({ + option_id: z.string().min(1), + option_title: z.string(), + amount: z.union([z.number(), z.string()]).optional(), + custom_amount: z.union([z.number(), z.string()]).optional(), + }) + .superRefine((data, ctx) => { + if (data.custom_amount && isNaN(castNumber(data.custom_amount))) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Invalid custom amount", + path: ["custom_amount"], + }) + } + }) + +export const CreateDraftOrderSchema = z + .object({ + email: z.string().optional(), + region_id: z.string().min(1), + customer_id: z.string().optional(), + shipping_address: AddressPayload, + billing_address: AddressPayload.nullable(), + existing_items: z.array(ExistingItemSchema).optional(), + custom_items: z.array(CustomItemSchema).optional(), + shipping_method: ShippingMethodSchema, + notification_order: z.boolean().optional(), + }) + .superRefine((data, ctx) => { + if (!data.existing_items?.length && !data.custom_items?.length) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: i18n.t("draftOrders.validation.requiredItems"), + path: ["custom_items"], + }) + + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: i18n.t("draftOrders.validation.requiredItems"), + path: ["existing_items"], + }) + } + + if (!data.email && !data.customer_id) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: i18n.t("draftOrders.validation.requiredEmailOrCustomer"), + path: ["customer_id"], + }) + + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: i18n.t("draftOrders.validation.requiredEmailOrCustomer"), + path: ["email"], + }) + } else if (!data.customer_id && data.email) { + const parsedEmail = z.string().email().safeParse(data.email) + + if (!parsedEmail.success) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: i18n.t("draftOrders.validation.invalidEmail"), + path: ["email"], + }) + } + } + }) + +export enum View { + EXISTING_ITEMS = "existing_items", + CUSTOM_ITEMS = "custom_items", +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/context.ts b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/context.ts new file mode 100644 index 0000000000..9fa5fa63ca --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/context.ts @@ -0,0 +1,5 @@ +import { createContext } from "react" +import { CreateDraftOrderContextValue } from "./types" + +export const CreateDraftOrderContext = + createContext(null) diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-address-details.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-address-details.tsx new file mode 100644 index 0000000000..32d789c240 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-address-details.tsx @@ -0,0 +1,276 @@ +import { Region } from "@medusajs/medusa" +import { Checkbox, Heading, Input, Label, Select, Text } from "@medusajs/ui" +import * as Collapsible from "@radix-ui/react-collapsible" +import { Control } from "react-hook-form" +import { useTranslation } from "react-i18next" +import { z } from "zod" + +import { ConditionalTooltip } from "../../../../../../components/common/conditional-tooltip" +import { Form } from "../../../../../../components/common/form" +import { CreateDraftOrderSchema } from "../constants" +import { useCreateDraftOrder } from "../hooks" + +export const CreateDraftOrderAddressDetails = () => { + const { t } = useTranslation() + const { form, region, sameAsShipping, setSameAsShipping } = + useCreateDraftOrder() + + return ( +
+
+ {t("fields.address")} + + {t("addresses.shippingAddress.label")} + + +
+
+
+ + {t("addresses.billingAddress.label")} + + +
+ + + + + +
+
+ ) +} + +const AddressFieldset = ({ + field, + control, + region, +}: { + field: "shipping_address" | "billing_address" + region: Region | null + control: Control> +}) => { + const { t } = useTranslation() + + return ( +
+
+ { + return ( + + + {t("fields.firstName")} + + + + + + + ) + }} + /> + { + return ( + + + {t("fields.lastName")} + + + + + + + ) + }} + /> +
+
+ { + return ( + + + {t("fields.company")} + + + + + + + ) + }} + /> + { + return ( + + + {t("fields.phone")} + + + + + + + ) + }} + /> +
+
+ { + return ( + + + {t("fields.address")} + + + + + + + ) + }} + /> + { + return ( + + + {t("fields.address2")} + + + + + + + ) + }} + /> +
+
+ { + return ( + + + {t("fields.city")} + + + + + + + ) + }} + /> + { + return ( + + + {t("fields.postalCode")} + + + + + + + ) + }} + /> +
+
+ { + return ( + + + {t("fields.province")} + + + + + + + ) + }} + /> + { + return ( + + + + {t("fields.country")} + + + + + + + + ) + }} + /> +
+
+ ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-customer-details.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-customer-details.tsx new file mode 100644 index 0000000000..0f64b296d3 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-customer-details.tsx @@ -0,0 +1,164 @@ +import { Customer } from "@medusajs/medusa" +import { Checkbox, Heading, Input, Label } from "@medusajs/ui" +import { useInfiniteQuery } from "@tanstack/react-query" +import { debounce } from "lodash" +import { useMedusa } from "medusa-react" +import { useCallback, useEffect, useState } from "react" +import { useTranslation } from "react-i18next" +import { json } from "react-router-dom" + +import { Combobox } from "../../../../../../components/common/combobox" +import { Form } from "../../../../../../components/common/form" +import { useCreateDraftOrder } from "../hooks" + +export const CreateDraftOrderCustomerDetails = () => { + const [useExistingCustomer, setUseExistingCustomer] = useState(true) + const [query, setQuery] = useState("") + const [debouncedQuery, setDebouncedQuery] = useState("") + + const { t } = useTranslation() + const { form, setCustomer } = useCreateDraftOrder() + + // eslint-disable-next-line react-hooks/exhaustive-deps + const debouncedUpdate = useCallback( + debounce((query) => setDebouncedQuery(query), 300), + [] + ) + + useEffect(() => { + debouncedUpdate(query) + + return () => debouncedUpdate.cancel() + }, [query, debouncedUpdate]) + + const { client } = useMedusa() + + const { data, fetchNextPage, isFetchingNextPage } = useInfiniteQuery( + ["customers", debouncedQuery], + async ({ pageParam = 0 }) => { + const res = await client.admin.customers.list({ + q: debouncedQuery, + limit: 10, + offset: pageParam, + has_account: true, // Only show customers with confirmed accounts + }) + return res + }, + { + getNextPageParam: (lastPage) => { + const moreCustomersExist = + lastPage.count > lastPage.offset + lastPage.limit + return moreCustomersExist ? lastPage.offset + lastPage.limit : undefined + }, + keepPreviousData: true, + } + ) + + const createLabel = (customer?: Customer) => { + if (!customer) { + return "" + } + + const { first_name, last_name, email } = customer + + const name = [first_name, last_name].filter(Boolean).join(" ") + + if (name) { + return `${name} (${email})` + } + + return email + } + + const handleCustomerChange = (cusId: string | undefined) => { + if (!cusId) { + setCustomer(null) + return + } + + const customer = data?.pages + .flatMap((page) => page.customers) + .find((c) => c.id === cusId) + + if (!customer) { + throw json({ message: "Customer not found" }, 400) + } + + form.setValue("email", customer.email, { + shouldDirty: true, + shouldTouch: true, + }) + setCustomer(customer) + } + + const options = + data?.pages.flatMap((page) => + page.customers.map((c) => ({ label: createLabel(c), value: c.id })) + ) ?? [] + + return ( +
+ {t("fields.customer")} +
+ {useExistingCustomer ? ( + { + return ( + + + {t("fields.customer")} + + + { + onChange(val) + handleCustomerChange(val) + }} + searchValue={query} + onSearchValueChange={setQuery} + fetchNextPage={fetchNextPage} + isFetchingNextPage={isFetchingNextPage} + options={options} + autoComplete="false" + /> + + + + ) + }} + /> + ) : ( + { + return ( + + + {t("fields.email")} + + + + + + + ) + }} + /> + )} +
+ +
+ ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-details.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-details.tsx new file mode 100644 index 0000000000..41bae5ed2e --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-details.tsx @@ -0,0 +1,34 @@ +import { Heading, Text } from "@medusajs/ui" +import { useTranslation } from "react-i18next" +import { Divider } from "../../../../../../components/common/divider" +import { CreateDraftOrderAddressDetails } from "./create-draft-order-address-details" +import { CreateDraftOrderCustomerDetails } from "./create-draft-order-customer-details" +import { CreateDraftOrderItemsDetails } from "./create-draft-order-items-details" +import { CreateDraftOrderRegionDetails } from "./create-draft-order-region-details" +import { CreateDraftOrderShippingMethodDetails } from "./create-draft-order-shipping-method-details" + +export const CreateDraftOrderDetails = () => { + const { t } = useTranslation() + + return ( +
+
+
+ {t("draftOrders.create.createDraftOrder")} + + {t("draftOrders.create.createDraftOrderHint")} + +
+ + + + + + + + + +
+
+ ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-items-details.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-items-details.tsx new file mode 100644 index 0000000000..16a2f6ed50 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-items-details.tsx @@ -0,0 +1,321 @@ +import { Trash } from "@medusajs/icons" +import { Button, CurrencyInput, Input, Text } from "@medusajs/ui" +import { useTranslation } from "react-i18next" + +import { ActionMenu } from "../../../../../../components/common/action-menu" +import { ConditionalTooltip } from "../../../../../../components/common/conditional-tooltip" +import { Form } from "../../../../../../components/common/form" +import { Thumbnail } from "../../../../../../components/common/thumbnail" +import { getStylizedAmount } from "../../../../../../lib/money-amount-helpers" +import { View } from "../constants" +import { useCreateDraftOrder } from "../hooks" + +export const CreateDraftOrderItemsDetails = () => { + const { t } = useTranslation() + + const { region, variants, custom, form, onOpenDrawer } = useCreateDraftOrder() + const { currency_code, currency } = region || {} + + return ( +
+
+ { + return ( + +
+ + {t("draftOrders.create.existingItemsLabel")} + + + {t("draftOrders.create.existingItemsHint")} + +
+ {variants.items.length > 0 ? ( + variants.items.map((item, index) => { + return ( +
+
+
+
+ +
+
+
+ + {item.product_title} + + {item.sku && ( + + ({item.sku}) + + )} +
+ + {item.variant_title} + +
+
+
+ + {getStylizedAmount( + item.unit_price, + currency_code! + )} + + variants.remove(index), + icon: , + }, + ], + }, + ]} + /> +
+
+
+ { + return ( + + + {t("fields.quantity")} + + + + onChange(Number(e.target.value)) + } + {...field} + /> + + + ) + }} + /> + { + return ( + + + {t( + "draftOrders.create.unitPriceOverrideLabel" + )} + + + + + + + ) + }} + /> +
+
+ ) + }) + ) : ( +
+ + {t("draftOrders.create.noExistingItemsAddedLabel")} + +
+ )} +
+ + + +
+ +
+ ) + }} + /> +
+
+ { + return ( + +
+ + {t("draftOrders.create.customItemsLabel")} + + + {t("draftOrders.create.customItemsHint")} + +
+ {custom.items.length > 0 ? ( + custom.items.map((item, index) => { + return ( +
+
+
+
+ + {item.title} + +
+
+ +
+ custom.remove(index), + icon: , + }, + ], + }, + ]} + /> +
+
+
+ { + return ( + + + {t("fields.quantity")} + + + + + + ) + }} + /> + { + return ( + + + {t("fields.unitPrice")} + + + + + + ) + }} + /> +
+
+ ) + }) + ) : ( +
+ + {t("draftOrders.create.noCustomItemsAddedLabel")} + +
+ )} +
+ + + +
+ +
+ ) + }} + /> +
+
+ ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-region-details.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-region-details.tsx new file mode 100644 index 0000000000..15422e1f97 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-region-details.tsx @@ -0,0 +1,114 @@ +import { Select } from "@medusajs/ui" +import { useAdminRegions, useMedusa } from "medusa-react" +import { useTranslation } from "react-i18next" +import { json } from "react-router-dom" + +import { useWatch } from "react-hook-form" +import { Form } from "../../../../../../components/common/form" +import { useCreateDraftOrder } from "../hooks" + +export const CreateDraftOrderRegionDetails = () => { + const { t } = useTranslation() + const { + form, + setRegion, + variants: { rebase }, + } = useCreateDraftOrder() + const { client } = useMedusa() + + const existingItems = useWatch({ + control: form.control, + name: "existing_items", + }) + + const { regions, isLoading, isError, error } = useAdminRegions({ + limit: 1000, + fields: "id,name,currency_code", + }) + + const handleRebaseUnitPrices = async (regionId: string) => { + if (!existingItems?.length) { + return + } + + const { variants } = await client.admin.variants + .list({ + region_id: regionId, + id: existingItems.map((i) => i.variant_id), + }) + .catch((_err) => { + // Show toast with error message + return { variants: [] } + }) + + rebase(variants) + } + + const handleResetShippingDetails = () => { + form.resetField("shipping_method") + form.resetField("shipping_address") + form.resetField("billing_address") + } + + const handleRegionChange = (regId: string) => { + const region = regions?.find((r) => r.id === regId) + + if (!region) { + throw json({ message: "Region not found" }, 400) + } + + setRegion(region) + } + + const onValueChange = (fn: (...event: any[]) => void) => { + return async (id: string) => { + fn(id) + await handleRebaseUnitPrices(id) + handleResetShippingDetails() + handleRegionChange(id) + } + } + + if (isError) { + throw error + } + + return ( +
+ { + return ( + + {t("fields.region")} + {t("draftOrders.create.chooseRegionHint")} + + + + + + ) + }} + /> +
+ ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-shipping-method-details.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-shipping-method-details.tsx new file mode 100644 index 0000000000..5c4caf6ce7 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/create-draft-order-shipping-method-details.tsx @@ -0,0 +1,185 @@ +import { useInfiniteQuery } from "@tanstack/react-query" +import debounce from "lodash/debounce" +import { useMedusa } from "medusa-react" +import { useCallback, useEffect, useState } from "react" +import { useTranslation } from "react-i18next" + +import { ShippingOption } from "@medusajs/medusa" +import { CurrencyInput, Heading, Input } from "@medusajs/ui" +import { json } from "react-router-dom" +import { Combobox } from "../../../../../../components/common/combobox" +import { ConditionalTooltip } from "../../../../../../components/common/conditional-tooltip" +import { Form } from "../../../../../../components/common/form" +import { getLocaleAmount } from "../../../../../../lib/money-amount-helpers" +import { useCreateDraftOrder } from "../hooks" + +export const CreateDraftOrderShippingMethodDetails = () => { + const [query, setQuery] = useState("") + const [debouncedQuery, setDebouncedQuery] = useState("") + + const { t } = useTranslation() + const { form, region } = useCreateDraftOrder() + + // eslint-disable-next-line react-hooks/exhaustive-deps + const debouncedUpdate = useCallback( + debounce((query) => setDebouncedQuery(query), 300), + [] + ) + + useEffect(() => { + debouncedUpdate(query) + + return () => debouncedUpdate.cancel() + }, [query, debouncedUpdate]) + + const { client } = useMedusa() + + const { data, fetchNextPage, isFetchingNextPage } = useInfiniteQuery( + ["shipping_options", region?.id, debouncedQuery], + async ({ pageParam = 0 }) => { + const res = await client.admin.shippingOptions.list({ + q: debouncedQuery || undefined, + limit: 10, + offset: pageParam, + is_return: false, + region_id: region?.id, + }) + return res + }, + { + getNextPageParam: (lastPage) => { + const moreCustomersExist = + lastPage.count > lastPage.offset + lastPage.limit + return moreCustomersExist ? lastPage.offset + lastPage.limit : undefined + }, + enabled: !!region?.id, + keepPreviousData: true, + } + ) + + const createLabel = (shippingOption?: ShippingOption) => { + if (!shippingOption) { + return "" + } + + return `${shippingOption.name} - ${getLocaleAmount( + shippingOption.amount || 0, + region?.currency_code! + )}` + } + + const options = + data?.pages + .flatMap((page) => page.shipping_options) + .map((so) => ({ + label: createLabel(so), + value: so.id, + })) || [] + + const handleShippingMethodChange = (optionId: string | undefined) => { + if (!optionId) { + return + } + + const option = data?.pages + .flatMap((page) => page.shipping_options) + .find((so) => so.id === optionId) + + if (!option) { + throw json({ message: "Shipping option not found" }, 400) + } + + form.setValue("shipping_method.option_title", option.name, { + shouldDirty: true, + shouldTouch: true, + }) + form.setValue("shipping_method.amount", option.amount || 0, { + shouldDirty: true, + shouldTouch: true, + }) + } + + return ( +
+ {t("fields.shipping")} +
+ { + return ( + + +
+ + {t("draftOrders.create.shippingOptionLabel")} + + + {t("draftOrders.create.shippingOptionHint")} + +
+ + { + handleShippingMethodChange(val) + onChange(val) + }} + disabled={!region || disabled} + searchValue={query} + onSearchValueChange={setQuery} + fetchNextPage={fetchNextPage} + isFetchingNextPage={isFetchingNextPage} + options={options} + autoComplete="false" + /> + + +
+
+ ) + }} + /> + { + return ( + + +
+ + {t("draftOrders.create.shippingPriceOverrideLabel")} + + + {t("draftOrders.create.shippingPriceOverrideHint")} + +
+ + {region ? ( + + ) : ( + + )} + + +
+
+ ) + }} + /> +
+
+ ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/index.ts b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/index.ts new file mode 100644 index 0000000000..2dde5ddf7c --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-details/index.ts @@ -0,0 +1 @@ +export * from "./create-draft-order-details" diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-drawer/add-custom-item-drawer.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-drawer/add-custom-item-drawer.tsx new file mode 100644 index 0000000000..6ba8a63df5 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-drawer/add-custom-item-drawer.tsx @@ -0,0 +1,125 @@ +import { Button, CurrencyInput, Hint, Input, Label } from "@medusajs/ui" +import { useState } from "react" +import { useTranslation } from "react-i18next" +import { z } from "zod" +import { SplitView } from "../../../../../../components/layout/split-view" +import { castNumber } from "../../../../../../lib/cast-number" +import { CustomItemSchema } from "../constants" +import { useCreateDraftOrder } from "../hooks" +import { CustomItem } from "../types" + +export const AddCustomItemDrawer = () => { + const { region, custom } = useCreateDraftOrder() + const { currency } = region || {} + + const currencyCode = currency?.code || "" + const nativeSymbol = currency?.symbol_native || "" + + const { t } = useTranslation() + + const [item, setItem] = useState>({ + title: "", + quantity: 1, + unit_price: undefined, + }) + const [errors, setErrors] = useState + > | null>(null) + + const handleSave = () => { + const parsed = CustomItemSchema.safeParse(item) + + if (!parsed.success) { + setErrors(parsed.error) + + return + } + + custom.update(parsed.data) + } + + return ( +
+
+
+
+ + + setItem((prev) => ({ ...prev, title: e.target.value })) + } + /> + +
+
+ + { + const val = castNumber(e.target.value) + setItem((prev) => ({ ...prev, quantity: val })) + }} + /> + +
+
+ + { + setItem((prev) => ({ ...prev, unit_price: value })) + }} + /> + +
+
+
+
+ + + + +
+
+ ) +} + +const getFirstErrorMessage = ( + errors: z.ZodError> | null, + field: string +): string | null => { + if (!errors) { + return null + } + + const fieldError = errors.errors.find((error) => error.path[0] === field) + + return fieldError ? fieldError.message : null +} + +const ErrorMessage = ({ + field, + errors, +}: { + errors: z.ZodError> | null + field: string +}) => { + const message = getFirstErrorMessage(errors, field) + + return message ? {message} : null +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-drawer/add-variant-drawer.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-drawer/add-variant-drawer.tsx new file mode 100644 index 0000000000..6146beb89d --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-drawer/add-variant-drawer.tsx @@ -0,0 +1,245 @@ +import { PricedVariant } from "@medusajs/medusa/dist/types/pricing" +import { Button, Checkbox } from "@medusajs/ui" +import { + OnChangeFn, + RowSelectionState, + createColumnHelper, +} from "@tanstack/react-table" +import { useAdminVariants } from "medusa-react" +import { useMemo, useState } from "react" +import { useTranslation } from "react-i18next" + +import { SplitView } from "../../../../../../components/layout/split-view" +import { DataTable } from "../../../../../../components/table/data-table" +import { MoneyAmountCell } from "../../../../../../components/table/table-cells/common/money-amount-cell" +import { PlaceholderCell } from "../../../../../../components/table/table-cells/common/placeholder-cell" +import { ProductCell } from "../../../../../../components/table/table-cells/product/product-cell" +import { useDataTable } from "../../../../../../hooks/use-data-table" +import { useProductVariantTableFilters } from "../../../../../products/product-detail/components/product-variant-section/use-variant-table-filters" +import { useProductVariantTableQuery } from "../../../../../products/product-detail/components/product-variant-section/use-variant-table-query" +import { useCreateDraftOrder } from "../hooks" +import { ExistingItem } from "../types" + +const PAGE_SIZE = 50 +const PREFIX = "av" + +const initRowState = (items: ExistingItem[]): RowSelectionState => { + return items.reduce((acc, curr) => { + acc[curr.variant_id] = true + return acc + }, {} as RowSelectionState) +} + +export const AddVariantDrawer = () => { + const { region, customer, variants: existing } = useCreateDraftOrder() + const { currency_code } = region || {} + + const [rowSelection, setRowSelection] = useState( + initRowState(existing.items) + ) + const [intermediate, setIntermediate] = useState( + existing.items + ) + + const { t } = useTranslation() + + const { searchParams, raw } = useProductVariantTableQuery({ + pageSize: PAGE_SIZE, + prefix: PREFIX, + }) + const { variants, count, isLoading, isError, error } = useAdminVariants({ + region_id: region?.id, + customer_id: customer?.id, + ...searchParams, + }) + + const updater: OnChangeFn = (fn) => { + const newState: RowSelectionState = + typeof fn === "function" ? fn(rowSelection) : fn + + const diff = Object.keys(newState).filter( + (k) => newState[k] !== rowSelection[k] + ) + + const addedVariants = variants?.filter((p) => diff.includes(p.id!)) ?? [] + + const newVariants: ExistingItem[] = addedVariants.map((v) => ({ + variant_id: v.id!, + variant_title: v.title!, + unit_price: v.original_price!, + sku: v.sku ?? undefined, + product_title: v.product?.title, + thumbnail: v.product?.thumbnail ?? undefined, + quantity: 1, + })) + + setIntermediate((prev) => { + const filteredPrev = prev.filter((p) => + Object.keys(newState).includes(p.variant_id) + ) + + const update = Array.from(new Set([...filteredPrev, ...newVariants])) + return update + }) + + setRowSelection(newState) + } + + const handleSave = () => { + existing.update(intermediate) + } + + const columns = useVariantTableColumns() + const filters = useProductVariantTableFilters() + + const { table } = useDataTable({ + data: variants || [], + columns, + count, + pageSize: PAGE_SIZE, + enablePagination: true, + enableRowSelection: true, + getRowId: (row) => row.id!, + rowSelection: { + state: rowSelection, + updater, + }, + meta: { + currencyCode: currency_code, + }, + prefix: PREFIX, + }) + + if (isError) { + throw error + } + + return ( +
+ +
+ + + + +
+
+ ) +} + +const columnHelper = createColumnHelper() + +const useVariantTableColumns = () => { + const { t } = useTranslation() + + return useMemo( + () => [ + columnHelper.display({ + id: "select", + header: ({ table }) => { + return ( + + table.toggleAllPageRowsSelected(!!value) + } + /> + ) + }, + cell: ({ row }) => { + return ( + row.toggleSelected(!!value)} + /> + ) + }, + }), + columnHelper.accessor("product", { + header: t("fields.product"), + cell: ({ getValue }) => { + const product = getValue() + + if (!product) { + return + } + + return + }, + }), + columnHelper.accessor("options", { + header: t("fields.variant"), + cell: ({ getValue }) => { + const options = getValue() + + const displayValue = options?.map((o) => o.value).join(" · ") + + return ( +
+ {displayValue} +
+ ) + }, + }), + columnHelper.accessor("sku", { + header: t("fields.sku"), + cell: ({ getValue }) => { + const sku = getValue() + + return ( +
+ {sku ?? "-"} +
+ ) + }, + }), + columnHelper.accessor("original_price", { + header: () => ( +
+ {t("fields.unitPrice")} +
+ ), + cell: ({ getValue, table }) => { + const price = getValue() + const { currencyCode } = table.options.meta as { + currencyCode?: string + } + + if (!price || !currencyCode) { + return + } + + return ( + + ) + }, + }), + ], + [t] + ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-drawer/create-draft-order-drawer.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-drawer/create-draft-order-drawer.tsx new file mode 100644 index 0000000000..8db405e6b7 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-drawer/create-draft-order-drawer.tsx @@ -0,0 +1,14 @@ +import { View } from "../constants" +import { AddCustomItemDrawer } from "./add-custom-item-drawer" +import { AddVariantDrawer } from "./add-variant-drawer" + +export const CreateDraftOrderDrawer = ({ view }: { view: View | null }) => { + switch (view) { + case View.EXISTING_ITEMS: + return + case View.CUSTOM_ITEMS: + return + default: + return null + } +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-drawer/index.ts b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-drawer/index.ts new file mode 100644 index 0000000000..d2e2c3104d --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-drawer/index.ts @@ -0,0 +1 @@ +export * from "./create-draft-order-drawer" diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-form.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-form.tsx new file mode 100644 index 0000000000..18d4608db7 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-form.tsx @@ -0,0 +1,455 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { Customer, Region } from "@medusajs/medusa" +import { Button, ProgressTabs } from "@medusajs/ui" +import { useAdminCreateDraftOrder } from "medusa-react" +import { useCallback, useMemo, useState } from "react" +import { useFieldArray, useForm } from "react-hook-form" +import { useTranslation } from "react-i18next" +import { useSearchParams } from "react-router-dom" +import { z } from "zod" + +import { SplitView } from "../../../../../components/layout/split-view" +import { + RouteFocusModal, + useRouteModal, +} from "../../../../../components/route-modal" +import { getDbAmount } from "../../../../../lib/money-amount-helpers" + +import { PricedVariant } from "@medusajs/medusa/dist/types/pricing" +import { castNumber } from "../../../../../lib/cast-number" +import { CreateDraftOrderSchema, View } from "./constants" +import { CreateDraftOrderContext } from "./context" +import { CreateDraftOrderDetails } from "./create-draft-order-details" +import { CreateDraftOrderDrawer } from "./create-draft-order-drawer" +import { CreateDraftOrderSummary } from "./create-draft-order-summary" +import { CustomItem, ExistingItem } from "./types" + +enum Tab { + DETAILS = "details", + SUMMARY = "summary", +} + +export const CreateDraftOrderForm = () => { + const [open, setOpen] = useState(false) + + const [view, setView] = useState(null) + const [tab, setTab] = useState(Tab.DETAILS) + const [detailsValidated, setDetailsValidated] = useState(false) + + const [region, setRegion] = useState(null) + const [customer, setCustomer] = useState(null) + + const [sameAsShipping, setSameAsShipping] = useState(true) + + const { t } = useTranslation() + const [, setSearchParams] = useSearchParams() + const { handleSuccess } = useRouteModal() + + const form = useForm>({ + defaultValues: { + email: "", + region_id: "", + shipping_method: { + amount: "", + custom_amount: "", + option_id: "", + option_title: "", + }, + shipping_address: { + address_1: "", + address_2: "", + city: "", + company: "", + country_code: "", + first_name: "", + last_name: "", + phone: "", + postal_code: "", + province: "", + }, + billing_address: null, + existing_items: [], + custom_items: [], + customer_id: "", + notification_order: true, + }, + resolver: zodResolver(CreateDraftOrderSchema), + }) + + const { + clearErrors, + formState: { isDirty }, + } = form + + const { + append: createCustomItem, + remove: deleteCustomItem, + fields: customItems, + } = useFieldArray({ + control: form.control, + name: "custom_items", + keyName: "ci_id", + }) + + const { + append: createExistingItem, + remove: deleteExistingItem, + update: updateExistingItem, + fields: existingItems, + } = useFieldArray({ + control: form.control, + name: "existing_items", + keyName: "ei_id", + }) + + const { mutateAsync, isLoading } = useAdminCreateDraftOrder() + + const handleSubmit = form.handleSubmit(async (values) => { + let { + shipping_address, + billing_address, + existing_items, + custom_items, + shipping_method, + email, + notification_order, + ...rest + } = values + + const emailValue = email || customer?.email + + if (!emailValue) { + form.setError("email", { + type: "manual", + message: "Email is required", + }) + + form.setError("customer_id", { + type: "manual", + message: "Customer is required", + }) + + return + } + + if (!billing_address) { + billing_address = shipping_address + } + + const preparedExistingItems = + existing_items?.map((item) => { + const { custom_unit_price, variant_id, quantity } = item + + const customUnitPriceCast = Number(custom_unit_price) + const customUnitPriceValue = !isNaN(customUnitPriceCast) + ? getDbAmount(customUnitPriceCast, region?.currency_code!) + : undefined + + return { + variant_id, + quantity, + unit_price: customUnitPriceValue, + } + }) || [] + + const preparedCustomItems = + custom_items?.map((item) => { + const { unit_price, quantity, title } = item + + const unitPriceCast = castNumber(unit_price) + const unitPriceValue = !isNaN(unitPriceCast) + ? getDbAmount(unitPriceCast, region?.currency_code!) + : undefined + + return { + title, + quantity, + unit_price: unitPriceValue, + } + }) || [] + + const items = [...preparedExistingItems, ...preparedCustomItems] + + const preparedShippingMethods = [ + { + option_id: shipping_method.option_id, + price: shipping_method.custom_amount + ? getDbAmount( + castNumber(shipping_method.custom_amount), + region?.currency_code! + ) + : undefined, + }, + ] + + await mutateAsync( + { + ...rest, + email: emailValue, + shipping_address, + billing_address, + items: items, + shipping_methods: preparedShippingMethods, + no_notification_order: !notification_order, + }, + { + onSuccess: ({ draft_order }) => { + handleSuccess(`../${draft_order.id}`) + }, + } + ) + }) + + const clearItemErrors = useCallback(() => { + clearErrors("custom_items") + clearErrors("existing_items") + }, [clearErrors]) + + const handleUpdateExistingItems = useCallback( + (items: ExistingItem[]) => { + handleUpdateEntities( + items, + existingItems, + deleteExistingItem, + createExistingItem, + "variant_id" + ) + + setView(null) + setOpen(false) + clearItemErrors() + }, + [createExistingItem, deleteExistingItem, existingItems, clearItemErrors] + ) + + const handleCreateCustomItem = useCallback( + (item: CustomItem) => { + createCustomItem(item) + + setView(null) + setOpen(false) + clearItemErrors() + }, + [createCustomItem, clearItemErrors] + ) + + const handleOpenDrawer = (view: View) => { + setView(view) + setOpen(true) + } + + const handleOpenChange = (open: boolean) => { + if (!open) { + setView(null) + setSearchParams( + {}, + { + replace: true, + } + ) + } + + setOpen(open) + } + + const handleContinue = form.handleSubmit(() => { + setTab(Tab.SUMMARY) + setDetailsValidated(true) + }) + + const handleRebaseUnitPrices = useCallback( + (variants: PricedVariant[]) => { + if (!variants.length) { + return + } + + for (const variant of variants) { + const index = existingItems.findIndex( + (item) => item.variant_id === variant.id + ) + + if (index === -1) { + continue + } + + updateExistingItem(index, { + ...existingItems[index], + unit_price: variant.original_price!, + }) + } + }, + [updateExistingItem, existingItems] + ) + + const handleTabChange = (tab: Tab) => { + switch (tab) { + case Tab.DETAILS: + setDetailsValidated(false) + setTab(tab) + break + case Tab.SUMMARY: + handleContinue() + break + } + } + + const detailsProgress = useMemo(() => { + if (detailsValidated) { + return "completed" + } + + if (isDirty) { + return "in-progress" + } + + return "not-started" + }, [detailsValidated, isDirty]) + + return ( + ({ + custom: { + items: customItems, + remove: deleteCustomItem, + update: handleCreateCustomItem, + }, + variants: { + items: existingItems, + remove: deleteExistingItem, + update: handleUpdateExistingItems, + rebase: handleRebaseUnitPrices, + }, + form, + region, + setRegion, + customer, + setCustomer, + sameAsShipping, + setSameAsShipping, + onOpenDrawer: handleOpenDrawer, + }), + [ + customItems, + deleteCustomItem, + handleCreateCustomItem, + existingItems, + deleteExistingItem, + handleUpdateExistingItems, + handleRebaseUnitPrices, + form, + customer, + region, + sameAsShipping, + ] + )} + > + +
+ handleTabChange(tab as Tab)} + className="flex h-full flex-col overflow-hidden" + > + +
+
+ + + {t("fields.details")} + + + {t("fields.summary")} + + +
+
+ + + + {tab === Tab.SUMMARY ? ( + + ) : ( + + )} +
+
+
+ + + + + + + + + + + + + + + +
+
+
+
+ ) +} + +const handleUpdateEntities = ( + newEntities: T[], + existingEntities: T[], + deleteEntity: (indices: number[]) => void, + createEntity: (entities: T[]) => void, + entityIdKey: keyof T +) => { + const newEntitiesIdMap = newEntities.map((e) => e[entityIdKey]) + const existingEntitiesIdMap = existingEntities.map((e) => e[entityIdKey]) + + const indicesToDelete = existingEntities.reduce((acc, e, i) => { + if (!newEntitiesIdMap.includes(e[entityIdKey])) { + acc.push(i) + } + return acc + }, [] as number[]) + + const entitiesToAdd = newEntities.filter( + (e) => !existingEntitiesIdMap.includes(e[entityIdKey]) + ) + + deleteEntity(indicesToDelete) + createEntity(entitiesToAdd) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-custom-items-summary.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-custom-items-summary.tsx new file mode 100644 index 0000000000..6c5762ba7e --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-custom-items-summary.tsx @@ -0,0 +1,62 @@ +import { Text } from "@medusajs/ui" + +import { Divider } from "../../../../../../components/common/divider" +import { castNumber } from "../../../../../../lib/cast-number" +import { + getDbAmount, + getLocaleAmount, +} from "../../../../../../lib/money-amount-helpers" +import { useCreateDraftOrder } from "../hooks" + +export const CreateDraftOrderCustomItemsSummary = () => { + const { form, region } = useCreateDraftOrder() + const { currency_code } = region || {} + + const items = form.getValues("custom_items") || [] + + if (!items.length) { + return null + } + + return ( +
+ {items.map((item, index) => { + const price = item.unit_price + ? getDbAmount(castNumber(item.unit_price), currency_code!) + : item.unit_price + const subtotal = price * item.quantity + + return ( +
+ + {item.title} + +
+
+ + {getLocaleAmount(price, currency_code!)} + +
+
+ + {item.quantity}x + +
+
+ + {getLocaleAmount(subtotal, currency_code!)} + +
+
+
+ ) + })} + +
+ ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-customer-summary.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-customer-summary.tsx new file mode 100644 index 0000000000..957f3b8a9c --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-customer-summary.tsx @@ -0,0 +1,105 @@ +import { Avatar, Text } from "@medusajs/ui" +import { useTranslation } from "react-i18next" +import { getFormattedAddress } from "../../../../../../lib/addresses" +import { useCreateDraftOrder } from "../hooks" + +export const CreateDraftOrderCustomerSummary = () => { + const { form, customer, sameAsShipping, region } = useCreateDraftOrder() + const { countries } = region || {} + + const { t } = useTranslation() + + const shippingAddress = form.getValues("shipping_address") + const shippingAddressCountry = countries?.find( + (c) => c.iso_2 === shippingAddress?.country_code + ) + + const billingAddress = form.getValues("billing_address") + const billingAddressCountry = countries?.find( + (c) => c.iso_2 === billingAddress?.country_code + ) + + const email = form.getValues("email") + const phone = + shippingAddress?.phone || billingAddress?.phone || customer?.phone + + const { first_name, last_name } = customer || shippingAddress || {} + const name = [first_name, last_name].filter(Boolean).join(" ") + const fallback = name + ? name[0].toUpperCase() + : email?.[0].toUpperCase() || "?" + + return ( +
+ {customer && ( +
+ + {t("fields.id")} + +
+ + + {name || email} + +
+
+ )} +
+ + {t("fields.email")} + + + {email} + +
+
+ + {t("fields.phone")} + + + {phone || "-"} + +
+
+ + {t("addresses.shippingAddress.label")} + + + {getFormattedAddress({ + address: { ...shippingAddress, country: shippingAddressCountry }, + }).map((line, i) => { + return ( + + {line} +
+
+ ) + })} +
+
+
+ + {t("addresses.billingAddress.label")} + + {sameAsShipping ? ( + + {t("addresses.billingAddress.sameAsShipping")} + + ) : ( + + {getFormattedAddress({ + address: { ...billingAddress, country: billingAddressCountry }, + }).map((line, i) => { + return ( + + {line} +
+
+ ) + })} +
+ )} +
+
+ ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-fields-summary.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-fields-summary.tsx new file mode 100644 index 0000000000..8becd9c2f8 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-fields-summary.tsx @@ -0,0 +1,42 @@ +import { Switch } from "@medusajs/ui" +import { useTranslation } from "react-i18next" +import { Form } from "../../../../../../components/common/form" +import { useCreateDraftOrder } from "../hooks" + +export const CreateDraftOrderFieldsSummary = () => { + const { t } = useTranslation() + const { form } = useCreateDraftOrder() + + return ( +
+ { + return ( + +
+
+ + {t("draftOrders.create.sendNotificationLabel")} + + + + +
+ + {t("draftOrders.create.sendNotificationHint")} + +
+ +
+ ) + }} + /> +
+ ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-summary.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-summary.tsx new file mode 100644 index 0000000000..b8b3ae5fc3 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-summary.tsx @@ -0,0 +1,30 @@ +import { Heading } from "@medusajs/ui" +import { useTranslation } from "react-i18next" + +import { Divider } from "../../../../../../components/common/divider" +import { CreateDraftOrderCustomItemsSummary } from "./create-draft-order-custom-items-summary" +import { CreateDraftOrderCustomerSummary } from "./create-draft-order-customer-summary" +import { CreateDraftOrderFieldsSummary } from "./create-draft-order-fields-summary" +import { CreateDraftOrderTotalSummary } from "./create-draft-order-total-summary" +import { CreateDraftOrderVariantItemsSummary } from "./create-draft-order-variant-items-summary" + +export const CreateDraftOrderSummary = () => { + const { t } = useTranslation() + + return ( +
+
+ {t("fields.summary")} + +
+ + + + + +
+ +
+
+ ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-total-summary.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-total-summary.tsx new file mode 100644 index 0000000000..44d8558285 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-total-summary.tsx @@ -0,0 +1,121 @@ +import { Text } from "@medusajs/ui" +import { useTranslation } from "react-i18next" +import { Divider } from "../../../../../../components/common/divider" +import { castNumber } from "../../../../../../lib/cast-number" +import { + getDbAmount, + getLocaleAmount, + getStylizedAmount, +} from "../../../../../../lib/money-amount-helpers" +import { useCreateDraftOrder } from "../hooks" +import { CustomItem, ExistingItem, ShippingMethod } from "../types" + +export const CreateDraftOrderTotalSummary = () => { + const { t } = useTranslation() + const { form, region } = useCreateDraftOrder() + const { currency_code } = region || {} + + const variantItems = form.getValues("existing_items") || [] + const variantItemsSubtotal = variantItems.reduce((acc, item) => { + const amount = getExistingItemSubtotal(item, currency_code!) + + return (acc += amount) + }, 0) + + const customItems = form.getValues("custom_items") || [] + const customItemsSubtotal = customItems.reduce((acc, item) => { + const amount = getCustomItemSubtotal(item, currency_code!) + + return (acc += amount) + }, 0) + + const count = variantItems.length + customItems.length + const subtotal = variantItemsSubtotal + customItemsSubtotal + + const shippingMethod = form.getValues("shipping_method") + const shippingSubtotal = getShippingSubtotal(shippingMethod, currency_code!) + + const total = subtotal + shippingSubtotal + + return ( +
+
+
+ + {t("fields.subtotal")} + + + {t("general.items", { count })} + + + {getLocaleAmount(subtotal, currency_code!)} + +
+
+ + {t("fields.shipping")} + + + {shippingMethod.option_title} + +
+ {shippingMethod.custom_amount && ( + + {getLocaleAmount( + castNumber(shippingMethod.amount || 0), + currency_code! + )} + + )} + + {getLocaleAmount(shippingSubtotal, currency_code!)} + +
+
+
+ +
+ + {t("fields.totalExclTax")} + + + {getStylizedAmount(total, currency_code!)} + +
+
+ ) +} + +const getExistingItemSubtotal = (item: ExistingItem, currency_code: string) => { + if (item.custom_unit_price) { + const customUnitPrice = castNumber(item.custom_unit_price) + return getDbAmount(customUnitPrice, currency_code) * item.quantity + } + + return item.unit_price * item.quantity +} + +const getCustomItemSubtotal = (item: CustomItem, currency_code: string) => { + return getDbAmount(castNumber(item.unit_price), currency_code) * item.quantity +} + +const getShippingSubtotal = ( + shippingMethod: ShippingMethod, + currency_code: string +) => { + if (shippingMethod.custom_amount) { + const customAmount = castNumber(shippingMethod.custom_amount) + return getDbAmount(customAmount, currency_code) + } + + if (shippingMethod.amount) { + const amount = + typeof shippingMethod.amount === "string" + ? Number(shippingMethod.amount.replace(",", ".")) + : shippingMethod.amount + + return amount + } + + return 0 +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-variant-items-summary.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-variant-items-summary.tsx new file mode 100644 index 0000000000..f6ea016503 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/create-draft-order-variant-items-summary.tsx @@ -0,0 +1,83 @@ +import { Copy, Text } from "@medusajs/ui" + +import { Divider } from "../../../../../../components/common/divider" +import { Thumbnail } from "../../../../../../components/common/thumbnail" +import { castNumber } from "../../../../../../lib/cast-number" +import { + getDbAmount, + getLocaleAmount, +} from "../../../../../../lib/money-amount-helpers" +import { useCreateDraftOrder } from "../hooks" + +export const CreateDraftOrderVariantItemsSummary = () => { + const { form, region } = useCreateDraftOrder() + const { currency_code } = region || {} + + const items = form.getValues("existing_items") || [] + + if (!items.length) { + return null + } + + return ( +
+ {items.map((item) => { + const price = item.custom_unit_price + ? getDbAmount(castNumber(item.custom_unit_price), currency_code!) + : item.unit_price + const subtotal = price * item.quantity + + return ( +
+
+ +
+ + {item.product_title} + + {item.sku && ( +
+ {item.sku} + +
+ )} + {item.variant_title} +
+
+
+
+ {item.custom_unit_price && ( + + {getLocaleAmount(item.unit_price, currency_code!)} + + )} + + {getLocaleAmount(price, currency_code!)} + +
+
+ + {item.quantity}x + +
+
+ + {getLocaleAmount(subtotal, currency_code!)} + +
+
+
+ ) + })} + +
+ ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/index.ts b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/index.ts new file mode 100644 index 0000000000..dc5ef29f0b --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/create-draft-order-summary/index.ts @@ -0,0 +1 @@ +export * from "./create-draft-order-summary" diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/hooks.ts b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/hooks.ts new file mode 100644 index 0000000000..47ca1c9b2b --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/hooks.ts @@ -0,0 +1,14 @@ +import { useContext } from "react" +import { CreateDraftOrderContext } from "./context" + +export const useCreateDraftOrder = () => { + const context = useContext(CreateDraftOrderContext) + + if (!context) { + throw new Error( + "useCreateDraftOrder must be used within a CreateDraftOrderProvider" + ) + } + + return context +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/index.ts b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/index.ts new file mode 100644 index 0000000000..458feb4ff1 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/index.ts @@ -0,0 +1 @@ +export * from "./create-draft-order-form" diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/types.ts b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/types.ts new file mode 100644 index 0000000000..c20ff0aa4d --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/components/create-draft-order-form/types.ts @@ -0,0 +1,46 @@ +import { Customer, Region } from "@medusajs/medusa" +import { FieldArrayWithId, UseFormReturn } from "react-hook-form" +import { z } from "zod" + +import { PricedVariant } from "@medusajs/medusa/dist/types/pricing" +import { + CreateDraftOrderSchema, + CustomItemSchema, + ExistingItemSchema, + ShippingMethodSchema, + View, +} from "./constants" + +export type ExistingItem = z.infer +export type CustomItem = z.infer +export type ShippingMethod = z.infer + +export type CreateDraftOrderContextValue = { + form: UseFormReturn> + region: Region | null + setRegion: (region: Region | null) => void + customer: Customer | null + setCustomer: (customer: Customer | null) => void + sameAsShipping: boolean + setSameAsShipping: (sameAsShipping: boolean) => void + variants: { + items: FieldArrayWithId< + z.infer, + "existing_items", + "ei_id" + >[] + remove: (index: number) => void + update: (items: ExistingItem[]) => void + rebase: (variants: PricedVariant[]) => void + } + custom: { + items: FieldArrayWithId< + z.infer, + "custom_items", + "ci_id" + >[] + remove: (index: number) => void + update: (items: CustomItem) => void + } + onOpenDrawer: (view: View) => void +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/draft-order-create.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/draft-order-create.tsx new file mode 100644 index 0000000000..3ff0a599ee --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/draft-order-create.tsx @@ -0,0 +1,10 @@ +import { RouteFocusModal } from "../../../components/route-modal" +import { CreateDraftOrderForm } from "./components/create-draft-order-form/create-draft-order-form" + +export const DraftOrderCreate = () => { + return ( + + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/index.ts b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/index.ts new file mode 100644 index 0000000000..33edf5e187 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-create/index.ts @@ -0,0 +1 @@ +export { DraftOrderCreate as Component } from "./draft-order-create" diff --git a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-detail/components/draft-order-summary-section/draft-order-summary-section.tsx b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-detail/components/draft-order-summary-section/draft-order-summary-section.tsx index 950eed4322..ed6b74fcc1 100644 --- a/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-detail/components/draft-order-summary-section/draft-order-summary-section.tsx +++ b/packages/admin-next/dashboard/src/routes/draft-orders/draft-order-detail/components/draft-order-summary-section/draft-order-summary-section.tsx @@ -5,6 +5,7 @@ import { Container, Copy, Heading, StatusBadge, Text } from "@medusajs/ui" import { useAdminReservations } from "medusa-react" import { useTranslation } from "react-i18next" import { ActionMenu } from "../../../../../components/common/action-menu" +import { Divider } from "../../../../../components/common/divider" import { Thumbnail } from "../../../../../components/common/thumbnail" import { getLocaleAmount, @@ -51,6 +52,69 @@ const Header = () => { ) } +const CustomItem = ({ + item, + currencyCode, + reservation, +}: { + item: { + id: string + title: string + unit_price: number + subtotal: number + quantity: number + } + currencyCode: string + reservation?: ReservationItemDTO | null +}) => { + const { t } = useTranslation() + + return ( +
+ + {item.title} + +
+
+ + {getLocaleAmount(item.unit_price, currencyCode)} + +
+
+
+ + {item.quantity}x + +
+
+ + {reservation + ? t("orders.reservations.allocatedLabel") + : t("orders.reservations.notAllocatedLabel")} + +
+
+
+ + {getLocaleAmount(item.subtotal || 0, currencyCode)} + +
+
+
+ ) +} + const Item = ({ item, currencyCode, @@ -129,9 +193,19 @@ const ItemBreakdown = ({ draftOrder }: { draftOrder: DraftOrder }) => { throw error } + const variantBasedItems = draftOrder.cart.items.filter( + (i) => i.variant_id !== null + ) + + const customItems = draftOrder.cart.items.filter((i) => i.variant_id === null) + + const showDivider = variantBasedItems.length > 0 && customItems.length > 0 + + console.log("customItems", customItems) + return (
- {draftOrder.cart.items.map((item) => { + {variantBasedItems.map((item) => { const reservation = reservations ? reservations.find((r) => r.line_item_id === item.id) : null @@ -145,6 +219,27 @@ const ItemBreakdown = ({ draftOrder }: { draftOrder: DraftOrder }) => { /> ) })} + {showDivider && } + {customItems.map((item) => { + const reservation = reservations + ? reservations.find((r) => r.line_item_id === item.id) + : null + + return ( + + ) + })}
) } @@ -194,7 +289,7 @@ const CostBreakdown = ({ draftOrder }: { draftOrder: DraftOrder }) => { const { t } = useTranslation() // Calculate tax rate since it's not included in the cart - const taxRate = calculateCartTaxRate(draftOrder.cart) + const taxRate = calculateCartTaxRate(draftOrder.cart).toFixed(2) return (
@@ -236,7 +331,7 @@ const CostBreakdown = ({ draftOrder }: { draftOrder: DraftOrder }) => { /> { return (
+
) } diff --git a/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-variant-section/use-variant-table-filters.tsx b/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-variant-section/use-variant-table-filters.tsx index 7af6069d17..7b5faa884c 100644 --- a/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-variant-section/use-variant-table-filters.tsx +++ b/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-variant-section/use-variant-table-filters.tsx @@ -22,6 +22,22 @@ export const useProductVariantTableFilters = () => { ], } + const allowBackorderFilter: Filter = { + key: "allow_backorder", + label: t("fields.allowBackorder"), + type: "select", + options: [ + { + label: t("fields.true"), + value: "true", + }, + { + label: t("fields.false"), + value: "false", + }, + ], + } + const dateFilters: Filter[] = [ { label: t("fields.createdAt"), key: "created_at" }, { label: t("fields.updatedAt"), key: "updated_at" }, @@ -31,7 +47,12 @@ export const useProductVariantTableFilters = () => { type: "date", })) - filters = [...filters, manageInventoryFilter, ...dateFilters] + filters = [ + ...filters, + manageInventoryFilter, + allowBackorderFilter, + ...dateFilters, + ] return filters } diff --git a/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-variant-section/use-variant-table-query.tsx b/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-variant-section/use-variant-table-query.tsx index a0092fc633..2a14cea3b6 100644 --- a/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-variant-section/use-variant-table-query.tsx +++ b/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-variant-section/use-variant-table-query.tsx @@ -9,12 +9,27 @@ export const useProductVariantTableQuery = ({ prefix?: string }) => { const queryObject = useQueryParams( - ["offset", "q", "manage_inventory", "order", "created_at", "updated_at"], + [ + "offset", + "q", + "manage_inventory", + "allow_backorder", + "order", + "created_at", + "updated_at", + ], prefix ) - const { offset, manage_inventory, created_at, updated_at, q, order } = - queryObject + const { + offset, + manage_inventory, + allow_backorder, + created_at, + updated_at, + q, + order, + } = queryObject const searchParams: AdminGetProductsVariantsParams = { limit: pageSize, @@ -22,6 +37,7 @@ export const useProductVariantTableQuery = ({ manage_inventory: manage_inventory ? manage_inventory === "true" : undefined, + allow_backorder: allow_backorder ? allow_backorder === "true" : undefined, order, created_at: created_at ? JSON.parse(created_at) : undefined, updated_at: updated_at ? JSON.parse(updated_at) : undefined, diff --git a/packages/admin-next/dashboard/src/routes/regions/region-create-shipping-option/components/create-shipping-option-form/create-shipping-option-form.tsx b/packages/admin-next/dashboard/src/routes/regions/region-create-shipping-option/components/create-shipping-option-form/create-shipping-option-form.tsx index c322b16a6f..f14c7d5d12 100644 --- a/packages/admin-next/dashboard/src/routes/regions/region-create-shipping-option/components/create-shipping-option-form/create-shipping-option-form.tsx +++ b/packages/admin-next/dashboard/src/routes/regions/region-create-shipping-option/components/create-shipping-option-form/create-shipping-option-form.tsx @@ -394,7 +394,7 @@ export const CreateShippingOptionForm = ({ control={form.control} name="amount" shouldUnregister - render={({ field }) => { + render={({ field: { onChange, ...field } }) => { return ( @@ -499,7 +500,7 @@ export const CreateShippingOptionForm = ({ control={form.control} name="min_subtotal" shouldUnregister - render={({ field }) => { + render={({ field: { onChange, ...field } }) => { return ( @@ -526,7 +528,7 @@ export const CreateShippingOptionForm = ({ control={form.control} name="max_subtotal" shouldUnregister - render={({ field }) => { + render={({ field: { onChange, ...field } }) => { return ( diff --git a/packages/admin-next/dashboard/src/routes/regions/region-edit-shipping-option/components/edit-shipping-option-form/edit-shipping-option-form.tsx b/packages/admin-next/dashboard/src/routes/regions/region-edit-shipping-option/components/edit-shipping-option-form/edit-shipping-option-form.tsx index c7dee64f4c..fdfb1504cf 100644 --- a/packages/admin-next/dashboard/src/routes/regions/region-edit-shipping-option/components/edit-shipping-option-form/edit-shipping-option-form.tsx +++ b/packages/admin-next/dashboard/src/routes/regions/region-edit-shipping-option/components/edit-shipping-option-form/edit-shipping-option-form.tsx @@ -321,7 +321,7 @@ export const EditShippingOptionForm = ({ control={form.control} name="amount" shouldUnregister - render={({ field }) => { + render={({ field: { onChange, ...field } }) => { return ( @@ -358,7 +359,7 @@ export const EditShippingOptionForm = ({ control={form.control} name="min_subtotal" shouldUnregister - render={({ field }) => { + render={({ field: { onChange, ...field } }) => { return ( @@ -383,7 +385,7 @@ export const EditShippingOptionForm = ({ control={form.control} name="max_subtotal" shouldUnregister - render={({ field }) => { + render={({ field: { onChange, ...field } }) => { return ( diff --git a/packages/design-system/ui-preset/src/theme/extension/theme.ts b/packages/design-system/ui-preset/src/theme/extension/theme.ts index f3649f13dd..8fb1bcc7d1 100644 --- a/packages/design-system/ui-preset/src/theme/extension/theme.ts +++ b/packages/design-system/ui-preset/src/theme/extension/theme.ts @@ -2,68 +2,6 @@ export const theme = { "extend": { "colors": { "ui": { - "button": { - "inverted": { - "DEFAULT": "var(--button-inverted)", - "pressed": { - "DEFAULT": "var(--button-inverted-pressed)" - }, - "hover": { - "DEFAULT": "var(--button-inverted-hover)" - } - }, - "neutral": { - "DEFAULT": "var(--button-neutral)", - "pressed": { - "DEFAULT": "var(--button-neutral-pressed)" - }, - "hover": { - "DEFAULT": "var(--button-neutral-hover)" - } - }, - "danger": { - "DEFAULT": "var(--button-danger)", - "pressed": { - "DEFAULT": "var(--button-danger-pressed)" - }, - "hover": { - "DEFAULT": "var(--button-danger-hover)" - } - }, - "transparent": { - "hover": { - "DEFAULT": "var(--button-transparent-hover)" - }, - "pressed": { - "DEFAULT": "var(--button-transparent-pressed)" - }, - "DEFAULT": "var(--button-transparent)" - } - }, - "code": { - "fg": { - "subtle": { - "DEFAULT": "var(--code-fg-subtle)" - }, - "muted": { - "DEFAULT": "var(--code-fg-muted)" - }, - "base": { - "DEFAULT": "var(--code-fg-base)" - } - }, - "bg": { - "subtle": { - "DEFAULT": "var(--code-bg-subtle)" - }, - "base": { - "DEFAULT": "var(--code-bg-base)" - } - }, - "border": { - "DEFAULT": "var(--code-border)" - } - }, "tag": { "green": { "bg": { @@ -82,23 +20,6 @@ export const theme = { "DEFAULT": "var(--tag-green-text)" } }, - "neutral": { - "bg": { - "DEFAULT": "var(--tag-neutral-bg)", - "hover": { - "DEFAULT": "var(--tag-neutral-bg-hover)" - } - }, - "border": { - "DEFAULT": "var(--tag-neutral-border)" - }, - "text": { - "DEFAULT": "var(--tag-neutral-text)" - }, - "icon": { - "DEFAULT": "var(--tag-neutral-icon)" - } - }, "red": { "bg": { "DEFAULT": "var(--tag-red-bg)", @@ -166,26 +87,43 @@ export const theme = { "text": { "DEFAULT": "var(--tag-blue-text)" } + }, + "neutral": { + "border": { + "DEFAULT": "var(--tag-neutral-border)" + }, + "text": { + "DEFAULT": "var(--tag-neutral-text)" + }, + "bg": { + "DEFAULT": "var(--tag-neutral-bg)", + "hover": { + "DEFAULT": "var(--tag-neutral-bg-hover)" + } + }, + "icon": { + "DEFAULT": "var(--tag-neutral-icon)" + } } }, "border": { - "strong": { - "DEFAULT": "var(--border-strong)" - }, "interactive": { "DEFAULT": "var(--border-interactive)" }, - "base": { - "DEFAULT": "var(--border-base)" - }, "error": { "DEFAULT": "var(--border-error)" }, + "danger": { + "DEFAULT": "var(--border-danger)" + }, "loud": { "DEFAULT": "var(--border-loud)" }, - "danger": { - "DEFAULT": "var(--border-danger)" + "strong": { + "DEFAULT": "var(--border-strong)" + }, + "base": { + "DEFAULT": "var(--border-base)" }, "transparent": { "DEFAULT": "var(--border-transparent)" @@ -208,16 +146,16 @@ export const theme = { } }, "subtle": { - "DEFAULT": "var(--bg-subtle)", "pressed": { "DEFAULT": "var(--bg-subtle-pressed)" }, + "DEFAULT": "var(--bg-subtle)", "hover": { "DEFAULT": "var(--bg-subtle-hover)" } }, - "component": { - "DEFAULT": "var(--bg-component)" + "interactive": { + "DEFAULT": "var(--bg-interactive)" }, "overlay": { "DEFAULT": "var(--bg-overlay)" @@ -231,16 +169,28 @@ export const theme = { } }, "field": { + "DEFAULT": "var(--bg-field)", "hover": { "DEFAULT": "var(--bg-field-hover)" }, - "DEFAULT": "var(--bg-field)" - }, - "interactive": { - "DEFAULT": "var(--bg-interactive)" + "component": { + "DEFAULT": "var(--bg-field-component)", + "hover": { + "DEFAULT": "var(--bg-field-component-hover)" + } + } }, "disabled": { "DEFAULT": "var(--bg-disabled)" + }, + "component": { + "hover": { + "DEFAULT": "var(--bg-component-hover)" + }, + "pressed": { + "DEFAULT": "var(--bg-component-pressed)" + }, + "DEFAULT": "var(--bg-component)" } }, "fg": { @@ -261,33 +211,79 @@ export const theme = { "error": { "DEFAULT": "var(--fg-error)" }, - "base": { - "DEFAULT": "var(--fg-base)" + "muted": { + "DEFAULT": "var(--fg-muted)" }, "disabled": { "DEFAULT": "var(--fg-disabled)" }, + "base": { + "DEFAULT": "var(--fg-base)" + }, "subtle": { "DEFAULT": "var(--fg-subtle)" + } + }, + "button": { + "danger": { + "DEFAULT": "var(--button-danger)", + "pressed": { + "DEFAULT": "var(--button-danger-pressed)" + }, + "hover": { + "DEFAULT": "var(--button-danger-hover)" + } }, - "muted": { - "DEFAULT": "var(--fg-muted)" + "transparent": { + "DEFAULT": "var(--button-transparent)", + "hover": { + "DEFAULT": "var(--button-transparent-hover)" + }, + "pressed": { + "DEFAULT": "var(--button-transparent-pressed)" + } + }, + "neutral": { + "DEFAULT": "var(--button-neutral)", + "hover": { + "DEFAULT": "var(--button-neutral-hover)" + }, + "pressed": { + "DEFAULT": "var(--button-neutral-pressed)" + } + }, + "inverted": { + "DEFAULT": "var(--button-inverted)", + "pressed": { + "DEFAULT": "var(--button-inverted-pressed)" + }, + "hover": { + "DEFAULT": "var(--button-inverted-hover)" + } } }, "contrast": { - "bg": { - "subtle": { - "DEFAULT": "var(--contrast-bg-subtle)" + "fg": { + "primary": { + "DEFAULT": "var(--contrast-fg-primary)" }, + "secondary": { + "DEFAULT": "var(--contrast-fg-secondary)" + } + }, + "bg": { "base": { - "DEFAULT": "var(--contrast-bg-base)", "pressed": { "DEFAULT": "var(--contrast-bg-base-pressed)" }, + "DEFAULT": "var(--contrast-bg-base)", "hover": { "DEFAULT": "var(--contrast-bg-base-hover)" } }, + "subtle": { + "DEFAULT": "var(--contrast-bg-subtle)" + }, "highlight": { "DEFAULT": "var(--contrast-bg-highlight)" } @@ -296,41 +292,57 @@ export const theme = { "base": { "DEFAULT": "var(--contrast-border-base)" } - }, + } + }, + "code": { "fg": { - "primary": { - "DEFAULT": "var(--contrast-fg-primary)" + "subtle": { + "DEFAULT": "var(--code-fg-subtle)" }, - "secondary": { - "DEFAULT": "var(--contrast-fg-secondary)" + "muted": { + "DEFAULT": "var(--code-fg-muted)" + }, + "base": { + "DEFAULT": "var(--code-fg-base)" } + }, + "bg": { + "base": { + "DEFAULT": "var(--code-bg-base)" + }, + "subtle": { + "DEFAULT": "var(--code-bg-subtle)" + } + }, + "border": { + "DEFAULT": "var(--code-border)" } } } }, "boxShadow": { + "borders-interactive-with-active": "var(--borders-interactive-with-active)", + "buttons-danger-focus": "var(--buttons-danger-focus)", + "buttons-danger": "var(--buttons-danger)", + "details-contrast-on-bg-interactive": "var(--details-contrast-on-bg-interactive)", + "borders-interactive-with-focus": "var(--borders-interactive-with-focus)", + "borders-error": "var(--borders-error)", + "borders-focus": "var(--borders-focus)", + "borders-interactive-with-shadow": "var(--borders-interactive-with-shadow)", + "details-switch-handle": "var(--details-switch-handle)", + "buttons-neutral": "var(--buttons-neutral)", + "details-commandbar": "var(--details-commandbar)", + "details-switch-background-focus": "var(--details-switch-background-focus)", + "buttons-inverted-focus": "var(--buttons-inverted-focus)", + "details-switch-background": "var(--details-switch-background)", + "borders-base": "var(--borders-base)", + "elevation-tooltip": "var(--elevation-tooltip)", "elevation-card-hover": "var(--elevation-card-hover)", + "elevation-modal": "var(--elevation-modal)", "buttons-inverted": "var(--buttons-inverted)", "elevation-card-rest": "var(--elevation-card-rest)", "buttons-neutral-focus": "var(--buttons-neutral-focus)", - "borders-interactive-with-active": "var(--borders-interactive-with-active)", - "buttons-danger-focus": "var(--buttons-danger-focus)", - "borders-base": "var(--borders-base)", - "buttons-danger": "var(--buttons-danger)", - "details-contrast-on-bg-interactive": "var(--details-contrast-on-bg-interactive)", - "details-switch-handle": "var(--details-switch-handle)", - "buttons-neutral": "var(--buttons-neutral)", - "borders-interactive-with-focus": "var(--borders-interactive-with-focus)", - "details-switch-background-focus": "var(--details-switch-background-focus)", - "borders-error": "var(--borders-error)", - "buttons-inverted-focus": "var(--buttons-inverted-focus)", - "borders-focus": "var(--borders-focus)", - "details-switch-background": "var(--details-switch-background)", - "elevation-tooltip": "var(--elevation-tooltip)", - "borders-interactive-with-shadow": "var(--borders-interactive-with-shadow)", - "elevation-flyout": "var(--elevation-flyout)", - "details-commandbar": "var(--details-commandbar)", - "elevation-modal": "var(--elevation-modal)" + "elevation-flyout": "var(--elevation-flyout)" } } } \ No newline at end of file diff --git a/packages/design-system/ui-preset/src/theme/tokens/colors.ts b/packages/design-system/ui-preset/src/theme/tokens/colors.ts index fa600b0c02..9fe603a056 100644 --- a/packages/design-system/ui-preset/src/theme/tokens/colors.ts +++ b/packages/design-system/ui-preset/src/theme/tokens/colors.ts @@ -1,33 +1,8 @@ export const colors = { "dark": { - "--button-transparent-pressed": "rgba(46, 48, 53, 1)", - "--border-base": "rgba(46, 48, 53, 1)", - "--tag-neutral-icon": "rgba(125, 130, 138, 1)", - "--bg-switch-off-hover": "rgba(70, 75, 80, 1)", "--fg-on-color": "rgba(255, 255, 255, 1)", - "--bg-switch-off": "rgba(53, 55, 60, 1)", - "--border-strong": "rgba(53, 55, 60, 1)", - "--fg-subtle": "rgba(173, 177, 184, 1)", - "--fg-base": "rgba(237, 238, 240, 1)", - "--bg-base-hover": "rgba(39, 40, 45, 1)", - "--bg-subtle-hover": "rgba(27, 27, 31, 1)", - "--fg-disabled": "rgba(60, 63, 68, 1)", - "--bg-subtle": "rgba(24, 24, 26, 1)", - "--tag-neutral-border": "rgba(60, 63, 68, 1)", - "--bg-subtle-pressed": "rgba(39, 40, 45, 1)", - "--tag-neutral-text": "rgba(173, 177, 184, 1)", - "--fg-muted": "rgba(105, 110, 119, 1)", - "--border-loud": "rgba(237, 238, 240, 1)", - "--bg-base-pressed": "rgba(46, 48, 53, 1)", - "--bg-disabled": "rgba(39, 40, 45, 1)", - "--code-fg-subtle": "rgba(105, 110, 119, 1)", - "--code-fg-base": "rgba(237, 238, 240, 1)", - "--code-bg-subtle": "rgba(24, 24, 26, 1)", - "--code-fg-muted": "rgba(70, 75, 80, 1)", - "--bg-highlight-hover": "rgba(30, 58, 138, 1)", "--border-danger": "rgba(190, 18, 60, 1)", "--border-interactive": "rgba(96, 165, 250, 1)", - "--bg-highlight": "rgba(23, 37, 84, 1)", "--fg-interactive-hover": "rgba(59, 130, 246, 1)", "--fg-error": "rgba(251, 113, 133, 1)", "--bg-interactive": "rgba(96, 165, 250, 1)", @@ -36,24 +11,18 @@ export const colors = { "--button-danger-gradient-from": "rgba(255, 255, 255, 1)", "--button-danger-gradient-to": "rgba(255, 255, 255, 0)", "--fg-interactive": "rgba(96, 165, 250, 1)", - "--bg-overlay": "rgba(24, 24, 26, 0.7)", - "--fg-on-inverted": "rgba(24, 24, 26, 1)", "--tag-red-border": "rgba(136, 19, 55, 1)", "--tag-red-bg": "rgba(76, 5, 25, 1)", - "--button-transparent-hover": "rgba(39, 40, 45, 1)", "--tag-blue-text": "rgba(96, 165, 250, 1)", "--tag-orange-text": "rgba(251, 191, 36, 1)", "--tag-green-text": "rgba(52, 211, 153, 1)", - "--tag-neutral-bg": "rgba(46, 48, 53, 1)", "--tag-orange-border": "rgba(120, 53, 15, 1)", "--tag-green-border": "rgba(6, 78, 59, 1)", - "--bg-base": "rgba(27, 27, 31, 1)", "--tag-red-text": "rgba(251, 113, 133, 1)", "--tag-green-bg-hover": "rgba(6, 78, 59, 1)", "--tag-purple-bg-hover": "rgba(76, 29, 149, 1)", "--tag-red-bg-hover": "rgba(136, 19, 55, 1)", "--border-transparent": "rgba(255, 255, 255, 0)", - "--code-border": "rgba(46, 48, 53, 1)", "--tag-orange-icon": "rgba(245, 158, 11, 1)", "--tag-purple-bg": "rgba(46, 16, 100, 1)", "--tag-blue-bg": "rgba(23, 37, 84, 1)", @@ -63,156 +32,195 @@ export const colors = { "--tag-blue-bg-hover": "rgba(30, 42, 138, 1)", "--tag-orange-bg": "rgba(69, 26, 3, 1)", "--tag-orange-bg-hover": "rgba(120, 53, 15, 1)", - "--code-bg-base": "rgba(27, 27, 31, 1)", - "--button-neutral": "rgba(39, 40, 45, 1)", - "--button-neutral-gradient-from": "rgba(255, 255, 255, 1)", - "--button-neutral-gradient-to": "rgba(255, 255, 255, 0)", - "--tag-neutral-bg-hover": "rgba(53, 55, 60, 1)", "--tag-blue-icon": "rgba(59, 130, 246, 1)", "--tag-red-icon": "rgba(244, 63, 94, 1)", "--tag-purple-icon": "rgba(139, 92, 246, 1)", "--tag-purple-text": "rgba(167, 139, 250, 1)", "--tag-green-icon": "rgba(16, 185, 129, 1)", - "--button-inverted": "rgba(237, 238, 240, 1)", - "--button-inverted-gradient-from": "rgba(24, 24, 26, 0)", - "--button-inverted-gradient-to": "rgba(24, 24, 26, 1)", - "--bg-component": "rgba(39, 40, 45, 1)", - "--bg-field": "rgba(39, 40, 45, 1)", - "--bg-field-hover": "rgba(46, 48, 53, 1)", - "--contrast-fg-primary": "rgba(28, 32, 36, 1)", - "--contrast-bg-base": "rgba(228, 228, 233, 1)", - "--contrast-fg-secondary": "rgba(96, 100, 108, 1)", - "--contrast-border-base": "rgba(185, 187, 198, 1)", - "--contrast-bg-base-pressed": "rgba(242, 242, 245, 1)", - "--contrast-bg-subtle": "rgba(211, 212, 219, 1)", - "--contrast-bg-base-hover": "rgba(235, 235, 239, 1)", - "--contrast-bg-highlight": "rgba(242, 242, 245, 1)", - "--button-neutral-pressed": "rgba(60, 63, 68, 1)", - "--button-neutral-pressed-gradient-from": "rgba(255, 255, 255, 1)", - "--button-neutral-pressed-gradient-to": "rgba(255, 255, 255, 0)", - "--button-inverted-pressed": "rgba(237, 238, 240, 1)", - "--button-inverted-pressed-gradient-from": "rgba(0, 0, 0, 0)", - "--button-inverted-pressed-gradient-to": "rgba(24, 24, 26, 1)", - "--button-neutral-hover": "rgba(53, 55, 60, 1)", - "--button-neutral-hover-gradient-from": "rgba(255, 255, 255, 1)", - "--button-neutral-hover-gradient-to": "rgba(255, 255, 255, 0)", - "--button-inverted-hover": "rgba(255, 255, 255, 1)", - "--button-inverted-hover-gradient-from": "rgba(24, 24, 26, 0)", - "--button-inverted-hover-gradient-to": "rgba(24, 24, 26, 1)", "--button-danger-pressed": "rgba(225, 29, 72, 1)", "--button-danger-pressed-gradient-from": "rgba(255, 255, 255, 1)", "--button-danger-pressed-gradient-to": "rgba(255, 255, 255, 0)", "--button-danger-hover": "rgba(190, 18, 60, 1)", "--button-danger-hover-gradient-from": "rgba(255, 255, 255, 1)", "--button-danger-hover-gradient-to": "rgba(255, 255, 255, 0)", - "--button-transparent": "rgba(0, 0, 0, 0.01)" - }, - "light": { - "--button-inverted": "rgba(3, 7, 18, 1)", + "--button-transparent": "rgba(0, 0, 0, 0.01)", + "--code-bg-base": "rgba(9, 9, 11, 1)", + "--button-neutral": "rgba(39, 39, 42, 1)", + "--button-neutral-gradient-from": "rgba(255, 255, 255, 1)", + "--button-neutral-gradient-to": "rgba(255, 255, 255, 0)", + "--button-transparent-hover": "rgba(39, 39, 42, 1)", + "--code-fg-subtle": "rgba(161, 161, 170, 1)", + "--tag-neutral-bg-hover": "rgba(63, 63, 70, 1)", + "--contrast-border-base": "rgba(82, 82, 91, 1)", + "--button-neutral-pressed": "rgba(82, 82, 91, 1)", + "--button-neutral-pressed-gradient-from": "rgba(255, 255, 255, 1)", + "--button-neutral-pressed-gradient-to": "rgba(255, 255, 255, 0)", + "--tag-neutral-bg": "rgba(39, 39, 42, 1)", + "--bg-highlight": "rgba(23, 37, 84, 1)", + "--border-base": "rgba(255, 255, 255, 0.1)", + "--code-fg-base": "rgba(250, 250, 250, 1)", + "--tag-neutral-icon": "rgba(113, 113, 122, 1)", + "--bg-switch-off-hover": "rgba(82, 82, 91, 1)", + "--bg-base": "rgba(24, 24, 27, 1)", + "--button-inverted-pressed": "rgba(161, 161, 170, 1)", + "--button-inverted-pressed-gradient-from": "rgba(255, 255, 255, 1)", + "--button-inverted-pressed-gradient-to": "rgba(255, 255, 255, 0)", + "--button-neutral-hover": "rgba(63, 63, 70, 1)", + "--button-neutral-hover-gradient-from": "rgba(255, 255, 255, 1)", + "--button-neutral-hover-gradient-to": "rgba(255, 255, 255, 0)", + "--bg-switch-off": "rgba(63, 63, 70, 1)", + "--border-strong": "rgba(255, 255, 255, 0.15)", + "--fg-subtle": "rgba(161, 161, 170, 1)", + "--bg-highlight-hover": "rgba(30, 58, 138, 1)", + "--button-inverted": "rgba(82, 82, 91, 1)", "--button-inverted-gradient-from": "rgba(255, 255, 255, 1)", "--button-inverted-gradient-to": "rgba(255, 255, 255, 0)", - "--code-fg-subtle": "rgba(156, 163, 175, 1)", - "--code-fg-muted": "rgba(107, 114, 128, 1)", - "--code-bg-subtle": "rgba(31, 41, 55, 1)", - "--code-fg-base": "rgba(249, 250, 251, 1)", + "--fg-base": "rgba(250, 250, 250, 1)", + "--code-border": "rgba(39, 39, 42, 1)", + "--contrast-bg-subtle": "rgba(39, 39, 42, 1)", + "--contrast-bg-base-hover": "rgba(82, 82, 91, 1)", + "--bg-base-hover": "rgba(39, 39, 42, 1)", + "--bg-subtle-hover": "rgba(24, 24, 27, 1)", + "--fg-disabled": "rgba(63, 63, 70, 1)", + "--bg-subtle": "rgba(9, 9, 11, 1)", + "--tag-neutral-border": "rgba(63, 63, 70, 1)", + "--bg-subtle-pressed": "rgba(39, 39, 42, 1)", + "--tag-neutral-text": "rgba(161, 161, 170, 1)", + "--fg-muted": "rgba(82, 82, 91, 1)", + "--bg-overlay": "rgba(9, 9, 11, 0.7)", + "--button-inverted-hover": "rgba(113, 113, 122, 1)", + "--button-inverted-hover-gradient-from": "rgba(255, 255, 255, 1)", + "--button-inverted-hover-gradient-to": "rgba(255, 255, 255, 0)", + "--fg-on-inverted": "rgba(9, 9, 11, 1)", + "--code-bg-subtle": "rgba(24, 24, 27, 1)", + "--bg-component": "rgba(39, 39, 42, 1)", + "--border-loud": "rgba(250, 250, 250, 1)", + "--bg-base-pressed": "rgba(63, 63, 70, 1)", + "--bg-disabled": "rgba(39, 39, 42, 1)", + "--contrast-bg-highlight": "rgba(82, 82, 91, 1)", + "--button-transparent-pressed": "rgba(63, 63, 70, 1)", + "--code-fg-muted": "rgba(82, 82, 91, 1)", + "--contrast-fg-secondary": "rgba(161, 161, 170, 1)", + "--contrast-bg-base-pressed": "rgba(113, 113, 122, 1)", + "--contrast-fg-primary": "rgba(250, 250, 250, 1)", + "--contrast-bg-base": "rgba(63, 63, 70, 1)", + "--bg-component-pressed": "rgba(82, 82, 91, 1)", + "--bg-component-hover": "rgba(63, 63, 70, 1)", + "--bg-field": "rgba(39, 39, 42, 1)", + "--bg-field-component": "rgba(24, 24, 27, 1)", + "--bg-field-component-hover": "rgba(24, 24, 27, 1)", + "--bg-field-hover": "rgba(39, 39, 42, 1)" + }, + "light": { "--tag-green-bg": "rgba(209, 250, 229, 1)", - "--border-strong": "rgba(209, 213, 219, 1)", "--border-interactive": "rgba(59, 130, 246, 1)", "--bg-highlight": "rgba(239, 246, 255, 1)", - "--tag-neutral-bg": "rgba(243, 244, 246, 1)", "--tag-red-bg": "rgba(255, 228, 230, 1)", "--tag-orange-bg": "rgba(254, 244, 199, 1)", "--bg-base": "rgba(255, 255, 255, 1)", - "--border-base": "rgba(229, 231, 235, 1)", "--tag-green-icon": "rgba(5, 150, 105, 1)", "--tag-purple-bg-hover": "rgba(221, 214, 254, 1)", "--tag-blue-border": "rgba(191, 219, 254, 1)", "--tag-orange-icon": "rgba(217, 119, 6, 1)", "--tag-purple-bg": "rgba(237, 233, 254, 1)", - "--bg-subtle": "rgba(249, 250, 251, 1)", "--tag-purple-text": "rgba(109, 40, 217, 1)", "--tag-blue-bg": "rgba(219, 234, 254, 1)", "--tag-blue-icon": "rgba(37, 99, 235, 1)", "--border-error": "rgba(225, 29, 72, 1)", - "--border-loud": "rgba(3, 7, 18, 1)", "--fg-on-inverted": "rgba(255, 255, 255, 1)", "--fg-on-color": "rgba(255, 255, 255, 1)", "--fg-interactive-hover": "rgba(37, 99, 235, 1)", "--fg-interactive": "rgba(59, 130, 246, 1)", "--fg-error": "rgba(225, 29, 72, 1)", "--border-danger": "rgba(190, 18, 60, 1)", - "--border-transparent": "rgba(3, 7, 18, 0)", - "--fg-base": "rgba(3, 7, 18, 1)", - "--fg-disabled": "rgba(209, 213, 219, 1)", - "--bg-subtle-pressed": "rgba(229, 231, 235, 1)", - "--fg-subtle": "rgba(75, 85, 99, 1)", - "--fg-muted": "rgba(156, 163, 175, 1)", - "--bg-subtle-hover": "rgba(243, 244, 246, 1)", - "--tag-neutral-border": "rgba(229, 231, 235, 1)", + "--fg-muted": "rgba(161, 161, 170, 1)", + "--bg-subtle-pressed": "rgba(228, 228, 231, 1)", "--tag-green-bg-hover": "rgba(167, 243, 208, 1)", "--tag-blue-bg-hover": "rgba(191, 219, 254, 1)", "--tag-red-icon": "rgba(225, 29, 72, 1)", - "--tag-neutral-text": "rgba(75, 85, 99, 1)", "--tag-red-bg-hover": "rgba(254, 205, 211, 1)", "--tag-red-text": "rgba(190, 18, 60, 1)", "--tag-purple-icon": "rgba(124, 58, 237, 1)", "--tag-blue-text": "rgba(29, 78, 216, 1)", "--tag-orange-bg-hover": "rgba(253, 230, 138, 1)", - "--tag-neutral-bg-hover": "rgba(229, 231, 235, 1)", "--tag-purple-border": "rgba(221, 214, 254, 1)", "--tag-orange-text": "rgba(180, 83, 9, 1)", - "--tag-neutral-icon": "rgba(107, 114, 128, 1)", "--tag-orange-border": "rgba(253, 230, 138, 1)", "--tag-red-border": "rgba(254, 205, 211, 1)", "--tag-green-border": "rgba(167, 243, 208, 1)", "--tag-green-text": "rgba(4, 120, 87, 1)", - "--code-bg-base": "rgba(17, 24, 39, 1)", - "--code-border": "rgba(55, 65, 81, 1)", - "--button-neutral": "rgba(255, 255, 255, 1)", - "--button-neutral-gradient-from": "rgba(3, 7, 18, 0)", - "--button-neutral-gradient-to": "rgba(3, 7, 18, 1)", "--button-danger": "rgba(225, 29, 72, 1)", "--button-danger-gradient-from": "rgba(255, 255, 255, 1)", "--button-danger-gradient-to": "rgba(255, 255, 255, 0)", - "--button-inverted-pressed": "rgba(31, 41, 55, 1)", - "--button-inverted-pressed-gradient-from": "rgba(255, 255, 255, 1)", - "--button-inverted-pressed-gradient-to": "rgba(255, 255, 255, 0)", "--button-danger-pressed": "rgba(159, 18, 57, 1)", "--button-danger-pressed-gradient-from": "rgba(255, 255, 255, 1)", "--button-danger-pressed-gradient-to": "rgba(255, 255, 255, 0)", - "--button-inverted-hover": "rgba(17, 24, 39, 1)", - "--button-inverted-hover-gradient-from": "rgba(255, 255, 255, 1)", - "--button-inverted-hover-gradient-to": "rgba(255, 255, 255, 0)", "--button-danger-hover": "rgba(190, 18, 60, 1)", "--button-danger-hover-gradient-from": "rgba(255, 255, 255, 1)", "--button-danger-hover-gradient-to": "rgba(255, 255, 255, 0)", - "--bg-component": "rgba(241, 243, 245, 1)", - "--bg-overlay": "rgba(3, 7, 18, 0.4)", - "--bg-switch-off": "rgba(229, 231, 235, 1)", - "--bg-field-hover": "rgba(243, 244, 246, 1)", - "--bg-field": "rgba(249, 250, 251, 1)", "--bg-interactive": "rgba(59, 130, 246, 1)", "--bg-highlight-hover": "rgba(219, 234, 254, 1)", - "--bg-switch-off-hover": "rgba(209, 213, 219, 1)", - "--bg-disabled": "rgba(243, 244, 246, 1)", - "--contrast-bg-subtle": "rgba(46, 48, 53, 1)", - "--contrast-bg-base": "rgba(27, 27, 31, 1)", - "--contrast-border-base": "rgba(60, 63, 68, 1)", - "--contrast-fg-primary": "rgba(237, 238, 240, 1)", - "--contrast-bg-base-pressed": "rgba(53, 55, 60, 1)", - "--contrast-fg-secondary": "rgba(173, 177, 184, 1)", - "--contrast-bg-highlight": "rgba(53, 55, 60, 1)", - "--contrast-bg-base-hover": "rgba(46, 48, 53, 1)", - "--button-transparent-hover": "rgba(243, 244, 246, 1)", - "--button-neutral-pressed": "rgba(229, 231, 235, 1)", - "--button-neutral-pressed-gradient-from": "rgba(3, 7, 18, 0)", - "--button-neutral-pressed-gradient-to": "rgba(3, 7, 18, 1)", - "--button-transparent-pressed": "rgba(229, 231, 235, 1)", - "--button-neutral-hover": "rgba(243, 244, 246, 1)", - "--button-neutral-hover-gradient-from": "rgba(3, 7, 18, 0)", - "--button-neutral-hover-gradient-to": "rgba(3, 7, 18, 1)", - "--bg-base-pressed": "rgba(243, 244, 246, 1)", - "--bg-base-hover": "rgba(249, 250, 251, 1)", - "--button-transparent": "rgba(255, 255, 255, 0.01)" + "--button-transparent": "rgba(255, 255, 255, 0.01)", + "--bg-overlay": "rgba(9, 9, 11, 0.4)", + "--tag-neutral-border": "rgba(228, 228, 231, 1)", + "--border-loud": "rgba(9, 9, 11, 1)", + "--contrast-fg-primary": "rgba(250, 250, 250, 1)", + "--bg-switch-off": "rgba(228, 228, 231, 1)", + "--contrast-bg-base-pressed": "rgba(63, 63, 70, 1)", + "--bg-base-pressed": "rgba(228, 228, 231, 1)", + "--tag-neutral-text": "rgba(82, 82, 91, 1)", + "--button-transparent-hover": "rgba(244, 244, 245, 1)", + "--contrast-bg-base": "rgba(24, 24, 27, 1)", + "--fg-disabled": "rgba(212, 212, 216, 1)", + "--bg-field": "rgba(250, 250, 250, 1)", + "--border-strong": "rgba(212, 212, 216, 1)", + "--bg-field-hover": "rgba(244, 244, 245, 1)", + "--contrast-border-base": "rgba(82, 82, 91, 1)", + "--fg-base": "rgba(9, 9, 11, 1)", + "--contrast-bg-subtle": "rgba(39, 39, 42, 1)", + "--contrast-fg-secondary": "rgba(161, 161, 170, 1)", + "--code-fg-subtle": "rgba(161, 161, 170, 1)", + "--tag-neutral-bg": "rgba(244, 244, 245, 1)", + "--button-transparent-pressed": "rgba(228, 228, 231, 1)", + "--tag-neutral-bg-hover": "rgba(228, 228, 231, 1)", + "--code-fg-muted": "rgba(113, 113, 122, 1)", + "--contrast-bg-highlight": "rgba(63, 63, 70, 1)", + "--tag-neutral-icon": "rgba(113, 113, 122, 1)", + "--border-base": "rgba(228, 228, 231, 1)", + "--code-bg-base": "rgba(24, 24, 27, 1)", + "--button-neutral": "rgba(255, 255, 255, 1)", + "--button-neutral-gradient-from": "rgba(9, 9, 11, 0)", + "--button-neutral-gradient-to": "rgba(9, 9, 11, 1)", + "--code-bg-subtle": "rgba(39, 39, 42, 1)", + "--button-neutral-hover": "rgba(244, 244, 245, 1)", + "--button-neutral-hover-gradient-from": "rgba(9, 9, 11, 0)", + "--button-neutral-hover-gradient-to": "rgba(9, 9, 11, 1)", + "--contrast-bg-base-hover": "rgba(39, 39, 42, 1)", + "--bg-subtle": "rgba(250, 250, 250, 1)", + "--bg-switch-off-hover": "rgba(212, 212, 216, 1)", + "--code-fg-base": "rgba(250, 250, 250, 1)", + "--bg-disabled": "rgba(244, 244, 245, 1)", + "--code-border": "rgba(63, 63, 70, 1)", + "--fg-subtle": "rgba(82, 82, 91, 1)", + "--bg-subtle-hover": "rgba(244, 244, 245, 1)", + "--button-neutral-pressed": "rgba(228, 228, 231, 1)", + "--button-neutral-pressed-gradient-from": "rgba(9, 9, 11, 0)", + "--button-neutral-pressed-gradient-to": "rgba(9, 9, 11, 1)", + "--border-transparent": "rgba(9, 9, 11, 0)", + "--button-inverted": "rgba(9, 9, 11, 1)", + "--button-inverted-gradient-from": "rgba(255, 255, 255, 1)", + "--button-inverted-gradient-to": "rgba(255, 255, 255, 0)", + "--button-inverted-pressed": "rgba(39, 39, 42, 1)", + "--button-inverted-pressed-gradient-from": "rgba(255, 255, 255, 1)", + "--button-inverted-pressed-gradient-to": "rgba(255, 255, 255, 0)", + "--button-inverted-hover": "rgba(24, 24, 27, 1)", + "--button-inverted-hover-gradient-from": "rgba(255, 255, 255, 1)", + "--button-inverted-hover-gradient-to": "rgba(255, 255, 255, 0)", + "--bg-component-hover": "rgba(244, 244, 245, 1)", + "--bg-field-component": "rgba(255, 255, 255, 1)", + "--bg-field-component-hover": "rgba(250, 250, 250, 1)", + "--bg-component-pressed": "rgba(228, 228, 231, 1)", + "--bg-component": "rgba(250, 250, 250, 1)", + "--bg-base-hover": "rgba(244, 244, 245, 1)" } } \ No newline at end of file diff --git a/packages/design-system/ui-preset/src/theme/tokens/components.ts b/packages/design-system/ui-preset/src/theme/tokens/components.ts index 5644c45b8d..76772755e5 100644 --- a/packages/design-system/ui-preset/src/theme/tokens/components.ts +++ b/packages/design-system/ui-preset/src/theme/tokens/components.ts @@ -4,30 +4,6 @@ export const components = { "backgroundImage": "linear-gradient(180deg, var(--button-danger-gradient-from), var(--button-danger-gradient-to))", "opacity": "10%" }, - ".button-neutral-gradient": { - "backgroundImage": "linear-gradient(180deg, var(--button-neutral-gradient-from), var(--button-neutral-gradient-to))", - "opacity": "6%" - }, - ".button-inverted-gradient": { - "backgroundImage": "linear-gradient(180deg, var(--button-inverted-gradient-from), var(--button-inverted-gradient-to))", - "opacity": "12%" - }, - ".button-neutral-pressed-gradient": { - "backgroundImage": "linear-gradient(180deg, var(--button-neutral-pressed-gradient-from), var(--button-neutral-pressed-gradient-to))", - "opacity": "6%" - }, - ".button-inverted-pressed-gradient": { - "backgroundImage": "linear-gradient(180deg, var(--button-inverted-pressed-gradient-from), var(--button-inverted-pressed-gradient-to))", - "opacity": "12%" - }, - ".button-neutral-hover-gradient": { - "backgroundImage": "linear-gradient(180deg, var(--button-neutral-hover-gradient-from), var(--button-neutral-hover-gradient-to))", - "opacity": "6%" - }, - ".button-inverted-hover-gradient": { - "backgroundImage": "linear-gradient(180deg, var(--button-inverted-hover-gradient-from), var(--button-inverted-hover-gradient-to))", - "opacity": "12%" - }, ".button-danger-pressed-gradient": { "backgroundImage": "linear-gradient(180deg, var(--button-danger-pressed-gradient-from), var(--button-danger-pressed-gradient-to))", "opacity": "10%" @@ -35,44 +11,68 @@ export const components = { ".button-danger-hover-gradient": { "backgroundImage": "linear-gradient(180deg, var(--button-danger-hover-gradient-from), var(--button-danger-hover-gradient-to))", "opacity": "10%" - } - }, - "light": { - ".button-inverted-gradient": { - "backgroundImage": "linear-gradient(180deg, var(--button-inverted-gradient-from), var(--button-inverted-gradient-to))", - "opacity": "16%" }, ".button-neutral-gradient": { "backgroundImage": "linear-gradient(180deg, var(--button-neutral-gradient-from), var(--button-neutral-gradient-to))", - "opacity": "3%" + "opacity": "6%" }, + ".button-neutral-pressed-gradient": { + "backgroundImage": "linear-gradient(180deg, var(--button-neutral-pressed-gradient-from), var(--button-neutral-pressed-gradient-to))", + "opacity": "6%" + }, + ".button-inverted-pressed-gradient": { + "backgroundImage": "linear-gradient(180deg, var(--button-inverted-pressed-gradient-from), var(--button-inverted-pressed-gradient-to))", + "opacity": "6%" + }, + ".button-neutral-hover-gradient": { + "backgroundImage": "linear-gradient(180deg, var(--button-neutral-hover-gradient-from), var(--button-neutral-hover-gradient-to))", + "opacity": "6%" + }, + ".button-inverted-gradient": { + "backgroundImage": "linear-gradient(180deg, var(--button-inverted-gradient-from), var(--button-inverted-gradient-to))", + "opacity": "6%" + }, + ".button-inverted-hover-gradient": { + "backgroundImage": "linear-gradient(180deg, var(--button-inverted-hover-gradient-from), var(--button-inverted-hover-gradient-to))", + "opacity": "6%" + } + }, + "light": { ".button-danger-gradient": { "backgroundImage": "linear-gradient(180deg, var(--button-danger-gradient-from), var(--button-danger-gradient-to))", "opacity": "16%" }, - ".button-inverted-pressed-gradient": { - "backgroundImage": "linear-gradient(180deg, var(--button-inverted-pressed-gradient-from), var(--button-inverted-pressed-gradient-to))", - "opacity": "16%" - }, ".button-danger-pressed-gradient": { "backgroundImage": "linear-gradient(180deg, var(--button-danger-pressed-gradient-from), var(--button-danger-pressed-gradient-to))", "opacity": "16%" }, - ".button-inverted-hover-gradient": { - "backgroundImage": "linear-gradient(180deg, var(--button-inverted-hover-gradient-from), var(--button-inverted-hover-gradient-to))", - "opacity": "16%" - }, ".button-danger-hover-gradient": { "backgroundImage": "linear-gradient(180deg, var(--button-danger-hover-gradient-from), var(--button-danger-hover-gradient-to))", "opacity": "16%" }, - ".button-neutral-pressed-gradient": { - "backgroundImage": "linear-gradient(180deg, var(--button-neutral-pressed-gradient-from), var(--button-neutral-pressed-gradient-to))", + ".button-neutral-gradient": { + "backgroundImage": "linear-gradient(180deg, var(--button-neutral-gradient-from), var(--button-neutral-gradient-to))", "opacity": "3%" }, ".button-neutral-hover-gradient": { "backgroundImage": "linear-gradient(180deg, var(--button-neutral-hover-gradient-from), var(--button-neutral-hover-gradient-to))", "opacity": "3%" + }, + ".button-neutral-pressed-gradient": { + "backgroundImage": "linear-gradient(180deg, var(--button-neutral-pressed-gradient-from), var(--button-neutral-pressed-gradient-to))", + "opacity": "3%" + }, + ".button-inverted-gradient": { + "backgroundImage": "linear-gradient(180deg, var(--button-inverted-gradient-from), var(--button-inverted-gradient-to))", + "opacity": "16%" + }, + ".button-inverted-pressed-gradient": { + "backgroundImage": "linear-gradient(180deg, var(--button-inverted-pressed-gradient-from), var(--button-inverted-pressed-gradient-to))", + "opacity": "16%" + }, + ".button-inverted-hover-gradient": { + "backgroundImage": "linear-gradient(180deg, var(--button-inverted-hover-gradient-from), var(--button-inverted-hover-gradient-to))", + "opacity": "16%" } } } \ No newline at end of file diff --git a/packages/design-system/ui-preset/src/theme/tokens/effects.ts b/packages/design-system/ui-preset/src/theme/tokens/effects.ts index a024d1d4d1..d8a52a98c8 100644 --- a/packages/design-system/ui-preset/src/theme/tokens/effects.ts +++ b/packages/design-system/ui-preset/src/theme/tokens/effects.ts @@ -1,50 +1,50 @@ export const effects = { "dark": { - "--details-switch-background-focus": "0px 0px 0px 1px rgba(27, 27, 31, 1), 0px 0px 0px 3px rgba(96, 165, 250, 0.8), 0px 1px 1px 0px rgba(0, 0, 0, 0.1) inset, 0px 2px 4px 0px rgba(0, 0, 0, 0.1) inset, 0px 0px 0px 0.5px rgba(255, 255, 255, 0.16) inset, 0px 0px 8px 0px rgba(0, 0, 0, 0.1) inset, 0px 2px 4px 0px rgba(0, 0, 0, 0.2)", - "--buttons-neutral-focus": "0px 1px 0px 0px rgba(255, 255, 255, 0.04), 0px -1px 0px 0px rgba(255, 255, 255, 0.04), 0px 0px 0px 1px rgba(255, 255, 255, 0.12), 0px 1px 1px 0px rgba(0, 0, 0, 0.4), 0px 2px 2px 0px rgba(0, 0, 0, 0.4), 0px 0px 0px 2px rgba(27, 27, 31, 1), 0px 0px 0px 4px rgba(96, 165, 250, 0.8)", - "--elevation-card-hover": "0px 0px 0px 1px rgba(255, 255, 255, 0.1), 0px 1px 2px -1px rgba(255, 255, 255, 0.16), 0px 2px 8px 0px rgba(0, 0, 0, 0.4)", - "--borders-focus": "0px 0px 0px 1px rgba(23, 23, 23, 1), 0px 0px 0px 3px rgba(96, 165, 250, 0.8)", - "--borders-interactive-with-focus": "0px 1px 2px 0px rgba(219, 234, 254, 0.5), 0px 0px 0px 1px rgba(96, 165, 250, 1), 0px 0px 0px 2px rgba(27, 27, 31, 1), 0px 0px 0px 4px rgba(96, 165, 250, 0.8)", "--borders-interactive-with-shadow": "0px 1px 2px 0px rgba(219, 234, 254, 0.5), 0px 0px 0px 1px rgba(96, 165, 250, 1)", "--details-contrast-on-bg-interactive": "0px 1px 2px 0px rgba(30, 58, 138, 0.6)", - "--elevation-card-rest": "0px 0px 0px 1px rgba(255, 255, 255, 0.1), 0px 1px 2px -1px rgba(255, 255, 255, 0.16), 0px 2px 4px 0px rgba(0, 0, 0, 0.4)", - "--elevation-tooltip": "0px 0px 0px 1px rgba(255, 255, 255, 0.1), 0px 4px 8px 0px rgba(0, 0, 0, 0.32)", - "--elevation-flyout": "0px 0px 0px 1px rgba(255, 255, 255, 0.1), 0px 8px 16px 0px rgba(0, 0, 0, 0.32)", "--details-switch-handle": "0px 0px 2px 1px rgba(255, 255, 255, 1) inset, 0px 1px 0px 0px rgba(255, 255, 255, 1) inset, 0px 0px 0px 0.5px rgba(0, 0, 0, 0.16), 0px 5px 4px 0px rgba(0, 0, 0, 0.1), 0px 3px 3px 0px rgba(0, 0, 0, 0.1), 0px 1px 2px 0px rgba(0, 0, 0, 0.1), 0px 0px 1px 0px rgba(0, 0, 0, 0.1)", - "--details-switch-background": "0px 1px 1px 0px rgba(0, 0, 0, 0.1) inset, 0px 2px 4px 0px rgba(0, 0, 0, 0.1) inset, 0px 0px 0px 0.5px rgba(255, 255, 255, 0.16) inset, 0px 0px 8px 0px rgba(0, 0, 0, 0.1) inset, 0px 2px 4px 0px rgba(0, 0, 0, 0.2)", "--borders-interactive-with-active": "0px 0px 0px 1px rgba(96, 165, 250, 1), 0px 0px 0px 4px rgba(59, 130, 246, 0.25)", - "--buttons-inverted": "0px 1px 0px 0px rgba(255, 255, 255, 0.04), 0px -1px 0px 0px rgba(255, 255, 255, 0.04), 0px 0px 0px 1px rgba(255, 255, 255, 0.6), 0px 1px 1px 0px rgba(0, 0, 0, 0.4), 0px 2px 2px 0px rgba(0, 0, 0, 0.4)", - "--buttons-danger": "0px 1px 0px 0px rgba(190, 18, 60, 0.04), 0px -1px 0px 0px rgba(190, 18, 60, 0.04), 0px 0px 0px 1px rgba(190, 18, 60, 0.6), 0px 1px 1px 0px rgba(0, 0, 0, 0.4), 0px 1px 1px 0px rgba(0, 0, 0, 0.4)", - "--buttons-neutral": "0px 1px 0px 0px rgba(255, 255, 255, 0.04), 0px -1px 0px 0px rgba(255, 255, 255, 0.04), 0px 0px 0px 1px rgba(255, 255, 255, 0.12), 0px 1px 1px 0px rgba(0, 0, 0, 0.4), 0px 2px 2px 0px rgba(0, 0, 0, 0.4)", - "--buttons-danger-focus": "0px 1px 0px 0px rgba(190, 18, 60, 0.04), 0px -1px 0px 0px rgba(190, 18, 60, 0.04), 0px 0px 0px 1px rgba(190, 18, 60, 0.6), 0px 1px 1px 0px rgba(0, 0, 0, 0.4), 0px 2px 2px 0px rgba(0, 0, 0, 0.4), 0px 0px 0px 2px rgba(27, 27, 31, 1), 0px 0px 0px 4px rgba(96, 165, 250, 0.8)", - "--borders-base": "0px 1px 0px 0px rgba(255, 255, 255, 0.04), 0px -1px 0px 0px rgba(255, 255, 255, 0.04), 0px 0px 0px 1px rgba(255, 255, 255, 0.12), 0px 1px 1px 0px rgba(0, 0, 0, 0.4), 0px 2px 2px 0px rgba(0, 0, 0, 0.4)", "--borders-error": "0px 0px 0px 1px rgba(244, 63, 94, 1), 0px 0px 0px 4px rgba(225, 29, 72, 0.25)", - "--buttons-inverted-focus": "0px 1px 0px 0px rgba(255, 255, 255, 0.04), 0px -1px 0px 0px rgba(255, 255, 255, 0.04), 0px 0px 0px 1px rgba(255, 255, 255, 0.6), 0px 1px 1px 0px rgba(0, 0, 0, 0.4), 0px 2px 2px 0px rgba(0, 0, 0, 0.4), 0px 0px 0px 2px rgba(27, 27, 31, 1), 0px 0px 0px 4px rgba(96, 165, 250, 0.8)", - "--details-commandbar": "0px 0px 0px 1px rgba(228, 228, 233, 1) inset, 0px 0px 0px 1.5px rgba(46, 48, 53, 0.2) inset, 0px 16px 32px 0px rgba(0, 0, 0, 0.32), 0px 2px 24px 0px rgba(0, 0, 0, 0.32)", - "--elevation-modal": "0px 0px 0px 1px rgba(23, 23, 23, 1) inset, 0px 0px 0px 1.5px rgba(46, 48, 53, 0.6) inset, 0px 0px 0px 1px rgba(255, 255, 255, 0.1), 0px 16px 32px 0px rgba(0, 0, 0, 0.32), 0px 2px 24px 0px rgba(0, 0, 0, 0.32)" + "--details-switch-background-focus": "0px 0px 0px 1px rgba(24, 24, 27, 1), 0px 0px 0px 3px rgba(96, 165, 250, 0.8), 0px 1px 1px 0px rgba(0, 0, 0, 0.1) inset, 0px 2px 4px 0px rgba(0, 0, 0, 0.1) inset, 0px 0px 0px 0.5px rgba(255, 255, 255, 0.12) inset, 0px 0px 8px 0px rgba(0, 0, 0, 0.1) inset, 0px 2px 4px 0px rgba(0, 0, 0, 0.2)", + "--elevation-card-rest": "0px 0px 0px 1px rgba(255, 255, 255, 0.12), 0px 1px 2px -1px rgba(255, 255, 255, 0.16), 0px 2px 4px 0px rgba(0, 0, 0, 0.4)", + "--borders-focus": "0px 0px 0px 1px rgba(24, 24, 27, 1), 0px 0px 0px 3px rgba(96, 165, 250, 0.8)", + "--buttons-danger": "0px 1px 0px 0px rgba(255, 255, 255, 0.12), 0px -0.75px 0px 0px rgba(255, 255, 255, 0.24), 0px 0px 0px 1px rgba(159, 18, 57, 1), 0px 1px 1px 0px rgba(0, 0, 0, 0.3), 0px 1px 1px 0px rgba(0, 0, 0, 0.3)", + "--borders-interactive-with-focus": "0px 1px 2px 0px rgba(219, 234, 254, 0.5), 0px 0px 0px 1px rgba(96, 165, 250, 1), 0px 0px 0px 2px rgba(24, 24, 27, 1), 0px 0px 0px 4px rgba(96, 165, 250, 0.8)", + "--details-commandbar": "0px 0px 0px 1px rgba(63, 63, 70, 1) inset, 0px 0px 0px 1.5px rgba(113, 113, 122, 0.4) inset, 0px 16px 32px 0px rgba(0, 0, 0, 0.32), 0px 2px 24px 0px rgba(0, 0, 0, 0.32)", + "--elevation-tooltip": "0px 0px 0px 1px rgba(255, 255, 255, 0.12), 0px 4px 8px 0px rgba(0, 0, 0, 0.32)", + "--elevation-flyout": "0px 0px 0px 1px rgba(255, 255, 255, 0.12), 0px 8px 16px 0px rgba(0, 0, 0, 0.32)", + "--details-switch-background": "0px 1px 1px 0px rgba(0, 0, 0, 0.1) inset, 0px 2px 4px 0px rgba(0, 0, 0, 0.1) inset, 0px 0px 0px 0.5px rgba(255, 255, 255, 0.12) inset, 0px 0px 8px 0px rgba(0, 0, 0, 0.1) inset, 0px 2px 4px 0px rgba(0, 0, 0, 0.2)", + "--elevation-modal": "0px 0px 0px 1px rgba(24, 24, 27, 1) inset, 0px 0px 0px 1.5px rgba(63, 63, 70, 0.4) inset, 0px 0px 0px 1px rgba(255, 255, 255, 0.12), 0px 16px 32px 0px rgba(0, 0, 0, 0.32), 0px 2px 24px 0px rgba(0, 0, 0, 0.32)", + "--elevation-card-hover": "0px 0px 0px 1px rgba(255, 255, 255, 0.12), 0px 1px 2px -1px rgba(255, 255, 255, 0.16), 0px 2px 8px 0px rgba(0, 0, 0, 0.4)", + "--buttons-inverted": "0px -0.75px 0px 0px rgba(255, 255, 255, 0.12), 0px 0px 0px 1px rgba(255, 255, 255, 0.36), 0px 1px 1px 0px rgba(0, 0, 0, 0.3), 0px 2px 2px 0px rgba(0, 0, 0, 0.3)", + "--buttons-neutral": "0px 1px 0px 0px rgba(255, 255, 255, 0.04), 0px -0.75px 0px 0px rgba(255, 255, 255, 0.08), 0px 0px 0px 1px rgba(255, 255, 255, 0.12), 0px 1px 1px 0px rgba(0, 0, 0, 0.3), 0px 2px 2px 0px rgba(0, 0, 0, 0.3)", + "--buttons-danger-focus": "0px 1px 0px 0px rgba(255, 255, 255, 0.12), 0px -0.75px 0px 0px rgba(255, 255, 255, 0.24), 0px 0px 0px 1px rgba(159, 18, 57, 1), 0px 1px 1px 0px rgba(0, 0, 0, 0.3), 0px 2px 2px 0px rgba(0, 0, 0, 0.3), 0px 0px 0px 2px rgba(27, 27, 31, 1), 0px 0px 0px 4px rgba(96, 165, 250, 0.8)", + "--borders-base": "0px 1px 0px 0px rgba(255, 255, 255, 0.04), 0px -0.75px 0px 0px rgba(255, 255, 255, 0.06), 0px 0px 0px 1px rgba(255, 255, 255, 0.12), 0px 1px 1px 0px rgba(0, 0, 0, 0.3), 0px 2px 2px 0px rgba(0, 0, 0, 0.3)", + "--buttons-neutral-focus": "0px 1px 0px 0px rgba(255, 255, 255, 0.04), 0px -0.75px 0px 0px rgba(255, 255, 255, 0.08), 0px 0px 0px 1px rgba(255, 255, 255, 0.12), 0px 1px 1px 0px rgba(0, 0, 0, 0.3), 0px 2px 2px 0px rgba(0, 0, 0, 0.3), 0px 0px 0px 2px rgba(24, 24, 27, 1), 0px 0px 0px 4px rgba(96, 165, 250, 0.8)", + "--buttons-inverted-focus": "0px -0.75px 0px 0px rgba(255, 255, 255, 0.12), 0px 0px 0px 1px rgba(255, 255, 255, 0.36), 0px 1px 1px 0px rgba(0, 0, 0, 0.3), 0px 2px 2px 0px rgba(0, 0, 0, 0.3), 0px 0px 0px 2px rgba(27, 27, 31, 1), 0px 0px 0px 4px rgba(96, 165, 250, 0.8)" }, "light": { - "--elevation-card-hover": "0px 0px 0px 1px rgba(3, 7, 18, 0.08), 0px 1px 2px -1px rgba(3, 7, 18, 0.08), 0px 2px 8px 0px rgba(3, 7, 18, 0.1)", - "--buttons-inverted": "0px 0.75px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 1px 2px 0px rgba(3, 7, 18, 0.4), 0px 0px 0px 1px rgba(3, 7, 18, 0.8)", - "--elevation-card-rest": "0px 0px 0px 1px rgba(3, 7, 18, 0.08), 0px 1px 2px -1px rgba(3, 7, 18, 0.08), 0px 2px 4px 0px rgba(3, 7, 18, 0.04)", - "--buttons-neutral-focus": "0px 1px 2px 0px rgba(3, 7, 18, 0.12), 0px 0px 0px 1px rgba(3, 7, 18, 0.08), 0px 0px 0px 2px rgba(255, 255, 255, 1), 0px 0px 0px 4px rgba(59, 130, 246, 0.6)", "--borders-interactive-with-active": "0px 0px 0px 1px rgba(59, 130, 246, 1), 0px 0px 0px 4px rgba(59, 130, 246, 0.2)", "--buttons-danger-focus": "0px 0.75px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 1px 2px 0px rgba(190, 18, 60, 0.4), 0px 0px 0px 1px rgba(190, 18, 60, 0.8), 0px 0px 0px 2px rgba(255, 255, 255, 1), 0px 0px 0px 4px rgba(59, 130, 246, 0.6)", - "--borders-base": "0px 1px 2px 0px rgba(3, 7, 18, 0.12), 0px 0px 0px 1px rgba(3, 7, 18, 0.08)", "--buttons-danger": "0px 0.75px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 1px 2px 0px rgba(190, 18, 60, 0.4), 0px 0px 0px 1px rgba(190, 18, 60, 0.8)", "--details-contrast-on-bg-interactive": "0px 1px 2px 0px rgba(30, 58, 138, 0.6)", - "--details-switch-handle": "0px 0px 2px 1px rgba(255, 255, 255, 1) inset, 0px 1px 0px 0px rgba(255, 255, 255, 1) inset, 0px 0px 0px 0.5px rgba(3, 7, 18, 0.02), 0px 5px 4px 0px rgba(3, 7, 18, 0.02), 0px 3px 3px 0px rgba(3, 7, 18, 0.04), 0px 1px 2px 0px rgba(3, 7, 18, 0.12), 0px 0px 1px 0px rgba(3, 7, 18, 0.08)", - "--buttons-neutral": "0px 1px 2px 0px rgba(3, 7, 18, 0.12), 0px 0px 0px 1px rgba(3, 7, 18, 0.08)", "--borders-interactive-with-focus": "0px 1px 2px 0px rgba(30, 58, 138, 0.5), 0px 0px 0px 1px rgba(59, 130, 246, 1), 0px 0px 0px 2px rgba(255, 255, 255, 1), 0px 0px 0px 4px rgba(59, 130, 246, 0.6)", - "--details-switch-background-focus": "0px 0px 0px 1px rgba(255, 255, 255, 1), 0px 0px 0px 3px rgba(59, 130, 246, 0.6), 0px 1px 1px 0px rgba(3, 7, 18, 0.04) inset, 0px 2px 4px 0px rgba(3, 7, 18, 0.04) inset, 0px 0px 0px 0.5px rgba(3, 7, 18, 0.06) inset, 0px 0px 8px 0px rgba(3, 7, 18, 0.02) inset, 0px 2px 4px 0px rgba(3, 7, 18, 0.04)", "--borders-error": "0px 0px 0px 1px rgba(225, 29, 72, 1), 0px 0px 0px 3px rgba(225, 29, 72, 0.15)", - "--buttons-inverted-focus": "0px 0.75px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 1px 2px 0px rgba(3, 7, 18, 0.4), 0px 0px 0px 1px rgba(3, 7, 18, 0.8), 0px 0px 0px 2px rgba(255, 255, 255, 1), 0px 0px 0px 4px rgba(59, 130, 246, 0.6)", "--borders-focus": "0px 0px 0px 1px rgba(255, 255, 255, 1), 0px 0px 0px 3px rgba(59, 130, 246, 0.6)", - "--details-switch-background": "0px 1px 1px 0px rgba(3, 7, 18, 0.04) inset, 0px 2px 4px 0px rgba(3, 7, 18, 0.04) inset, 0px 0px 0px 0.5px rgba(3, 7, 18, 0.06) inset, 0px 0px 8px 0px rgba(3, 7, 18, 0.02) inset, 0px 2px 4px 0px rgba(3, 7, 18, 0.04)", - "--elevation-tooltip": "0px 0px 0px 1px rgba(3, 7, 18, 0.08), 0px 4px 8px 0px rgba(3, 7, 18, 0.08)", "--borders-interactive-with-shadow": "0px 1px 2px 0px rgba(30, 58, 138, 0.5), 0px 0px 0px 1px rgba(59, 130, 246, 1)", - "--elevation-flyout": "0px 0px 0px 1px rgba(3, 7, 18, 0.08), 0px 8px 16px 0px rgba(3, 7, 18, 0.08)", - "--details-commandbar": "0px 0px 0px 1px rgba(27, 27, 31, 1) inset, 0px 0px 0px 1.5px rgba(255, 255, 255, 0.2) inset, 0px 16px 32px 0px rgba(3, 7, 18, 0.16), 0px 2px 24px 0px rgba(3, 7, 18, 0.08)", - "--elevation-modal": "0px 0px 0px 1px rgba(255, 255, 255, 1) inset, 0px 0px 0px 1.5px rgba(229, 231, 235, 0.6) inset, 0px 0px 0px 1px rgba(3, 7, 18, 0.08), 0px 16px 32px 0px rgba(3, 7, 18, 0.08), 0px 2px 24px 0px rgba(3, 7, 18, 0.08)" + "--details-switch-handle": "0px 0px 2px 1px rgba(255, 255, 255, 1) inset, 0px 1px 0px 0px rgba(255, 255, 255, 1) inset, 0px 0px 0px 0.5px rgba(9, 9, 11, 0.02), 0px 5px 4px 0px rgba(9, 9, 11, 0.02), 0px 3px 3px 0px rgba(9, 9, 11, 0.04), 0px 1px 2px 0px rgba(9, 9, 11, 0.12), 0px 0px 1px 0px rgba(9, 9, 11, 0.08)", + "--buttons-neutral": "0px 1px 2px 0px rgba(9, 9, 11, 0.12), 0px 0px 0px 1px rgba(9, 9, 11, 0.08)", + "--details-commandbar": "0px 0px 0px 1px rgba(24, 24, 27, 1) inset, 0px 0px 0px 1.5px rgba(255, 255, 255, 0.2) inset, 0px 16px 32px 0px rgba(9, 9, 11, 0.16), 0px 2px 24px 0px rgba(9, 9, 11, 0.08)", + "--details-switch-background-focus": "0px 0px 0px 1px rgba(255, 255, 255, 1), 0px 0px 0px 3px rgba(59, 130, 246, 0.6), 0px 1px 1px 0px rgba(9, 9, 11, 0.04) inset, 0px 2px 4px 0px rgba(9, 9, 11, 0.04) inset, 0px 0px 0px 0.5px rgba(9, 9, 11, 0.06) inset, 0px 0px 8px 0px rgba(9, 9, 11, 0.02) inset, 0px 2px 4px 0px rgba(9, 9, 11, 0.04)", + "--buttons-inverted-focus": "0px 0.75px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 1px 2px 0px rgba(9, 9, 11, 0.4), 0px 0px 0px 1px rgba(9, 9, 11, 0.8), 0px 0px 0px 2px rgba(255, 255, 255, 1), 0px 0px 0px 4px rgba(59, 130, 246, 0.6)", + "--details-switch-background": "0px 1px 1px 0px rgba(9, 9, 11, 0.04) inset, 0px 2px 4px 0px rgba(9, 9, 11, 0.04) inset, 0px 0px 0px 0.5px rgba(9, 9, 11, 0.06) inset, 0px 0px 8px 0px rgba(9, 9, 11, 0.02) inset, 0px 2px 4px 0px rgba(9, 9, 11, 0.04)", + "--borders-base": "0px 1px 2px 0px rgba(9, 9, 11, 0.12), 0px 0px 0px 1px rgba(9, 9, 11, 0.08)", + "--elevation-tooltip": "0px 0px 0px 1px rgba(9, 9, 11, 0.08), 0px 4px 8px 0px rgba(9, 9, 11, 0.08)", + "--elevation-card-hover": "0px 0px 0px 1px rgba(9, 9, 11, 0.08), 0px 1px 2px -1px rgba(9, 9, 11, 0.08), 0px 2px 8px 0px rgba(9, 9, 11, 0.1)", + "--elevation-modal": "0px 0px 0px 1px rgba(255, 255, 255, 1) inset, 0px 0px 0px 1.5px rgba(228, 228, 231, 0.6) inset, 0px 0px 0px 1px rgba(9, 9, 11, 0.08), 0px 16px 32px 0px rgba(9, 9, 11, 0.08), 0px 2px 24px 0px rgba(9, 9, 11, 0.08)", + "--buttons-inverted": "0px 0.75px 0px 0px rgba(255, 255, 255, 0.2) inset, 0px 1px 2px 0px rgba(9, 9, 11, 0.4), 0px 0px 0px 1px rgba(9, 9, 11, 0.8)", + "--elevation-card-rest": "0px 0px 0px 1px rgba(9, 9, 11, 0.08), 0px 1px 2px -1px rgba(9, 9, 11, 0.08), 0px 2px 4px 0px rgba(9, 9, 11, 0.04)", + "--buttons-neutral-focus": "0px 1px 2px 0px rgba(9, 9, 11, 0.12), 0px 0px 0px 1px rgba(9, 9, 11, 0.08), 0px 0px 0px 2px rgba(255, 255, 255, 1), 0px 0px 0px 4px rgba(59, 130, 246, 0.6)", + "--elevation-flyout": "0px 0px 0px 1px rgba(9, 9, 11, 0.08), 0px 8px 16px 0px rgba(9, 9, 11, 0.08)" } } \ No newline at end of file diff --git a/packages/design-system/ui-preset/src/theme/tokens/typography.ts b/packages/design-system/ui-preset/src/theme/tokens/typography.ts index a8534b6a20..2ea793f9e6 100644 --- a/packages/design-system/ui-preset/src/theme/tokens/typography.ts +++ b/packages/design-system/ui-preset/src/theme/tokens/typography.ts @@ -179,16 +179,16 @@ export const typography = { "fontWeight": "500", "fontFamily": "Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji" }, - ".code-body": { - "fontSize": "0.75rem", - "lineHeight": "1.375rem", - "fontWeight": "400", - "fontFamily": "Roboto Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace" - }, ".code-label": { "fontSize": "0.75rem", "lineHeight": "1.25rem", "fontWeight": "400", "fontFamily": "Roboto Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace" + }, + ".code-body": { + "fontSize": "0.75rem", + "lineHeight": "1.375rem", + "fontWeight": "400", + "fontFamily": "Roboto Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace" } } \ No newline at end of file diff --git a/packages/design-system/ui/src/components/select/select.tsx b/packages/design-system/ui/src/components/select/select.tsx index 054f3f05cd..6807dbf45a 100644 --- a/packages/design-system/ui/src/components/select/select.tsx +++ b/packages/design-system/ui/src/components/select/select.tsx @@ -68,7 +68,7 @@ const triggerVariants = cva({ "hover:bg-ui-bg-field-hover", "focus-visible:shadow-borders-interactive-with-active data-[state=open]:!shadow-borders-interactive-with-active", "aria-[invalid=true]:border-ui-border-error aria-[invalid=true]:shadow-borders-error", - "invalid::border-ui-border-error invalid:shadow-borders-error", + "invalid:border-ui-border-error invalid:shadow-borders-error", "disabled:!bg-ui-bg-disabled disabled:!text-ui-fg-disabled", "group/trigger" ), diff --git a/packages/generated/client-types/src/lib/models/AdminGetProductsVariantsParams.ts b/packages/generated/client-types/src/lib/models/AdminGetProductsVariantsParams.ts index f7fef60162..9a5223f2a7 100644 --- a/packages/generated/client-types/src/lib/models/AdminGetProductsVariantsParams.ts +++ b/packages/generated/client-types/src/lib/models/AdminGetProductsVariantsParams.ts @@ -40,6 +40,29 @@ export interface AdminGetProductsVariantsParams { * Filter product variants by whether they are allowed to be backordered or not. */ allow_backorder?: boolean + /** + * Filter by available inventory quantity + */ + inventory_quantity?: + | number + | { + /** + * filter by inventory quantity less than this number + */ + lt?: number + /** + * filter by inventory quantity greater than this number + */ + gt?: number + /** + * filter by inventory quantity less than or equal to this number + */ + lte?: number + /** + * filter by inventory quantity greater than or equal to this number + */ + gte?: number + } /** * Filter by a creation date range. */ diff --git a/packages/generated/client-types/src/lib/models/AdminGetVariantsParams.ts b/packages/generated/client-types/src/lib/models/AdminGetVariantsParams.ts index 084ab62e93..e9fd347492 100644 --- a/packages/generated/client-types/src/lib/models/AdminGetVariantsParams.ts +++ b/packages/generated/client-types/src/lib/models/AdminGetVariantsParams.ts @@ -24,6 +24,18 @@ export interface AdminGetVariantsParams { * Limit the number of product variants returned. */ limit?: number + /** + * The field to sort the data by. By default, the sort order is ascending. To change the order to descending, prefix the field name with `-`. + */ + order?: string + /** + * Filter product variants by whether their inventory is managed or not. + */ + manage_inventory?: boolean + /** + * Filter product variants by whether they are allowed to be backordered or not. + */ + allow_backorder?: boolean /** * The ID of the cart to use for the price selection context. */ diff --git a/packages/medusa/src/api/routes/admin/products/list-variants.ts b/packages/medusa/src/api/routes/admin/products/list-variants.ts index de0e9fb985..bf41b65077 100644 --- a/packages/medusa/src/api/routes/admin/products/list-variants.ts +++ b/packages/medusa/src/api/routes/admin/products/list-variants.ts @@ -9,7 +9,10 @@ import { Request, Response } from "express" import { Transform, Type } from "class-transformer" import { ProductVariantService } from "../../../../services" -import { DateComparisonOperator } from "../../../../types/common" +import { + DateComparisonOperator, + NumericalComparisonOperator, +} from "../../../../types/common" import { IsType } from "../../../../utils" import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean" @@ -32,6 +35,28 @@ import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean" * - (query) manage_inventory {boolean} Filter product variants by whether their inventory is managed or not. * - (query) allow_backorder {boolean} Filter product variants by whether they are allowed to be backordered or not. * - in: query + * name: inventory_quantity + * description: Filter by available inventory quantity + * schema: + * oneOf: + * - type: number + * description: a specific number to filter by. + * - type: object + * description: filter using less and greater than comparisons. + * properties: + * lt: + * type: number + * description: filter by inventory quantity less than this number + * gt: + * type: number + * description: filter by inventory quantity greater than this number + * lte: + * type: number + * description: filter by inventory quantity less than or equal to this number + * gte: + * type: number + * description: filter by inventory quantity greater than or equal to this number + * - in: query * name: created_at * description: Filter by a creation date range. * schema: @@ -189,6 +214,13 @@ export class AdminGetProductsVariantsParams { @IsOptional() order?: string + /** + * Number filters to apply on product variants' `inventory_quantity` field. + */ + @IsOptional() + @IsType([Number, NumericalComparisonOperator]) + inventory_quantity?: number | NumericalComparisonOperator + /** * Filter product variants by whether their inventory is managed or not. */ diff --git a/packages/medusa/src/api/routes/admin/variants/list-variants.ts b/packages/medusa/src/api/routes/admin/variants/list-variants.ts index 0c320d37fe..f69faaebda 100644 --- a/packages/medusa/src/api/routes/admin/variants/list-variants.ts +++ b/packages/medusa/src/api/routes/admin/variants/list-variants.ts @@ -1,3 +1,4 @@ +import { IsBoolean, IsInt, IsOptional, IsString } from "class-validator" import { CartService, PricingService, @@ -5,16 +6,16 @@ import { RegionService, SalesChannelService, } from "../../../../services" -import { IsInt, IsOptional, IsString } from "class-validator" -import { AdminPriceSelectionParams } from "../../../../types/price-selection" import { IInventoryService } from "@medusajs/types" -import { IsType } from "../../../../utils/validators/is-type" -import { NumericalComparisonOperator } from "../../../../types/common" -import { PricedVariant } from "../../../../types/pricing" -import ProductVariantService from "../../../../services/product-variant" -import { Type } from "class-transformer" +import { Transform, Type } from "class-transformer" import { omit } from "lodash" +import ProductVariantService from "../../../../services/product-variant" +import { NumericalComparisonOperator } from "../../../../types/common" +import { AdminPriceSelectionParams } from "../../../../types/price-selection" +import { PricedVariant } from "../../../../types/pricing" +import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean" +import { IsType } from "../../../../utils/validators/is-type" /** * @oas [get] /admin/variants @@ -40,6 +41,9 @@ import { omit } from "lodash" * - (query) fields {string} "Comma-separated fields that should be included in the returned product variants." * - (query) offset=0 {number} The number of product variants to skip when retrieving the product variants. * - (query) limit=100 {number} Limit the number of product variants returned. + * - (query) order {string} The field to sort the data by. By default, the sort order is ascending. To change the order to descending, prefix the field name with `-`. + * - (query) manage_inventory {boolean} Filter product variants by whether their inventory is managed or not. + * - (query) allow_backorder {boolean} Filter product variants by whether they are allowed to be backordered or not. * - in: query * name: cart_id * style: form @@ -320,4 +324,27 @@ export class AdminGetVariantsParams extends AdminPriceSelectionParams { @IsOptional() @IsType([Number, NumericalComparisonOperator]) inventory_quantity?: number | NumericalComparisonOperator + + /** + * The field to sort the data by. By default, the sort order is ascending. To change the order to descending, prefix the field name with `-`. + */ + @IsString() + @IsOptional() + order?: string + + /** + * Filter product variants by whether their inventory is managed or not. + */ + @IsBoolean() + @IsOptional() + @Transform(({ value }) => optionalBooleanMapper.get(value.toLowerCase())) + manage_inventory?: boolean + + /** + * Filter product variants by whether they are allowed to be backordered or not. + */ + @IsBoolean() + @IsOptional() + @Transform(({ value }) => optionalBooleanMapper.get(value.toLowerCase())) + allow_backorder?: boolean }