fix(admin-ui): Gift Card manage page (#3532)
**What** - Updates GC manage page to use product page sections - Revamps Denomination section - Updates the location of several components to reflect that they are now shared between the GC and products domain   Resolves CORE-1089
This commit is contained in:
1 parent
3171b0e518
commit
bfef22b33e
77 files changed
+1286
-1706
No files matched your search
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@medusajs/admin-ui": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
fix(admin-ui): Revamps gift card manage page
|
||||||
@@ -1,25 +1,30 @@
|
|||||||
import * as RadixSwitch from "@radix-ui/react-switch"
|
import * as RadixSwitch from "@radix-ui/react-switch"
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A controlled switch component atom.
|
* A controlled switch component atom.
|
||||||
*/
|
*/
|
||||||
function Switch(props: RadixSwitch.SwitchProps) {
|
const Switch = React.forwardRef<HTMLButtonElement, RadixSwitch.SwitchProps>(
|
||||||
return (
|
(props, ref) => {
|
||||||
<RadixSwitch.Root
|
return (
|
||||||
{...props}
|
<RadixSwitch.Root
|
||||||
disabled={props.disabled}
|
ref={ref}
|
||||||
className={clsx(
|
{...props}
|
||||||
"transition-bg radix-state-checked:bg-violet-60 h-[18px] w-8 rounded-full bg-gray-300"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<RadixSwitch.Thumb
|
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"radix-state-checked:translate-x-[19px] block h-2 w-2 translate-x-[5px] rounded-full bg-white transition-transform"
|
"transition-bg radix-state-checked:bg-violet-60 h-[18px] w-8 rounded-full bg-gray-300"
|
||||||
)}
|
)}
|
||||||
/>
|
>
|
||||||
</RadixSwitch.Root>
|
<RadixSwitch.Thumb
|
||||||
)
|
className={clsx(
|
||||||
}
|
"radix-state-checked:translate-x-[19px] block h-2 w-2 translate-x-[5px] rounded-full bg-white transition-transform"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</RadixSwitch.Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Switch.displayName = "Switch"
|
||||||
|
|
||||||
export default Switch
|
export default Switch
|
||||||
@@ -42,7 +42,7 @@ const Tooltip = ({
|
|||||||
sideOffset={8}
|
sideOffset={8}
|
||||||
align="center"
|
align="center"
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"inter-small-semibold text-grey-50",
|
"inter-small-semibold text-grey-50 z-[999]",
|
||||||
"bg-grey-0 shadow-dropdown rounded-rounded py-2 px-3",
|
"bg-grey-0 shadow-dropdown rounded-rounded py-2 px-3",
|
||||||
"border-grey-20 border border-solid",
|
"border-grey-20 border border-solid",
|
||||||
className
|
className
|
||||||
|
|||||||
File renamed without changes.
+5
-5
@@ -1,13 +1,13 @@
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { Controller } from "react-hook-form"
|
import { Controller } from "react-hook-form"
|
||||||
import { NestedPriceObject, PricesFormType } from "."
|
import { NestedPriceObject, PricesFormType } from "."
|
||||||
import IncludesTaxTooltip from "../../../../components/atoms/includes-tax-tooltip"
|
|
||||||
import CoinsIcon from "../../../../components/fundamentals/icons/coins-icon"
|
|
||||||
import MapPinIcon from "../../../../components/fundamentals/icons/map-pin-icon"
|
|
||||||
import TriangleRightIcon from "../../../../components/fundamentals/icons/triangle-right-icon"
|
|
||||||
import useToggleState from "../../../../hooks/use-toggle-state"
|
import useToggleState from "../../../../hooks/use-toggle-state"
|
||||||
import { currencies } from "../../../../utils/currencies"
|
import { currencies } from "../../../../utils/currencies"
|
||||||
import { NestedForm } from "../../../../utils/nested-form"
|
import { NestedForm } from "../../../../utils/nested-form"
|
||||||
|
import IncludesTaxTooltip from "../../../atoms/includes-tax-tooltip"
|
||||||
|
import CoinsIcon from "../../../fundamentals/icons/coins-icon"
|
||||||
|
import MapPinIcon from "../../../fundamentals/icons/map-pin-icon"
|
||||||
|
import TriangleRightIcon from "../../../fundamentals/icons/triangle-right-icon"
|
||||||
import PriceFormInput from "./price-form-input"
|
import PriceFormInput from "./price-form-input"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -21,7 +21,7 @@ const NestedPrice = ({ form, nestedPrice }: Props) => {
|
|||||||
const { control, path } = form
|
const { control, path } = form
|
||||||
const { currencyPrice, regionPrices } = nestedPrice
|
const { currencyPrice, regionPrices } = nestedPrice
|
||||||
return (
|
return (
|
||||||
<div key={currencyPrice.id} className="gap-y-2xsmall flex flex-col">
|
<div className="gap-y-2xsmall flex flex-col">
|
||||||
<div className="gap-x-base p-2xsmall hover:bg-grey-5 focus-within:bg-grey-5 rounded-rounded relative grid grid-cols-[1fr_223px] justify-between pl-10 transition-colors">
|
<div className="gap-x-base p-2xsmall hover:bg-grey-5 focus-within:bg-grey-5 rounded-rounded relative grid grid-cols-[1fr_223px] justify-between pl-10 transition-colors">
|
||||||
<button
|
<button
|
||||||
className={clsx(
|
className={clsx(
|
||||||
+1
-1
@@ -1,8 +1,8 @@
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import AmountField from "react-currency-input-field"
|
import AmountField from "react-currency-input-field"
|
||||||
import InputError from "../../../../components/atoms/input-error"
|
|
||||||
import { currencies } from "../../../../utils/currencies"
|
import { currencies } from "../../../../utils/currencies"
|
||||||
|
import InputError from "../../../atoms/input-error"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
currencyCode: string
|
currencyCode: string
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
import { Controller, useFieldArray, useWatch } from "react-hook-form"
|
||||||
|
import { currencies } from "../../../../utils/currencies"
|
||||||
|
import { NestedForm } from "../../../../utils/nested-form"
|
||||||
|
import IncludesTaxTooltip from "../../../atoms/includes-tax-tooltip"
|
||||||
|
import InputError from "../../../atoms/input-error"
|
||||||
|
import Switch from "../../../atoms/switch"
|
||||||
|
import CoinsIcon from "../../../fundamentals/icons/coins-icon"
|
||||||
|
import IconTooltip from "../../../molecules/icon-tooltip"
|
||||||
|
import PriceFormInput from "../../general/prices-form/price-form-input"
|
||||||
|
|
||||||
|
type DenominationType = {
|
||||||
|
amount: number
|
||||||
|
currency_code: string
|
||||||
|
includes_tax?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DenominationFormType = {
|
||||||
|
defaultDenomination: DenominationType
|
||||||
|
currencyDenominations: DenominationType[]
|
||||||
|
useSameValue: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
form: NestedForm<DenominationFormType>
|
||||||
|
}
|
||||||
|
|
||||||
|
const DenominationForm = ({ form }: Props) => {
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
path,
|
||||||
|
formState: { errors },
|
||||||
|
} = form
|
||||||
|
|
||||||
|
const { fields } = useFieldArray({
|
||||||
|
control,
|
||||||
|
name: path("currencyDenominations"),
|
||||||
|
keyName: "fieldKey",
|
||||||
|
})
|
||||||
|
|
||||||
|
const defaultCurrency = useWatch({
|
||||||
|
control,
|
||||||
|
name: path("defaultDenomination"),
|
||||||
|
})
|
||||||
|
|
||||||
|
const useSameValue = useWatch({
|
||||||
|
control,
|
||||||
|
name: path("useSameValue"),
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="gap-y-xlarge flex flex-col">
|
||||||
|
<div>
|
||||||
|
<div className="gap-x-2xsmall flex items-center">
|
||||||
|
<h2 className="inter-large-semibold">Default currency</h2>
|
||||||
|
<IconTooltip
|
||||||
|
type="info"
|
||||||
|
content="The denomination in your store's default currency"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="pt-small gap-x-base rounded-rounded relative grid grid-cols-[1fr_223px] justify-between transition-colors">
|
||||||
|
<div className="gap-x-small flex items-center">
|
||||||
|
<div className="bg-grey-10 rounded-rounded text-grey-50 flex h-10 w-10 items-center justify-center">
|
||||||
|
<CoinsIcon size={20} />
|
||||||
|
</div>
|
||||||
|
<div className="gap-x-xsmall flex items-center">
|
||||||
|
<span className="inter-base-semibold">
|
||||||
|
{defaultCurrency.currency_code.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
<span className="inter-base-regular text-grey-50">
|
||||||
|
{currencies[defaultCurrency.currency_code.toUpperCase()].name}
|
||||||
|
</span>
|
||||||
|
<IncludesTaxTooltip includesTax={defaultCurrency.includes_tax} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
name={path(`defaultDenomination.amount`)}
|
||||||
|
control={control}
|
||||||
|
rules={{
|
||||||
|
required:
|
||||||
|
"An amount for your store's default currency is required",
|
||||||
|
}}
|
||||||
|
render={({ field: { value, onChange, name } }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PriceFormInput
|
||||||
|
onChange={onChange}
|
||||||
|
amount={value !== null ? value : undefined}
|
||||||
|
currencyCode={defaultCurrency.currency_code}
|
||||||
|
errors={errors}
|
||||||
|
/>
|
||||||
|
<InputError errors={errors} name={name} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="flex items-start justify-between">
|
||||||
|
<div className="gap-y-2xsmall flex flex-col">
|
||||||
|
<h2 className="inter-base-semibold">
|
||||||
|
Use value for all currencies?
|
||||||
|
</h2>
|
||||||
|
<p className="inter-small-regular text-grey-50 max-w-[60%]">
|
||||||
|
If enabled the value used for the store's default currency
|
||||||
|
code will also be applied to all other currencies in your store.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name={path("useSameValue")}
|
||||||
|
render={({ field: { value, onChange, ...rest } }) => {
|
||||||
|
return (
|
||||||
|
<Switch checked={value} onCheckedChange={onChange} {...rest} />
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!useSameValue && (
|
||||||
|
<div>
|
||||||
|
<div className="gap-x-2xsmall flex items-center">
|
||||||
|
<h2 className="inter-base-semibold">Other currencies</h2>
|
||||||
|
<IconTooltip
|
||||||
|
type="info"
|
||||||
|
content="The denomination in your store's other currencies"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="gap-y-xsmall pt-small flex flex-col">
|
||||||
|
{fields.map((denom, index) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={denom.fieldKey}
|
||||||
|
className="gap-x-base rounded-rounded relative grid grid-cols-[1fr_223px] justify-between transition-colors"
|
||||||
|
>
|
||||||
|
<div className="gap-x-small flex items-center">
|
||||||
|
<div className="bg-grey-10 rounded-rounded text-grey-50 flex h-10 w-10 items-center justify-center">
|
||||||
|
<CoinsIcon size={20} />
|
||||||
|
</div>
|
||||||
|
<div className="gap-x-xsmall flex items-center">
|
||||||
|
<span className="inter-base-semibold">
|
||||||
|
{denom.currency_code.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
<span className="inter-base-regular text-grey-50">
|
||||||
|
{currencies[denom.currency_code.toUpperCase()].name}
|
||||||
|
</span>
|
||||||
|
<IncludesTaxTooltip includesTax={denom.includes_tax} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
name={path(`currencyDenominations.${index}.amount`)}
|
||||||
|
control={control}
|
||||||
|
render={({
|
||||||
|
field: { value, onChange },
|
||||||
|
formState: { errors },
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<PriceFormInput
|
||||||
|
onChange={onChange}
|
||||||
|
amount={value !== null ? value : undefined}
|
||||||
|
currencyCode={denom.currency_code}
|
||||||
|
errors={errors}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DenominationForm
|
||||||
+2
-2
@@ -1,10 +1,10 @@
|
|||||||
import { Controller } from "react-hook-form"
|
import { Controller } from "react-hook-form"
|
||||||
import InputField from "../../../../components/molecules/input"
|
|
||||||
import { NextSelect } from "../../../../components/molecules/select/next-select"
|
|
||||||
import { Option } from "../../../../types/shared"
|
import { Option } from "../../../../types/shared"
|
||||||
import { countries } from "../../../../utils/countries"
|
import { countries } from "../../../../utils/countries"
|
||||||
import FormValidator from "../../../../utils/form-validator"
|
import FormValidator from "../../../../utils/form-validator"
|
||||||
import { NestedForm } from "../../../../utils/nested-form"
|
import { NestedForm } from "../../../../utils/nested-form"
|
||||||
|
import InputField from "../../../molecules/input"
|
||||||
|
import { NextSelect } from "../../../molecules/select/next-select"
|
||||||
|
|
||||||
export type CustomsFormType = {
|
export type CustomsFormType = {
|
||||||
mid_code: string | null
|
mid_code: string | null
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import InputField from "../../../../components/molecules/input"
|
|
||||||
import FormValidator from "../../../../utils/form-validator"
|
import FormValidator from "../../../../utils/form-validator"
|
||||||
import { NestedForm } from "../../../../utils/nested-form"
|
import { NestedForm } from "../../../../utils/nested-form"
|
||||||
|
import InputField from "../../../molecules/input"
|
||||||
|
|
||||||
export type DimensionsFormType = {
|
export type DimensionsFormType = {
|
||||||
length: number | null
|
length: number | null
|
||||||
+5
-3
@@ -1,6 +1,6 @@
|
|||||||
import { Controller } from "react-hook-form"
|
import { Controller } from "react-hook-form"
|
||||||
import Switch from "../../../../components/atoms/switch"
|
|
||||||
import { NestedForm } from "../../../../utils/nested-form"
|
import { NestedForm } from "../../../../utils/nested-form"
|
||||||
|
import Switch from "../../../atoms/switch"
|
||||||
|
|
||||||
export type DiscountableFormType = {
|
export type DiscountableFormType = {
|
||||||
value: boolean
|
value: boolean
|
||||||
@@ -8,9 +8,10 @@ export type DiscountableFormType = {
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
form: NestedForm<DiscountableFormType>
|
form: NestedForm<DiscountableFormType>
|
||||||
|
isGiftCard?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const DiscountableForm = ({ form }: Props) => {
|
const DiscountableForm = ({ form, isGiftCard }: Props) => {
|
||||||
const { control, path } = form
|
const { control, path } = form
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -25,7 +26,8 @@ const DiscountableForm = ({ form }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p className="inter-base-regular text-grey-50">
|
<p className="inter-base-regular text-grey-50">
|
||||||
When unchecked discounts will not be applied to this product.
|
When unchecked discounts will not be applied to this{" "}
|
||||||
|
{isGiftCard ? "gift card" : "product"}.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
+17
-10
@@ -1,7 +1,7 @@
|
|||||||
import InputField from "../../../../components/molecules/input"
|
|
||||||
import TextArea from "../../../../components/molecules/textarea"
|
|
||||||
import FormValidator from "../../../../utils/form-validator"
|
import FormValidator from "../../../../utils/form-validator"
|
||||||
import { NestedForm } from "../../../../utils/nested-form"
|
import { NestedForm } from "../../../../utils/nested-form"
|
||||||
|
import InputField from "../../../molecules/input"
|
||||||
|
import TextArea from "../../../molecules/textarea"
|
||||||
|
|
||||||
export type GeneralFormType = {
|
export type GeneralFormType = {
|
||||||
title: string
|
title: string
|
||||||
@@ -14,9 +14,10 @@ export type GeneralFormType = {
|
|||||||
type Props = {
|
type Props = {
|
||||||
form: NestedForm<GeneralFormType>
|
form: NestedForm<GeneralFormType>
|
||||||
requireHandle?: boolean
|
requireHandle?: boolean
|
||||||
|
isGiftCard?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const GeneralForm = ({ form, requireHandle = true }: Props) => {
|
const GeneralForm = ({ form, requireHandle = true, isGiftCard }: Props) => {
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
path,
|
path,
|
||||||
@@ -28,7 +29,7 @@ const GeneralForm = ({ form, requireHandle = true }: Props) => {
|
|||||||
<div className="gap-x-large mb-small grid grid-cols-2">
|
<div className="gap-x-large mb-small grid grid-cols-2">
|
||||||
<InputField
|
<InputField
|
||||||
label="Title"
|
label="Title"
|
||||||
placeholder="Winter Jacket"
|
placeholder={isGiftCard ? "Gift Card" : "Winter Jacket"}
|
||||||
required
|
required
|
||||||
{...register(path("title"), {
|
{...register(path("title"), {
|
||||||
required: "Title is required",
|
required: "Title is required",
|
||||||
@@ -50,7 +51,8 @@ const GeneralForm = ({ form, requireHandle = true }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p className="inter-base-regular text-grey-50 mb-large">
|
<p className="inter-base-regular text-grey-50 mb-large">
|
||||||
Give your product a short and clear title.
|
Give your {isGiftCard ? "gift card" : "product"} a short and clear
|
||||||
|
title.
|
||||||
<br />
|
<br />
|
||||||
50-60 characters is the recommended length for search engines.
|
50-60 characters is the recommended length for search engines.
|
||||||
</p>
|
</p>
|
||||||
@@ -59,10 +61,12 @@ const GeneralForm = ({ form, requireHandle = true }: Props) => {
|
|||||||
label="Handle"
|
label="Handle"
|
||||||
tooltipContent={
|
tooltipContent={
|
||||||
!requireHandle
|
!requireHandle
|
||||||
? "The handle is the part of the URL that identifies the product. If not specified, it will be generated from the title."
|
? `The handle is the part of the URL that identifies the ${
|
||||||
|
isGiftCard ? "gift card" : "product"
|
||||||
|
}. If not specified, it will be generated from the title.`
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
placeholder="winter-jacket"
|
placeholder={isGiftCard ? "gift-card" : "winter-jacket"}
|
||||||
required={requireHandle}
|
required={requireHandle}
|
||||||
{...register(path("handle"), {
|
{...register(path("handle"), {
|
||||||
required: requireHandle ? "Handle is required" : undefined,
|
required: requireHandle ? "Handle is required" : undefined,
|
||||||
@@ -74,7 +78,7 @@ const GeneralForm = ({ form, requireHandle = true }: Props) => {
|
|||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
label="Material"
|
label="Material"
|
||||||
placeholder="100% cotton"
|
placeholder={isGiftCard ? "Paper" : "100% Cotton"}
|
||||||
{...register(path("material"), {
|
{...register(path("material"), {
|
||||||
minLength: FormValidator.minOneCharRule("Material"),
|
minLength: FormValidator.minOneCharRule("Material"),
|
||||||
pattern: FormValidator.whiteSpaceRule("Material"),
|
pattern: FormValidator.whiteSpaceRule("Material"),
|
||||||
@@ -84,14 +88,17 @@ const GeneralForm = ({ form, requireHandle = true }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
<TextArea
|
<TextArea
|
||||||
label="Description"
|
label="Description"
|
||||||
placeholder="A warm and cozy jacket..."
|
placeholder={
|
||||||
|
isGiftCard ? "The gift card is..." : "A warm and cozy jacket..."
|
||||||
|
}
|
||||||
rows={3}
|
rows={3}
|
||||||
className="mb-small"
|
className="mb-small"
|
||||||
{...register(path("description"))}
|
{...register(path("description"))}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
/>
|
/>
|
||||||
<p className="inter-base-regular text-grey-50">
|
<p className="inter-base-regular text-grey-50">
|
||||||
Give your product a short and clear description.
|
Give your {isGiftCard ? "gift card" : "product"} a short and clear
|
||||||
|
description.
|
||||||
<br />
|
<br />
|
||||||
120-160 characters is the recommended length for search engines.
|
120-160 characters is the recommended length for search engines.
|
||||||
</p>
|
</p>
|
||||||
+5
-7
@@ -6,15 +6,13 @@ import {
|
|||||||
useFieldArray,
|
useFieldArray,
|
||||||
useWatch,
|
useWatch,
|
||||||
} from "react-hook-form"
|
} from "react-hook-form"
|
||||||
import FileUploadField from "../../../../components/atoms/file-upload-field"
|
|
||||||
import Button from "../../../../components/fundamentals/button"
|
|
||||||
import CheckCircleFillIcon from "../../../../components/fundamentals/icons/check-circle-fill-icon"
|
|
||||||
import TrashIcon from "../../../../components/fundamentals/icons/trash-icon"
|
|
||||||
import Actionables, {
|
|
||||||
ActionType,
|
|
||||||
} from "../../../../components/molecules/actionables"
|
|
||||||
import { FormImage } from "../../../../types/shared"
|
import { FormImage } from "../../../../types/shared"
|
||||||
import { NestedForm } from "../../../../utils/nested-form"
|
import { NestedForm } from "../../../../utils/nested-form"
|
||||||
|
import FileUploadField from "../../../atoms/file-upload-field"
|
||||||
|
import Button from "../../../fundamentals/button"
|
||||||
|
import CheckCircleFillIcon from "../../../fundamentals/icons/check-circle-fill-icon"
|
||||||
|
import TrashIcon from "../../../fundamentals/icons/trash-icon"
|
||||||
|
import Actionables, { ActionType } from "../../../molecules/actionables"
|
||||||
|
|
||||||
type ImageType = { selected: boolean } & FormImage
|
type ImageType = { selected: boolean } & FormImage
|
||||||
|
|
||||||
+14
-17
@@ -1,18 +1,18 @@
|
|||||||
import { Controller } from "react-hook-form"
|
import { Controller } from "react-hook-form"
|
||||||
|
import NestedMultiselect from "../../../../domain/categories/components/multiselect"
|
||||||
|
import {
|
||||||
|
FeatureFlag,
|
||||||
|
useFeatureFlag,
|
||||||
|
} from "../../../../providers/feature-flag-provider"
|
||||||
|
import { Option } from "../../../../types/shared"
|
||||||
|
import { NestedForm } from "../../../../utils/nested-form"
|
||||||
|
import InputHeader from "../../../fundamentals/input-header"
|
||||||
import {
|
import {
|
||||||
NextCreateableSelect,
|
NextCreateableSelect,
|
||||||
NextSelect,
|
NextSelect,
|
||||||
} from "../../../../components/molecules/select/next-select"
|
} from "../../../molecules/select/next-select"
|
||||||
import TagInput from "../../../../components/molecules/tag-input"
|
import TagInput from "../../../molecules/tag-input"
|
||||||
import { Option } from "../../../../types/shared"
|
|
||||||
import { NestedForm } from "../../../../utils/nested-form"
|
|
||||||
import useOrganizeData from "./use-organize-data"
|
import useOrganizeData from "./use-organize-data"
|
||||||
import NestedMultiselect from "../../../categories/components/multiselect"
|
|
||||||
import InputHeader from "../../../../components/fundamentals/input-header"
|
|
||||||
import {
|
|
||||||
useFeatureFlag,
|
|
||||||
FeatureFlag,
|
|
||||||
} from "../../../../providers/feature-flag-provider"
|
|
||||||
|
|
||||||
export type OrganizeFormType = {
|
export type OrganizeFormType = {
|
||||||
type: Option | null
|
type: Option | null
|
||||||
@@ -86,21 +86,18 @@ const OrganizeForm = ({ form }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isFeatureEnabled(FeatureFlag.PRODUCT_CATEGORIES) && (
|
{isFeatureEnabled(FeatureFlag.PRODUCT_CATEGORIES) &&
|
||||||
|
categoriesOptions?.length ? (
|
||||||
<>
|
<>
|
||||||
<InputHeader label="Categories" className="mb-2" />
|
<InputHeader label="Categories" className="mb-2" />
|
||||||
<Controller
|
<Controller
|
||||||
name={path("categories")}
|
name={path("categories")}
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field: { value, onChange } }) => {
|
render={({ field: { value, onChange } }) => {
|
||||||
if (categoriesOptions.length === 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const initiallySelected = (value || []).reduce((acc, val) => {
|
const initiallySelected = (value || []).reduce((acc, val) => {
|
||||||
acc[val] = true
|
acc[val] = true
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {} as Record<string, true>)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NestedMultiselect
|
<NestedMultiselect
|
||||||
@@ -112,7 +109,7 @@ const OrganizeForm = ({ form }: Props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
) : null}
|
||||||
|
|
||||||
<div className="mb-large" />
|
<div className="mb-large" />
|
||||||
|
|
||||||
+4
-5
@@ -3,14 +3,13 @@ import { useMemo } from "react"
|
|||||||
import {
|
import {
|
||||||
useAdminCollections,
|
useAdminCollections,
|
||||||
useAdminProductCategories,
|
useAdminProductCategories,
|
||||||
useAdminProductTypes,
|
useAdminProductTypes
|
||||||
} from "medusa-react"
|
} from "medusa-react"
|
||||||
|
|
||||||
import { NestedMultiselectOption } from "../../../categories/components/multiselect"
|
import { NestedMultiselectOption } from "../../../../domain/categories/components/multiselect"
|
||||||
import { transformCategoryToNestedFormOptions } from "../../../categories/utils/transform-response"
|
import { transformCategoryToNestedFormOptions } from "../../../../domain/categories/utils/transform-response"
|
||||||
import {
|
import {
|
||||||
useFeatureFlag,
|
FeatureFlag, useFeatureFlag
|
||||||
FeatureFlag,
|
|
||||||
} from "../../../../providers/feature-flag-provider"
|
} from "../../../../providers/feature-flag-provider"
|
||||||
|
|
||||||
const useOrganizeData = () => {
|
const useOrganizeData = () => {
|
||||||
+6
-6
@@ -1,10 +1,10 @@
|
|||||||
import { useAdminSalesChannels } from "medusa-react"
|
import { useAdminSalesChannels } from "medusa-react"
|
||||||
import React, { useContext, useMemo, useState } from "react"
|
import React, { useMemo, useState } from "react"
|
||||||
import { usePagination, useRowSelect, useTable } from "react-table"
|
import { usePagination, useRowSelect, useTable } from "react-table"
|
||||||
import Button from "../../../../components/fundamentals/button"
|
|
||||||
import Modal from "../../../../components/molecules/modal"
|
|
||||||
import { LayeredModalContext } from "../../../../components/molecules/modal/layered-modal"
|
|
||||||
import { useDebounce } from "../../../../hooks/use-debounce"
|
import { useDebounce } from "../../../../hooks/use-debounce"
|
||||||
|
import Button from "../../../fundamentals/button"
|
||||||
|
import Modal from "../../../molecules/modal"
|
||||||
|
import { useLayeredModal } from "../../../molecules/modal/layered-modal"
|
||||||
import SalesChannelTable, { useSalesChannelsTableColumns } from "./table"
|
import SalesChannelTable, { useSalesChannelsTableColumns } from "./table"
|
||||||
import { useSalesChannelsModal } from "./use-sales-channels-modal"
|
import { useSalesChannelsModal } from "./use-sales-channels-modal"
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ const AddScreen = () => {
|
|||||||
return salesChannels?.filter(({ id }) => !ids.includes(id)) || []
|
return salesChannels?.filter(({ id }) => !ids.includes(id)) || []
|
||||||
}, [salesChannels, source])
|
}, [salesChannels, source])
|
||||||
|
|
||||||
const { pop, reset } = useContext(LayeredModalContext)
|
const { pop, reset } = useLayeredModal()
|
||||||
|
|
||||||
const state = useTable(
|
const state = useTable(
|
||||||
{
|
{
|
||||||
@@ -120,7 +120,7 @@ const AddScreen = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useAddChannelsModalScreen = () => {
|
export const useAddChannelsModalScreen = () => {
|
||||||
const { pop } = React.useContext(LayeredModalContext)
|
const { pop } = useLayeredModal()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: "Add Sales Channels",
|
title: "Add Sales Channels",
|
||||||
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react"
|
||||||
|
import { usePagination, useRowSelect, useTable } from "react-table"
|
||||||
import SalesChannelTable, {
|
import SalesChannelTable, {
|
||||||
SalesChannelTableActions,
|
SalesChannelTableActions,
|
||||||
useSalesChannelsTableColumns,
|
useSalesChannelsTableColumns,
|
||||||
} from "./table"
|
} from "./table"
|
||||||
import { usePagination, useRowSelect, useTable } from "react-table"
|
|
||||||
|
|
||||||
import Modal from "../../../../components/molecules/modal"
|
|
||||||
import { useDebounce } from "../../../../hooks/use-debounce"
|
import { useDebounce } from "../../../../hooks/use-debounce"
|
||||||
|
import Modal from "../../../molecules/modal"
|
||||||
import { useSalesChannelsModal } from "./use-sales-channels-modal"
|
import { useSalesChannelsModal } from "./use-sales-channels-modal"
|
||||||
|
|
||||||
const LIMIT = 12
|
const LIMIT = 12
|
||||||
+5
-6
@@ -1,10 +1,9 @@
|
|||||||
import { SalesChannel } from "@medusajs/medusa"
|
import { SalesChannel } from "@medusajs/medusa"
|
||||||
import React from "react"
|
import Button from "../../../fundamentals/button"
|
||||||
import Button from "../../../../components/fundamentals/button"
|
import Modal from "../../../molecules/modal"
|
||||||
import Modal from "../../../../components/molecules/modal"
|
|
||||||
import LayeredModal, {
|
import LayeredModal, {
|
||||||
LayeredModalContext,
|
useLayeredModal,
|
||||||
} from "../../../../components/molecules/modal/layered-modal"
|
} from "../../../molecules/modal/layered-modal"
|
||||||
import AvailableScreen from "./available-screen"
|
import AvailableScreen from "./available-screen"
|
||||||
import { SalesChannelsModalContext } from "./use-sales-channels-modal"
|
import { SalesChannelsModalContext } from "./use-sales-channels-modal"
|
||||||
|
|
||||||
@@ -19,7 +18,7 @@ type Props = {
|
|||||||
* Re-usable Sales Channels Modal, used for adding and editing sales channels both when creating a new product and editing an existing product.
|
* Re-usable Sales Channels Modal, used for adding and editing sales channels both when creating a new product and editing an existing product.
|
||||||
*/
|
*/
|
||||||
const SalesChannelsModal = ({ open, source = [], onClose, onSave }: Props) => {
|
const SalesChannelsModal = ({ open, source = [], onClose, onSave }: Props) => {
|
||||||
const context = React.useContext(LayeredModalContext)
|
const context = useLayeredModal()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SalesChannelsModalContext.Provider
|
<SalesChannelsModalContext.Provider
|
||||||
+7
-7
@@ -2,12 +2,12 @@ import { SalesChannel } from "@medusajs/medusa"
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import React, { useMemo } from "react"
|
import React, { useMemo } from "react"
|
||||||
import { TableInstance } from "react-table"
|
import { TableInstance } from "react-table"
|
||||||
import Button from "../../../../components/fundamentals/button"
|
import Button from "../../../fundamentals/button"
|
||||||
import PlusIcon from "../../../../components/fundamentals/icons/plus-icon"
|
import PlusIcon from "../../../fundamentals/icons/plus-icon"
|
||||||
import IndeterminateCheckbox from "../../../../components/molecules/indeterminate-checkbox"
|
import IndeterminateCheckbox from "../../../molecules/indeterminate-checkbox"
|
||||||
import { LayeredModalContext } from "../../../../components/molecules/modal/layered-modal"
|
import { useLayeredModal } from "../../../molecules/modal/layered-modal"
|
||||||
import Table from "../../../../components/molecules/table"
|
import Table from "../../../molecules/table"
|
||||||
import TableContainer from "../../../../components/organisms/table-container"
|
import TableContainer from "../../../organisms/table-container"
|
||||||
import { useAddChannelsModalScreen } from "./add-screen"
|
import { useAddChannelsModalScreen } from "./add-screen"
|
||||||
|
|
||||||
type SalesChannelsTableProps = {
|
type SalesChannelsTableProps = {
|
||||||
@@ -181,7 +181,7 @@ export const SalesChannelTableActions = ({
|
|||||||
"translate-y-[0px]": showAddChannels,
|
"translate-y-[0px]": showAddChannels,
|
||||||
}
|
}
|
||||||
|
|
||||||
const { push } = React.useContext(LayeredModalContext)
|
const { push } = useLayeredModal()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-x-xsmall flex h-[34px] overflow-hidden">
|
<div className="space-x-xsmall flex h-[34px] overflow-hidden">
|
||||||
File renamed without changes.
+5
-5
@@ -1,11 +1,11 @@
|
|||||||
import { FieldArrayWithId, useFieldArray } from "react-hook-form"
|
import { FieldArrayWithId, useFieldArray } from "react-hook-form"
|
||||||
import FileUploadField from "../../../../components/atoms/file-upload-field"
|
|
||||||
import TrashIcon from "../../../../components/fundamentals/icons/trash-icon"
|
|
||||||
import Actionables, {
|
|
||||||
ActionType,
|
|
||||||
} from "../../../../components/molecules/actionables"
|
|
||||||
import { FormImage } from "../../../../types/shared"
|
import { FormImage } from "../../../../types/shared"
|
||||||
import { NestedForm } from "../../../../utils/nested-form"
|
import { NestedForm } from "../../../../utils/nested-form"
|
||||||
|
import FileUploadField from "../../../atoms/file-upload-field"
|
||||||
|
import TrashIcon from "../../../fundamentals/icons/trash-icon"
|
||||||
|
import Actionables, {
|
||||||
|
ActionType
|
||||||
|
} from "../../../molecules/actionables"
|
||||||
|
|
||||||
export type ThumbnailFormType = {
|
export type ThumbnailFormType = {
|
||||||
images: FormImage[]
|
images: FormImage[]
|
||||||
+4
-4
@@ -1,11 +1,11 @@
|
|||||||
import { UseFormReturn } from "react-hook-form"
|
import { UseFormReturn } from "react-hook-form"
|
||||||
import InputError from "../../../../../components/atoms/input-error"
|
|
||||||
import IconTooltip from "../../../../../components/molecules/icon-tooltip"
|
|
||||||
import Accordion from "../../../../../components/organisms/accordion"
|
|
||||||
import { nestedForm } from "../../../../../utils/nested-form"
|
import { nestedForm } from "../../../../../utils/nested-form"
|
||||||
|
import InputError from "../../../../atoms/input-error"
|
||||||
|
import IconTooltip from "../../../../molecules/icon-tooltip"
|
||||||
|
import Accordion from "../../../../organisms/accordion"
|
||||||
|
import { PricesFormType } from "../../../general/prices-form"
|
||||||
import CustomsForm, { CustomsFormType } from "../../customs-form"
|
import CustomsForm, { CustomsFormType } from "../../customs-form"
|
||||||
import DimensionsForm, { DimensionsFormType } from "../../dimensions-form"
|
import DimensionsForm, { DimensionsFormType } from "../../dimensions-form"
|
||||||
import { PricesFormType } from "../../prices-form"
|
|
||||||
import VariantGeneralForm, {
|
import VariantGeneralForm, {
|
||||||
VariantGeneralFormType,
|
VariantGeneralFormType,
|
||||||
} from "../variant-general-form"
|
} from "../variant-general-form"
|
||||||
+8
-8
@@ -1,18 +1,18 @@
|
|||||||
|
import { useFieldArray, UseFormReturn } from "react-hook-form"
|
||||||
import CustomsForm, { CustomsFormType } from "../../customs-form"
|
import CustomsForm, { CustomsFormType } from "../../customs-form"
|
||||||
import DimensionsForm, { DimensionsFormType } from "../../dimensions-form"
|
import DimensionsForm, { DimensionsFormType } from "../../dimensions-form"
|
||||||
import { UseFormReturn, useFieldArray } from "react-hook-form"
|
|
||||||
import VariantGeneralForm, {
|
import VariantGeneralForm, {
|
||||||
VariantGeneralFormType,
|
VariantGeneralFormType
|
||||||
} from "../variant-general-form"
|
} from "../variant-general-form"
|
||||||
import VariantStockForm, { VariantStockFormType } from "../variant-stock-form"
|
import VariantStockForm, { VariantStockFormType } from "../variant-stock-form"
|
||||||
|
|
||||||
import Accordion from "../../../../../components/organisms/accordion"
|
|
||||||
import IconTooltip from "../../../../../components/molecules/icon-tooltip"
|
|
||||||
import InputField from "../../../../../components/molecules/input"
|
|
||||||
import { PricesFormType } from "../../prices-form"
|
|
||||||
import VariantPricesForm from "../variant-prices-form"
|
|
||||||
import { nestedForm } from "../../../../../utils/nested-form"
|
|
||||||
import { useFeatureFlag } from "../../../../../providers/feature-flag-provider"
|
import { useFeatureFlag } from "../../../../../providers/feature-flag-provider"
|
||||||
|
import { nestedForm } from "../../../../../utils/nested-form"
|
||||||
|
import IconTooltip from "../../../../molecules/icon-tooltip"
|
||||||
|
import InputField from "../../../../molecules/input"
|
||||||
|
import Accordion from "../../../../organisms/accordion"
|
||||||
|
import { PricesFormType } from "../../../general/prices-form"
|
||||||
|
import VariantPricesForm from "../variant-prices-form"
|
||||||
|
|
||||||
export type EditFlowVariantFormType = {
|
export type EditFlowVariantFormType = {
|
||||||
/**
|
/**
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import InputField from "../../../../../components/molecules/input"
|
|
||||||
import FormValidator from "../../../../../utils/form-validator"
|
import FormValidator from "../../../../../utils/form-validator"
|
||||||
import { NestedForm } from "../../../../../utils/nested-form"
|
import { NestedForm } from "../../../../../utils/nested-form"
|
||||||
|
import InputField from "../../../../molecules/input"
|
||||||
|
|
||||||
export type VariantGeneralFormType = {
|
export type VariantGeneralFormType = {
|
||||||
title: string | null
|
title: string | null
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import { NestedForm } from "../../../../../utils/nested-form"
|
import { NestedForm } from "../../../../../utils/nested-form"
|
||||||
import PricesForm, { PricesFormType } from "../../prices-form"
|
import PricesForm, { PricesFormType } from "../../../general/prices-form"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
form: NestedForm<PricesFormType>
|
form: NestedForm<PricesFormType>
|
||||||
+1
-1
@@ -2,8 +2,8 @@ import { isEqual } from "lodash"
|
|||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import { useWatch } from "react-hook-form"
|
import { useWatch } from "react-hook-form"
|
||||||
import { VariantOptionValueType } from "."
|
import { VariantOptionValueType } from "."
|
||||||
|
import { AddVariantsFormType } from "../../../../../domain/products/new/add-variants"
|
||||||
import { NestedForm } from "../../../../../utils/nested-form"
|
import { NestedForm } from "../../../../../utils/nested-form"
|
||||||
import { AddVariantsFormType } from "../../../new/add-variants"
|
|
||||||
|
|
||||||
const useCheckOptions = (variantForm: NestedForm<AddVariantsFormType>) => {
|
const useCheckOptions = (variantForm: NestedForm<AddVariantsFormType>) => {
|
||||||
const { control: variantControl, path: variantPath } = variantForm
|
const { control: variantControl, path: variantPath } = variantForm
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { Controller, useFieldArray } from "react-hook-form"
|
import { Controller, useFieldArray } from "react-hook-form"
|
||||||
import { NextCreateableSelect } from "../../../../../components/molecules/select/next-select"
|
|
||||||
import { NestedForm } from "../../../../../utils/nested-form"
|
import { NestedForm } from "../../../../../utils/nested-form"
|
||||||
|
import { NextCreateableSelect } from "../../../../molecules/select/next-select"
|
||||||
|
|
||||||
export type VariantOptionValueType = {
|
export type VariantOptionValueType = {
|
||||||
option_id: string
|
option_id: string
|
||||||
+11
-11
@@ -1,20 +1,20 @@
|
|||||||
import { Controller, useFieldArray } from "react-hook-form"
|
|
||||||
import { InventoryLevelDTO, StockLocationDTO } from "@medusajs/medusa"
|
import { InventoryLevelDTO, StockLocationDTO } from "@medusajs/medusa"
|
||||||
|
import { Controller, useFieldArray } from "react-hook-form"
|
||||||
|
|
||||||
import BuildingsIcon from "../../../../../components/fundamentals/icons/buildings-icon"
|
|
||||||
import Button from "../../../../../components/fundamentals/button"
|
|
||||||
import FeatureToggle from "../../../../../components/fundamentals/feature-toggle"
|
|
||||||
import IconBadge from "../../../../../components/fundamentals/icon-badge"
|
|
||||||
import InputField from "../../../../../components/molecules/input"
|
|
||||||
import { LayeredModalContext } from "../../../../../components/molecules/modal/layered-modal"
|
|
||||||
import { ManageLocationsScreen } from "../../variant-inventory-form/variant-stock-form"
|
|
||||||
import { NestedForm } from "../../../../../utils/nested-form"
|
|
||||||
import React from "react"
|
|
||||||
import Switch from "../../../../../components/atoms/switch"
|
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { sum } from "lodash"
|
import { sum } from "lodash"
|
||||||
import { useAdminStockLocations } from "medusa-react"
|
import { useAdminStockLocations } from "medusa-react"
|
||||||
|
import React from "react"
|
||||||
import { useFeatureFlag } from "../../../../../providers/feature-flag-provider"
|
import { useFeatureFlag } from "../../../../../providers/feature-flag-provider"
|
||||||
|
import { NestedForm } from "../../../../../utils/nested-form"
|
||||||
|
import Switch from "../../../../atoms/switch"
|
||||||
|
import Button from "../../../../fundamentals/button"
|
||||||
|
import FeatureToggle from "../../../../fundamentals/feature-toggle"
|
||||||
|
import IconBadge from "../../../../fundamentals/icon-badge"
|
||||||
|
import BuildingsIcon from "../../../../fundamentals/icons/buildings-icon"
|
||||||
|
import InputField from "../../../../molecules/input"
|
||||||
|
import { LayeredModalContext } from "../../../../molecules/modal/layered-modal"
|
||||||
|
import { ManageLocationsScreen } from "../../variant-inventory-form/variant-stock-form"
|
||||||
|
|
||||||
export type VariantStockFormType = {
|
export type VariantStockFormType = {
|
||||||
manage_inventory: boolean
|
manage_inventory: boolean
|
||||||
File renamed without changes.
+12
-13
@@ -1,16 +1,15 @@
|
|||||||
import { Controller, useFieldArray } from "react-hook-form"
|
|
||||||
import { InventoryLevelDTO, StockLocationDTO } from "@medusajs/medusa"
|
import { InventoryLevelDTO, StockLocationDTO } from "@medusajs/medusa"
|
||||||
import React, { useContext, useMemo, useState } from "react"
|
|
||||||
|
|
||||||
import BuildingsIcon from "../../../../../components/fundamentals/icons/buildings-icon"
|
|
||||||
import Button from "../../../../../components/fundamentals/button"
|
|
||||||
import IconBadge from "../../../../../components/fundamentals/icon-badge"
|
|
||||||
import InputField from "../../../../../components/molecules/input"
|
|
||||||
import { LayeredModalContext } from "../../../../../components/molecules/modal/layered-modal"
|
|
||||||
import Modal from "../../../../../components/molecules/modal"
|
|
||||||
import { NestedForm } from "../../../../../utils/nested-form"
|
|
||||||
import Switch from "../../../../../components/atoms/switch"
|
|
||||||
import { useAdminStockLocations } from "medusa-react"
|
import { useAdminStockLocations } from "medusa-react"
|
||||||
|
import React, { useMemo, useState } from "react"
|
||||||
|
import { Controller, useFieldArray } from "react-hook-form"
|
||||||
|
import { NestedForm } from "../../../../../utils/nested-form"
|
||||||
|
import Switch from "../../../../atoms/switch"
|
||||||
|
import Button from "../../../../fundamentals/button"
|
||||||
|
import IconBadge from "../../../../fundamentals/icon-badge"
|
||||||
|
import BuildingsIcon from "../../../../fundamentals/icons/buildings-icon"
|
||||||
|
import InputField from "../../../../molecules/input"
|
||||||
|
import Modal from "../../../../molecules/modal"
|
||||||
|
import { useLayeredModal } from "../../../../molecules/modal/layered-modal"
|
||||||
|
|
||||||
export type VariantStockFormType = {
|
export type VariantStockFormType = {
|
||||||
manage_inventory?: boolean
|
manage_inventory?: boolean
|
||||||
@@ -34,7 +33,7 @@ const VariantStockForm = ({ form, locationLevels }: Props) => {
|
|||||||
[locationLevels]
|
[locationLevels]
|
||||||
)
|
)
|
||||||
|
|
||||||
const layeredModalContext = useContext(LayeredModalContext)
|
const layeredModalContext = useLayeredModal()
|
||||||
|
|
||||||
const { stock_locations: locations, isLoading } = useAdminStockLocations()
|
const { stock_locations: locations, isLoading } = useAdminStockLocations()
|
||||||
|
|
||||||
@@ -238,7 +237,7 @@ const ManageLocationsForm = ({
|
|||||||
locationOptions,
|
locationOptions,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
}: ManageLocationFormProps) => {
|
}: ManageLocationFormProps) => {
|
||||||
const layeredModalContext = useContext(LayeredModalContext)
|
const layeredModalContext = useLayeredModal()
|
||||||
const { pop } = layeredModalContext
|
const { pop } = layeredModalContext
|
||||||
|
|
||||||
const existingLocations = useMemo(() => {
|
const existingLocations = useMemo(() => {
|
||||||
@@ -2,6 +2,7 @@ import clsx from "clsx"
|
|||||||
import React, { useImperativeHandle } from "react"
|
import React, { useImperativeHandle } from "react"
|
||||||
|
|
||||||
import CheckIcon from "../../fundamentals/icons/check-icon"
|
import CheckIcon from "../../fundamentals/icons/check-icon"
|
||||||
|
import MinusIcon from "../../fundamentals/icons/minus-icon"
|
||||||
|
|
||||||
type IndeterminateCheckboxProps = {
|
type IndeterminateCheckboxProps = {
|
||||||
type?: "checkbox" | "radio"
|
type?: "checkbox" | "radio"
|
||||||
@@ -66,6 +67,7 @@ const IndeterminateCheckbox = React.forwardRef<
|
|||||||
>
|
>
|
||||||
<span className="self-center">
|
<span className="self-center">
|
||||||
{checked && <CheckIcon size={16} />}
|
{checked && <CheckIcon size={16} />}
|
||||||
|
{indeterminate && <MinusIcon size={16} />}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -1,262 +0,0 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
|
||||||
import { useAdminCreateVariant } from "medusa-react"
|
|
||||||
import React from "react"
|
|
||||||
import { Controller, useForm } from "react-hook-form"
|
|
||||||
import useNotification from "../../../hooks/use-notification"
|
|
||||||
import { getErrorMessage } from "../../../utils/error-messages"
|
|
||||||
import FormValidator from "../../../utils/form-validator"
|
|
||||||
import Button from "../../fundamentals/button"
|
|
||||||
import PlusIcon from "../../fundamentals/icons/plus-icon"
|
|
||||||
import TrashIcon from "../../fundamentals/icons/trash-icon"
|
|
||||||
import IconTooltip from "../../molecules/icon-tooltip"
|
|
||||||
import Modal from "../../molecules/modal"
|
|
||||||
import CurrencyInput from "../currency-input"
|
|
||||||
import { useValuesFieldArray } from "./use-values-field-array"
|
|
||||||
|
|
||||||
type AddDenominationModalProps = {
|
|
||||||
giftCard: Omit<Product, "beforeInsert">
|
|
||||||
storeCurrency: string
|
|
||||||
currencyCodes: string[]
|
|
||||||
handleClose: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const AddDenominationModal: React.FC<AddDenominationModalProps> = ({
|
|
||||||
giftCard,
|
|
||||||
storeCurrency,
|
|
||||||
currencyCodes,
|
|
||||||
handleClose,
|
|
||||||
}) => {
|
|
||||||
const { watch, handleSubmit, control } = useForm<{
|
|
||||||
default_price: number
|
|
||||||
prices: {
|
|
||||||
price: {
|
|
||||||
amount: number
|
|
||||||
currency_code: string
|
|
||||||
}
|
|
||||||
}[]
|
|
||||||
}>()
|
|
||||||
const notification = useNotification()
|
|
||||||
const { mutate, isLoading } = useAdminCreateVariant(giftCard.id)
|
|
||||||
|
|
||||||
// passed to useValuesFieldArray so new prices are intialized with the currenct default price
|
|
||||||
const defaultValue = watch("default_price", 10000)
|
|
||||||
|
|
||||||
const { fields, appendPrice, deletePrice, availableCurrencies } =
|
|
||||||
useValuesFieldArray(
|
|
||||||
currencyCodes,
|
|
||||||
{
|
|
||||||
control,
|
|
||||||
name: "prices",
|
|
||||||
keyName: "indexId",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
defaultAmount: defaultValue,
|
|
||||||
defaultCurrencyCode: storeCurrency,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const onSubmit = async (data: any) => {
|
|
||||||
const prices = [
|
|
||||||
{
|
|
||||||
amount: data.default_price,
|
|
||||||
currency_code: storeCurrency,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
if (data.prices) {
|
|
||||||
data.prices.forEach((p) => {
|
|
||||||
prices.push({
|
|
||||||
amount: p.price.amount,
|
|
||||||
currency_code: p.price.currency_code,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
mutate(
|
|
||||||
{
|
|
||||||
title: `${giftCard.variants.length}`,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
value: `${data.default_price}`,
|
|
||||||
option_id: giftCard.options[0].id,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
prices,
|
|
||||||
inventory_quantity: 0,
|
|
||||||
manage_inventory: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
notification("Success", "Denomination added successfully", "success")
|
|
||||||
handleClose()
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
const errorMessage = () => {
|
|
||||||
// @ts-ignore
|
|
||||||
if (error.response?.data?.type === "duplicate_error") {
|
|
||||||
return `A denomination with that default value already exists`
|
|
||||||
} else {
|
|
||||||
return getErrorMessage(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
notification("Error", errorMessage(), "error")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal handleClose={handleClose} isLargeModal>
|
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
|
||||||
<Modal.Body>
|
|
||||||
<Modal.Header handleClose={handleClose}>
|
|
||||||
<span className="inter-xlarge-semibold">Add Denomination</span>
|
|
||||||
</Modal.Header>
|
|
||||||
<Modal.Content>
|
|
||||||
<div className="mb-xlarge flex-1">
|
|
||||||
<div className="mb-base flex gap-x-2">
|
|
||||||
<h3 className="inter-base-semibold">Default Value</h3>
|
|
||||||
<IconTooltip content="This is the denomination in your store's default currency" />
|
|
||||||
</div>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="default_price"
|
|
||||||
rules={{
|
|
||||||
required: "Default value is required",
|
|
||||||
max: FormValidator.maxInteger("Default value", storeCurrency),
|
|
||||||
}}
|
|
||||||
render={({ field: { onChange, value, ref } }) => {
|
|
||||||
return (
|
|
||||||
<CurrencyInput.Root
|
|
||||||
currentCurrency={storeCurrency}
|
|
||||||
readOnly
|
|
||||||
size="medium"
|
|
||||||
>
|
|
||||||
<CurrencyInput.Amount
|
|
||||||
ref={ref}
|
|
||||||
label="Amount"
|
|
||||||
amount={value}
|
|
||||||
onChange={onChange}
|
|
||||||
/>
|
|
||||||
</CurrencyInput.Root>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div className="mb-base flex gap-x-2">
|
|
||||||
<h3 className="inter-base-semibold">Other Values</h3>
|
|
||||||
<IconTooltip content="Here you can add values in other currencies" />
|
|
||||||
</div>
|
|
||||||
<div className="gap-y-xsmall flex flex-col">
|
|
||||||
{fields.map((field, index) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={field.indexId}
|
|
||||||
className="mb-xsmall flex items-end last:mb-0"
|
|
||||||
>
|
|
||||||
<div className="flex-1">
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
key={field.indexId}
|
|
||||||
name={`prices.${index}.price`}
|
|
||||||
rules={{
|
|
||||||
required: FormValidator.required("Price"),
|
|
||||||
validate: (val) => {
|
|
||||||
return FormValidator.validateMaxInteger(
|
|
||||||
"Price",
|
|
||||||
val.amount,
|
|
||||||
val.currency_code
|
|
||||||
)
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
defaultValue={field.price}
|
|
||||||
render={({ field: { onChange, value } }) => {
|
|
||||||
const codes = [
|
|
||||||
value?.currency_code,
|
|
||||||
...availableCurrencies,
|
|
||||||
]
|
|
||||||
codes.sort()
|
|
||||||
return (
|
|
||||||
<CurrencyInput.Root
|
|
||||||
currencyCodes={codes}
|
|
||||||
currentCurrency={value?.currency_code}
|
|
||||||
size="medium"
|
|
||||||
readOnly={index === 0}
|
|
||||||
onChange={(code) =>
|
|
||||||
onChange({ ...value, currency_code: code })
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<CurrencyInput.Amount
|
|
||||||
label="Amount"
|
|
||||||
onChange={(amount) =>
|
|
||||||
onChange({ ...value, amount })
|
|
||||||
}
|
|
||||||
amount={value?.amount}
|
|
||||||
/>
|
|
||||||
</CurrencyInput.Root>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="small"
|
|
||||||
className="ml-large h-10 w-10"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
<TrashIcon
|
|
||||||
onClick={deletePrice(index)}
|
|
||||||
className="text-grey-40"
|
|
||||||
size="20"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div className="mt-large mb-small">
|
|
||||||
<Button
|
|
||||||
onClick={appendPrice}
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="small"
|
|
||||||
disabled={availableCurrencies?.length === 0}
|
|
||||||
>
|
|
||||||
<PlusIcon size={20} />
|
|
||||||
Add a price
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Modal.Content>
|
|
||||||
<Modal.Footer>
|
|
||||||
<div className="flex w-full justify-end">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="small"
|
|
||||||
onClick={handleClose}
|
|
||||||
className="mr-2 min-w-[130px] justify-center"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
size="small"
|
|
||||||
className="mr-2 min-w-[130px] justify-center"
|
|
||||||
type="submit"
|
|
||||||
loading={isLoading}
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Modal.Footer>
|
|
||||||
</Modal.Body>
|
|
||||||
</form>
|
|
||||||
</Modal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AddDenominationModal
|
|
||||||
-72
@@ -1,72 +0,0 @@
|
|||||||
import { useFieldArray, UseFieldArrayOptions, useWatch } from "react-hook-form"
|
|
||||||
|
|
||||||
type UseValuesFieldArrayOptions = {
|
|
||||||
defaultAmount: number
|
|
||||||
defaultCurrencyCode: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ValuesFormValue = {
|
|
||||||
price: {
|
|
||||||
currency_code: string
|
|
||||||
amount: number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useValuesFieldArray = <TKeyName extends string = "id">(
|
|
||||||
currencyCodes: string[],
|
|
||||||
{ control, name, keyName }: UseFieldArrayOptions<TKeyName>,
|
|
||||||
options: UseValuesFieldArrayOptions = {
|
|
||||||
defaultAmount: 1000,
|
|
||||||
defaultCurrencyCode: "usd",
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
const { defaultAmount } = options
|
|
||||||
const { fields, append, remove } = useFieldArray<ValuesFormValue, TKeyName>({
|
|
||||||
control,
|
|
||||||
name,
|
|
||||||
keyName,
|
|
||||||
})
|
|
||||||
const watchedFields = useWatch({
|
|
||||||
control,
|
|
||||||
name,
|
|
||||||
defaultValue: fields,
|
|
||||||
})
|
|
||||||
|
|
||||||
const selectedCurrencies = watchedFields.map(
|
|
||||||
(field) => field?.price?.currency_code
|
|
||||||
)
|
|
||||||
const availableCurrencies = currencyCodes?.filter(
|
|
||||||
(currency) => !selectedCurrencies.includes(currency)
|
|
||||||
)
|
|
||||||
|
|
||||||
const controlledFields = fields.map((field, index) => {
|
|
||||||
return {
|
|
||||||
...field,
|
|
||||||
...watchedFields[index],
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const appendPrice = () => {
|
|
||||||
const newCurrency = availableCurrencies[0]
|
|
||||||
append({
|
|
||||||
price: {
|
|
||||||
currency_code: newCurrency,
|
|
||||||
amount: defaultAmount,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const deletePrice = (index) => {
|
|
||||||
return () => {
|
|
||||||
remove(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
fields: controlledFields,
|
|
||||||
appendPrice,
|
|
||||||
deletePrice,
|
|
||||||
availableCurrencies,
|
|
||||||
selectedCurrencies,
|
|
||||||
} as const
|
|
||||||
}
|
|
||||||
@@ -1,179 +0,0 @@
|
|||||||
import _ from "lodash"
|
|
||||||
import * as React from "react"
|
|
||||||
import { v4 as uuidv4 } from "uuid"
|
|
||||||
import Button from "../../fundamentals/button"
|
|
||||||
import PlusIcon from "../../fundamentals/icons/plus-icon"
|
|
||||||
import TrashIcon from "../../fundamentals/icons/trash-icon"
|
|
||||||
import IconTooltip from "../../molecules/icon-tooltip"
|
|
||||||
import Modal from "../../molecules/modal"
|
|
||||||
import CurrencyInput from "../../organisms/currency-input"
|
|
||||||
|
|
||||||
export type PriceType = {
|
|
||||||
currency_code: string
|
|
||||||
amount: number
|
|
||||||
id?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type EditDenominationsModalProps = {
|
|
||||||
defaultDenominations: PriceType[]
|
|
||||||
handleClose: () => void
|
|
||||||
onSubmit: (denominations: PriceType[]) => void
|
|
||||||
defaultNewAmount?: number
|
|
||||||
defaultNewCurrencyCode?: string
|
|
||||||
currencyCodes?: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const EditDenominationsModal = ({
|
|
||||||
defaultDenominations = [],
|
|
||||||
onSubmit,
|
|
||||||
handleClose,
|
|
||||||
currencyCodes = [],
|
|
||||||
defaultNewAmount = 1000,
|
|
||||||
}: EditDenominationsModalProps) => {
|
|
||||||
const [denominations, setDenominations] = React.useState(
|
|
||||||
augmentWithIds(defaultDenominations)
|
|
||||||
)
|
|
||||||
const selectedCurrencies = denominations.map(
|
|
||||||
(denomination) => denomination.currency_code
|
|
||||||
)
|
|
||||||
const availableCurrencies = currencyCodes?.filter(
|
|
||||||
(currency) => !selectedCurrencies.includes(currency)
|
|
||||||
)
|
|
||||||
|
|
||||||
const onAmountChange = (index) => {
|
|
||||||
return (amount) => {
|
|
||||||
const newDenominations = denominations.slice()
|
|
||||||
newDenominations[index] = { ...newDenominations[index], amount }
|
|
||||||
setDenominations(newDenominations)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onCurrencyChange = (index) => {
|
|
||||||
return (currencyCode) => {
|
|
||||||
const newDenominations = denominations.slice()
|
|
||||||
newDenominations[index] = {
|
|
||||||
...newDenominations[index],
|
|
||||||
currency_code: currencyCode,
|
|
||||||
}
|
|
||||||
setDenominations(newDenominations)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onClickDelete = (index) => {
|
|
||||||
return () => {
|
|
||||||
const newDenominations = denominations.slice()
|
|
||||||
newDenominations.splice(index, 1)
|
|
||||||
setDenominations(newDenominations)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const appendDenomination = () => {
|
|
||||||
const newDenomination = {
|
|
||||||
amount: defaultNewAmount,
|
|
||||||
currency_code: availableCurrencies[0],
|
|
||||||
}
|
|
||||||
setDenominations([...denominations, augmentWithId(newDenomination)])
|
|
||||||
}
|
|
||||||
|
|
||||||
const submitHandler = () => {
|
|
||||||
const strippedDenominations = stripDenominationFromIndexId(denominations)
|
|
||||||
|
|
||||||
if (onSubmit) {
|
|
||||||
onSubmit(strippedDenominations)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal handleClose={handleClose}>
|
|
||||||
<Modal.Body>
|
|
||||||
<Modal.Header handleClose={handleClose}>
|
|
||||||
<span className="inter-xlarge-semibold">Edit Denominations</span>
|
|
||||||
</Modal.Header>
|
|
||||||
<Modal.Content>
|
|
||||||
<div className="pt-1">
|
|
||||||
<div className="flex items-center">
|
|
||||||
<label className="inter-base-semibold text-grey-90 mr-1.5">
|
|
||||||
Prices
|
|
||||||
</label>
|
|
||||||
<IconTooltip content={"Helpful denominations"} />
|
|
||||||
</div>
|
|
||||||
{denominations.map((field, index) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={field.indexId}
|
|
||||||
className="mt-xsmall flex items-center first:mt-0"
|
|
||||||
>
|
|
||||||
<div className="flex-1">
|
|
||||||
<CurrencyInput.Root
|
|
||||||
currencyCodes={currencyCodes}
|
|
||||||
currentCurrency={field.currency_code}
|
|
||||||
onChange={onCurrencyChange(index)}
|
|
||||||
size="medium"
|
|
||||||
>
|
|
||||||
<CurrencyInput.Amount
|
|
||||||
label="Amount"
|
|
||||||
onChange={onAmountChange(index)}
|
|
||||||
amount={field.amount}
|
|
||||||
/>
|
|
||||||
</CurrencyInput.Root>
|
|
||||||
</div>
|
|
||||||
<button className="ml-2xlarge">
|
|
||||||
<TrashIcon
|
|
||||||
onClick={onClickDelete(index)}
|
|
||||||
className="text-grey-40"
|
|
||||||
size="20"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<div className="mt-large">
|
|
||||||
<Button
|
|
||||||
onClick={appendDenomination}
|
|
||||||
type="button"
|
|
||||||
variant="ghost"
|
|
||||||
size="small"
|
|
||||||
disabled={availableCurrencies.length === 0}
|
|
||||||
>
|
|
||||||
<PlusIcon size={20} />
|
|
||||||
Add a price
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Modal.Content>
|
|
||||||
<Modal.Footer>
|
|
||||||
<div className="flex w-full justify-end">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="small"
|
|
||||||
onClick={handleClose}
|
|
||||||
className="mr-2 min-w-[130px] justify-center"
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="primary"
|
|
||||||
size="small"
|
|
||||||
className="mr-2 min-w-[130px] justify-center"
|
|
||||||
onClick={submitHandler}
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Modal.Footer>
|
|
||||||
</Modal.Body>
|
|
||||||
</Modal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EditDenominationsModal
|
|
||||||
|
|
||||||
const augmentWithId = (obj) => ({ ...obj, indexId: uuidv4() })
|
|
||||||
|
|
||||||
const augmentWithIds = (list) => {
|
|
||||||
return list.map(augmentWithId)
|
|
||||||
}
|
|
||||||
|
|
||||||
const stripDenominationFromIndexId = (list) => {
|
|
||||||
return list.map((element) => _.omit(element, "indexId"))
|
|
||||||
}
|
|
||||||
+175
@@ -0,0 +1,175 @@
|
|||||||
|
import { Product } from "@medusajs/medusa"
|
||||||
|
import { useAdminCreateVariant, useAdminStore } from "medusa-react"
|
||||||
|
import { useCallback, useMemo } from "react"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import useNotification from "../../../hooks/use-notification"
|
||||||
|
import { getErrorMessage } from "../../../utils/error-messages"
|
||||||
|
import { nestedForm } from "../../../utils/nested-form"
|
||||||
|
import DenominationForm, {
|
||||||
|
DenominationFormType,
|
||||||
|
} from "../../forms/gift-card/denomination-form"
|
||||||
|
import Button from "../../fundamentals/button"
|
||||||
|
import Modal from "../../molecules/modal"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
/**
|
||||||
|
* Whether the modal is open or not
|
||||||
|
*/
|
||||||
|
open: boolean
|
||||||
|
/**
|
||||||
|
* Callback to close the modal
|
||||||
|
*/
|
||||||
|
onClose: () => void
|
||||||
|
/**
|
||||||
|
* Gift card
|
||||||
|
*/
|
||||||
|
giftCard: Product
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddDenominationModalFormType = {
|
||||||
|
denominations: DenominationFormType
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddDenominationModal = ({ open, onClose, giftCard }: Props) => {
|
||||||
|
const { mutate, isLoading: isMutating } = useAdminCreateVariant(giftCard.id)
|
||||||
|
|
||||||
|
const { store } = useAdminStore()
|
||||||
|
|
||||||
|
const defaultValues: AddDenominationModalFormType | undefined =
|
||||||
|
useMemo(() => {
|
||||||
|
if (!store) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
denominations: {
|
||||||
|
defaultDenomination: {
|
||||||
|
currency_code: store.default_currency_code,
|
||||||
|
includes_tax: store.currencies.find(
|
||||||
|
(c) => c.code === store.default_currency_code
|
||||||
|
)?.includes_tax,
|
||||||
|
},
|
||||||
|
currencyDenominations: store.currencies
|
||||||
|
.filter((c) => c.code !== store.default_currency_code)
|
||||||
|
.map((currency) => {
|
||||||
|
return {
|
||||||
|
currency_code: currency.code,
|
||||||
|
includes_tax: currency.includes_tax,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
} as AddDenominationModalFormType
|
||||||
|
}, [store])
|
||||||
|
|
||||||
|
const form = useForm<AddDenominationModalFormType>({
|
||||||
|
defaultValues,
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isDirty },
|
||||||
|
} = form
|
||||||
|
|
||||||
|
const notification = useNotification()
|
||||||
|
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
reset(defaultValues)
|
||||||
|
onClose()
|
||||||
|
}, [reset, defaultValues, onClose])
|
||||||
|
|
||||||
|
const onSubmit = handleSubmit((data) => {
|
||||||
|
const payload = {
|
||||||
|
title: `${giftCard.variants.length}`,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: `${data.denominations.defaultDenomination.amount}`,
|
||||||
|
option_id: giftCard.options[0].id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
amount: data.denominations.defaultDenomination.amount,
|
||||||
|
currency_code: data.denominations.defaultDenomination.currency_code,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
inventory_quantity: 0,
|
||||||
|
manage_inventory: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
data.denominations.currencyDenominations.forEach((currency) => {
|
||||||
|
if (
|
||||||
|
(currency.amount !== null && currency.amount !== undefined) ||
|
||||||
|
data.denominations.useSameValue
|
||||||
|
) {
|
||||||
|
payload.prices.push({
|
||||||
|
amount: data.denominations.useSameValue
|
||||||
|
? data.denominations.defaultDenomination.amount
|
||||||
|
: currency.amount,
|
||||||
|
currency_code: currency.currency_code,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mutate(payload, {
|
||||||
|
onSuccess: () => {
|
||||||
|
notification(
|
||||||
|
"Denomination added",
|
||||||
|
"A new denomination was succesfully added",
|
||||||
|
"success"
|
||||||
|
)
|
||||||
|
handleClose()
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
const errorMessage = () => {
|
||||||
|
// @ts-ignore
|
||||||
|
if (error.response?.data?.type === "duplicate_error") {
|
||||||
|
return `A denomination with that default value already exists`
|
||||||
|
} else {
|
||||||
|
return getErrorMessage(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notification("Error", errorMessage(), "error")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} handleClose={handleClose}>
|
||||||
|
<Modal.Body>
|
||||||
|
<Modal.Header handleClose={handleClose}>
|
||||||
|
<h1 className="inter-xlarge-semibold">Add Denomination</h1>
|
||||||
|
</Modal.Header>
|
||||||
|
<form onSubmit={onSubmit}>
|
||||||
|
<Modal.Content>
|
||||||
|
<DenominationForm form={nestedForm(form, "denominations")} />
|
||||||
|
</Modal.Content>
|
||||||
|
<Modal.Footer>
|
||||||
|
<div className="gap-x-xsmall flex w-full items-center justify-end">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="small"
|
||||||
|
type="button"
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="small"
|
||||||
|
type="submit"
|
||||||
|
disabled={isMutating || !isDirty}
|
||||||
|
loading={isMutating}
|
||||||
|
>
|
||||||
|
Save and close
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal.Footer>
|
||||||
|
</form>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddDenominationModal
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
import { ProductVariant } from "@medusajs/medusa"
|
||||||
|
import {
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
useReactTable,
|
||||||
|
} from "@tanstack/react-table"
|
||||||
|
import Table from "../../molecules/table"
|
||||||
|
import { useDenominationColumns } from "./use-denominations-columns"
|
||||||
|
|
||||||
|
type DenominationsTableProps = {
|
||||||
|
denominations: ProductVariant[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const DenominationsTable = ({ denominations }: DenominationsTableProps) => {
|
||||||
|
const columns = useDenominationColumns()
|
||||||
|
|
||||||
|
const { getHeaderGroups, getRowModel } = useReactTable({
|
||||||
|
columns,
|
||||||
|
data: denominations,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table>
|
||||||
|
<Table.Head>
|
||||||
|
{getHeaderGroups().map((headerGroup) => {
|
||||||
|
return (
|
||||||
|
<Table.HeadRow key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map((header) => {
|
||||||
|
return (
|
||||||
|
<Table.HeadCell
|
||||||
|
key={header.id}
|
||||||
|
className="inter-small-semibold text-grey-50"
|
||||||
|
style={{
|
||||||
|
width: header.getSize(),
|
||||||
|
maxWidth: header.getSize(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</Table.HeadCell>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Table.HeadRow>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Table.Head>
|
||||||
|
<Table.Body>
|
||||||
|
{getRowModel().rows.map((row) => {
|
||||||
|
return (
|
||||||
|
<Table.Row key={row.id} className="last-of-type:border-b-0">
|
||||||
|
{row.getVisibleCells().map((cell) => {
|
||||||
|
return (
|
||||||
|
<Table.Cell
|
||||||
|
key={cell.id}
|
||||||
|
style={{
|
||||||
|
width: cell.column.getSize(),
|
||||||
|
maxWidth: cell.column.getSize(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</Table.Cell>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Table.Row>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</Table.Body>
|
||||||
|
</Table>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DenominationsTable
|
||||||
+191
@@ -0,0 +1,191 @@
|
|||||||
|
import { MoneyAmount, ProductVariant, Store } from "@medusajs/medusa"
|
||||||
|
import { useQueryClient } from "@tanstack/react-query"
|
||||||
|
import {
|
||||||
|
adminProductKeys,
|
||||||
|
useAdminStore,
|
||||||
|
useAdminUpdateVariant,
|
||||||
|
} from "medusa-react"
|
||||||
|
import { useCallback, useEffect } from "react"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import useNotification from "../../../hooks/use-notification"
|
||||||
|
import { getErrorMessage } from "../../../utils/error-messages"
|
||||||
|
import { nestedForm } from "../../../utils/nested-form"
|
||||||
|
import DenominationForm, {
|
||||||
|
DenominationFormType,
|
||||||
|
} from "../../forms/gift-card/denomination-form"
|
||||||
|
import Button from "../../fundamentals/button"
|
||||||
|
import Modal from "../../molecules/modal"
|
||||||
|
|
||||||
|
type EditDenominationsModalProps = {
|
||||||
|
open: boolean
|
||||||
|
onClose: () => void
|
||||||
|
denomination: ProductVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
type EditDenominationModalFormType = {
|
||||||
|
denominations: DenominationFormType
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditDenominationsModal = ({
|
||||||
|
denomination,
|
||||||
|
onClose,
|
||||||
|
open,
|
||||||
|
}: EditDenominationsModalProps) => {
|
||||||
|
const { store } = useAdminStore()
|
||||||
|
const { mutate, isLoading } = useAdminUpdateVariant(denomination.product_id)
|
||||||
|
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
|
||||||
|
const form = useForm<EditDenominationModalFormType>({
|
||||||
|
defaultValues: getDefaultValues(store, denomination.prices),
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isDirty },
|
||||||
|
} = form
|
||||||
|
|
||||||
|
const notification = useNotification()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
reset(getDefaultValues(store, denomination.prices))
|
||||||
|
}
|
||||||
|
}, [open, store, denomination.prices, reset])
|
||||||
|
|
||||||
|
const handleClose = useCallback(() => {
|
||||||
|
reset()
|
||||||
|
onClose()
|
||||||
|
}, [reset, onClose])
|
||||||
|
|
||||||
|
const onSubmit = handleSubmit((data) => {
|
||||||
|
const payload = {
|
||||||
|
prices: [
|
||||||
|
{
|
||||||
|
amount: data.denominations.defaultDenomination.amount,
|
||||||
|
currency_code: data.denominations.defaultDenomination.currency_code,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
data.denominations.currencyDenominations.forEach((currency) => {
|
||||||
|
if (
|
||||||
|
(currency.amount !== undefined && currency.amount !== null) ||
|
||||||
|
data.denominations.useSameValue
|
||||||
|
) {
|
||||||
|
payload.prices.push({
|
||||||
|
amount: data.denominations.useSameValue
|
||||||
|
? data.denominations.defaultDenomination.amount
|
||||||
|
: currency.amount,
|
||||||
|
currency_code: currency.currency_code,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mutate(
|
||||||
|
{
|
||||||
|
variant_id: denomination.id,
|
||||||
|
...payload,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
notification(
|
||||||
|
"Denomination updated",
|
||||||
|
"A new denomination was succesfully updated",
|
||||||
|
"success"
|
||||||
|
)
|
||||||
|
queryClient.invalidateQueries(adminProductKeys.all)
|
||||||
|
handleClose()
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
notification("Error", getErrorMessage(error), "error")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={open} handleClose={handleClose}>
|
||||||
|
<Modal.Body>
|
||||||
|
<Modal.Header handleClose={handleClose}>
|
||||||
|
<h1 className="inter-xlarge-semibold">Edit Denominations</h1>
|
||||||
|
</Modal.Header>
|
||||||
|
<form onSubmit={onSubmit}>
|
||||||
|
<Modal.Content>
|
||||||
|
<DenominationForm form={nestedForm(form, "denominations")} />
|
||||||
|
</Modal.Content>
|
||||||
|
<Modal.Footer>
|
||||||
|
<div className="gap-x-2xsmall flex w-full items-center justify-end">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="small"
|
||||||
|
type="button"
|
||||||
|
onClick={handleClose}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
size="small"
|
||||||
|
type="submit"
|
||||||
|
loading={isLoading}
|
||||||
|
disabled={!isDirty || isLoading}
|
||||||
|
>
|
||||||
|
Save and close
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal.Footer>
|
||||||
|
</form>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const getDefaultValues = (store: Store | undefined, prices: MoneyAmount[]) => {
|
||||||
|
if (!store) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultValues = {
|
||||||
|
denominations: {
|
||||||
|
defaultDenomination: {
|
||||||
|
currency_code: store.default_currency_code,
|
||||||
|
includes_tax: store.currencies.find(
|
||||||
|
(c) => c.code === store.default_currency_code
|
||||||
|
)?.includes_tax,
|
||||||
|
amount: findPrice(store.default_currency_code, prices),
|
||||||
|
},
|
||||||
|
currencyDenominations: store.currencies
|
||||||
|
.filter((c) => c.code !== store.default_currency_code)
|
||||||
|
.map((currency) => {
|
||||||
|
return {
|
||||||
|
currency_code: currency.code,
|
||||||
|
includes_tax: currency.includes_tax,
|
||||||
|
amount: findPrice(currency.code, prices),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
} as EditDenominationModalFormType
|
||||||
|
|
||||||
|
if (
|
||||||
|
defaultValues.denominations.currencyDenominations.every(
|
||||||
|
(c) => c.amount === defaultValues.denominations.defaultDenomination.amount
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
defaultValues.denominations.useSameValue = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValues
|
||||||
|
}
|
||||||
|
|
||||||
|
const findPrice = (currencyCode: string, prices: MoneyAmount[]) => {
|
||||||
|
return prices.find(
|
||||||
|
(p) =>
|
||||||
|
p.currency_code === currencyCode &&
|
||||||
|
p.region_id === null &&
|
||||||
|
p.price_list_id === null
|
||||||
|
)?.amount
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EditDenominationsModal
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
import { Product } from "@medusajs/medusa"
|
||||||
|
import useToggleState from "../../../hooks/use-toggle-state"
|
||||||
|
import PlusIcon from "../../fundamentals/icons/plus-icon"
|
||||||
|
import Section from "../section"
|
||||||
|
import AddDenominationModal from "./add-denominations-modal"
|
||||||
|
import DenominationsTable from "./denominations-table"
|
||||||
|
|
||||||
|
type GiftCardDenominationsSectionProps = {
|
||||||
|
giftCard: Product
|
||||||
|
}
|
||||||
|
|
||||||
|
const GiftCardDenominationsSection = ({
|
||||||
|
giftCard,
|
||||||
|
}: GiftCardDenominationsSectionProps) => {
|
||||||
|
const {
|
||||||
|
state: addDenomination,
|
||||||
|
close: closeAddDenomination,
|
||||||
|
open: openAddDenomination,
|
||||||
|
} = useToggleState()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Section
|
||||||
|
title="Denominations"
|
||||||
|
forceDropdown
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
label: "Add Denomination",
|
||||||
|
onClick: openAddDenomination,
|
||||||
|
icon: <PlusIcon size={20} />,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<div className="mb-large mt-base">
|
||||||
|
<DenominationsTable denominations={[...giftCard.variants]} />
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
|
||||||
|
<AddDenominationModal
|
||||||
|
giftCard={giftCard}
|
||||||
|
open={addDenomination}
|
||||||
|
onClose={closeAddDenomination}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GiftCardDenominationsSection
|
||||||
+176
@@ -0,0 +1,176 @@
|
|||||||
|
import { MoneyAmount, ProductVariant } from "@medusajs/medusa"
|
||||||
|
import { createColumnHelper } from "@tanstack/react-table"
|
||||||
|
import { useAdminDeleteVariant, useAdminStore } from "medusa-react"
|
||||||
|
import { useMemo } from "react"
|
||||||
|
import useImperativeDialog from "../../../hooks/use-imperative-dialog"
|
||||||
|
import useNotification from "../../../hooks/use-notification"
|
||||||
|
import useToggleState from "../../../hooks/use-toggle-state"
|
||||||
|
import { getErrorMessage } from "../../../utils/error-messages"
|
||||||
|
import { normalizeAmount } from "../../../utils/prices"
|
||||||
|
import Tooltip from "../../atoms/tooltip"
|
||||||
|
import EditIcon from "../../fundamentals/icons/edit-icon"
|
||||||
|
import TrashIcon from "../../fundamentals/icons/trash-icon"
|
||||||
|
import Actionables, { ActionType } from "../../molecules/actionables"
|
||||||
|
import EditDenominationsModal from "./edit-denominations-modal"
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<ProductVariant>()
|
||||||
|
|
||||||
|
export const useDenominationColumns = () => {
|
||||||
|
const { store } = useAdminStore()
|
||||||
|
|
||||||
|
const columns = useMemo(() => {
|
||||||
|
if (!store) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultCurrency = store.default_currency_code
|
||||||
|
|
||||||
|
return [
|
||||||
|
columnHelper.display({
|
||||||
|
header: "Denomination",
|
||||||
|
id: "denomination",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const defaultDenomination = row.original.prices.find(
|
||||||
|
(p) =>
|
||||||
|
p.currency_code === defaultCurrency &&
|
||||||
|
p.region_id === null &&
|
||||||
|
p.price_list_id === null
|
||||||
|
)
|
||||||
|
|
||||||
|
return defaultDenomination ? (
|
||||||
|
<p>
|
||||||
|
{normalizeAmount(defaultCurrency, defaultDenomination.amount)}{" "}
|
||||||
|
<span className="text-grey-50">
|
||||||
|
{defaultCurrency.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
"-"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.display({
|
||||||
|
header: "In other currencies",
|
||||||
|
id: "other_currencies",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const otherCurrencies = row.original.prices.filter(
|
||||||
|
(p) =>
|
||||||
|
p.currency_code !== defaultCurrency &&
|
||||||
|
p.region_id === null &&
|
||||||
|
p.price_list_id === null
|
||||||
|
)
|
||||||
|
|
||||||
|
let remainder: MoneyAmount[] = []
|
||||||
|
|
||||||
|
if (otherCurrencies.length > 2) {
|
||||||
|
remainder = otherCurrencies.splice(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return otherCurrencies.length > 0 ? (
|
||||||
|
<p>
|
||||||
|
{otherCurrencies.map((p, index) => {
|
||||||
|
return (
|
||||||
|
<span key={index}>
|
||||||
|
{normalizeAmount(p.currency_code, p.amount)}{" "}
|
||||||
|
<span className="text-grey-50">
|
||||||
|
{p.currency_code.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
{index < otherCurrencies.length - 1 && ", "}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
{remainder.length > 0 && (
|
||||||
|
<Tooltip
|
||||||
|
content={
|
||||||
|
<ul>
|
||||||
|
{remainder.map((p, index) => {
|
||||||
|
return (
|
||||||
|
<li key={index}>
|
||||||
|
<p>
|
||||||
|
{normalizeAmount(p.currency_code, p.amount)}{" "}
|
||||||
|
<span className="text-grey-50">
|
||||||
|
{p.currency_code.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className="text-grey-50 cursor-default">{`, and ${remainder.length} more`}</span>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
) : (
|
||||||
|
"-"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
columnHelper.display({
|
||||||
|
id: "actions",
|
||||||
|
cell: ({ row: { original } }) => <Actions original={original} />,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}, [store])
|
||||||
|
|
||||||
|
return columns
|
||||||
|
}
|
||||||
|
|
||||||
|
const Actions = ({ original }: { original: ProductVariant }) => {
|
||||||
|
const { state, open, close } = useToggleState()
|
||||||
|
|
||||||
|
const { mutateAsync } = useAdminDeleteVariant(original.product_id)
|
||||||
|
const notification = useNotification()
|
||||||
|
|
||||||
|
const dialog = useImperativeDialog()
|
||||||
|
|
||||||
|
const onDelete = async () => {
|
||||||
|
const shouldDelete = await dialog({
|
||||||
|
heading: "Delete denomination",
|
||||||
|
text: "Are you sure you want to delete this denomination?",
|
||||||
|
})
|
||||||
|
|
||||||
|
if (shouldDelete) {
|
||||||
|
mutateAsync(original.id, {
|
||||||
|
onSuccess: () => {
|
||||||
|
notification(
|
||||||
|
"Denomination deleted",
|
||||||
|
"Denomination was successfully deleted",
|
||||||
|
"success"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
notification("Error", getErrorMessage(error), "error")
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions: ActionType[] = [
|
||||||
|
{
|
||||||
|
label: "Edit",
|
||||||
|
onClick: open,
|
||||||
|
icon: <EditIcon size={20} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Delete",
|
||||||
|
onClick: onDelete,
|
||||||
|
icon: <TrashIcon size={20} />,
|
||||||
|
variant: "danger",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center justify-end">
|
||||||
|
<Actionables actions={actions} forceDropdown />
|
||||||
|
</div>
|
||||||
|
<EditDenominationsModal
|
||||||
|
open={state}
|
||||||
|
onClose={close}
|
||||||
|
denomination={original}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
+7
-7
@@ -1,15 +1,15 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
import Button from "../../../../../components/fundamentals/button"
|
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
||||||
import Modal from "../../../../../components/molecules/modal"
|
import { countries } from "../../../utils/countries"
|
||||||
import { countries } from "../../../../../utils/countries"
|
import { nestedForm } from "../../../utils/nested-form"
|
||||||
import { nestedForm } from "../../../../../utils/nested-form"
|
import CustomsForm, { CustomsFormType } from "../../forms/product/customs-form"
|
||||||
import CustomsForm, { CustomsFormType } from "../../../components/customs-form"
|
|
||||||
import DimensionsForm, {
|
import DimensionsForm, {
|
||||||
DimensionsFormType,
|
DimensionsFormType,
|
||||||
} from "../../../components/dimensions-form"
|
} from "../../forms/product/dimensions-form"
|
||||||
import useEditProductActions from "../../hooks/use-edit-product-actions"
|
import Button from "../../fundamentals/button"
|
||||||
|
import Modal from "../../molecules/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
product: Product
|
product: Product
|
||||||
+6
-6
@@ -1,15 +1,15 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import EditIcon from "../../../../../components/fundamentals/icons/edit-icon"
|
import useToggleState from "../../../hooks/use-toggle-state"
|
||||||
import { ActionType } from "../../../../../components/molecules/actionables"
|
import EditIcon from "../../fundamentals/icons/edit-icon"
|
||||||
import Section from "../../../../../components/organisms/section"
|
import { ActionType } from "../../molecules/actionables"
|
||||||
import useToggleState from "../../../../../hooks/use-toggle-state"
|
import Section from "../section"
|
||||||
import AttributeModal from "./attribute-modal"
|
import AttributeModal from "./attribute-modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
product: Product
|
product: Product
|
||||||
}
|
}
|
||||||
|
|
||||||
const AttributesSection = ({ product }: Props) => {
|
const ProductAttributesSection = ({ product }: Props) => {
|
||||||
const { state, toggle, close } = useToggleState()
|
const { state, toggle, close } = useToggleState()
|
||||||
|
|
||||||
const actions: ActionType[] = [
|
const actions: ActionType[] = [
|
||||||
@@ -64,4 +64,4 @@ const Attribute = ({ attribute, value }: AttributeProps) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AttributesSection
|
export default ProductAttributesSection
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
import { Product, SalesChannel } from "@medusajs/medusa"
|
import { Product, SalesChannel } from "@medusajs/medusa"
|
||||||
import { useAdminUpdateProduct } from "medusa-react"
|
import { useAdminUpdateProduct } from "medusa-react"
|
||||||
import SalesChannelsModal from "../../../components/sales-channels-modal"
|
import SalesChannelsModal from "../../forms/product/sales-channels-modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
product: Product
|
product: Product
|
||||||
+19
-11
@@ -1,17 +1,17 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
import Button from "../../../../../components/fundamentals/button"
|
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
||||||
import Modal from "../../../../../components/molecules/modal"
|
import { nestedForm } from "../../../utils/nested-form"
|
||||||
import { nestedForm } from "../../../../../utils/nested-form"
|
|
||||||
import DiscountableForm, {
|
import DiscountableForm, {
|
||||||
DiscountableFormType,
|
DiscountableFormType,
|
||||||
} from "../../../components/discountable-form"
|
} from "../../forms/product/discountable-form"
|
||||||
import GeneralForm, { GeneralFormType } from "../../../components/general-form"
|
import GeneralForm, { GeneralFormType } from "../../forms/product/general-form"
|
||||||
import OrganizeForm, {
|
import OrganizeForm, {
|
||||||
OrganizeFormType,
|
OrganizeFormType,
|
||||||
} from "../../../components/organize-form"
|
} from "../../forms/product/organize-form"
|
||||||
import useEditProductActions from "../../hooks/use-edit-product-actions"
|
import Button from "../../fundamentals/button"
|
||||||
|
import Modal from "../../molecules/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
product: Product
|
product: Product
|
||||||
@@ -39,7 +39,7 @@ const GeneralModal = ({ product, open, onClose }: Props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reset(getDefaultValues(product))
|
reset(getDefaultValues(product))
|
||||||
}, [product])
|
}, [product, reset])
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
reset(getDefaultValues(product))
|
reset(getDefaultValues(product))
|
||||||
@@ -90,12 +90,20 @@ const GeneralModal = ({ product, open, onClose }: Props) => {
|
|||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<GeneralForm form={nestedForm(form, "general")} />
|
<GeneralForm
|
||||||
|
form={nestedForm(form, "general")}
|
||||||
|
isGiftCard={product.is_giftcard}
|
||||||
|
/>
|
||||||
<div className="my-xlarge">
|
<div className="my-xlarge">
|
||||||
<h2 className="inter-base-semibold mb-base">Organize Product</h2>
|
<h2 className="inter-base-semibold mb-base">
|
||||||
|
Organize {product.is_giftcard ? "Gift Card" : "Product"}
|
||||||
|
</h2>
|
||||||
<OrganizeForm form={nestedForm(form, "organize")} />
|
<OrganizeForm form={nestedForm(form, "organize")} />
|
||||||
</div>
|
</div>
|
||||||
<DiscountableForm form={nestedForm(form, "discountable")} />
|
<DiscountableForm
|
||||||
|
form={nestedForm(form, "discountable")}
|
||||||
|
isGiftCard={product.is_giftcard}
|
||||||
|
/>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
<div className="flex w-full justify-end gap-x-2">
|
<div className="flex w-full justify-end gap-x-2">
|
||||||
+15
-15
@@ -1,19 +1,19 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import FeatureToggle from "../../../../../components/fundamentals/feature-toggle"
|
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
||||||
import ChannelsIcon from "../../../../../components/fundamentals/icons/channels-icon"
|
import useToggleState from "../../../hooks/use-toggle-state"
|
||||||
import EditIcon from "../../../../../components/fundamentals/icons/edit-icon"
|
|
||||||
import TrashIcon from "../../../../../components/fundamentals/icons/trash-icon"
|
|
||||||
import { ActionType } from "../../../../../components/molecules/actionables"
|
|
||||||
import SalesChannelsDisplay from "../../../../../components/molecules/sales-channels-display"
|
|
||||||
import StatusSelector from "../../../../../components/molecules/status-selector"
|
|
||||||
import DelimitedList from "../../../../../components/molecules/delimited-list"
|
|
||||||
import Section from "../../../../../components/organisms/section"
|
|
||||||
import {
|
import {
|
||||||
useFeatureFlag,
|
|
||||||
FeatureFlag,
|
FeatureFlag,
|
||||||
} from "../../../../../providers/feature-flag-provider"
|
useFeatureFlag,
|
||||||
import useToggleState from "../../../../../hooks/use-toggle-state"
|
} from "../../../providers/feature-flag-provider"
|
||||||
import useEditProductActions from "../../hooks/use-edit-product-actions"
|
import FeatureToggle from "../../fundamentals/feature-toggle"
|
||||||
|
import ChannelsIcon from "../../fundamentals/icons/channels-icon"
|
||||||
|
import EditIcon from "../../fundamentals/icons/edit-icon"
|
||||||
|
import TrashIcon from "../../fundamentals/icons/trash-icon"
|
||||||
|
import { ActionType } from "../../molecules/actionables"
|
||||||
|
import DelimitedList from "../../molecules/delimited-list"
|
||||||
|
import SalesChannelsDisplay from "../../molecules/sales-channels-display"
|
||||||
|
import StatusSelector from "../../molecules/status-selector"
|
||||||
|
import Section from "../section"
|
||||||
import ChannelsModal from "./channels-modal"
|
import ChannelsModal from "./channels-modal"
|
||||||
import GeneralModal from "./general-modal"
|
import GeneralModal from "./general-modal"
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ type Props = {
|
|||||||
product: Product
|
product: Product
|
||||||
}
|
}
|
||||||
|
|
||||||
const GeneralSection = ({ product }: Props) => {
|
const ProductGeneralSection = ({ product }: Props) => {
|
||||||
const { onDelete, onStatusChange } = useEditProductActions(product.id)
|
const { onDelete, onStatusChange } = useEditProductActions(product.id)
|
||||||
const {
|
const {
|
||||||
state: infoState,
|
state: infoState,
|
||||||
@@ -174,4 +174,4 @@ const ProductSalesChannels = ({ product }: Props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default GeneralSection
|
export default ProductGeneralSection
|
||||||
+5
-5
@@ -1,14 +1,14 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import { ActionType } from "../../../../../components/molecules/actionables"
|
import useToggleState from "../../../hooks/use-toggle-state"
|
||||||
import Section from "../../../../../components/organisms/section"
|
import { ActionType } from "../../molecules/actionables"
|
||||||
import useToggleState from "../../../../../hooks/use-toggle-state"
|
import Section from "../../organisms/section"
|
||||||
import MediaModal from "./media-modal"
|
import MediaModal from "./media-modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
product: Product
|
product: Product
|
||||||
}
|
}
|
||||||
|
|
||||||
const MediaSection = ({ product }: Props) => {
|
const ProductMediaSection = ({ product }: Props) => {
|
||||||
const { state, close, toggle } = useToggleState()
|
const { state, close, toggle } = useToggleState()
|
||||||
|
|
||||||
const actions: ActionType[] = [
|
const actions: ActionType[] = [
|
||||||
@@ -46,4 +46,4 @@ const MediaSection = ({ product }: Props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MediaSection
|
export default ProductMediaSection
|
||||||
+9
-9
@@ -1,14 +1,14 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
import Button from "../../../../../components/fundamentals/button"
|
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
||||||
import Modal from "../../../../../components/molecules/modal"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import useNotification from "../../../../../hooks/use-notification"
|
import { FormImage } from "../../../types/shared"
|
||||||
import { FormImage } from "../../../../../types/shared"
|
import { prepareImages } from "../../../utils/images"
|
||||||
import { prepareImages } from "../../../../../utils/images"
|
import { nestedForm } from "../../../utils/nested-form"
|
||||||
import { nestedForm } from "../../../../../utils/nested-form"
|
import MediaForm, { MediaFormType } from "../../forms/product/media-form"
|
||||||
import MediaForm, { MediaFormType } from "../../../components/media-form"
|
import Button from "../../fundamentals/button"
|
||||||
import useEditProductActions from "../../hooks/use-edit-product-actions"
|
import Modal from "../../molecules/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
product: Product
|
product: Product
|
||||||
@@ -36,7 +36,7 @@ const MediaModal = ({ product, open, onClose }: Props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reset(getDefaultValues(product))
|
reset(getDefaultValues(product))
|
||||||
}, [product])
|
}, [product, reset])
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
reset(getDefaultValues(product))
|
reset(getDefaultValues(product))
|
||||||
+5
-5
@@ -1,15 +1,15 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import JSONView from "../../../../../components/molecules/json-view"
|
import JSONView from "../../molecules/json-view"
|
||||||
import Section from "../../../../../components/organisms/section"
|
import Section from "../section"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
product: Product
|
product: Product
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Temporary component, should be replaced with <RawJson /> but since the design is different we will use this to not break the existing design across admin. */
|
/** Temporary component, should be replaced with <RawJson /> but since the design is different we will use this to not break the existing design across admin. */
|
||||||
const RawSection = ({ product }: Props) => {
|
const ProductRawSection = ({ product }: Props) => {
|
||||||
return (
|
return (
|
||||||
<Section title="Raw Product">
|
<Section title={product.is_giftcard ? "Raw Gift Card" : "Raw Product"}>
|
||||||
<div className="pt-base">
|
<div className="pt-base">
|
||||||
<JSONView data={product} />
|
<JSONView data={product} />
|
||||||
</div>
|
</div>
|
||||||
@@ -17,4 +17,4 @@ const RawSection = ({ product }: Props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RawSection
|
export default ProductRawSection
|
||||||
+9
-9
@@ -1,19 +1,19 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import TwoStepDelete from "../../../../../components/atoms/two-step-delete"
|
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
||||||
import Button from "../../../../../components/fundamentals/button"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import Section from "../../../../../components/organisms/section"
|
import useToggleState from "../../../hooks/use-toggle-state"
|
||||||
import useNotification from "../../../../../hooks/use-notification"
|
import { getErrorMessage } from "../../../utils/error-messages"
|
||||||
import useToggleState from "../../../../../hooks/use-toggle-state"
|
import TwoStepDelete from "../../atoms/two-step-delete"
|
||||||
import { getErrorMessage } from "../../../../../utils/error-messages"
|
import Button from "../../fundamentals/button"
|
||||||
import useEditProductActions from "../../hooks/use-edit-product-actions"
|
import Section from "../../organisms/section"
|
||||||
import ThumbnailModal from "./thumbnail-modal"
|
import ThumbnailModal from "./thumbnail-modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
product: Product
|
product: Product
|
||||||
}
|
}
|
||||||
|
|
||||||
const TumbnailSection = ({ product }: Props) => {
|
const ProductThumbnailSection = ({ product }: Props) => {
|
||||||
const { onUpdate, updating } = useEditProductActions(product.id)
|
const { onUpdate, updating } = useEditProductActions(product.id)
|
||||||
const { state, toggle, close } = useToggleState()
|
const { state, toggle, close } = useToggleState()
|
||||||
|
|
||||||
@@ -78,4 +78,4 @@ const TumbnailSection = ({ product }: Props) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TumbnailSection
|
export default ProductThumbnailSection
|
||||||
+9
-9
@@ -1,16 +1,16 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
import { Product } from "@medusajs/medusa"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
import Button from "../../../../../components/fundamentals/button"
|
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
||||||
import Modal from "../../../../../components/molecules/modal"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import useNotification from "../../../../../hooks/use-notification"
|
import { FormImage } from "../../../types/shared"
|
||||||
import { FormImage } from "../../../../../types/shared"
|
import { prepareImages } from "../../../utils/images"
|
||||||
import { prepareImages } from "../../../../../utils/images"
|
import { nestedForm } from "../../../utils/nested-form"
|
||||||
import { nestedForm } from "../../../../../utils/nested-form"
|
|
||||||
import ThumbnailForm, {
|
import ThumbnailForm, {
|
||||||
ThumbnailFormType,
|
ThumbnailFormType,
|
||||||
} from "../../../components/thumbnail-form"
|
} from "../../forms/product/thumbnail-form"
|
||||||
import useEditProductActions from "../../hooks/use-edit-product-actions"
|
import Button from "../../fundamentals/button"
|
||||||
|
import Modal from "../../molecules/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
product: Product
|
product: Product
|
||||||
@@ -38,7 +38,7 @@ const ThumbnailModal = ({ product, open, onClose }: Props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reset(getDefaultValues(product))
|
reset(getDefaultValues(product))
|
||||||
}, [product])
|
}, [product, reset])
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
reset(getDefaultValues(product))
|
reset(getDefaultValues(product))
|
||||||
+13
-11
@@ -1,17 +1,17 @@
|
|||||||
import { AdminPostProductsProductVariantsReq, Product } from "@medusajs/medusa"
|
import { AdminPostProductsProductVariantsReq, Product } from "@medusajs/medusa"
|
||||||
|
import { useContext, useEffect } from "react"
|
||||||
import EditFlowVariantForm, {
|
import EditFlowVariantForm, {
|
||||||
EditFlowVariantFormType,
|
EditFlowVariantFormType,
|
||||||
} from "../../../components/variant-form/edit-flow-variant-form"
|
} from "../../forms/product/variant-form/edit-flow-variant-form"
|
||||||
import LayeredModal, {
|
import LayeredModal, {
|
||||||
LayeredModalContext,
|
LayeredModalContext,
|
||||||
} from "../../../../../components/molecules/modal/layered-modal"
|
} from "../../molecules/modal/layered-modal"
|
||||||
import { useContext, useEffect } from "react"
|
|
||||||
|
|
||||||
import Button from "../../../../../components/fundamentals/button"
|
|
||||||
import Modal from "../../../../../components/molecules/modal"
|
|
||||||
import useEditProductActions from "../../hooks/use-edit-product-actions"
|
|
||||||
import { useForm } from "react-hook-form"
|
|
||||||
import { useMedusa } from "medusa-react"
|
import { useMedusa } from "medusa-react"
|
||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
||||||
|
import Button from "../../fundamentals/button"
|
||||||
|
import Modal from "../../molecules/modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
@@ -32,7 +32,7 @@ const AddVariantModal = ({ open, onClose, product }: Props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reset(getDefaultValues(product))
|
reset(getDefaultValues(product))
|
||||||
}, [product])
|
}, [product, reset])
|
||||||
|
|
||||||
const resetAndClose = () => {
|
const resetAndClose = () => {
|
||||||
reset(getDefaultValues(product))
|
reset(getDefaultValues(product))
|
||||||
@@ -40,7 +40,7 @@ const AddVariantModal = ({ open, onClose, product }: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createStockLocationsForVariant = async (
|
const createStockLocationsForVariant = async (
|
||||||
productRes,
|
productRes: Product,
|
||||||
stock_locations: { stocked_quantity: number; location_id: string }[]
|
stock_locations: { stocked_quantity: number; location_id: string }[]
|
||||||
) => {
|
) => {
|
||||||
const { variants } = productRes
|
const { variants } = productRes
|
||||||
@@ -48,9 +48,11 @@ const AddVariantModal = ({ open, onClose, product }: Props) => {
|
|||||||
const pvMap = new Map(product.variants.map((v) => [v.id, true]))
|
const pvMap = new Map(product.variants.map((v) => [v.id, true]))
|
||||||
const addedVariant = variants.find((variant) => !pvMap.get(variant.id))
|
const addedVariant = variants.find((variant) => !pvMap.get(variant.id))
|
||||||
|
|
||||||
const inventory = await client.admin.variants.getInventory(addedVariant.id)
|
if (!addedVariant) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
console.log(inventory)
|
const inventory = await client.admin.variants.getInventory(addedVariant.id)
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
inventory.variant.inventory
|
inventory.variant.inventory
|
||||||
+7
-8
@@ -4,21 +4,20 @@ import {
|
|||||||
ProductVariant,
|
ProductVariant,
|
||||||
VariantInventory,
|
VariantInventory,
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import { useMedusa } from "medusa-react"
|
import { useAdminVariantsInventory, useMedusa } from "medusa-react"
|
||||||
import { useAdminVariantsInventory } from "medusa-react"
|
|
||||||
import { useContext } from "react"
|
import { useContext } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
|
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
||||||
|
import { removeNullish } from "../../../utils/remove-nullish"
|
||||||
import EditFlowVariantForm, {
|
import EditFlowVariantForm, {
|
||||||
EditFlowVariantFormType,
|
EditFlowVariantFormType,
|
||||||
} from "../../../components/variant-inventory-form/edit-flow-variant-form"
|
} from "../../forms/product/variant-form/edit-flow-variant-form"
|
||||||
|
import Button from "../../fundamentals/button"
|
||||||
|
import Modal from "../../molecules/modal"
|
||||||
import LayeredModal, {
|
import LayeredModal, {
|
||||||
LayeredModalContext,
|
LayeredModalContext,
|
||||||
} from "../../../../../components/molecules/modal/layered-modal"
|
} from "../../molecules/modal/layered-modal"
|
||||||
import Button from "../../../../../components/fundamentals/button"
|
|
||||||
import Modal from "../../../../../components/molecules/modal"
|
|
||||||
import { createUpdatePayload } from "./edit-variants-modal/edit-variant-screen"
|
import { createUpdatePayload } from "./edit-variants-modal/edit-variant-screen"
|
||||||
import useEditProductActions from "../../hooks/use-edit-product-actions"
|
|
||||||
import { removeNullish } from "../../../../../utils/remove-nullish"
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
+15
-11
@@ -1,20 +1,20 @@
|
|||||||
|
import { Product, ProductVariant } from "@medusajs/medusa"
|
||||||
import EditFlowVariantForm, {
|
import EditFlowVariantForm, {
|
||||||
EditFlowVariantFormType,
|
EditFlowVariantFormType,
|
||||||
} from "../../../components/variant-form/edit-flow-variant-form"
|
} from "../../forms/product/variant-form/edit-flow-variant-form"
|
||||||
import LayeredModal, {
|
import LayeredModal, {
|
||||||
LayeredModalContext,
|
LayeredModalContext,
|
||||||
} from "../../../../../components/molecules/modal/layered-modal"
|
} from "../../molecules/modal/layered-modal"
|
||||||
import { Product, ProductVariant } from "@medusajs/medusa"
|
|
||||||
|
|
||||||
import Button from "../../../../../components/fundamentals/button"
|
import { useMedusa } from "medusa-react"
|
||||||
import Modal from "../../../../../components/molecules/modal"
|
import { useContext } from "react"
|
||||||
import { countries } from "../../../../../utils/countries"
|
import { useForm } from "react-hook-form"
|
||||||
|
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
||||||
|
import { countries } from "../../../utils/countries"
|
||||||
|
import Button from "../../fundamentals/button"
|
||||||
|
import Modal from "../../molecules/modal"
|
||||||
import { createAddPayload } from "./add-variant-modal"
|
import { createAddPayload } from "./add-variant-modal"
|
||||||
import { createUpdatePayload } from "./edit-variants-modal/edit-variant-screen"
|
import { createUpdatePayload } from "./edit-variants-modal/edit-variant-screen"
|
||||||
import { useContext } from "react"
|
|
||||||
import useEditProductActions from "../../hooks/use-edit-product-actions"
|
|
||||||
import { useForm } from "react-hook-form"
|
|
||||||
import { useMedusa } from "medusa-react"
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
@@ -51,7 +51,7 @@ const EditVariantModal = ({
|
|||||||
const { client } = useMedusa()
|
const { client } = useMedusa()
|
||||||
|
|
||||||
const createStockLocationsForVariant = async (
|
const createStockLocationsForVariant = async (
|
||||||
productRes,
|
productRes: Product,
|
||||||
stock_locations: { stocked_quantity: number; location_id: string }[]
|
stock_locations: { stocked_quantity: number; location_id: string }[]
|
||||||
) => {
|
) => {
|
||||||
const { variants } = productRes
|
const { variants } = productRes
|
||||||
@@ -59,6 +59,10 @@ const EditVariantModal = ({
|
|||||||
const pvMap = new Map(product.variants.map((v) => [v.id, true]))
|
const pvMap = new Map(product.variants.map((v) => [v.id, true]))
|
||||||
const addedVariant = variants.find((variant) => !pvMap.get(variant.id))
|
const addedVariant = variants.find((variant) => !pvMap.get(variant.id))
|
||||||
|
|
||||||
|
if (!addedVariant) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const inventory = await client.admin.variants.getInventory(addedVariant.id)
|
const inventory = await client.admin.variants.getInventory(addedVariant.id)
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
+14
-12
@@ -3,18 +3,18 @@ import {
|
|||||||
Product,
|
Product,
|
||||||
ProductVariant,
|
ProductVariant,
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
|
import React, { useContext, useEffect, useMemo } from "react"
|
||||||
import EditFlowVariantForm, {
|
import EditFlowVariantForm, {
|
||||||
EditFlowVariantFormType,
|
EditFlowVariantFormType,
|
||||||
} from "../../../../components/variant-form/edit-flow-variant-form"
|
} from "../../../forms/product/variant-form/edit-flow-variant-form"
|
||||||
import React, { useContext, useEffect, useMemo } from "react"
|
|
||||||
|
|
||||||
import Button from "../../../../../../components/fundamentals/button"
|
|
||||||
import { LayeredModalContext } from "../../../../../../components/molecules/modal/layered-modal"
|
|
||||||
import Modal from "../../../../../../components/molecules/modal"
|
|
||||||
import { getEditVariantDefaultValues } from "../edit-variant-modal"
|
|
||||||
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
|
||||||
import { useEditVariantsModal } from "./use-edit-variants-modal"
|
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
|
import useEditProductActions from "../../../../hooks/use-edit-product-actions"
|
||||||
|
import Button from "../../../fundamentals/button"
|
||||||
|
import Modal from "../../../molecules/modal"
|
||||||
|
import { LayeredModalContext } from "../../../molecules/modal/layered-modal"
|
||||||
|
import { getEditVariantDefaultValues } from "../edit-variant-modal"
|
||||||
|
import { useEditVariantsModal } from "./use-edit-variants-modal"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
variant: ProductVariant
|
variant: ProductVariant
|
||||||
@@ -27,23 +27,25 @@ const EditVariantScreen = ({ variant, product }: Props) => {
|
|||||||
defaultValues: getEditVariantDefaultValues(variant, product),
|
defaultValues: getEditVariantDefaultValues(variant, product),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const { reset: formReset } = form
|
||||||
|
|
||||||
const { pop, reset } = useContext(LayeredModalContext)
|
const { pop, reset } = useContext(LayeredModalContext)
|
||||||
const { updatingVariant, onUpdateVariant } = useEditProductActions(product.id)
|
const { updatingVariant, onUpdateVariant } = useEditProductActions(product.id)
|
||||||
|
|
||||||
const popAndReset = () => {
|
const popAndReset = () => {
|
||||||
form.reset(getEditVariantDefaultValues(variant, product))
|
formReset(getEditVariantDefaultValues(variant, product))
|
||||||
pop()
|
pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeAndReset = () => {
|
const closeAndReset = () => {
|
||||||
form.reset(getEditVariantDefaultValues(variant, product))
|
formReset(getEditVariantDefaultValues(variant, product))
|
||||||
reset()
|
reset()
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
form.reset(getEditVariantDefaultValues(variant, product))
|
formReset(getEditVariantDefaultValues(variant, product))
|
||||||
}, [variant, product])
|
}, [variant, product, formReset])
|
||||||
|
|
||||||
const onSubmitAndBack = form.handleSubmit((data) => {
|
const onSubmitAndBack = form.handleSubmit((data) => {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
+5
-4
@@ -6,12 +6,12 @@ import {
|
|||||||
useFieldArray,
|
useFieldArray,
|
||||||
useForm,
|
useForm,
|
||||||
} from "react-hook-form"
|
} from "react-hook-form"
|
||||||
import Button from "../../../../../../components/fundamentals/button"
|
import useEditProductActions from "../../../../hooks/use-edit-product-actions"
|
||||||
import Modal from "../../../../../../components/molecules/modal"
|
import Button from "../../../fundamentals/button"
|
||||||
|
import Modal from "../../../molecules/modal"
|
||||||
import LayeredModal, {
|
import LayeredModal, {
|
||||||
LayeredModalContext,
|
LayeredModalContext,
|
||||||
} from "../../../../../../components/molecules/modal/layered-modal"
|
} from "../../../molecules/modal/layered-modal"
|
||||||
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
|
||||||
import { EditVariantsModalContext } from "./use-edit-variants-modal"
|
import { EditVariantsModalContext } from "./use-edit-variants-modal"
|
||||||
import { VariantCard } from "./variant-card"
|
import { VariantCard } from "./variant-card"
|
||||||
|
|
||||||
@@ -56,6 +56,7 @@ const EditVariantsModal = ({ open, onClose, product }: Props) => {
|
|||||||
|
|
||||||
const moveCard = useCallback((dragIndex: number, hoverIndex: number) => {
|
const moveCard = useCallback((dragIndex: number, hoverIndex: number) => {
|
||||||
move(dragIndex, hoverIndex)
|
move(dragIndex, hoverIndex)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const renderCard = useCallback(
|
const renderCard = useCallback(
|
||||||
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
import React, { useContext } from "react"
|
import React, { useContext } from "react"
|
||||||
|
|
||||||
type EditVariantsModalContext = {
|
type EditVariantsModalContextType = {
|
||||||
onClose: () => void
|
onClose: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EditVariantsModalContext =
|
export const EditVariantsModalContext =
|
||||||
React.createContext<EditVariantsModalContext | null>(null)
|
React.createContext<EditVariantsModalContextType | null>(null)
|
||||||
|
|
||||||
export const useEditVariantsModal = () => {
|
export const useEditVariantsModal = () => {
|
||||||
const context = useContext(EditVariantsModalContext)
|
const context = useContext(EditVariantsModalContext)
|
||||||
+9
-11
@@ -5,17 +5,15 @@ import { useContext, useMemo, useRef } from "react"
|
|||||||
import { useDrag, useDrop } from "react-dnd"
|
import { useDrag, useDrop } from "react-dnd"
|
||||||
import { useFormContext } from "react-hook-form"
|
import { useFormContext } from "react-hook-form"
|
||||||
import { VariantItem } from "."
|
import { VariantItem } from "."
|
||||||
import Button from "../../../../../../components/fundamentals/button"
|
import { DragItem } from "../../../../types/shared"
|
||||||
import EditIcon from "../../../../../../components/fundamentals/icons/edit-icon"
|
import FormValidator from "../../../../utils/form-validator"
|
||||||
import GripIcon from "../../../../../../components/fundamentals/icons/grip-icon"
|
import Button from "../../../fundamentals/button"
|
||||||
import MoreHorizontalIcon from "../../../../../../components/fundamentals/icons/more-horizontal-icon"
|
import EditIcon from "../../../fundamentals/icons/edit-icon"
|
||||||
import Actionables, {
|
import GripIcon from "../../../fundamentals/icons/grip-icon"
|
||||||
ActionType,
|
import MoreHorizontalIcon from "../../../fundamentals/icons/more-horizontal-icon"
|
||||||
} from "../../../../../../components/molecules/actionables"
|
import Actionables, { ActionType } from "../../../molecules/actionables"
|
||||||
import InputField from "../../../../../../components/molecules/input"
|
import InputField from "../../../molecules/input"
|
||||||
import { LayeredModalContext } from "../../../../../../components/molecules/modal/layered-modal"
|
import { LayeredModalContext } from "../../../molecules/modal/layered-modal"
|
||||||
import { DragItem } from "../../../../../../types/shared"
|
|
||||||
import FormValidator from "../../../../../../utils/form-validator"
|
|
||||||
import { useEditVariantScreen } from "./edit-variant-screen"
|
import { useEditVariantScreen } from "./edit-variant-screen"
|
||||||
|
|
||||||
const ItemTypes = {
|
const ItemTypes = {
|
||||||
+11
-14
@@ -1,29 +1,26 @@
|
|||||||
import OptionsProvider, { useOptionsContext } from "./options-provider"
|
|
||||||
import { Product, ProductVariant } from "@medusajs/medusa"
|
import { Product, ProductVariant } from "@medusajs/medusa"
|
||||||
|
import OptionsProvider, { useOptionsContext } from "./options-provider"
|
||||||
|
|
||||||
import { ActionType } from "../../../../../components/molecules/actionables"
|
import { useState } from "react"
|
||||||
|
import useEditProductActions from "../../../hooks/use-edit-product-actions"
|
||||||
|
import useToggleState from "../../../hooks/use-toggle-state"
|
||||||
|
import EditIcon from "../../fundamentals/icons/edit-icon"
|
||||||
|
import GearIcon from "../../fundamentals/icons/gear-icon"
|
||||||
|
import PlusIcon from "../../fundamentals/icons/plus-icon"
|
||||||
|
import { ActionType } from "../../molecules/actionables"
|
||||||
|
import Section from "../../organisms/section"
|
||||||
import AddVariantModal from "./add-variant-modal"
|
import AddVariantModal from "./add-variant-modal"
|
||||||
import EditIcon from "../../../../../components/fundamentals/icons/edit-icon"
|
|
||||||
import EditVariantInventoryModal from "./edit-variant-inventory-modal"
|
import EditVariantInventoryModal from "./edit-variant-inventory-modal"
|
||||||
import EditVariantModal from "./edit-variant-modal"
|
import EditVariantModal from "./edit-variant-modal"
|
||||||
import EditVariantsModal from "./edit-variants-modal"
|
import EditVariantsModal from "./edit-variants-modal"
|
||||||
import GearIcon from "../../../../../components/fundamentals/icons/gear-icon"
|
|
||||||
import OptionsModal from "./options-modal"
|
import OptionsModal from "./options-modal"
|
||||||
import PlusIcon from "../../../../../components/fundamentals/icons/plus-icon"
|
|
||||||
import Section from "../../../../../components/organisms/section"
|
|
||||||
import VariantsTable from "./table"
|
import VariantsTable from "./table"
|
||||||
import useEditProductActions from "../../hooks/use-edit-product-actions"
|
|
||||||
import { useFeatureFlag } from "../../../../../providers/feature-flag-provider"
|
|
||||||
import { useState } from "react"
|
|
||||||
import useToggleState from "../../../../../hooks/use-toggle-state"
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
product: Product
|
product: Product
|
||||||
}
|
}
|
||||||
|
|
||||||
const VariantsSection = ({ product }: Props) => {
|
const ProductVariantsSection = ({ product }: Props) => {
|
||||||
const { isFeatureEnabled } = useFeatureFlag()
|
|
||||||
|
|
||||||
const [variantToEdit, setVariantToEdit] = useState<
|
const [variantToEdit, setVariantToEdit] = useState<
|
||||||
| {
|
| {
|
||||||
base: ProductVariant
|
base: ProductVariant
|
||||||
@@ -202,4 +199,4 @@ const ProductOptions = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VariantsSection
|
export default ProductVariantsSection
|
||||||
+7
-7
@@ -6,13 +6,13 @@ import {
|
|||||||
} from "medusa-react"
|
} from "medusa-react"
|
||||||
import { useEffect, useMemo } from "react"
|
import { useEffect, useMemo } from "react"
|
||||||
import { useFieldArray, useForm } from "react-hook-form"
|
import { useFieldArray, useForm } from "react-hook-form"
|
||||||
import Button from "../../../../../components/fundamentals/button"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
import PlusIcon from "../../../../../components/fundamentals/icons/plus-icon"
|
import FormValidator from "../../../utils/form-validator"
|
||||||
import TrashIcon from "../../../../../components/fundamentals/icons/trash-icon"
|
import Button from "../../fundamentals/button"
|
||||||
import InputField from "../../../../../components/molecules/input"
|
import PlusIcon from "../../fundamentals/icons/plus-icon"
|
||||||
import Modal from "../../../../../components/molecules/modal"
|
import TrashIcon from "../../fundamentals/icons/trash-icon"
|
||||||
import useNotification from "../../../../../hooks/use-notification"
|
import InputField from "../../molecules/input"
|
||||||
import FormValidator from "../../../../../utils/form-validator"
|
import Modal from "../../molecules/modal"
|
||||||
import { useOptionsContext } from "./options-provider"
|
import { useOptionsContext } from "./options-provider"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
+4
-4
@@ -8,7 +8,7 @@ type OptionsContext = {
|
|||||||
refetch: () => void
|
refetch: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const OptionsContext = createContext<OptionsContext | null>(null)
|
const OptionsContextType = createContext<OptionsContext | null>(null)
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
product: Product
|
product: Product
|
||||||
@@ -30,14 +30,14 @@ const OptionsProvider = ({ product, children }: Props) => {
|
|||||||
}, [products, status])
|
}, [products, status])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OptionsContext.Provider value={{ options, status, refetch }}>
|
<OptionsContextType.Provider value={{ options, status, refetch }}>
|
||||||
{children}
|
{children}
|
||||||
</OptionsContext.Provider>
|
</OptionsContextType.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useOptionsContext = () => {
|
export const useOptionsContext = () => {
|
||||||
const context = useContext(OptionsContext)
|
const context = useContext(OptionsContextType)
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error("useOptionsContext must be used within a OptionsProvider")
|
throw new Error("useOptionsContext must be used within a OptionsProvider")
|
||||||
}
|
}
|
||||||
+7
-7
@@ -1,14 +1,14 @@
|
|||||||
import { Column, useTable } from "react-table"
|
import { Column, useTable } from "react-table"
|
||||||
|
|
||||||
import Actionables from "../../../../../components/molecules/actionables"
|
|
||||||
import BuildingsIcon from "../../../../../components/fundamentals/icons/buildings-icon"
|
|
||||||
import DuplicateIcon from "../../../../../components/fundamentals/icons/duplicate-icon"
|
|
||||||
import EditIcon from "../../../../../components/fundamentals/icons/edit-icon"
|
|
||||||
import { ProductVariant } from "@medusajs/medusa"
|
import { ProductVariant } from "@medusajs/medusa"
|
||||||
import Table from "../../../../../components/molecules/table"
|
|
||||||
import TrashIcon from "../../../../../components/fundamentals/icons/trash-icon"
|
|
||||||
import { useFeatureFlag } from "../../../../../providers/feature-flag-provider"
|
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
|
import { useFeatureFlag } from "../../../providers/feature-flag-provider"
|
||||||
|
import BuildingsIcon from "../../fundamentals/icons/buildings-icon"
|
||||||
|
import DuplicateIcon from "../../fundamentals/icons/duplicate-icon"
|
||||||
|
import EditIcon from "../../fundamentals/icons/edit-icon"
|
||||||
|
import TrashIcon from "../../fundamentals/icons/trash-icon"
|
||||||
|
import Actionables from "../../molecules/actionables"
|
||||||
|
import Table from "../../molecules/table"
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
variants: ProductVariant[]
|
variants: ProductVariant[]
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
import { useAdminDeleteVariant } from "medusa-react"
|
|
||||||
import React, { useState } from "react"
|
|
||||||
import EditIcon from "../../../components/fundamentals/icons/edit-icon"
|
|
||||||
import TrashIcon from "../../../components/fundamentals/icons/trash-icon"
|
|
||||||
import Table from "../../../components/molecules/table"
|
|
||||||
import DeletePrompt from "../../../components/organisms/delete-prompt"
|
|
||||||
import { stringDisplayPrice } from "../../../utils/prices"
|
|
||||||
|
|
||||||
type DenominationTableProps = {
|
|
||||||
giftCardId: string
|
|
||||||
denominations: any[]
|
|
||||||
defaultCurrency: string
|
|
||||||
setEditDenom: (denom) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const DenominationTable: React.FC<DenominationTableProps> = ({
|
|
||||||
giftCardId,
|
|
||||||
denominations,
|
|
||||||
defaultCurrency,
|
|
||||||
setEditDenom,
|
|
||||||
}) => {
|
|
||||||
const [selectedDenom, setSelectedDenom] = useState<string | null>(null)
|
|
||||||
|
|
||||||
const deleteDenomination = useAdminDeleteVariant(giftCardId)
|
|
||||||
|
|
||||||
const getDenominationPrices = (denomination) => {
|
|
||||||
const sortHelper = (p1, p2) => {
|
|
||||||
const curr1 = p1.currency_code
|
|
||||||
const curr2 = p2.currency_code
|
|
||||||
|
|
||||||
if (curr1 < curr2) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
if (curr1 > curr2) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return denomination.prices
|
|
||||||
.filter((p) => p.currency_code !== defaultCurrency) // without default
|
|
||||||
.sort(sortHelper) // sort by currency code
|
|
||||||
.map((p) =>
|
|
||||||
stringDisplayPrice({ currencyCode: p.currency_code, amount: p.amount })
|
|
||||||
) // get formatted price
|
|
||||||
.join(", ") // concatenate to single comma separated string
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDenominationRow = (denomination, index) => {
|
|
||||||
let defaultPrice = denomination.prices.find(
|
|
||||||
(p) => p.currency_code === defaultCurrency
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!defaultPrice) {
|
|
||||||
defaultPrice = denomination.prices[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Table.Row
|
|
||||||
key={`denomination-${index}`}
|
|
||||||
color={"inherit"}
|
|
||||||
actions={[
|
|
||||||
{
|
|
||||||
label: "Edit denomination",
|
|
||||||
onClick: () => setEditDenom(denomination),
|
|
||||||
icon: <EditIcon size={20} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Delete denomination",
|
|
||||||
variant: "danger",
|
|
||||||
onClick: () => setSelectedDenom(denomination.id),
|
|
||||||
icon: <TrashIcon size={20} />,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Table.Cell>
|
|
||||||
{stringDisplayPrice({
|
|
||||||
currencyCode: defaultPrice.currency_code,
|
|
||||||
amount: defaultPrice.amount,
|
|
||||||
})}
|
|
||||||
</Table.Cell>
|
|
||||||
<Table.Cell>{getDenominationPrices(denomination)}</Table.Cell>
|
|
||||||
<Table.Cell></Table.Cell>
|
|
||||||
</Table.Row>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDeleteDenomination = async () => {
|
|
||||||
deleteDenomination.mutateAsync(selectedDenom!)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="h-full w-full overflow-y-auto">
|
|
||||||
<Table>
|
|
||||||
<Table.Head>
|
|
||||||
<Table.HeadRow>
|
|
||||||
<Table.HeadCell>Default Value</Table.HeadCell>
|
|
||||||
<Table.HeadCell>Other Values</Table.HeadCell>
|
|
||||||
</Table.HeadRow>
|
|
||||||
</Table.Head>
|
|
||||||
<Table.Body className="text-grey-90">
|
|
||||||
{denominations?.map((d, idx) => getDenominationRow(d, idx))}
|
|
||||||
</Table.Body>
|
|
||||||
</Table>
|
|
||||||
{selectedDenom && (
|
|
||||||
<DeletePrompt
|
|
||||||
handleClose={() => setSelectedDenom(null)}
|
|
||||||
text="Are you sure you want to delete this denomination from your Medusa Store?"
|
|
||||||
heading="Delete denomination"
|
|
||||||
onDelete={() => handleDeleteDenomination()}
|
|
||||||
successText="Successfully deleted denomination"
|
|
||||||
confirmText="Yes, delete"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default DenominationTable
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import { useAdminUpdateProduct } from "medusa-react"
|
|
||||||
import React, { useEffect, useState } from "react"
|
|
||||||
import { FormProvider, useForm, useFormContext } from "react-hook-form"
|
|
||||||
import useNotification from "../../../../hooks/use-notification"
|
|
||||||
import { handleFormError } from "../../../../utils/handle-form-error"
|
|
||||||
import { trimValues } from "../../../../utils/trim-values"
|
|
||||||
import { ManageGiftCardFormData } from "../utils/types"
|
|
||||||
import { formValuesToUpdateGiftCardMapper } from "./mappers"
|
|
||||||
|
|
||||||
type GiftCardFormProviderProps = {
|
|
||||||
giftCardId: string
|
|
||||||
giftCard: ManageGiftCardFormData
|
|
||||||
children: React.ReactNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GiftCardFormProvider = ({
|
|
||||||
giftCardId,
|
|
||||||
giftCard,
|
|
||||||
children,
|
|
||||||
}: GiftCardFormProviderProps) => {
|
|
||||||
const [imageDirtyState, setImageDirtyState] = useState(false)
|
|
||||||
|
|
||||||
const methods = useForm<ManageGiftCardFormData>()
|
|
||||||
|
|
||||||
const resetForm = () => {
|
|
||||||
methods.reset({
|
|
||||||
handle: giftCard.handle || undefined,
|
|
||||||
description: giftCard.description || undefined,
|
|
||||||
subtitle: giftCard.subtitle || undefined,
|
|
||||||
tags: giftCard.tags || [],
|
|
||||||
title: giftCard.title || undefined,
|
|
||||||
type: giftCard.type || undefined,
|
|
||||||
thumbnail: giftCard.thumbnail || 0,
|
|
||||||
images: giftCard.images || [],
|
|
||||||
})
|
|
||||||
setImageDirtyState(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
resetForm()
|
|
||||||
}, [giftCard])
|
|
||||||
|
|
||||||
const { mutate: update } = useAdminUpdateProduct(giftCardId)
|
|
||||||
const notification = useNotification()
|
|
||||||
|
|
||||||
const onUpdate = methods.handleSubmit(async (values) => {
|
|
||||||
const cleanedValues = trimValues(values)
|
|
||||||
|
|
||||||
const payload = await formValuesToUpdateGiftCardMapper(cleanedValues)
|
|
||||||
|
|
||||||
update(payload, {
|
|
||||||
onSuccess: () => {
|
|
||||||
notification("Success", "Product updated successfully", "success")
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}, handleFormError)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormProvider {...methods}>
|
|
||||||
<GiftCardFormContext.Provider
|
|
||||||
value={{
|
|
||||||
onUpdate,
|
|
||||||
resetForm,
|
|
||||||
setImageDirtyState,
|
|
||||||
additionalDirtyState: {
|
|
||||||
images: imageDirtyState,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<form>{children}</form>
|
|
||||||
</GiftCardFormContext.Provider>
|
|
||||||
</FormProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const GiftCardFormContext = React.createContext<{
|
|
||||||
onUpdate: (
|
|
||||||
e?: React.BaseSyntheticEvent<object, any, any> | undefined
|
|
||||||
) => Promise<void>
|
|
||||||
resetForm: () => void
|
|
||||||
setImageDirtyState: (value: boolean) => void
|
|
||||||
additionalDirtyState: Record<string, boolean>
|
|
||||||
} | null>(null)
|
|
||||||
|
|
||||||
export const useGiftCardForm = () => {
|
|
||||||
const context = React.useContext(GiftCardFormContext)
|
|
||||||
const form = useFormContext<ManageGiftCardFormData>()
|
|
||||||
if (!context) {
|
|
||||||
throw new Error("useGiftCardForm must be a child of GiftCardFormContext")
|
|
||||||
}
|
|
||||||
return { form, ...context }
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import { AdminPostProductsProductReq, Product } from "@medusajs/medusa"
|
|
||||||
import { prepareImages } from "../../../../utils/images"
|
|
||||||
import { ManageGiftCardFormData } from "../utils/types"
|
|
||||||
|
|
||||||
export const formValuesToUpdateGiftCardMapper = async (
|
|
||||||
values: ManageGiftCardFormData
|
|
||||||
): Promise<AdminPostProductsProductReq> => {
|
|
||||||
const imagePayload = {} as Pick<
|
|
||||||
AdminPostProductsProductReq,
|
|
||||||
"images" | "thumbnail"
|
|
||||||
>
|
|
||||||
|
|
||||||
if (values.images?.length) {
|
|
||||||
const images = await prepareImages(values.images)
|
|
||||||
imagePayload.images = images.map((img) => img.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (values.thumbnail) {
|
|
||||||
imagePayload.thumbnail = imagePayload.images?.length
|
|
||||||
? imagePayload.images[values.thumbnail]
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: values.title,
|
|
||||||
subtitle: values.subtitle ?? undefined,
|
|
||||||
description: values.description ?? undefined,
|
|
||||||
handle: values.handle ?? undefined,
|
|
||||||
type: values.type
|
|
||||||
? { id: values.type?.value, value: values.type.label }
|
|
||||||
: undefined,
|
|
||||||
tags: values.tags?.map((tag) => ({ value: tag })) ?? [],
|
|
||||||
// @ts-ignore
|
|
||||||
sales_channels: undefined,
|
|
||||||
...imagePayload,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const giftCardToFormValuesMapper = (
|
|
||||||
giftCard: Product
|
|
||||||
): ManageGiftCardFormData => {
|
|
||||||
let thumbnail = giftCard.images?.length
|
|
||||||
? giftCard.images.findIndex((img) => img.url === giftCard.thumbnail)
|
|
||||||
: 0
|
|
||||||
thumbnail = thumbnail === -1 ? 0 : thumbnail
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: giftCard.title,
|
|
||||||
subtitle: giftCard.subtitle,
|
|
||||||
description: giftCard.description,
|
|
||||||
handle: giftCard.handle || undefined,
|
|
||||||
type: giftCard.type
|
|
||||||
? { label: giftCard.type.value, value: giftCard.type.id }
|
|
||||||
: null,
|
|
||||||
tags: giftCard.tags.map((t) => t.value),
|
|
||||||
images: giftCard.images.map((img) => ({ url: img.url })),
|
|
||||||
thumbnail,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +1,20 @@
|
|||||||
|
import { Product } from "@medusajs/medusa"
|
||||||
import { useAdminProducts } from "medusa-react"
|
import { useAdminProducts } from "medusa-react"
|
||||||
import { useEffect, useState } from "react"
|
import { useNavigate } from "react-router-dom"
|
||||||
import toast from "react-hot-toast"
|
import BackButton from "../../../components/atoms/back-button"
|
||||||
import Spinner from "../../../components/atoms/spinner"
|
import Spinner from "../../../components/atoms/spinner"
|
||||||
import Toaster from "../../../components/declarative-toaster"
|
import GiftCardDenominationsSection from "../../../components/organisms/gift-card-denominations-section"
|
||||||
import FormToasterContainer from "../../../components/molecules/form-toaster"
|
import ProductAttributesSection from "../../../components/organisms/product-attributes-section"
|
||||||
import { checkForDirtyState } from "../../../utils/form-helpers"
|
import ProductGeneralSection from "../../../components/organisms/product-general-section"
|
||||||
import {
|
import ProductMediaSection from "../../../components/organisms/product-media-section"
|
||||||
GiftCardFormProvider,
|
import ProductRawSection from "../../../components/organisms/product-raw-section"
|
||||||
useGiftCardForm,
|
import ProductThumbnailSection from "../../../components/organisms/product-thumbnail-section"
|
||||||
} from "./form/gift-card-form-context"
|
import { getErrorStatus } from "../../../utils/get-error-status"
|
||||||
import { giftCardToFormValuesMapper } from "./form/mappers"
|
|
||||||
import Denominations from "./sections/denominations"
|
|
||||||
import Images from "./sections/images"
|
|
||||||
import Information from "./sections/information"
|
|
||||||
|
|
||||||
const ManageGiftCard = () => {
|
const Manage = () => {
|
||||||
const { products } = useAdminProducts(
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
const { products, error } = useAdminProducts(
|
||||||
{
|
{
|
||||||
is_giftcard: true,
|
is_giftcard: true,
|
||||||
},
|
},
|
||||||
@@ -24,7 +23,7 @@ const ManageGiftCard = () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const giftCard = products?.[0]
|
const giftCard = products?.[0] as Product | undefined
|
||||||
|
|
||||||
if (!giftCard) {
|
if (!giftCard) {
|
||||||
return (
|
return (
|
||||||
@@ -34,72 +33,42 @@ const ManageGiftCard = () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
const errorStatus = getErrorStatus(error)
|
||||||
|
|
||||||
|
if (errorStatus) {
|
||||||
|
// If the product is not found, redirect to the 404 page
|
||||||
|
if (errorStatus.status === 404) {
|
||||||
|
navigate("/404")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let the error boundary handle the error
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GiftCardFormProvider
|
<div className="pb-5xlarge">
|
||||||
giftCard={giftCardToFormValuesMapper(giftCard)}
|
<BackButton
|
||||||
giftCardId={giftCard.id}
|
path="/a/gift-cards"
|
||||||
>
|
label="Back to Gift Cards"
|
||||||
<div className="gap-y-large pb-xlarge flex flex-col">
|
className="mb-xsmall"
|
||||||
<Information giftCard={giftCard} />
|
/>
|
||||||
<Denominations giftCard={giftCard} />
|
<div className="gap-x-base grid grid-cols-12">
|
||||||
<Images />
|
<div className="gap-y-xsmall col-span-8 flex flex-col">
|
||||||
|
<ProductGeneralSection product={giftCard} />
|
||||||
|
<GiftCardDenominationsSection giftCard={giftCard} />
|
||||||
|
<ProductAttributesSection product={giftCard} />
|
||||||
|
<ProductRawSection product={giftCard} />
|
||||||
|
</div>
|
||||||
|
<div className="gap-y-xsmall col-span-4 flex flex-col">
|
||||||
|
<ProductThumbnailSection product={giftCard} />
|
||||||
|
<ProductMediaSection product={giftCard} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<UpdateNotification />
|
</div>
|
||||||
</GiftCardFormProvider>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const TOAST_ID = "edit-gc-dirty"
|
export default Manage
|
||||||
|
|
||||||
const UpdateNotification = ({ isLoading = false }) => {
|
|
||||||
const {
|
|
||||||
form: { formState },
|
|
||||||
onUpdate,
|
|
||||||
resetForm,
|
|
||||||
additionalDirtyState,
|
|
||||||
} = useGiftCardForm()
|
|
||||||
const [visible, setVisible] = useState(false)
|
|
||||||
const [blocking, setBlocking] = useState(true)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const timeout = setTimeout(setBlocking, 300, false)
|
|
||||||
return () => clearTimeout(timeout)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const isDirty = checkForDirtyState(
|
|
||||||
formState.dirtyFields,
|
|
||||||
additionalDirtyState
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!blocking) {
|
|
||||||
setVisible(isDirty)
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
toast.dismiss(TOAST_ID)
|
|
||||||
}
|
|
||||||
}, [isDirty])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Toaster
|
|
||||||
visible={visible}
|
|
||||||
duration={Infinity}
|
|
||||||
id={TOAST_ID}
|
|
||||||
position="bottom-right"
|
|
||||||
>
|
|
||||||
<FormToasterContainer isLoading={isLoading}>
|
|
||||||
<FormToasterContainer.Actions>
|
|
||||||
<FormToasterContainer.ActionButton onClick={onUpdate}>
|
|
||||||
Save
|
|
||||||
</FormToasterContainer.ActionButton>
|
|
||||||
<FormToasterContainer.DiscardButton onClick={resetForm}>
|
|
||||||
Discard
|
|
||||||
</FormToasterContainer.DiscardButton>
|
|
||||||
</FormToasterContainer.Actions>
|
|
||||||
</FormToasterContainer>
|
|
||||||
</Toaster>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ManageGiftCard
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
import { Product, ProductVariant } from "@medusajs/medusa"
|
|
||||||
import {
|
|
||||||
useAdminDeleteVariant,
|
|
||||||
useAdminStore,
|
|
||||||
useAdminUpdateVariant,
|
|
||||||
} from "medusa-react"
|
|
||||||
import React, { useState } from "react"
|
|
||||||
import PlusIcon from "../../../../components/fundamentals/icons/plus-icon"
|
|
||||||
import AddDenominationModal from "../../../../components/organisms/add-denomination-modal"
|
|
||||||
import BodyCard from "../../../../components/organisms/body-card"
|
|
||||||
import EditDenominationsModal from "../../../../components/organisms/edit-denominations-modal"
|
|
||||||
import useNotification from "../../../../hooks/use-notification"
|
|
||||||
import { getErrorMessage } from "../../../../utils/error-messages"
|
|
||||||
import DenominationTable from "../denomination-table"
|
|
||||||
|
|
||||||
type DenominationsProps = {
|
|
||||||
giftCard: Product
|
|
||||||
}
|
|
||||||
|
|
||||||
const Denominations: React.FC<DenominationsProps> = ({ giftCard }) => {
|
|
||||||
const [editDenom, setEditDenom] = useState<Omit<
|
|
||||||
ProductVariant,
|
|
||||||
"beforeInsert"
|
|
||||||
> | null>(null)
|
|
||||||
const [addDenom, setAddDenom] = useState(false)
|
|
||||||
|
|
||||||
const { store } = useAdminStore()
|
|
||||||
const updateGiftCardVariant = useAdminUpdateVariant(giftCard.id)
|
|
||||||
const deleteGiftCardVariant = useAdminDeleteVariant(giftCard.id)
|
|
||||||
const notification = useNotification()
|
|
||||||
|
|
||||||
const currencyCodes =
|
|
||||||
store?.currencies
|
|
||||||
.filter((currency) => currency.code !== store.default_currency_code)
|
|
||||||
.map((currency) => currency.code) || []
|
|
||||||
|
|
||||||
const submitDenomations = (denoms) => {
|
|
||||||
if (!denoms.length) {
|
|
||||||
// if a update would result in the variant having 0 prices, then we delete it instead
|
|
||||||
deleteGiftCardVariant.mutate(editDenom!.id, {
|
|
||||||
onSuccess: () => {
|
|
||||||
notification(
|
|
||||||
"Success",
|
|
||||||
"Successfully updated denominations",
|
|
||||||
"success"
|
|
||||||
)
|
|
||||||
setEditDenom(null)
|
|
||||||
},
|
|
||||||
onError: (err) => notification("Error", getErrorMessage(err), "error"),
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
updateGiftCardVariant.mutate(
|
|
||||||
{
|
|
||||||
variant_id: editDenom!.id,
|
|
||||||
prices: denoms.map(({ amount, currency_code }) => ({
|
|
||||||
amount,
|
|
||||||
currency_code,
|
|
||||||
})),
|
|
||||||
title: editDenom!.title,
|
|
||||||
inventory_quantity: editDenom!.inventory_quantity,
|
|
||||||
options: editDenom!.options.map((opt) => ({
|
|
||||||
option_id: opt.option_id,
|
|
||||||
value: opt.value,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
notification(
|
|
||||||
"Success",
|
|
||||||
"Successfully updated denominations",
|
|
||||||
"success"
|
|
||||||
)
|
|
||||||
setEditDenom(null)
|
|
||||||
},
|
|
||||||
onError: (err) => notification("Error", getErrorMessage(err), "error"),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<BodyCard
|
|
||||||
title="Denominations"
|
|
||||||
subtitle="Manage your denominations"
|
|
||||||
className={"h-auto w-full"}
|
|
||||||
actionables={[
|
|
||||||
{
|
|
||||||
label: "Add Denomination",
|
|
||||||
onClick: () => setAddDenom(true),
|
|
||||||
icon: <PlusIcon size={20} />,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<DenominationTable
|
|
||||||
giftCardId={giftCard.id}
|
|
||||||
denominations={giftCard.variants || []}
|
|
||||||
defaultCurrency={store?.default_currency_code || ""}
|
|
||||||
setEditDenom={setEditDenom}
|
|
||||||
/>
|
|
||||||
</BodyCard>
|
|
||||||
{editDenom && (
|
|
||||||
<EditDenominationsModal
|
|
||||||
currencyCodes={store?.currencies.map((c) => c.code)}
|
|
||||||
onSubmit={submitDenomations}
|
|
||||||
defaultDenominations={editDenom.prices.map((p) => ({
|
|
||||||
amount: p.amount,
|
|
||||||
currency_code: p.currency_code,
|
|
||||||
id: p.id,
|
|
||||||
}))}
|
|
||||||
handleClose={() => setEditDenom(null)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{addDenom && (
|
|
||||||
<AddDenominationModal
|
|
||||||
giftCard={giftCard}
|
|
||||||
handleClose={() => setAddDenom(false)}
|
|
||||||
storeCurrency={store?.default_currency_code!}
|
|
||||||
currencyCodes={currencyCodes}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Denominations
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import { Controller, useFieldArray } from "react-hook-form"
|
|
||||||
import FileUploadField from "../../../../components/atoms/file-upload-field"
|
|
||||||
import BodyCard from "../../../../components/organisms/body-card"
|
|
||||||
import RadioGroup from "../../../../components/organisms/radio-group"
|
|
||||||
import ImageTable, {
|
|
||||||
ImageTableDataType,
|
|
||||||
} from "../../../../components/templates/image-table"
|
|
||||||
import { nestedForm } from "../../../../utils/nested-form"
|
|
||||||
import { useGiftCardForm } from "../form/gift-card-form-context"
|
|
||||||
|
|
||||||
const Images = () => {
|
|
||||||
const { form, setImageDirtyState } = useGiftCardForm()
|
|
||||||
|
|
||||||
const { fields, append, remove } = useFieldArray({
|
|
||||||
control: form.control,
|
|
||||||
name: "images",
|
|
||||||
})
|
|
||||||
|
|
||||||
const handleRemove = (index: number) => {
|
|
||||||
setImageDirtyState(true)
|
|
||||||
remove(index)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleFilesChosen = (files: File[]) => {
|
|
||||||
if (files.length) {
|
|
||||||
const toAppend = files.map((file) => ({
|
|
||||||
url: URL.createObjectURL(file),
|
|
||||||
name: file.name,
|
|
||||||
size: file.size,
|
|
||||||
nativeFile: file,
|
|
||||||
}))
|
|
||||||
|
|
||||||
append(toAppend)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BodyCard title="Images" subtitle="Add up to 10 images to your product">
|
|
||||||
<div className="mt-base">
|
|
||||||
<Controller
|
|
||||||
name="thumbnail"
|
|
||||||
control={form.control}
|
|
||||||
render={({ field: { value, onChange } }) => {
|
|
||||||
return (
|
|
||||||
<RadioGroup.Root
|
|
||||||
value={value ? `${value}` : undefined}
|
|
||||||
onValueChange={(value) => {
|
|
||||||
onChange(parseInt(value))
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ImageTable
|
|
||||||
data={fields as ImageTableDataType[]}
|
|
||||||
form={nestedForm(form, "images")}
|
|
||||||
onDelete={handleRemove}
|
|
||||||
/>
|
|
||||||
{fields.map((field, index) => {
|
|
||||||
return (
|
|
||||||
<div key={field.id} className="flex items-center">
|
|
||||||
<input
|
|
||||||
className="hidden"
|
|
||||||
{...form.register(`images.${index}.url`)}
|
|
||||||
defaultValue={field.url}
|
|
||||||
/>
|
|
||||||
{field.nativeFile && (
|
|
||||||
<>
|
|
||||||
<input
|
|
||||||
className="hidden"
|
|
||||||
{...form.register(`images.${index}.name`)}
|
|
||||||
defaultValue={field.name}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
className="hidden"
|
|
||||||
{...form.register(`images.${index}.size`)}
|
|
||||||
defaultValue={field.size}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
name={`images.${index}.nativeFile`}
|
|
||||||
control={form.control}
|
|
||||||
defaultValue={field.nativeFile}
|
|
||||||
render={() => <></>}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</RadioGroup.Root>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="mt-2xlarge">
|
|
||||||
<FileUploadField
|
|
||||||
onFileChosen={handleFilesChosen}
|
|
||||||
placeholder="1200 x 1600 (3:4) recommended, up to 10MB each"
|
|
||||||
filetypes={["image/gif", "image/jpeg", "image/png", "image/webp"]}
|
|
||||||
className="py-large"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</BodyCard>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Images
|
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
import { Product } from "@medusajs/medusa"
|
|
||||||
import {
|
|
||||||
useAdminDeleteProduct,
|
|
||||||
useAdminProductTypes,
|
|
||||||
useAdminUpdateProduct,
|
|
||||||
} from "medusa-react"
|
|
||||||
import React from "react"
|
|
||||||
import { Controller } from "react-hook-form"
|
|
||||||
import { useNavigate } from "react-router-dom"
|
|
||||||
import TrashIcon from "../../../../components/fundamentals/icons/trash-icon"
|
|
||||||
import UnpublishIcon from "../../../../components/fundamentals/icons/unpublish-icon"
|
|
||||||
import Input from "../../../../components/molecules/input"
|
|
||||||
import Select from "../../../../components/molecules/select"
|
|
||||||
import StatusSelector from "../../../../components/molecules/status-selector"
|
|
||||||
import TagInput from "../../../../components/molecules/tag-input"
|
|
||||||
import TextArea from "../../../../components/molecules/textarea"
|
|
||||||
import BodyCard from "../../../../components/organisms/body-card"
|
|
||||||
import DetailsCollapsible from "../../../../components/organisms/details-collapsible"
|
|
||||||
import useNotification from "../../../../hooks/use-notification"
|
|
||||||
import { getErrorMessage } from "../../../../utils/error-messages"
|
|
||||||
import FormValidator from "../../../../utils/form-validator"
|
|
||||||
import { useGiftCardForm } from "../form/gift-card-form-context"
|
|
||||||
|
|
||||||
type InformationProps = {
|
|
||||||
giftCard: Omit<Product, "beforeInsert">
|
|
||||||
}
|
|
||||||
|
|
||||||
const Information: React.FC<InformationProps> = ({ giftCard }) => {
|
|
||||||
const {
|
|
||||||
form: {
|
|
||||||
register,
|
|
||||||
setValue,
|
|
||||||
control,
|
|
||||||
formState: { errors },
|
|
||||||
},
|
|
||||||
} = useGiftCardForm()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const notification = useNotification()
|
|
||||||
const { product_types } = useAdminProductTypes(undefined, {
|
|
||||||
cacheTime: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
const typeOptions =
|
|
||||||
product_types?.map((tag) => ({ label: tag.value, value: tag.id })) || []
|
|
||||||
|
|
||||||
const updateGiftCard = useAdminUpdateProduct(giftCard.id)
|
|
||||||
const deleteGiftCard = useAdminDeleteProduct(giftCard.id)
|
|
||||||
|
|
||||||
const setNewType = (value: string) => {
|
|
||||||
const newType = {
|
|
||||||
label: value,
|
|
||||||
value,
|
|
||||||
}
|
|
||||||
|
|
||||||
typeOptions.push(newType)
|
|
||||||
setValue("type", newType)
|
|
||||||
|
|
||||||
return newType
|
|
||||||
}
|
|
||||||
|
|
||||||
const onUpdate = () => {
|
|
||||||
updateGiftCard.mutate(
|
|
||||||
{
|
|
||||||
// @ts-ignore
|
|
||||||
status: giftCard.status === "draft" ? "published" : "draft",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
onSuccess: () => {
|
|
||||||
notification("Success", "Gift card updated successfully", "success")
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
notification("Error", getErrorMessage(error), "error")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onDelete = () => {
|
|
||||||
deleteGiftCard.mutate(undefined, {
|
|
||||||
onSuccess: () => {
|
|
||||||
navigate("/a/gift-cards")
|
|
||||||
notification("Success", "Gift card updated successfully", "success")
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
notification("Error", getErrorMessage(error), "error")
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BodyCard
|
|
||||||
title="Product information"
|
|
||||||
subtitle="Manage the settings for your Gift Card"
|
|
||||||
className={"h-auto w-full"}
|
|
||||||
status={
|
|
||||||
<GiftCardStatusSelector
|
|
||||||
currentStatus={giftCard.status}
|
|
||||||
onUpdate={onUpdate}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
actionables={[
|
|
||||||
{
|
|
||||||
label:
|
|
||||||
giftCard?.status !== "published"
|
|
||||||
? "Publish Gift Card"
|
|
||||||
: "Unpublish Gift Card",
|
|
||||||
onClick: onUpdate,
|
|
||||||
icon: <UnpublishIcon size="16" />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Delete Gift Card",
|
|
||||||
onClick: onDelete,
|
|
||||||
variant: "danger",
|
|
||||||
icon: <TrashIcon size="16" />,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<div className="flex flex-col space-y-6">
|
|
||||||
<div className="gap-large grid grid-cols-2">
|
|
||||||
<Input
|
|
||||||
label="Name"
|
|
||||||
placeholder="Add name"
|
|
||||||
required
|
|
||||||
defaultValue={giftCard?.title}
|
|
||||||
{...register("title", {
|
|
||||||
required: FormValidator.required("Name"),
|
|
||||||
pattern: FormValidator.whiteSpaceRule("Name"),
|
|
||||||
minLength: FormValidator.minOneCharRule("Name"),
|
|
||||||
})}
|
|
||||||
errors={errors}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
label="Subtitle"
|
|
||||||
placeholder="Add a subtitle"
|
|
||||||
{...register("subtitle", {
|
|
||||||
pattern: FormValidator.whiteSpaceRule("Subtitle"),
|
|
||||||
minLength: FormValidator.minOneCharRule("Subtitle"),
|
|
||||||
})}
|
|
||||||
errors={errors}
|
|
||||||
/>
|
|
||||||
<TextArea
|
|
||||||
label="Description"
|
|
||||||
placeholder="Add a description"
|
|
||||||
{...register("description", {
|
|
||||||
pattern: FormValidator.whiteSpaceRule("Description"),
|
|
||||||
minLength: FormValidator.minOneCharRule("Description"),
|
|
||||||
})}
|
|
||||||
errors={errors}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<DetailsCollapsible
|
|
||||||
triggerProps={{ className: "ml-2" }}
|
|
||||||
contentProps={{
|
|
||||||
forceMount: true,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="gap-large grid grid-cols-2">
|
|
||||||
<Input
|
|
||||||
label="Handle"
|
|
||||||
placeholder="Product handle"
|
|
||||||
{...register("handle", {
|
|
||||||
pattern: FormValidator.whiteSpaceRule("Handle"),
|
|
||||||
minLength: FormValidator.minOneCharRule("Handle"),
|
|
||||||
})}
|
|
||||||
tooltipContent="URL of the product"
|
|
||||||
errors={errors}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="type"
|
|
||||||
render={({ field: { value, onChange } }) => {
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
label="Type"
|
|
||||||
placeholder="Select type..."
|
|
||||||
options={typeOptions}
|
|
||||||
onChange={onChange}
|
|
||||||
value={value}
|
|
||||||
isCreatable
|
|
||||||
onCreateOption={(value) => {
|
|
||||||
return setNewType(value)
|
|
||||||
}}
|
|
||||||
clearSelected
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
name="tags"
|
|
||||||
render={({ field: { onChange, value } }) => {
|
|
||||||
return (
|
|
||||||
<TagInput
|
|
||||||
label="Tags (separated by comma)"
|
|
||||||
className="w-full"
|
|
||||||
placeholder="Spring, Summer..."
|
|
||||||
onChange={onChange}
|
|
||||||
values={value || []}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
control={control}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</DetailsCollapsible>
|
|
||||||
</div>
|
|
||||||
</BodyCard>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const GiftCardStatusSelector = ({
|
|
||||||
currentStatus,
|
|
||||||
onUpdate,
|
|
||||||
}: {
|
|
||||||
currentStatus: "draft" | "proposed" | "published" | "rejected"
|
|
||||||
onUpdate: () => void
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<StatusSelector
|
|
||||||
activeState="Published"
|
|
||||||
draftState="Draft"
|
|
||||||
isDraft={currentStatus === "draft"}
|
|
||||||
onChange={onUpdate}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Information
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { FormImage, Option } from "../../../../types/shared"
|
|
||||||
|
|
||||||
export type ManageGiftCardFormData = {
|
|
||||||
title: string
|
|
||||||
handle?: string
|
|
||||||
subtitle?: string | null
|
|
||||||
description?: string | null
|
|
||||||
type: Option | null
|
|
||||||
tags?: string[]
|
|
||||||
images: FormImage[]
|
|
||||||
thumbnail: number
|
|
||||||
}
|
|
||||||
+2
-2
@@ -1,11 +1,11 @@
|
|||||||
import { SalesChannel, StockLocationExpandedDTO } from "@medusajs/medusa"
|
import { SalesChannel, StockLocationExpandedDTO } from "@medusajs/medusa"
|
||||||
import {
|
import {
|
||||||
useAdminAddLocationToSalesChannel,
|
useAdminAddLocationToSalesChannel,
|
||||||
useAdminRemoveLocationFromSalesChannel,
|
useAdminRemoveLocationFromSalesChannel
|
||||||
} from "medusa-react"
|
} from "medusa-react"
|
||||||
|
|
||||||
|
import SalesChannelsModal from "../../../../../components/forms/product/sales-channels-modal"
|
||||||
import Button from "../../../../../components/fundamentals/button"
|
import Button from "../../../../../components/fundamentals/button"
|
||||||
import SalesChannelsModal from "../../../../products/components/sales-channels-modal"
|
|
||||||
import useToggleState from "../../../../../hooks/use-toggle-state"
|
import useToggleState from "../../../../../hooks/use-toggle-state"
|
||||||
|
|
||||||
const EditSalesChannels = ({
|
const EditSalesChannels = ({
|
||||||
|
|||||||
+1
-1
@@ -1,10 +1,10 @@
|
|||||||
import { SalesChannel, StockLocationExpandedDTO } from "@medusajs/medusa"
|
import { SalesChannel, StockLocationExpandedDTO } from "@medusajs/medusa"
|
||||||
import { useFieldArray } from "react-hook-form"
|
import { useFieldArray } from "react-hook-form"
|
||||||
|
import SalesChannelsModal from "../../../../../components/forms/product/sales-channels-modal"
|
||||||
import Button from "../../../../../components/fundamentals/button"
|
import Button from "../../../../../components/fundamentals/button"
|
||||||
import SalesChannelsList from "../../../../../components/molecules/sales-channels-list"
|
import SalesChannelsList from "../../../../../components/molecules/sales-channels-list"
|
||||||
import useToggleState from "../../../../../hooks/use-toggle-state"
|
import useToggleState from "../../../../../hooks/use-toggle-state"
|
||||||
import { NestedForm } from "../../../../../utils/nested-form"
|
import { NestedForm } from "../../../../../utils/nested-form"
|
||||||
import SalesChannelsModal from "../../../../products/components/sales-channels-modal"
|
|
||||||
import { AddSalesChannelsFormType } from "../../../../products/new/add-sales-channels"
|
import { AddSalesChannelsFormType } from "../../../../products/new/add-sales-channels"
|
||||||
|
|
||||||
const SalesChannelsForm = ({
|
const SalesChannelsForm = ({
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import clsx from "clsx"
|
|||||||
import { useAdminShippingOptions } from "medusa-react"
|
import { useAdminShippingOptions } from "medusa-react"
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import { Controller, useWatch } from "react-hook-form"
|
import { Controller, useWatch } from "react-hook-form"
|
||||||
|
import PriceFormInput from "../../../../components/forms/general/prices-form/price-form-input"
|
||||||
import Button from "../../../../components/fundamentals/button"
|
import Button from "../../../../components/fundamentals/button"
|
||||||
import TrashIcon from "../../../../components/fundamentals/icons/trash-icon"
|
import TrashIcon from "../../../../components/fundamentals/icons/trash-icon"
|
||||||
import { NextSelect } from "../../../../components/molecules/select/next-select"
|
import { NextSelect } from "../../../../components/molecules/select/next-select"
|
||||||
import { NestedForm } from "../../../../utils/nested-form"
|
import { NestedForm } from "../../../../utils/nested-form"
|
||||||
import { formatAmountWithSymbol } from "../../../../utils/prices"
|
import { formatAmountWithSymbol } from "../../../../utils/prices"
|
||||||
import PriceFormInput from "../../../products/components/prices-form/price-form-input"
|
|
||||||
|
|
||||||
export type ShippingFormType = {
|
export type ShippingFormType = {
|
||||||
option: {
|
option: {
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import { useAdminProduct } from "medusa-react"
|
|||||||
import { useNavigate, useParams } from "react-router-dom"
|
import { useNavigate, useParams } from "react-router-dom"
|
||||||
import BackButton from "../../../components/atoms/back-button"
|
import BackButton from "../../../components/atoms/back-button"
|
||||||
import Spinner from "../../../components/atoms/spinner"
|
import Spinner from "../../../components/atoms/spinner"
|
||||||
|
import ProductAttributesSection from "../../../components/organisms/product-attributes-section"
|
||||||
|
import ProductGeneralSection from "../../../components/organisms/product-general-section"
|
||||||
|
import ProductMediaSection from "../../../components/organisms/product-media-section"
|
||||||
|
import ProductRawSection from "../../../components/organisms/product-raw-section"
|
||||||
|
import ProductThumbnailSection from "../../../components/organisms/product-thumbnail-section"
|
||||||
|
import ProductVariantsSection from "../../../components/organisms/product-variants-section"
|
||||||
import { getErrorStatus } from "../../../utils/get-error-status"
|
import { getErrorStatus } from "../../../utils/get-error-status"
|
||||||
import AttributesSection from "./sections/attributes"
|
|
||||||
import GeneralSection from "./sections/general"
|
|
||||||
import MediaSection from "./sections/media"
|
|
||||||
import RawSection from "./sections/raw"
|
|
||||||
import ThumbnailSection from "./sections/thumbnail"
|
|
||||||
import VariantsSection from "./sections/variants"
|
|
||||||
|
|
||||||
const Edit = () => {
|
const Edit = () => {
|
||||||
const { id } = useParams()
|
const { id } = useParams()
|
||||||
@@ -17,13 +17,9 @@ const Edit = () => {
|
|||||||
const { product, status, error } = useAdminProduct(id || "")
|
const { product, status, error } = useAdminProduct(id || "")
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
let message = "An unknown error occurred"
|
|
||||||
|
|
||||||
const errorStatus = getErrorStatus(error)
|
const errorStatus = getErrorStatus(error)
|
||||||
|
|
||||||
if (errorStatus) {
|
if (errorStatus) {
|
||||||
message = errorStatus.message
|
|
||||||
|
|
||||||
// If the product is not found, redirect to the 404 page
|
// If the product is not found, redirect to the 404 page
|
||||||
if (errorStatus.status === 404) {
|
if (errorStatus.status === 404) {
|
||||||
navigate("/404")
|
navigate("/404")
|
||||||
@@ -53,14 +49,14 @@ const Edit = () => {
|
|||||||
/>
|
/>
|
||||||
<div className="gap-x-base grid grid-cols-12">
|
<div className="gap-x-base grid grid-cols-12">
|
||||||
<div className="gap-y-xsmall col-span-8 flex flex-col">
|
<div className="gap-y-xsmall col-span-8 flex flex-col">
|
||||||
<GeneralSection product={product} />
|
<ProductGeneralSection product={product} />
|
||||||
<VariantsSection product={product} />
|
<ProductVariantsSection product={product} />
|
||||||
<AttributesSection product={product} />
|
<ProductAttributesSection product={product} />
|
||||||
<RawSection product={product} />
|
<ProductRawSection product={product} />
|
||||||
</div>
|
</div>
|
||||||
<div className="gap-y-xsmall col-span-4 flex flex-col">
|
<div className="gap-y-xsmall col-span-4 flex flex-col">
|
||||||
<ThumbnailSection product={product} />
|
<ProductThumbnailSection product={product} />
|
||||||
<MediaSection product={product} />
|
<ProductMediaSection product={product} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,12 +4,12 @@ import { useAdminStore } from "medusa-react"
|
|||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { useFieldArray } from "react-hook-form"
|
import { useFieldArray } from "react-hook-form"
|
||||||
import Switch from "../../../components/atoms/switch"
|
import Switch from "../../../components/atoms/switch"
|
||||||
|
import SalesChannelsModal from "../../../components/forms/product/sales-channels-modal"
|
||||||
import Button from "../../../components/fundamentals/button"
|
import Button from "../../../components/fundamentals/button"
|
||||||
import ChannelsIcon from "../../../components/fundamentals/icons/channels-icon"
|
import ChannelsIcon from "../../../components/fundamentals/icons/channels-icon"
|
||||||
import SalesChannelsDisplay from "../../../components/molecules/sales-channels-display"
|
import SalesChannelsDisplay from "../../../components/molecules/sales-channels-display"
|
||||||
import useToggleState from "../../../hooks/use-toggle-state"
|
import useToggleState from "../../../hooks/use-toggle-state"
|
||||||
import { NestedForm } from "../../../utils/nested-form"
|
import { NestedForm } from "../../../utils/nested-form"
|
||||||
import SalesChannelsModal from "../components/sales-channels-modal"
|
|
||||||
|
|
||||||
export type AddSalesChannelsFormType = {
|
export type AddSalesChannelsFormType = {
|
||||||
channels: SalesChannel[]
|
channels: SalesChannel[]
|
||||||
|
|||||||
@@ -2,6 +2,13 @@ import clsx from "clsx"
|
|||||||
import { useCallback, useContext, useEffect, useMemo } from "react"
|
import { useCallback, useContext, useEffect, useMemo } from "react"
|
||||||
import { Controller, useFieldArray, useForm, useWatch } from "react-hook-form"
|
import { Controller, useFieldArray, useForm, useWatch } from "react-hook-form"
|
||||||
import { v4 as uuidv4 } from "uuid"
|
import { v4 as uuidv4 } from "uuid"
|
||||||
|
import { CustomsFormType } from "../../../../components/forms/product/customs-form"
|
||||||
|
import { DimensionsFormType } from "../../../../components/forms/product/dimensions-form"
|
||||||
|
import CreateFlowVariantForm, {
|
||||||
|
CreateFlowVariantFormType
|
||||||
|
} from "../../../../components/forms/product/variant-form/create-flow-variant-form"
|
||||||
|
import { VariantOptionType } from "../../../../components/forms/product/variant-form/variant-select-options-form"
|
||||||
|
import useCheckOptions from "../../../../components/forms/product/variant-form/variant-select-options-form/hooks"
|
||||||
import Button from "../../../../components/fundamentals/button"
|
import Button from "../../../../components/fundamentals/button"
|
||||||
import PlusIcon from "../../../../components/fundamentals/icons/plus-icon"
|
import PlusIcon from "../../../../components/fundamentals/icons/plus-icon"
|
||||||
import TrashIcon from "../../../../components/fundamentals/icons/trash-icon"
|
import TrashIcon from "../../../../components/fundamentals/icons/trash-icon"
|
||||||
@@ -9,19 +16,12 @@ import IconTooltip from "../../../../components/molecules/icon-tooltip"
|
|||||||
import InputField from "../../../../components/molecules/input"
|
import InputField from "../../../../components/molecules/input"
|
||||||
import Modal from "../../../../components/molecules/modal"
|
import Modal from "../../../../components/molecules/modal"
|
||||||
import LayeredModal, {
|
import LayeredModal, {
|
||||||
LayeredModalContext,
|
LayeredModalContext
|
||||||
} from "../../../../components/molecules/modal/layered-modal"
|
} from "../../../../components/molecules/modal/layered-modal"
|
||||||
import TagInput from "../../../../components/molecules/tag-input"
|
import TagInput from "../../../../components/molecules/tag-input"
|
||||||
import { useDebounce } from "../../../../hooks/use-debounce"
|
import { useDebounce } from "../../../../hooks/use-debounce"
|
||||||
import useToggleState from "../../../../hooks/use-toggle-state"
|
import useToggleState from "../../../../hooks/use-toggle-state"
|
||||||
import { NestedForm } from "../../../../utils/nested-form"
|
import { NestedForm } from "../../../../utils/nested-form"
|
||||||
import { CustomsFormType } from "../../components/customs-form"
|
|
||||||
import { DimensionsFormType } from "../../components/dimensions-form"
|
|
||||||
import CreateFlowVariantForm, {
|
|
||||||
CreateFlowVariantFormType,
|
|
||||||
} from "../../components/variant-form/create-flow-variant-form"
|
|
||||||
import { VariantOptionType } from "../../components/variant-form/variant-select-options-form"
|
|
||||||
import useCheckOptions from "../../components/variant-form/variant-select-options-form/hooks"
|
|
||||||
import NewVariant from "./new-variant"
|
import NewVariant from "./new-variant"
|
||||||
|
|
||||||
type ProductOptionType = {
|
type ProductOptionType = {
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ import { useContext, useEffect, useRef } from "react"
|
|||||||
import { useDrag, useDrop } from "react-dnd"
|
import { useDrag, useDrop } from "react-dnd"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
import Tooltip from "../../../../../components/atoms/tooltip"
|
import Tooltip from "../../../../../components/atoms/tooltip"
|
||||||
|
import { CustomsFormType } from "../../../../../components/forms/product/customs-form"
|
||||||
|
import { DimensionsFormType } from "../../../../../components/forms/product/dimensions-form"
|
||||||
|
import CreateFlowVariantForm, {
|
||||||
|
CreateFlowVariantFormType
|
||||||
|
} from "../../../../../components/forms/product/variant-form/create-flow-variant-form"
|
||||||
|
import { VariantOptionValueType } from "../../../../../components/forms/product/variant-form/variant-select-options-form"
|
||||||
import Button from "../../../../../components/fundamentals/button"
|
import Button from "../../../../../components/fundamentals/button"
|
||||||
import CheckCircleFillIcon from "../../../../../components/fundamentals/icons/check-circle-fill-icon"
|
import CheckCircleFillIcon from "../../../../../components/fundamentals/icons/check-circle-fill-icon"
|
||||||
import EditIcon from "../../../../../components/fundamentals/icons/edit-icon"
|
import EditIcon from "../../../../../components/fundamentals/icons/edit-icon"
|
||||||
@@ -14,17 +20,11 @@ import Actionables from "../../../../../components/molecules/actionables"
|
|||||||
import IconTooltip from "../../../../../components/molecules/icon-tooltip"
|
import IconTooltip from "../../../../../components/molecules/icon-tooltip"
|
||||||
import Modal from "../../../../../components/molecules/modal"
|
import Modal from "../../../../../components/molecules/modal"
|
||||||
import LayeredModal, {
|
import LayeredModal, {
|
||||||
LayeredModalContext,
|
LayeredModalContext
|
||||||
} from "../../../../../components/molecules/modal/layered-modal"
|
} from "../../../../../components/molecules/modal/layered-modal"
|
||||||
import useImperativeDialog from "../../../../../hooks/use-imperative-dialog"
|
import useImperativeDialog from "../../../../../hooks/use-imperative-dialog"
|
||||||
import useToggleState from "../../../../../hooks/use-toggle-state"
|
import useToggleState from "../../../../../hooks/use-toggle-state"
|
||||||
import { DragItem } from "../../../../../types/shared"
|
import { DragItem } from "../../../../../types/shared"
|
||||||
import { CustomsFormType } from "../../../components/customs-form"
|
|
||||||
import { DimensionsFormType } from "../../../components/dimensions-form"
|
|
||||||
import CreateFlowVariantForm, {
|
|
||||||
CreateFlowVariantFormType,
|
|
||||||
} from "../../../components/variant-form/create-flow-variant-form"
|
|
||||||
import { VariantOptionValueType } from "../../../components/variant-form/variant-select-options-form"
|
|
||||||
|
|
||||||
const ItemTypes = {
|
const ItemTypes = {
|
||||||
CARD: "card",
|
CARD: "card",
|
||||||
|
|||||||
@@ -1,36 +1,36 @@
|
|||||||
import AddSalesChannelsForm, {
|
|
||||||
AddSalesChannelsFormType,
|
|
||||||
} from "./add-sales-channels"
|
|
||||||
import AddVariantsForm, { AddVariantsFormType } from "./add-variants"
|
|
||||||
import { AdminPostProductsReq, ProductVariant } from "@medusajs/medusa"
|
import { AdminPostProductsReq, ProductVariant } from "@medusajs/medusa"
|
||||||
import CustomsForm, { CustomsFormType } from "../components/customs-form"
|
|
||||||
import DimensionsForm, {
|
|
||||||
DimensionsFormType,
|
|
||||||
} from "../components/dimensions-form"
|
|
||||||
import DiscountableForm, {
|
|
||||||
DiscountableFormType,
|
|
||||||
} from "../components/discountable-form"
|
|
||||||
import { FormImage, ProductStatus } from "../../../types/shared"
|
|
||||||
import GeneralForm, { GeneralFormType } from "../components/general-form"
|
|
||||||
import MediaForm, { MediaFormType } from "../components/media-form"
|
|
||||||
import OrganizeForm, { OrganizeFormType } from "../components/organize-form"
|
|
||||||
import ThumbnailForm, { ThumbnailFormType } from "../components/thumbnail-form"
|
|
||||||
import { useAdminCreateProduct, useMedusa } from "medusa-react"
|
import { useAdminCreateProduct, useMedusa } from "medusa-react"
|
||||||
import { useForm, useWatch } from "react-hook-form"
|
import { useForm, useWatch } from "react-hook-form"
|
||||||
|
import CustomsForm, { CustomsFormType } from "../../../components/forms/product/customs-form"
|
||||||
|
import DimensionsForm, {
|
||||||
|
DimensionsFormType
|
||||||
|
} from "../../../components/forms/product/dimensions-form"
|
||||||
|
import DiscountableForm, {
|
||||||
|
DiscountableFormType
|
||||||
|
} from "../../../components/forms/product/discountable-form"
|
||||||
|
import GeneralForm, { GeneralFormType } from "../../../components/forms/product/general-form"
|
||||||
|
import MediaForm, { MediaFormType } from "../../../components/forms/product/media-form"
|
||||||
|
import OrganizeForm, { OrganizeFormType } from "../../../components/forms/product/organize-form"
|
||||||
|
import ThumbnailForm, { ThumbnailFormType } from "../../../components/forms/product/thumbnail-form"
|
||||||
|
import { FormImage, ProductStatus } from "../../../types/shared"
|
||||||
|
import AddSalesChannelsForm, {
|
||||||
|
AddSalesChannelsFormType
|
||||||
|
} from "./add-sales-channels"
|
||||||
|
import AddVariantsForm, { AddVariantsFormType } from "./add-variants"
|
||||||
|
|
||||||
import Accordion from "../../../components/organisms/accordion"
|
|
||||||
import Button from "../../../components/fundamentals/button"
|
|
||||||
import CrossIcon from "../../../components/fundamentals/icons/cross-icon"
|
|
||||||
import FeatureToggle from "../../../components/fundamentals/feature-toggle"
|
|
||||||
import FocusModal from "../../../components/molecules/modal/focus-modal"
|
|
||||||
import { PricesFormType } from "../components/prices-form"
|
|
||||||
import { getErrorMessage } from "../../../utils/error-messages"
|
|
||||||
import { nestedForm } from "../../../utils/nested-form"
|
|
||||||
import { prepareImages } from "../../../utils/images"
|
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
import { useFeatureFlag } from "../../../providers/feature-flag-provider"
|
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
|
import { PricesFormType } from "../../../components/forms/general/prices-form"
|
||||||
|
import Button from "../../../components/fundamentals/button"
|
||||||
|
import FeatureToggle from "../../../components/fundamentals/feature-toggle"
|
||||||
|
import CrossIcon from "../../../components/fundamentals/icons/cross-icon"
|
||||||
|
import FocusModal from "../../../components/molecules/modal/focus-modal"
|
||||||
|
import Accordion from "../../../components/organisms/accordion"
|
||||||
import useNotification from "../../../hooks/use-notification"
|
import useNotification from "../../../hooks/use-notification"
|
||||||
|
import { useFeatureFlag } from "../../../providers/feature-flag-provider"
|
||||||
|
import { getErrorMessage } from "../../../utils/error-messages"
|
||||||
|
import { prepareImages } from "../../../utils/images"
|
||||||
|
import { nestedForm } from "../../../utils/nested-form"
|
||||||
|
|
||||||
type NewProductForm = {
|
type NewProductForm = {
|
||||||
general: GeneralFormType
|
general: GeneralFormType
|
||||||
@@ -357,14 +357,14 @@ const createPayload = (
|
|||||||
mid_code: data.customs.mid_code || undefined,
|
mid_code: data.customs.mid_code || undefined,
|
||||||
type: data.organize.type
|
type: data.organize.type
|
||||||
? {
|
? {
|
||||||
value: data.organize.type.label,
|
value: data.organize.type.label,
|
||||||
id: data.organize.type.value,
|
id: data.organize.type.value,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
tags: data.organize.tags
|
tags: data.organize.tags
|
||||||
? data.organize.tags.map((t) => ({
|
? data.organize.tags.map((t) => ({
|
||||||
value: t,
|
value: t,
|
||||||
}))
|
}))
|
||||||
: undefined,
|
: undefined,
|
||||||
categories: data.organize.categories?.length
|
categories: data.organize.categories?.length
|
||||||
? data.organize.categories.map((id) => ({ id }))
|
? data.organize.categories.map((id) => ({ id }))
|
||||||
|
|||||||
+1
-1
@@ -2,12 +2,12 @@ import { Region } from "@medusajs/medusa"
|
|||||||
import { Controller, UseFormReturn } from "react-hook-form"
|
import { Controller, UseFormReturn } from "react-hook-form"
|
||||||
import IncludesTaxTooltip from "../../../../../components/atoms/includes-tax-tooltip"
|
import IncludesTaxTooltip from "../../../../../components/atoms/includes-tax-tooltip"
|
||||||
import Switch from "../../../../../components/atoms/switch"
|
import Switch from "../../../../../components/atoms/switch"
|
||||||
|
import PriceFormInput from "../../../../../components/forms/general/prices-form/price-form-input"
|
||||||
import InputHeader from "../../../../../components/fundamentals/input-header"
|
import InputHeader from "../../../../../components/fundamentals/input-header"
|
||||||
import InputField from "../../../../../components/molecules/input"
|
import InputField from "../../../../../components/molecules/input"
|
||||||
import { NextSelect } from "../../../../../components/molecules/select/next-select"
|
import { NextSelect } from "../../../../../components/molecules/select/next-select"
|
||||||
import { Option, ShippingOptionPriceType } from "../../../../../types/shared"
|
import { Option, ShippingOptionPriceType } from "../../../../../types/shared"
|
||||||
import FormValidator from "../../../../../utils/form-validator"
|
import FormValidator from "../../../../../utils/form-validator"
|
||||||
import PriceFormInput from "../../../../products/components/prices-form/price-form-input"
|
|
||||||
import { useShippingOptionFormData } from "./use-shipping-option-form-data"
|
import { useShippingOptionFormData } from "./use-shipping-option-form-data"
|
||||||
|
|
||||||
type Requirement = {
|
type Requirement = {
|
||||||
|
|||||||
+6
-5
@@ -2,6 +2,7 @@ import {
|
|||||||
AdminPostProductsProductReq,
|
AdminPostProductsProductReq,
|
||||||
AdminPostProductsProductVariantsReq,
|
AdminPostProductsProductVariantsReq,
|
||||||
AdminPostProductsProductVariantsVariantReq,
|
AdminPostProductsProductVariantsVariantReq,
|
||||||
|
Product,
|
||||||
} from "@medusajs/medusa"
|
} from "@medusajs/medusa"
|
||||||
import {
|
import {
|
||||||
useAdminCreateVariant,
|
useAdminCreateVariant,
|
||||||
@@ -12,11 +13,11 @@ import {
|
|||||||
useAdminUpdateVariant,
|
useAdminUpdateVariant,
|
||||||
} from "medusa-react"
|
} from "medusa-react"
|
||||||
|
|
||||||
import { getErrorMessage } from "../../../../utils/error-messages"
|
|
||||||
import { removeNullish } from "../../../../utils/remove-nullish"
|
|
||||||
import useImperativeDialog from "../../../../hooks/use-imperative-dialog"
|
|
||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom"
|
||||||
import useNotification from "../../../../hooks/use-notification"
|
import { getErrorMessage } from "../utils/error-messages"
|
||||||
|
import { removeNullish } from "../utils/remove-nullish"
|
||||||
|
import useImperativeDialog from "./use-imperative-dialog"
|
||||||
|
import useNotification from "./use-notification"
|
||||||
|
|
||||||
const useEditProductActions = (productId: string) => {
|
const useEditProductActions = (productId: string) => {
|
||||||
const dialog = useImperativeDialog()
|
const dialog = useImperativeDialog()
|
||||||
@@ -49,7 +50,7 @@ const useEditProductActions = (productId: string) => {
|
|||||||
|
|
||||||
const onAddVariant = (
|
const onAddVariant = (
|
||||||
payload: AdminPostProductsProductVariantsReq,
|
payload: AdminPostProductsProductVariantsReq,
|
||||||
onSuccess: (variantRes) => void,
|
onSuccess: (variantRes: Product) => void,
|
||||||
successMessage = "Variant was created successfully"
|
successMessage = "Variant was created successfully"
|
||||||
) => {
|
) => {
|
||||||
addVariant.mutate(payload, {
|
addVariant.mutate(payload, {
|
||||||
Reference in new issue
Block a user