From 4342ac884bc3fe473576ef10d291f3547e0ffc62 Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Fri, 31 Mar 2023 12:07:24 +0200 Subject: [PATCH] feat(admin-ui, medusa): admin UI metadata (#3644) --- .changeset/famous-bananas-pay.md | 7 + packages/admin-ui/package.json | 1 + .../atoms/date-picker/date-picker.tsx | 32 +- .../atoms/date-picker/time-picker.tsx | 6 +- .../components/atoms/date-picker/types.tsx | 4 +- .../customer-group-general-form/index.tsx | 34 ++ .../discount-conditions-form/index.tsx | 0 .../discount-configuration-form/index.tsx | 159 ++++++++ .../discount/discount-general-form/index.tsx | 192 +++++++++ .../general/address-contact-form/index.tsx | 87 ++++ .../general/address-location-form/index.tsx | 129 ++++++ .../forms/general/metadata-form/index.tsx | 371 ++++++++++++++++++ .../general/prices-form/price-form-input.tsx | 19 +- .../gift-card/denomination-form/index.tsx | 10 +- .../organize-form/use-organize-data.tsx | 16 +- .../edit-flow-variant-form/index.tsx | 9 + .../icons/ellipsis-vertical-icon/index.tsx | 26 ++ .../src/components/molecules/input/index.tsx | 7 +- .../components/organisms/accordion/index.tsx | 2 +- .../ui/src/components/organisms/body-card.tsx | 4 +- .../edit-denominations-modal.tsx | 2 +- .../product-general-section/general-modal.tsx | 20 +- .../product-general-section/index.tsx | 12 + .../add-variant-modal.tsx | 13 +- .../edit-variant-modal.tsx | 2 + .../edit-variant-screen.tsx | 2 + .../edit-variants-modal/index.tsx | 8 +- .../components/organisms/raw-json/index.tsx | 2 +- .../src/components/templates/address-form.tsx | 8 +- .../templates/collection-modal/index.tsx | 89 +++-- .../customer-groups-table.tsx | 27 +- .../use-customer-orders-columns.tsx | 2 +- .../ui/src/domain/customers/details/edit.tsx | 99 +++-- .../ui/src/domain/customers/details/index.tsx | 144 +++---- .../groups/context/customer-group-context.tsx | 81 ---- .../customers/groups/customer-group-modal.tsx | 214 ++++++---- .../src/domain/customers/groups/details.tsx | 58 +-- .../ui/src/domain/customers/groups/index.tsx | 38 +- .../configurations/edit-configurations.tsx | 194 ++------- .../details/configurations/index.tsx | 15 +- .../details/general/edit-general.tsx | 235 ++++------- .../discounts/details/general/index.tsx | 13 +- .../discount-form/add-conditions-modal.tsx | 2 +- .../form/discount-form-context.tsx | 9 +- .../new/discount-form/form/mappers.ts | 82 +--- .../discounts/new/discount-form/index.tsx | 14 +- .../domain/orders/details/address-modal.tsx | 149 ++++--- .../ui/src/domain/orders/details/index.tsx | 104 ++--- .../domain/orders/draft-orders/details.tsx | 4 +- .../modals/add-product-category.tsx | 17 +- .../modals/edit-product-category.tsx | 19 +- .../shipping-option-card/edit-modal.tsx | 14 +- .../components/shipping-option-form/index.tsx | 10 + .../general-section/edit-region.modal.tsx | 15 +- .../create-return-shipping-option.modal.tsx | 2 + .../create-shipping-option-modal.tsx | 2 + packages/medusa/src/services/order.ts | 8 + packages/medusa/src/utils/set-metadata.ts | 6 +- yarn.lock | 17 +- 59 files changed, 1904 insertions(+), 963 deletions(-) create mode 100644 .changeset/famous-bananas-pay.md create mode 100644 packages/admin-ui/ui/src/components/forms/customer-group/customer-group-general-form/index.tsx create mode 100644 packages/admin-ui/ui/src/components/forms/discount/discount-conditions-form/index.tsx create mode 100644 packages/admin-ui/ui/src/components/forms/discount/discount-configuration-form/index.tsx create mode 100644 packages/admin-ui/ui/src/components/forms/discount/discount-general-form/index.tsx create mode 100644 packages/admin-ui/ui/src/components/forms/general/address-contact-form/index.tsx create mode 100644 packages/admin-ui/ui/src/components/forms/general/address-location-form/index.tsx create mode 100644 packages/admin-ui/ui/src/components/forms/general/metadata-form/index.tsx create mode 100644 packages/admin-ui/ui/src/components/fundamentals/icons/ellipsis-vertical-icon/index.tsx delete mode 100644 packages/admin-ui/ui/src/domain/customers/groups/context/customer-group-context.tsx diff --git a/.changeset/famous-bananas-pay.md b/.changeset/famous-bananas-pay.md new file mode 100644 index 0000000000..466531a731 --- /dev/null +++ b/.changeset/famous-bananas-pay.md @@ -0,0 +1,7 @@ +--- +"@medusajs/admin-ui": patch +"@medusajs/medusa": patch +--- + +feat(admin-ui): Adds metadata forms to all applicable domains in the UI. +fix(medusa): Fixes an issue where metadata was not being set for order addresses using `setMetadata`. diff --git a/packages/admin-ui/package.json b/packages/admin-ui/package.json index 10e3acb400..627e96300b 100644 --- a/packages/admin-ui/package.json +++ b/packages/admin-ui/package.json @@ -86,6 +86,7 @@ "@testing-library/user-event": "^14.4.3", "@types/pluralize": "^0.0.29", "@types/react": "^18.0.27", + "@types/react-datepicker": "^4.10.0", "@types/react-dom": "^18.0.10", "@types/react-table": "^7.7.9", "typescript": "^4.9.3", diff --git a/packages/admin-ui/ui/src/components/atoms/date-picker/date-picker.tsx b/packages/admin-ui/ui/src/components/atoms/date-picker/date-picker.tsx index c27f43183a..d6f35a881a 100644 --- a/packages/admin-ui/ui/src/components/atoms/date-picker/date-picker.tsx +++ b/packages/admin-ui/ui/src/components/atoms/date-picker/date-picker.tsx @@ -11,7 +11,7 @@ import InputHeader from "../../fundamentals/input-header" import CustomHeader from "./custom-header" import { DateTimePickerProps } from "./types" -const getDateClassname = (d, tempDate) => { +const getDateClassname = (d: Date, tempDate: Date) => { return moment(d).format("YY,MM,DD") === moment(tempDate).format("YY,MM,DD") ? "date chosen" : `date ${ @@ -29,12 +29,18 @@ const DatePicker: React.FC = ({ tooltipContent, tooltip, }) => { - const [tempDate, setTempDate] = useState(date) + const [tempDate, setTempDate] = useState(date || null) const [isOpen, setIsOpen] = useState(false) useEffect(() => setTempDate(date), [isOpen]) const submitDate = () => { + if (!tempDate || !date) { + onSubmitDate(null) + setIsOpen(false) + return + } + // update only date, month and year const newDate = new Date(date.getTime()) newDate.setUTCDate(tempDate.getUTCDate()) @@ -68,7 +74,9 @@ const DatePicker: React.FC = ({ @@ -78,7 +86,10 @@ const DatePicker: React.FC = ({ sideOffset={8} className="rounded-rounded border-grey-20 bg-grey-0 shadow-dropdown w-full border px-8" > - + setTempDate(date)} + />
@@ -84,13 +84,13 @@ const TimePicker: React.FC = ({ setSelectedHour(n)} + onSelect={(n) => setSelectedHour(n as number)} className="pr-4" /> setSelectedMinute(n)} + onSelect={(n) => setSelectedMinute(n as number)} />
diff --git a/packages/admin-ui/ui/src/components/atoms/date-picker/types.tsx b/packages/admin-ui/ui/src/components/atoms/date-picker/types.tsx index 88b4d662d0..f9c4b90752 100644 --- a/packages/admin-ui/ui/src/components/atoms/date-picker/types.tsx +++ b/packages/admin-ui/ui/src/components/atoms/date-picker/types.tsx @@ -1,6 +1,6 @@ import { InputHeaderProps } from "../../fundamentals/input-header" export type DateTimePickerProps = { - date: Date - onSubmitDate: (newDate: Date) => void + date: Date | null + onSubmitDate: (newDate: Date | null) => void } & InputHeaderProps diff --git a/packages/admin-ui/ui/src/components/forms/customer-group/customer-group-general-form/index.tsx b/packages/admin-ui/ui/src/components/forms/customer-group/customer-group-general-form/index.tsx new file mode 100644 index 0000000000..51780f244b --- /dev/null +++ b/packages/admin-ui/ui/src/components/forms/customer-group/customer-group-general-form/index.tsx @@ -0,0 +1,34 @@ +import FormValidator from "../../../../utils/form-validator" +import { NestedForm } from "../../../../utils/nested-form" +import InputField from "../../../molecules/input" + +export type CustomerGroupGeneralFormType = { + name: string +} + +type CustomerGroupGeneralFormProps = { + form: NestedForm +} + +export const CustomerGroupGeneralForm = ({ + form, +}: CustomerGroupGeneralFormProps) => { + const { + register, + path, + formState: { errors }, + } = form + + return ( +
+ +
+ ) +} diff --git a/packages/admin-ui/ui/src/components/forms/discount/discount-conditions-form/index.tsx b/packages/admin-ui/ui/src/components/forms/discount/discount-conditions-form/index.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/admin-ui/ui/src/components/forms/discount/discount-configuration-form/index.tsx b/packages/admin-ui/ui/src/components/forms/discount/discount-configuration-form/index.tsx new file mode 100644 index 0000000000..e7231d3b5d --- /dev/null +++ b/packages/admin-ui/ui/src/components/forms/discount/discount-configuration-form/index.tsx @@ -0,0 +1,159 @@ +import { Controller } from "react-hook-form" +import { NestedForm } from "../../../../utils/nested-form" +import DatePicker from "../../../atoms/date-picker/date-picker" +import TimePicker from "../../../atoms/date-picker/time-picker" +import AvailabilityDuration from "../../../molecules/availability-duration" +import InputField from "../../../molecules/input" +import SwitchableItem from "../../../molecules/switchable-item" + +export type DiscountConfigurationFormType = { + starts_at: Date + ends_at: Date | null + usage_limit: number | null + valid_duration: string | null +} + +type DiscountConfigurationFormProps = { + form: NestedForm + isDynamic?: boolean +} + +const DiscountConfigurationForm = ({ + form, + isDynamic, +}: DiscountConfigurationFormProps) => { + const { control, path } = form + + return ( +
+
+ { + return ( + { + if (value) { + onChange(null) + } else { + onChange(new Date()) + } + }} + title="Discount has a start date?" + description="Schedule the discount to activate in the future." + > +
+ + +
+
+ ) + }} + /> + { + return ( + { + if (value) { + onChange(null) + } else { + onChange( + new Date(new Date().getTime() + 7 * 24 * 60 * 60 * 1000) + ) + } + }} + title="Discount has an expiry date?" + description="Schedule the discount to deactivate in the future." + > +
+ + +
+
+ ) + }} + /> + { + return ( + { + if (value) { + onChange(null) + } else { + onChange(10) + } + }} + title="Limit the number of redemtions?" + description="Limit applies across all customers, not per customer." + > + onChange(value.target.valueAsNumber)} + /> + + ) + }} + /> + {isDynamic && ( + { + return ( + { + if (value) { + onChange(null) + } else { + onChange("P0Y0M0DT00H00M") + } + }} + title="Availability duration?" + description="Set the duration of the discount." + > + + + ) + }} + /> + )} +
+
+ ) +} + +export default DiscountConfigurationForm diff --git a/packages/admin-ui/ui/src/components/forms/discount/discount-general-form/index.tsx b/packages/admin-ui/ui/src/components/forms/discount/discount-general-form/index.tsx new file mode 100644 index 0000000000..e0fa85c201 --- /dev/null +++ b/packages/admin-ui/ui/src/components/forms/discount/discount-general-form/index.tsx @@ -0,0 +1,192 @@ +import clsx from "clsx" +import { useAdminRegions } from "medusa-react" +import { useMemo } from "react" +import { Controller, useWatch } from "react-hook-form" +import { Option } from "../../../../types/shared" +import FormValidator from "../../../../utils/form-validator" +import { NestedForm } from "../../../../utils/nested-form" +import InputError from "../../../atoms/input-error" +import IconTooltip from "../../../molecules/icon-tooltip" +import IndeterminateCheckbox from "../../../molecules/indeterminate-checkbox" +import InputField from "../../../molecules/input" +import { NextSelect } from "../../../molecules/select/next-select" +import TextArea from "../../../molecules/textarea" +import PriceFormInput from "../../general/prices-form/price-form-input" + +type DiscountRegionOption = Option & { + currency_code: string +} + +enum DiscountRuleType { + FIXED = "fixed", + PERCENTAGE = "percentage", + FREE_SHIPPING = "free_shipping", +} + +export type DiscountGeneralFormType = { + region_ids: DiscountRegionOption[] + code: string + value?: number + description: string + is_dynamic?: boolean +} + +type DiscountGeneralFormProps = { + form: NestedForm + type: DiscountRuleType + isEdit?: boolean +} + +const DiscountGeneralForm = ({ + form, + type, + isEdit, +}: DiscountGeneralFormProps) => { + const { + register, + path, + control, + formState: { errors }, + } = form + + const { regions } = useAdminRegions() + + const regionOptions = useMemo(() => { + return ( + regions?.map((r) => ({ + value: r.id, + label: r.name, + currency_code: r.currency_code, + })) || [] + ) + }, [regions]) + + const selectedRegionCurrency = useWatch({ + control, + name: path("region_ids.0.currency_code"), + defaultValue: "usd", + }) + + return ( +
+ { + return ( + { + onChange(type === DiscountRuleType.FIXED ? [value] : value) + }} + label="Choose valid regions" + isMulti={type !== DiscountRuleType.FIXED} + selectAll={type !== DiscountRuleType.FIXED} + isSearchable + required + options={regionOptions} + errors={errors} + /> + ) + }} + /> +
+
+ + {type === DiscountRuleType.FIXED ? ( + { + return ( + + ) + }} + /> + ) : type === DiscountRuleType.PERCENTAGE ? ( + + ) : null} +
+

+ The code your customers will enter during checkout. This will appear + on your customer's invoice. Uppercase letters and numbers only. +

+
+
+