fix(medusa, admin-ui, medusa-react): Gift Card update fixes and admin UI cleanup (#3676)
* fix gc domain issues * add changeset * update changeset * more minor fixes, remove breadcrumb * more cleanup * address feedback
This commit is contained in:
committed by
GitHub
parent
a5ad6c0542
commit
788ddc0f43
7
.changeset/cuddly-trees-cry.md
Normal file
7
.changeset/cuddly-trees-cry.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"medusa-react": patch
|
||||
"@medusajs/admin-ui": patch
|
||||
"@medusajs/medusa": patch
|
||||
---
|
||||
|
||||
fix(admin-ui, medusa-react, medusa): Minor fixes to GC domain in admin UI. Also fixes GC update payload type in medusa-react and medusa.
|
||||
@@ -60,6 +60,7 @@ const DatePicker: React.FC<DateTimePickerProps> = ({
|
||||
"shadow-input border-violet-60": isOpen,
|
||||
"border-grey-20": !isOpen,
|
||||
})}
|
||||
type="button"
|
||||
>
|
||||
<InputContainer className="shadown-none border-0 focus-within:shadow-none">
|
||||
<div className="text-grey-50 flex w-full justify-between pr-0.5">
|
||||
|
||||
@@ -53,6 +53,7 @@ const TimePicker: React.FC<DateTimePickerProps> = ({
|
||||
"shadow-input border-violet-60": isOpen,
|
||||
"border-grey-20": !isOpen,
|
||||
})}
|
||||
type="button"
|
||||
>
|
||||
<InputContainer className="shadown-none border-0 focus-within:shadow-none">
|
||||
<div className="text-grey-50 flex w-full justify-between pr-0.5">
|
||||
|
||||
@@ -34,7 +34,11 @@ const NumberScroller: React.FC<NumberScrollerProps> = ({
|
||||
}
|
||||
)}
|
||||
>
|
||||
<button onClick={() => onSelect(n)} className="h-full w-full py-2">
|
||||
<button
|
||||
onClick={() => onSelect(n)}
|
||||
className="h-full w-full py-2"
|
||||
type="button"
|
||||
>
|
||||
{n.toLocaleString("en-US", { minimumIntegerDigits: 2 })}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Spacer that ensures a consistent bottom margin on all pages
|
||||
* Temporary fix to BodyCard and main layout not having a consistent bottom margin
|
||||
*/
|
||||
const Spacer = () => {
|
||||
return <div className="h-xlarge w-full" />
|
||||
}
|
||||
|
||||
export default Spacer
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Controller } from "react-hook-form"
|
||||
import FormValidator from "../../../../utils/form-validator"
|
||||
import { NestedForm } from "../../../../utils/nested-form"
|
||||
import { formatAmountWithSymbol } from "../../../../utils/prices"
|
||||
import PriceFormInput from "../../general/prices-form/price-form-input"
|
||||
|
||||
export type GiftCardBalanceFormType = {
|
||||
amount: number
|
||||
}
|
||||
|
||||
export type GiftCardBalanceFormProps = {
|
||||
form: NestedForm<GiftCardBalanceFormType>
|
||||
currencyCode: string
|
||||
originalAmount?: number
|
||||
}
|
||||
|
||||
const GiftCardBalanceForm = ({
|
||||
form,
|
||||
currencyCode,
|
||||
originalAmount,
|
||||
}: GiftCardBalanceFormProps) => {
|
||||
const {
|
||||
control,
|
||||
path,
|
||||
formState: { errors },
|
||||
} = form
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name={path("amount")}
|
||||
rules={{
|
||||
required: FormValidator.required("Balance"),
|
||||
min: {
|
||||
value: 0,
|
||||
message: "Balance must be greater than 0",
|
||||
},
|
||||
max: originalAmount
|
||||
? {
|
||||
value: originalAmount,
|
||||
message: `The updated balance cannot exceed the original value of ${formatAmountWithSymbol(
|
||||
{
|
||||
amount: originalAmount,
|
||||
currency: currencyCode,
|
||||
}
|
||||
)}`,
|
||||
}
|
||||
: undefined,
|
||||
}}
|
||||
control={control}
|
||||
render={({ field: { value, onChange, name } }) => {
|
||||
return (
|
||||
<PriceFormInput
|
||||
label="Amount"
|
||||
currencyCode={currencyCode}
|
||||
onChange={onChange}
|
||||
amount={value}
|
||||
name={name}
|
||||
errors={errors}
|
||||
required
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default GiftCardBalanceForm
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Controller } from "react-hook-form"
|
||||
import { NestedForm } from "../../../../utils/nested-form"
|
||||
import DatePicker from "../../../atoms/date-picker/date-picker"
|
||||
import TimePicker from "../../../atoms/date-picker/time-picker"
|
||||
import SwitchableItem from "../../../molecules/switchable-item"
|
||||
|
||||
export type GiftCardEndsAtFormType = {
|
||||
ends_at: Date | null
|
||||
}
|
||||
|
||||
type GiftCardEndsAtFormProps = {
|
||||
form: NestedForm<GiftCardEndsAtFormType>
|
||||
}
|
||||
|
||||
const GiftCardEndsAtForm = ({ form }: GiftCardEndsAtFormProps) => {
|
||||
const { control, path } = form
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name={path("ends_at")}
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => {
|
||||
return (
|
||||
<SwitchableItem
|
||||
open={!!value}
|
||||
onSwitch={() => {
|
||||
if (value) {
|
||||
onChange(null)
|
||||
} else {
|
||||
onChange(
|
||||
new Date(new Date().getTime() + 7 * 24 * 60 * 60 * 1000)
|
||||
)
|
||||
}
|
||||
}}
|
||||
title="Gift Card has an expiry date?"
|
||||
description="Schedule the Gift Card to deactivate in the future."
|
||||
>
|
||||
<div className="gap-x-xsmall flex items-center">
|
||||
<DatePicker
|
||||
date={value!}
|
||||
label="Expiry date"
|
||||
onSubmitDate={onChange}
|
||||
/>
|
||||
<TimePicker
|
||||
label="Expiry time"
|
||||
date={value!}
|
||||
onSubmitDate={onChange}
|
||||
/>
|
||||
</div>
|
||||
</SwitchableItem>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default GiftCardEndsAtForm
|
||||
@@ -0,0 +1,43 @@
|
||||
import FormValidator from "../../../../utils/form-validator"
|
||||
import { NestedForm } from "../../../../utils/nested-form"
|
||||
import InputField from "../../../molecules/input"
|
||||
import TextArea from "../../../molecules/textarea"
|
||||
|
||||
export type GiftCardReceiverFormType = {
|
||||
email: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
type GiftCardReceiverFormProps = {
|
||||
form: NestedForm<GiftCardReceiverFormType>
|
||||
}
|
||||
|
||||
const GiftCardReceiverForm = ({ form }: GiftCardReceiverFormProps) => {
|
||||
const {
|
||||
register,
|
||||
path,
|
||||
formState: { errors },
|
||||
} = form
|
||||
|
||||
return (
|
||||
<div className="gap-y-large flex flex-col">
|
||||
<InputField
|
||||
label="Email"
|
||||
{...register(path("email"), {
|
||||
required: FormValidator.required("Email"),
|
||||
pattern: FormValidator.email("Email"),
|
||||
})}
|
||||
required
|
||||
errors={errors}
|
||||
/>
|
||||
<TextArea
|
||||
label="Personal Message"
|
||||
placeholder="Write a personal message here"
|
||||
rows={7}
|
||||
{...register(path("message"))}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default GiftCardReceiverForm
|
||||
@@ -0,0 +1,78 @@
|
||||
import { useAdminRegions } from "medusa-react"
|
||||
import { useEffect, useMemo } from "react"
|
||||
import { Controller, useWatch } from "react-hook-form"
|
||||
import { Option } from "../../../../types/shared"
|
||||
import FormValidator from "../../../../utils/form-validator"
|
||||
import { NestedForm } from "../../../../utils/nested-form"
|
||||
import { NextSelect } from "../../../molecules/select/next-select"
|
||||
|
||||
type RegionOption = Option & {
|
||||
currency_code: string
|
||||
}
|
||||
|
||||
export type GiftCardRegionFormType = {
|
||||
region_id: RegionOption
|
||||
}
|
||||
|
||||
type GiftCardRegionFormProps = {
|
||||
form: NestedForm<GiftCardRegionFormType>
|
||||
}
|
||||
|
||||
const GiftCardRegionForm = ({ form }: GiftCardRegionFormProps) => {
|
||||
const {
|
||||
control,
|
||||
path,
|
||||
formState: { errors },
|
||||
setValue,
|
||||
} = form
|
||||
|
||||
const { regions } = useAdminRegions({
|
||||
limit: 100,
|
||||
})
|
||||
|
||||
const regionOptions: RegionOption[] = useMemo(() => {
|
||||
return (
|
||||
regions?.map((r) => ({
|
||||
label: r.name,
|
||||
value: r.id,
|
||||
currency_code: r.currency_code,
|
||||
})) || []
|
||||
)
|
||||
}, [regions])
|
||||
|
||||
const subscriber = useWatch({
|
||||
control,
|
||||
name: path("region_id"),
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!subscriber) {
|
||||
setValue(path("region_id"), regionOptions[0], {
|
||||
shouldDirty: true,
|
||||
})
|
||||
}
|
||||
}, [subscriber, regionOptions, setValue, path])
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name={path("region_id")}
|
||||
rules={{
|
||||
required: FormValidator.required("Region"),
|
||||
}}
|
||||
control={control}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<NextSelect
|
||||
label="Region"
|
||||
required
|
||||
{...field}
|
||||
errors={errors}
|
||||
options={regionOptions}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default GiftCardRegionForm
|
||||
@@ -1,22 +1,23 @@
|
||||
import React from "react"
|
||||
import React, { PropsWithChildren } from "react"
|
||||
import Actionables, { ActionType } from "../../molecules/actionables"
|
||||
|
||||
type BannerCardProps = {
|
||||
type BannerCardProps = PropsWithChildren<{
|
||||
actions?: ActionType[]
|
||||
title: string
|
||||
thumbnail: string | null
|
||||
} & React.RefAttributes<HTMLDivElement>
|
||||
thumbnail?: string | null
|
||||
}> &
|
||||
React.RefAttributes<HTMLDivElement>
|
||||
|
||||
type BannerCardDescriptionProps = {
|
||||
type BannerCardDescriptionProps = PropsWithChildren<{
|
||||
cta?: {
|
||||
label: string
|
||||
onClick: (e: React.MouseEvent<HTMLButtonElement>) => void
|
||||
}
|
||||
}
|
||||
}>
|
||||
|
||||
const BannerCard: React.FC<BannerCardProps> & {
|
||||
Description: React.FC<BannerCardDescriptionProps>
|
||||
Footer: React.FC
|
||||
Footer: React.FC<PropsWithChildren>
|
||||
} = ({ title, thumbnail, actions, children }) => {
|
||||
return (
|
||||
<div className="rounded-rounded bg-grey-0 border-grey-20 p-base medium:p-xlarge w-full border">
|
||||
@@ -63,7 +64,7 @@ const Description: React.FC<BannerCardDescriptionProps> = ({
|
||||
)
|
||||
}
|
||||
|
||||
const Footer: React.FC = ({ children }) => {
|
||||
const Footer = ({ children }: PropsWithChildren) => {
|
||||
return <div className="mt-base">{children}</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import clsx from "clsx"
|
||||
import React from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import ChevronRightIcon from "../../fundamentals/icons/chevron-right-icon"
|
||||
|
||||
type BreadcrumbProps = React.HtmlHTMLAttributes<HTMLDivElement> & {
|
||||
previousRoute?: string
|
||||
previousBreadcrumb?: string
|
||||
currentPage: string
|
||||
}
|
||||
|
||||
const Breadcrumb: React.FC<BreadcrumbProps> = ({
|
||||
previousRoute = "/a/settings",
|
||||
previousBreadcrumb = "Settings",
|
||||
currentPage,
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
const navigate = useNavigate()
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"inter-small-semibold text-grey-50 mb-4 flex w-full items-center",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span
|
||||
className="text-violet-60 cursor-pointer"
|
||||
onClick={() => navigate(previousRoute)}
|
||||
>
|
||||
{previousBreadcrumb}
|
||||
</span>
|
||||
<span className="mx-0.5">
|
||||
<ChevronRightIcon size={16} />
|
||||
</span>
|
||||
<span>{currentPage}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Breadcrumb
|
||||
@@ -64,7 +64,7 @@ const BodyCard: React.FC<BodyCardProps> = ({
|
||||
"border-grey-20 border-b border-solid": setBorders,
|
||||
})}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
{customHeader ? (
|
||||
<div>{customHeader}</div>
|
||||
@@ -112,8 +112,8 @@ const BodyCard: React.FC<BodyCardProps> = ({
|
||||
<Button
|
||||
key={i}
|
||||
onClick={event.onClick}
|
||||
className="first:ml-xsmall min-w-[130px] justify-center"
|
||||
variant={i === 0 ? "primary" : "ghost"}
|
||||
className="first:ml-xsmall justify-center"
|
||||
variant={i === 0 ? "primary" : "secondary"}
|
||||
size={"small"}
|
||||
type={event.type}
|
||||
>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useState } from "react"
|
||||
|
||||
import useNotification from "../../hooks/use-notification"
|
||||
import { getErrorMessage } from "../../utils/error-messages"
|
||||
import Button from "../fundamentals/button"
|
||||
import Modal from "../molecules/modal"
|
||||
import { getErrorMessage } from "../../utils/error-messages"
|
||||
import useNotification from "../../hooks/use-notification"
|
||||
|
||||
type DeletePromptProps = {
|
||||
heading?: string
|
||||
@@ -54,10 +54,10 @@ const DeletePrompt: React.FC<DeletePromptProps> = ({
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Footer>
|
||||
<div className="flex h-8 w-full justify-end">
|
||||
<div className="gap-x-xsmall flex h-8 w-full justify-end">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="text-small min-w-24 mr-2 justify-center"
|
||||
variant="secondary"
|
||||
className="justify-center"
|
||||
size="small"
|
||||
onClick={handleClose}
|
||||
>
|
||||
@@ -66,7 +66,7 @@ const DeletePrompt: React.FC<DeletePromptProps> = ({
|
||||
<Button
|
||||
loading={isLoading}
|
||||
size="small"
|
||||
className="text-small w-24 justify-center"
|
||||
className="justify-center"
|
||||
variant="nuclear"
|
||||
onClick={handleSubmit}
|
||||
disabled={isLoading}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useMemo } from "react"
|
||||
import { currencies } from "../../../utils/currencies"
|
||||
import { normalizeAmount } from "../../../utils/prices"
|
||||
import EditIcon from "../../fundamentals/icons/edit-icon"
|
||||
import TrashIcon from "../../fundamentals/icons/trash-icon"
|
||||
@@ -75,7 +74,7 @@ const GiftCardBanner: React.FC<GiftCardBannerProps> = ({
|
||||
)} ${defaultCurrency.toUpperCase()}`
|
||||
})
|
||||
.filter(Boolean)
|
||||
}, [variants, defaultCurrency, currencies])
|
||||
}, [variants, defaultCurrency])
|
||||
|
||||
return (
|
||||
<BannerCard title={title} thumbnail={thumbnail} actions={actions}>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { getErrorMessage } from "../../../utils/error-messages"
|
||||
import Button from "../../fundamentals/button"
|
||||
import InputField from "../../molecules/input"
|
||||
import Modal from "../../molecules/modal"
|
||||
import Select from "../../molecules/select"
|
||||
import { NextSelect } from "../../molecules/select/next-select"
|
||||
|
||||
type InviteModalProps = {
|
||||
handleClose: () => void
|
||||
@@ -68,10 +68,13 @@ const InviteModal: React.FC<InviteModalProps> = ({ handleClose }) => {
|
||||
name="role"
|
||||
control={control}
|
||||
defaultValue={{ label: "Member", value: "member" }}
|
||||
render={({ field: { value, onChange } }) => {
|
||||
render={({ field: { value, onChange, onBlur, ref } }) => {
|
||||
return (
|
||||
<Select
|
||||
<NextSelect
|
||||
label="Role"
|
||||
placeholder="Select role"
|
||||
onBlur={onBlur}
|
||||
ref={ref}
|
||||
onChange={onChange}
|
||||
options={roleOptions}
|
||||
value={value}
|
||||
|
||||
@@ -27,7 +27,7 @@ function RawJSON(props: RawJSONProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<BodyCard className={"mb-4 h-auto min-h-0 w-full"} title={title}>
|
||||
<BodyCard className={"h-auto min-h-0 w-full"} title={title}>
|
||||
<div className="flex flex-grow items-center">
|
||||
<JSONView data={data} />
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react"
|
||||
import PageDescription from "../atoms/page-description"
|
||||
import Spacer from "../atoms/spacer"
|
||||
|
||||
const SettingsOverview: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
@@ -11,6 +12,7 @@ const SettingsOverview: React.FC<React.PropsWithChildren> = ({ children }) => {
|
||||
<div className="medium:grid-cols-2 gap-x-base gap-y-xsmall grid auto-cols-fr grid-cols-1">
|
||||
{children}
|
||||
</div>
|
||||
<Spacer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ const TwoSplitPane: React.FC<TwoSplitPaneProps> = ({
|
||||
{Children.map(children, (child, i) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx("h-full w-full", {
|
||||
className={clsx("w-full", {
|
||||
"col-span-2": threeCols && i === 1,
|
||||
})}
|
||||
key={i}
|
||||
|
||||
@@ -107,7 +107,7 @@ const CollectionDetails = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="!pb-xlarge flex flex-col">
|
||||
<div className="flex flex-col">
|
||||
<BackButton
|
||||
className="mb-xsmall"
|
||||
path="/a/products?view=collections"
|
||||
|
||||
@@ -45,7 +45,7 @@ const CustomerDetail = () => {
|
||||
path="/a/customers"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
<div className="gap-y-small flex flex-col">
|
||||
<div className="gap-y-xsmall flex flex-col">
|
||||
<Section>
|
||||
<div className="flex w-full items-start justify-between">
|
||||
<div className="gap-x-base flex w-full items-center">
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Route, Routes } from "react-router-dom"
|
||||
import Spacer from "../../components/atoms/spacer"
|
||||
import BodyCard from "../../components/organisms/body-card"
|
||||
import CustomerTable from "../../components/templates/customer-table"
|
||||
import Details from "./details"
|
||||
@@ -7,15 +8,14 @@ import CustomersPageTableHeader from "./header"
|
||||
|
||||
const CustomerIndex = () => {
|
||||
return (
|
||||
<div className="flex h-full grow flex-col">
|
||||
<div className="flex w-full grow flex-col">
|
||||
<BodyCard
|
||||
customHeader={<CustomersPageTableHeader activeView="customers" />}
|
||||
className="h-fit"
|
||||
>
|
||||
<CustomerTable />
|
||||
</BodyCard>
|
||||
</div>
|
||||
<div>
|
||||
<BodyCard
|
||||
customHeader={<CustomersPageTableHeader activeView="customers" />}
|
||||
className="h-fit"
|
||||
>
|
||||
<CustomerTable />
|
||||
</BodyCard>
|
||||
<Spacer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -22,54 +22,56 @@ const DiscountDetailsConditions: React.FC<DiscountDetailsConditionsProps> = ({
|
||||
|
||||
return (
|
||||
<ConditionsProvider discount={discount}>
|
||||
<BodyCard
|
||||
title="Conditions"
|
||||
className="min-h-[200px]"
|
||||
forceDropdown
|
||||
actionables={[
|
||||
{
|
||||
label: "Add condition",
|
||||
icon: <PlusIcon size={16} />,
|
||||
onClick: () => setShow(true),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{conditions.length ? (
|
||||
<div
|
||||
style={{
|
||||
gridTemplateRows: `repeat(${Math.ceil(
|
||||
conditions?.length / 2
|
||||
)}, minmax(0, 1fr))`,
|
||||
}}
|
||||
className="gap-y-base gap-x-xlarge grid grid-flow-col grid-cols-2"
|
||||
>
|
||||
{conditions.map((condition, i) => (
|
||||
<NumberedItem
|
||||
key={i}
|
||||
title={condition.title}
|
||||
index={i + 1}
|
||||
description={condition.description}
|
||||
actions={condition.actions}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="gap-y-small flex flex-1 flex-col items-center justify-center">
|
||||
<span className="inter-base-regular text-grey-50">
|
||||
This discount has no conditions
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<BodyCard
|
||||
title="Conditions"
|
||||
className="min-h-[200px]"
|
||||
forceDropdown
|
||||
actionables={[
|
||||
{
|
||||
label: "Add condition",
|
||||
icon: <PlusIcon size={16} />,
|
||||
onClick: () => setShow(true),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{conditions.length ? (
|
||||
<div
|
||||
style={{
|
||||
gridTemplateRows: `repeat(${Math.ceil(
|
||||
conditions?.length / 2
|
||||
)}, minmax(0, 1fr))`,
|
||||
}}
|
||||
className="gap-y-base gap-x-xlarge grid grid-flow-col grid-cols-2"
|
||||
>
|
||||
{conditions.map((condition, i) => (
|
||||
<NumberedItem
|
||||
key={i}
|
||||
title={condition.title}
|
||||
index={i + 1}
|
||||
description={condition.description}
|
||||
actions={condition.actions}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="gap-y-small flex flex-1 flex-col items-center justify-center">
|
||||
<span className="inter-base-regular text-grey-50">
|
||||
This discount has no conditions
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</BodyCard>
|
||||
<AddCondition show={show} onClose={() => setShow(false)} />
|
||||
{selectedCondition && (
|
||||
<EditConditionsModal
|
||||
open={!!selectedCondition}
|
||||
condition={selectedCondition}
|
||||
discount={discount}
|
||||
onClose={() => deSelectCondition()}
|
||||
/>
|
||||
)}
|
||||
</BodyCard>
|
||||
<AddCondition show={show} onClose={() => setShow(false)} />
|
||||
{selectedCondition && (
|
||||
<EditConditionsModal
|
||||
open={!!selectedCondition}
|
||||
condition={selectedCondition}
|
||||
discount={discount}
|
||||
onClose={() => deSelectCondition()}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</ConditionsProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useAdminDeleteDiscount, useAdminDiscount } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import { useParams } from "react-router-dom"
|
||||
import BackButton from "../../../components/atoms/back-button"
|
||||
import Spinner from "../../../components/atoms/spinner"
|
||||
import Breadcrumb from "../../../components/molecules/breadcrumb"
|
||||
import DeletePrompt from "../../../components/organisms/delete-prompt"
|
||||
import RawJSON from "../../../components/organisms/raw-json"
|
||||
import useNotification from "../../../hooks/use-notification"
|
||||
@@ -50,22 +50,22 @@ const Edit = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
<Breadcrumb
|
||||
currentPage="Add Discount"
|
||||
previousBreadcrumb="Discount"
|
||||
previousRoute="/a/discounts"
|
||||
<BackButton
|
||||
label="Back to Discounts"
|
||||
path="/a/discounts"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
{isLoading || !discount ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<Spinner variant="secondary" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<div className="gap-y-xsmall flex flex-col">
|
||||
<DiscountFormProvider>
|
||||
<General discount={discount} />
|
||||
<Configurations discount={discount} />
|
||||
<DiscountDetailsConditions discount={discount} />
|
||||
<RawJSON data={discount} title="Raw discount" rootName="discount" />
|
||||
<RawJSON data={discount} title="Raw discount" />
|
||||
</DiscountFormProvider>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState } from "react"
|
||||
import { Route, Routes } from "react-router-dom"
|
||||
import Fade from "../../components/atoms/fade-wrapper"
|
||||
import Spacer from "../../components/atoms/spacer"
|
||||
import PlusIcon from "../../components/fundamentals/icons/plus-icon"
|
||||
import BodyCard from "../../components/organisms/body-card"
|
||||
import TableViewHeader from "../../components/organisms/custom-table-header"
|
||||
@@ -31,6 +32,7 @@ const DiscountIndex = () => {
|
||||
>
|
||||
<DiscountTable />
|
||||
</BodyCard>
|
||||
<Spacer />
|
||||
</div>
|
||||
<DiscountFormProvider>
|
||||
<Fade isVisible={isOpen} isFullScreen={true}>
|
||||
|
||||
@@ -1,161 +1,136 @@
|
||||
import { useAdminCreateGiftCard, useAdminRegions } from "medusa-react"
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useAdminCreateGiftCard } from "medusa-react"
|
||||
import React, { useEffect } from "react"
|
||||
import { useForm, useWatch } from "react-hook-form"
|
||||
import GiftCardBalanceForm, {
|
||||
GiftCardBalanceFormType,
|
||||
} from "../../components/forms/gift-card/gift-card-balance-form"
|
||||
import GiftCardEndsAtForm, {
|
||||
GiftCardEndsAtFormType,
|
||||
} from "../../components/forms/gift-card/gift-card-ends-at-form"
|
||||
import GiftCardReceiverForm, {
|
||||
GiftCardReceiverFormType,
|
||||
} from "../../components/forms/gift-card/gift-card-receiver-form"
|
||||
import GiftCardRegionForm, {
|
||||
GiftCardRegionFormType,
|
||||
} from "../../components/forms/gift-card/gift-card-region-form"
|
||||
import Button from "../../components/fundamentals/button"
|
||||
import InputField from "../../components/molecules/input"
|
||||
import Modal from "../../components/molecules/modal"
|
||||
import Select from "../../components/molecules/select"
|
||||
import TextArea from "../../components/molecules/textarea"
|
||||
import CurrencyInput from "../../components/organisms/currency-input"
|
||||
import useNotification from "../../hooks/use-notification"
|
||||
import { getErrorMessage } from "../../utils/error-messages"
|
||||
import { focusByName } from "../../utils/focus-by-name"
|
||||
import { validateEmail } from "../../utils/validate-email"
|
||||
import { nestedForm } from "../../utils/nested-form"
|
||||
|
||||
type CustomGiftcardProps = {
|
||||
onDismiss: () => void
|
||||
onClose: () => void
|
||||
open: boolean
|
||||
}
|
||||
|
||||
const CustomGiftcard: React.FC<CustomGiftcardProps> = ({ onDismiss }) => {
|
||||
const { isLoading, regions } = useAdminRegions()
|
||||
const [selectedRegion, setSelectedRegion] = useState<any>(null)
|
||||
const [giftCardAmount, setGiftCardAmount] = useState(0)
|
||||
type CustomGiftCardFormType = {
|
||||
region: GiftCardRegionFormType
|
||||
ends_at: GiftCardEndsAtFormType
|
||||
balance: GiftCardBalanceFormType
|
||||
receiver: GiftCardReceiverFormType
|
||||
}
|
||||
|
||||
const { register, handleSubmit } = useForm()
|
||||
const CustomGiftcard: React.FC<CustomGiftcardProps> = ({ onClose, open }) => {
|
||||
const form = useForm<CustomGiftCardFormType>()
|
||||
const {
|
||||
handleSubmit,
|
||||
reset,
|
||||
control,
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
const currencySubscriber = useWatch({
|
||||
control,
|
||||
name: "region.region_id.currency_code",
|
||||
defaultValue: "usd",
|
||||
})
|
||||
|
||||
const notification = useNotification()
|
||||
|
||||
const { mutate, isLoading: isSubmitting } = useAdminCreateGiftCard()
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading) {
|
||||
setSelectedRegion({
|
||||
value: regions[0],
|
||||
label: regions[0].name,
|
||||
})
|
||||
if (open) {
|
||||
reset()
|
||||
}
|
||||
}, [isLoading])
|
||||
}, [open, reset])
|
||||
|
||||
const onSubmit = (data) => {
|
||||
if (!giftCardAmount) {
|
||||
notification("Error", "Please enter an amount", "error")
|
||||
focusByName("amount")
|
||||
return
|
||||
}
|
||||
|
||||
if (!validateEmail(data.metadata.email)) {
|
||||
notification("Error", "Invalid email address", "error")
|
||||
focusByName("metadata.email")
|
||||
return
|
||||
}
|
||||
|
||||
const update = {
|
||||
region_id: selectedRegion.value.id,
|
||||
value: Math.round(
|
||||
giftCardAmount / (1 + selectedRegion.value.tax_rate / 100)
|
||||
),
|
||||
...data,
|
||||
}
|
||||
|
||||
mutate(update, {
|
||||
onSuccess: () => {
|
||||
notification("Success", "Created Custom Gift Card", "success")
|
||||
onDismiss()
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
mutate(
|
||||
{
|
||||
region_id: data.region.region_id.value,
|
||||
value: data.balance.amount,
|
||||
ends_at: data.ends_at.ends_at || undefined,
|
||||
metadata: {
|
||||
email: data.receiver.email,
|
||||
personal_message: data.receiver.message,
|
||||
},
|
||||
},
|
||||
onError: (error) => {
|
||||
notification("Error", getErrorMessage(error), "error")
|
||||
onDismiss()
|
||||
},
|
||||
})
|
||||
}
|
||||
{
|
||||
onSuccess: () => {
|
||||
notification(
|
||||
"Created gift card",
|
||||
"Custom gift card was created successfully",
|
||||
"success"
|
||||
)
|
||||
onClose()
|
||||
},
|
||||
onError: (error) => {
|
||||
notification("Error", getErrorMessage(error), "error")
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Modal handleClose={onDismiss}>
|
||||
<Modal open={open} handleClose={onClose}>
|
||||
<Modal.Body>
|
||||
<Modal.Header handleClose={onDismiss}>
|
||||
<Modal.Header handleClose={onClose}>
|
||||
<h2 className="inter-xlarge-semibold">Custom Gift Card</h2>
|
||||
</Modal.Header>
|
||||
<Modal.Content>
|
||||
<div className="flex flex-col">
|
||||
<span className="inter-base-semibold">Value</span>
|
||||
<div className="gap-x-2xsmall mt-4 flex">
|
||||
<div className="w-[267px]">
|
||||
<Select
|
||||
label={"Region"}
|
||||
value={selectedRegion}
|
||||
onChange={(value) => setSelectedRegion(value)}
|
||||
options={
|
||||
regions?.map((r) => ({
|
||||
value: r,
|
||||
label: r.name,
|
||||
})) || []
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-[415px]">
|
||||
<CurrencyInput.Root
|
||||
size="medium"
|
||||
currencyCodes={
|
||||
isLoading ? undefined : regions?.map((r) => r.currency_code)
|
||||
}
|
||||
readOnly
|
||||
currentCurrency={selectedRegion?.value?.currency_code}
|
||||
>
|
||||
<CurrencyInput.Amount
|
||||
label={"Amount"}
|
||||
amount={giftCardAmount}
|
||||
onChange={(value) => {
|
||||
setGiftCardAmount(value || 0)
|
||||
}}
|
||||
name="amount"
|
||||
required={true}
|
||||
<form onSubmit={onSubmit}>
|
||||
<Modal.Content>
|
||||
<div className="gap-y-xlarge flex flex-col">
|
||||
<div>
|
||||
<h2 className="inter-base-semibold mb-base">Details</h2>
|
||||
<div className="gap-x-xsmall grid grid-cols-2">
|
||||
<GiftCardRegionForm form={nestedForm(form, "region")} />
|
||||
<GiftCardBalanceForm
|
||||
form={nestedForm(form, "balance")}
|
||||
currencyCode={currencySubscriber}
|
||||
/>
|
||||
</CurrencyInput.Root>
|
||||
</div>
|
||||
</div>
|
||||
<GiftCardEndsAtForm form={nestedForm(form, "ends_at")} />
|
||||
<div>
|
||||
<h2 className="inter-base-semibold mb-base">Receiver</h2>
|
||||
<GiftCardReceiverForm form={nestedForm(form, "receiver")} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8">
|
||||
<span className="inter-base-semibold">Receiver</span>
|
||||
<div className="gap-y-xsmall mt-4 grid grid-cols-1">
|
||||
<InputField
|
||||
label={"Email"}
|
||||
required
|
||||
{...register("metadata.email", { required: true })}
|
||||
placeholder="lebron@james.com"
|
||||
type="email"
|
||||
/>
|
||||
<TextArea
|
||||
label={"Personal Message"}
|
||||
rows={7}
|
||||
placeholder="Something nice to someone special"
|
||||
{...register("metadata.personal_message")}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Footer>
|
||||
<div className="gap-x-xsmall flex w-full justify-end">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={onClose}
|
||||
size="small"
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
size="small"
|
||||
disabled={isSubmitting || !isDirty}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Create and send
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Footer>
|
||||
<div className="gap-x-xsmall flex w-full justify-end">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={onDismiss}
|
||||
size="small"
|
||||
className="w-[112px]"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
type="submit"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
size="small"
|
||||
className="w-[112px]"
|
||||
disabled={isSubmitting}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Create & Send
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
</Modal.Footer>
|
||||
</form>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@@ -1,111 +1,137 @@
|
||||
import { AdminPostGiftCardsGiftCardReq, Region } from "@medusajs/medusa"
|
||||
import { useMemo } from "react"
|
||||
import { Controller, useForm } from "react-hook-form"
|
||||
import { GiftCard } from "@medusajs/medusa"
|
||||
import { useAdminUpdateGiftCard } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import GiftCardEndsAtForm, {
|
||||
GiftCardEndsAtFormType,
|
||||
} from "../../../components/forms/gift-card/gift-card-ends-at-form"
|
||||
import GiftCardRegionForm, {
|
||||
GiftCardRegionFormType,
|
||||
} from "../../../components/forms/gift-card/gift-card-region-form"
|
||||
import Button from "../../../components/fundamentals/button"
|
||||
import Modal from "../../../components/molecules/modal"
|
||||
import Select from "../../../components/molecules/select"
|
||||
import { Option } from "../../../types/shared"
|
||||
import useNotification from "../../../hooks/use-notification"
|
||||
import { getErrorMessage } from "../../../utils/error-messages"
|
||||
import { nestedForm } from "../../../utils/nested-form"
|
||||
|
||||
type EditGiftCardModalProps = {
|
||||
handleClose: () => void
|
||||
handleSave: (update: AdminPostGiftCardsGiftCardReq) => void
|
||||
updating: boolean
|
||||
regions: Region[] | undefined
|
||||
region: Region
|
||||
onClose: () => void
|
||||
open: boolean
|
||||
giftCard: GiftCard
|
||||
}
|
||||
|
||||
type EditGiftCardModalFormData = {
|
||||
region: Option
|
||||
type EditGiftCardFormType = {
|
||||
region: GiftCardRegionFormType
|
||||
ends_at: GiftCardEndsAtFormType
|
||||
}
|
||||
|
||||
const EditGiftCardModal = ({
|
||||
handleClose,
|
||||
handleSave,
|
||||
updating,
|
||||
regions,
|
||||
region,
|
||||
open,
|
||||
onClose,
|
||||
giftCard,
|
||||
}: EditGiftCardModalProps) => {
|
||||
const { control, handleSubmit } = useForm<EditGiftCardModalFormData>({
|
||||
defaultValues: {
|
||||
region: {
|
||||
value: region.id,
|
||||
label: region.name,
|
||||
const form = useForm<EditGiftCardFormType>({
|
||||
defaultValues: getDefaultValues(giftCard),
|
||||
})
|
||||
const {
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
const { mutate, isLoading } = useAdminUpdateGiftCard(giftCard.id)
|
||||
|
||||
const notification = useNotification()
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
mutate(
|
||||
{
|
||||
region_id: data.region.region_id.value,
|
||||
ends_at: data.ends_at.ends_at,
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
notification(
|
||||
"Updated Gift card",
|
||||
"Gift card was succesfully updated",
|
||||
"success"
|
||||
)
|
||||
|
||||
onClose()
|
||||
},
|
||||
onError: (err) => {
|
||||
notification(
|
||||
"Failed to update Gift card",
|
||||
getErrorMessage(err),
|
||||
"error"
|
||||
)
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
const onSubmit = (data: EditGiftCardModalFormData) => {
|
||||
handleSave({ region_id: data.region.value })
|
||||
}
|
||||
|
||||
const regionOptions: Option[] = useMemo(() => {
|
||||
return (
|
||||
regions?.map((r) => ({
|
||||
label: r.name,
|
||||
value: r.id,
|
||||
})) || []
|
||||
)
|
||||
}, [regions])
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
reset(getDefaultValues(giftCard))
|
||||
}
|
||||
}, [open, reset, giftCard])
|
||||
|
||||
return (
|
||||
<Modal handleClose={handleClose} isLargeModal={true}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Modal.Body isLargeModal={true}>
|
||||
<Modal.Header handleClose={handleClose}>
|
||||
<span className="inter-xlarge-semibold">
|
||||
Edit Gift Card Details
|
||||
</span>
|
||||
</Modal.Header>
|
||||
<Modal open={open} handleClose={onClose}>
|
||||
<Modal.Body>
|
||||
<Modal.Header handleClose={onClose}>
|
||||
<h1 className="inter-xlarge-semibold">Edit Gift Card</h1>
|
||||
</Modal.Header>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Modal.Content>
|
||||
{/* TODO: Missing backend support for updating code
|
||||
<InputField
|
||||
label="Code"
|
||||
name="code"
|
||||
value={code}
|
||||
onChange={({ currentTarget }) => setCode(currentTarget.value)}
|
||||
className="mb-4"
|
||||
/> */}
|
||||
<Controller
|
||||
control={control}
|
||||
name="region"
|
||||
render={({ field: { value, onChange } }) => {
|
||||
return (
|
||||
<Select
|
||||
label="Region"
|
||||
options={regionOptions}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<div className="gap-y-xlarge flex flex-col">
|
||||
<div>
|
||||
<h2 className="inter-base-semibold mb-base">Details</h2>
|
||||
<GiftCardRegionForm form={nestedForm(form, "region")} />
|
||||
</div>
|
||||
<GiftCardEndsAtForm form={nestedForm(form, "ends_at")} />
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Footer>
|
||||
<div className="flex w-full justify-end">
|
||||
<div className="gap-x-xsmall flex w-full justify-end">
|
||||
<Button
|
||||
variant="ghost"
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={handleClose}
|
||||
className="mr-2"
|
||||
onClick={onClose}
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
loading={updating}
|
||||
disabled={updating}
|
||||
variant="primary"
|
||||
className="min-w-[100px]"
|
||||
size="small"
|
||||
type="submit"
|
||||
disabled={isLoading || !isDirty}
|
||||
loading={isLoading}
|
||||
>
|
||||
Save
|
||||
Save and close
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
</Modal.Body>
|
||||
</form>
|
||||
</form>
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const getDefaultValues = (giftCard: GiftCard): EditGiftCardFormType => {
|
||||
return {
|
||||
region: {
|
||||
region_id: {
|
||||
label: giftCard.region.name,
|
||||
value: giftCard.region.id,
|
||||
currency_code: giftCard.region.currency_code,
|
||||
},
|
||||
},
|
||||
ends_at: {
|
||||
ends_at: giftCard.ends_at,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default EditGiftCardModal
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
import { AdminPostGiftCardsGiftCardReq } from "@medusajs/medusa"
|
||||
import {
|
||||
useAdminGiftCard,
|
||||
useAdminRegions,
|
||||
useAdminUpdateGiftCard,
|
||||
} from "medusa-react"
|
||||
import { useAdminGiftCard, useAdminUpdateGiftCard } from "medusa-react"
|
||||
import moment from "moment"
|
||||
import { useState } from "react"
|
||||
import { useParams } from "react-router-dom"
|
||||
import BackButton from "../../../components/atoms/back-button"
|
||||
import Spinner from "../../../components/atoms/spinner"
|
||||
import Badge from "../../../components/fundamentals/badge"
|
||||
import DollarSignIcon from "../../../components/fundamentals/icons/dollar-sign-icon"
|
||||
import EditIcon from "../../../components/fundamentals/icons/edit-icon"
|
||||
import PublishIcon from "../../../components/fundamentals/icons/publish-icon"
|
||||
import UnpublishIcon from "../../../components/fundamentals/icons/unpublish-icon"
|
||||
import Breadcrumb from "../../../components/molecules/breadcrumb"
|
||||
import StatusSelector from "../../../components/molecules/status-selector"
|
||||
import BodyCard from "../../../components/organisms/body-card"
|
||||
import RawJSON from "../../../components/organisms/raw-json"
|
||||
import useNotification from "../../../hooks/use-notification"
|
||||
import useToggleState from "../../../hooks/use-toggle-state"
|
||||
import { getErrorMessage } from "../../../utils/error-messages"
|
||||
import { formatAmountWithSymbol } from "../../../utils/prices"
|
||||
import EditGiftCardModal from "./edit-gift-card-modal"
|
||||
@@ -29,45 +21,46 @@ const GiftCardDetails = () => {
|
||||
const { gift_card: giftCard, isLoading } = useAdminGiftCard(id!, {
|
||||
enabled: !!id,
|
||||
})
|
||||
const { regions } = useAdminRegions()
|
||||
|
||||
const updateGiftCard = useAdminUpdateGiftCard(giftCard?.id!)
|
||||
|
||||
const notification = useNotification()
|
||||
|
||||
const [showUpdateBalance, setShowUpdateBalance] = useState(false)
|
||||
const [showEdit, setShowEdit] = useState(false)
|
||||
const {
|
||||
state: editState,
|
||||
open: openEdit,
|
||||
close: closeEdit,
|
||||
} = useToggleState()
|
||||
|
||||
const {
|
||||
state: balanceState,
|
||||
open: openBalance,
|
||||
close: closeBalance,
|
||||
} = useToggleState()
|
||||
|
||||
const actions = [
|
||||
{
|
||||
label: "Edit",
|
||||
onClick: () => setShowEdit(true),
|
||||
label: "Edit details",
|
||||
onClick: openEdit,
|
||||
icon: <EditIcon size={20} />,
|
||||
},
|
||||
{
|
||||
label: `${giftCard?.is_disabled ? "Activate" : "Disable"}`,
|
||||
onClick: () => handleUpdate({ is_disabled: !giftCard?.is_disabled }),
|
||||
icon: giftCard?.is_disabled ? (
|
||||
<PublishIcon size={20} />
|
||||
) : (
|
||||
<UnpublishIcon size={20} />
|
||||
),
|
||||
},
|
||||
{
|
||||
label: "Update balance",
|
||||
onClick: () => setShowUpdateBalance(true),
|
||||
onClick: openBalance,
|
||||
icon: <DollarSignIcon size={20} />,
|
||||
},
|
||||
]
|
||||
|
||||
const handleUpdate = (data: AdminPostGiftCardsGiftCardReq) => {
|
||||
const updateStatus = (data: { is_disabled?: boolean }) => {
|
||||
updateGiftCard.mutate(
|
||||
{ ...data },
|
||||
{ is_disabled: data.is_disabled },
|
||||
{
|
||||
onSuccess: () => {
|
||||
notification("Success", "Succesfully updated Gift Card", "success")
|
||||
setShowEdit(false)
|
||||
setShowUpdateBalance(false)
|
||||
notification(
|
||||
"Updated status",
|
||||
"Succesfully updated the status of the Gift Card",
|
||||
"success"
|
||||
)
|
||||
},
|
||||
onError: (err) => notification("Error", getErrorMessage(err), "error"),
|
||||
}
|
||||
@@ -76,10 +69,10 @@ const GiftCardDetails = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Breadcrumb
|
||||
currentPage={"Gift Card Details"}
|
||||
previousBreadcrumb={"Gift Cards"}
|
||||
previousRoute="/a/gift-cards"
|
||||
<BackButton
|
||||
label="Back to Gift Cards"
|
||||
path="/a/gift-cards"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
{isLoading || !giftCard ? (
|
||||
<div className="bg-grey-0 border-grey-20 rounded-rounded py-xlarge flex w-full items-center justify-center border">
|
||||
@@ -87,83 +80,89 @@ const GiftCardDetails = () => {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<BodyCard
|
||||
className={"h-auto min-h-0 w-full"}
|
||||
title={`${giftCard?.code}`}
|
||||
subtitle={`Gift Card id: ${giftCard?.id}`}
|
||||
status={
|
||||
<StatusSelector
|
||||
isDraft={!!giftCard?.is_disabled}
|
||||
activeState={"Active"}
|
||||
draftState={"Disable"}
|
||||
onChange={() =>
|
||||
handleUpdate({ is_disabled: !giftCard?.is_disabled })
|
||||
}
|
||||
/>
|
||||
}
|
||||
actionables={actions}
|
||||
>
|
||||
<div className="flex justify-between">
|
||||
<div className="mt-6 flex space-x-6 divide-x">
|
||||
<div className="flex flex-col">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
Original amount
|
||||
<div className="gap-y-xsmall flex flex-col">
|
||||
<BodyCard
|
||||
className={"h-auto min-h-0 w-full"}
|
||||
title={`${giftCard?.code}`}
|
||||
status={
|
||||
<StatusSelector
|
||||
isDraft={!!giftCard?.is_disabled}
|
||||
activeState={"Active"}
|
||||
draftState={"Disable"}
|
||||
onChange={() =>
|
||||
updateStatus({ is_disabled: !giftCard.is_disabled })
|
||||
}
|
||||
/>
|
||||
}
|
||||
actionables={actions}
|
||||
>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex space-x-6 divide-x">
|
||||
<div className="flex flex-col">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
Original amount
|
||||
</div>
|
||||
<div>
|
||||
{formatAmountWithSymbol({
|
||||
amount: giftCard.value,
|
||||
currency: giftCard.region.currency_code,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{formatAmountWithSymbol({
|
||||
amount: giftCard?.value,
|
||||
currency: giftCard?.region.currency_code,
|
||||
})}
|
||||
<div className="flex flex-col pl-6">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
Balance
|
||||
</div>
|
||||
<div>
|
||||
{formatAmountWithSymbol({
|
||||
amount: giftCard.balance,
|
||||
currency: giftCard.region.currency_code,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col pl-6">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
Balance
|
||||
<div className="flex flex-col pl-6">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
Region
|
||||
</div>
|
||||
<div>{giftCard.region.name}</div>
|
||||
</div>
|
||||
<div>
|
||||
{formatAmountWithSymbol({
|
||||
amount: giftCard?.balance,
|
||||
currency: giftCard?.region.currency_code,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col pl-6">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
Created
|
||||
</div>
|
||||
<div>
|
||||
{moment(giftCard?.created_at).format("DD MMM YYYY")}
|
||||
{giftCard.ends_at && (
|
||||
<div className="flex flex-col pl-6">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
Expires on
|
||||
</div>
|
||||
<div>
|
||||
{moment(giftCard.ends_at).format("DD MMM YYYY")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col pl-6">
|
||||
<div className="inter-smaller-regular text-grey-50 mb-1">
|
||||
Created
|
||||
</div>
|
||||
<div>
|
||||
{moment(giftCard.created_at).format("DD MMM YYYY")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-end">
|
||||
<Badge variant="default">{giftCard?.region?.name}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</BodyCard>
|
||||
<div className="mt-large">
|
||||
<RawJSON data={giftCard} title="Raw gift card" rootName="product" />
|
||||
</BodyCard>
|
||||
<RawJSON data={giftCard} title="Raw gift card" />
|
||||
</div>
|
||||
|
||||
<UpdateBalanceModal
|
||||
giftCard={giftCard}
|
||||
onClose={closeBalance}
|
||||
open={balanceState}
|
||||
/>
|
||||
|
||||
<EditGiftCardModal
|
||||
onClose={closeEdit}
|
||||
open={editState}
|
||||
giftCard={giftCard}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{showUpdateBalance && giftCard && (
|
||||
<UpdateBalanceModal
|
||||
giftCard={giftCard}
|
||||
currencyCode={giftCard?.region.currency_code}
|
||||
handleClose={() => setShowUpdateBalance(false)}
|
||||
handleSave={handleUpdate}
|
||||
updating={updateGiftCard.isLoading}
|
||||
/>
|
||||
)}
|
||||
{showEdit && giftCard && (
|
||||
<EditGiftCardModal
|
||||
handleClose={() => setShowEdit(false)}
|
||||
handleSave={handleUpdate}
|
||||
regions={regions}
|
||||
region={giftCard.region}
|
||||
updating={updateGiftCard.isLoading}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,109 +1,109 @@
|
||||
import { AdminPostGiftCardsGiftCardReq, GiftCard } from "@medusajs/medusa"
|
||||
import clsx from "clsx"
|
||||
import { Controller, useForm, useWatch } from "react-hook-form"
|
||||
import Tooltip from "../../../components/atoms/tooltip"
|
||||
import { GiftCard } from "@medusajs/medusa"
|
||||
import { useAdminUpdateGiftCard } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import GiftCardBalanceForm, {
|
||||
GiftCardBalanceFormType,
|
||||
} from "../../../components/forms/gift-card/gift-card-balance-form"
|
||||
import Button from "../../../components/fundamentals/button"
|
||||
import Modal from "../../../components/molecules/modal"
|
||||
import CurrencyInput from "../../../components/organisms/currency-input"
|
||||
import useNotification from "../../../hooks/use-notification"
|
||||
import { getErrorMessage } from "../../../utils/error-messages"
|
||||
import { nestedForm } from "../../../utils/nested-form"
|
||||
|
||||
type UpdateBalanceModalProps = {
|
||||
handleClose: () => void
|
||||
handleSave: (update: AdminPostGiftCardsGiftCardReq) => void
|
||||
currencyCode: string
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
giftCard: GiftCard
|
||||
updating: boolean
|
||||
}
|
||||
|
||||
type UpdateBalanceModalFormData = {
|
||||
balance: number
|
||||
balance: GiftCardBalanceFormType
|
||||
}
|
||||
|
||||
const UpdateBalanceModal = ({
|
||||
handleClose,
|
||||
handleSave,
|
||||
currencyCode,
|
||||
open,
|
||||
onClose,
|
||||
giftCard,
|
||||
updating,
|
||||
}: UpdateBalanceModalProps) => {
|
||||
const { control, handleSubmit } = useForm<UpdateBalanceModalFormData>({
|
||||
defaultValues: {
|
||||
balance: giftCard.balance,
|
||||
},
|
||||
const form = useForm<UpdateBalanceModalFormData>({
|
||||
defaultValues: getDefaultValues(giftCard),
|
||||
})
|
||||
|
||||
const balance = useWatch({
|
||||
control,
|
||||
name: "balance",
|
||||
const {
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
const { mutate, isLoading } = useAdminUpdateGiftCard(giftCard.id)
|
||||
|
||||
const notification = useNotification()
|
||||
|
||||
const onSubmit = handleSubmit((data) => {
|
||||
mutate(
|
||||
{
|
||||
balance: data.balance.amount,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
notification(
|
||||
"Balance updated",
|
||||
"Gift card balance was updated",
|
||||
"success"
|
||||
)
|
||||
|
||||
onClose()
|
||||
},
|
||||
onError: (err) => {
|
||||
notification(
|
||||
"Failed to update balance",
|
||||
getErrorMessage(err),
|
||||
"error"
|
||||
)
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
reset(getDefaultValues(giftCard))
|
||||
}
|
||||
}, [open, reset, giftCard])
|
||||
|
||||
return (
|
||||
<Modal handleClose={handleClose}>
|
||||
<Modal open={open} handleClose={onClose}>
|
||||
<Modal.Body>
|
||||
<form onSubmit={handleSubmit(handleSave)}>
|
||||
<Modal.Header handleClose={handleClose}>
|
||||
<span className="inter-xlarge-semibold">Update Balance</span>
|
||||
<span
|
||||
className={clsx(
|
||||
"inter-small-regular mt-2xsmall transition-display text-rose-50 delay-75",
|
||||
{
|
||||
hidden: !(balance > giftCard.value),
|
||||
}
|
||||
)}
|
||||
>
|
||||
Balance can't be updated to a value that is greater than the
|
||||
original amount
|
||||
</span>
|
||||
</Modal.Header>
|
||||
<Modal.Header handleClose={onClose}>
|
||||
<h1 className="inter-xlarge-semibold">Update Balance</h1>
|
||||
</Modal.Header>
|
||||
<form onSubmit={onSubmit}>
|
||||
<Modal.Content>
|
||||
<CurrencyInput.Root
|
||||
readOnly
|
||||
currentCurrency={currencyCode}
|
||||
size="small"
|
||||
>
|
||||
<Controller
|
||||
control={control}
|
||||
name="balance"
|
||||
rules={{
|
||||
required: true,
|
||||
}}
|
||||
render={({ field: { value, onChange } }) => {
|
||||
return (
|
||||
<CurrencyInput.Amount
|
||||
amount={value}
|
||||
label="Price"
|
||||
onChange={onChange}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</CurrencyInput.Root>
|
||||
<GiftCardBalanceForm
|
||||
form={nestedForm(form, "balance")}
|
||||
currencyCode={giftCard.region.currency_code}
|
||||
originalAmount={giftCard.value}
|
||||
/>
|
||||
</Modal.Content>
|
||||
<Modal.Footer>
|
||||
<div className="flex w-full justify-end">
|
||||
<div className="gap-x-xsmall flex w-full justify-end">
|
||||
<Button
|
||||
variant="ghost"
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={handleClose}
|
||||
className="mr-2"
|
||||
onClick={onClose}
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
loading={updating}
|
||||
variant="primary"
|
||||
className="min-w-[100px]"
|
||||
size="small"
|
||||
type="submit"
|
||||
disabled={balance > giftCard.value || updating}
|
||||
loading={isLoading}
|
||||
disabled={isLoading || !isDirty}
|
||||
>
|
||||
{balance > giftCard.value ? (
|
||||
<Tooltip content="Balance is above original value">
|
||||
Save
|
||||
</Tooltip>
|
||||
) : (
|
||||
"Save"
|
||||
)}
|
||||
Save and close
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Footer>
|
||||
@@ -112,4 +112,13 @@ const UpdateBalanceModal = ({
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const getDefaultValues = (giftCard: GiftCard): UpdateBalanceModalFormData => {
|
||||
return {
|
||||
balance: {
|
||||
amount: giftCard.balance,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default UpdateBalanceModal
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import {
|
||||
useAdminDeleteProduct,
|
||||
useAdminProducts,
|
||||
@@ -7,6 +8,7 @@ import {
|
||||
import { useMemo, useState } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import PageDescription from "../../components/atoms/page-description"
|
||||
import Spacer from "../../components/atoms/spacer"
|
||||
import Spinner from "../../components/atoms/spinner"
|
||||
import PlusIcon from "../../components/fundamentals/icons/plus-icon"
|
||||
import BannerCard from "../../components/molecules/banner-card"
|
||||
@@ -15,6 +17,7 @@ import DeletePrompt from "../../components/organisms/delete-prompt"
|
||||
import GiftCardBanner from "../../components/organisms/gift-card-banner"
|
||||
import GiftCardTable from "../../components/templates/gift-card-table"
|
||||
import useNotification from "../../hooks/use-notification"
|
||||
import useToggleState from "../../hooks/use-toggle-state"
|
||||
import { ProductStatus } from "../../types/shared"
|
||||
import { getErrorMessage } from "../../utils/error-messages"
|
||||
import CustomGiftcard from "./custom-giftcard"
|
||||
@@ -26,8 +29,6 @@ const Overview = () => {
|
||||
})
|
||||
const { store } = useAdminStore()
|
||||
const [showCreate, setShowCreate] = useState(false)
|
||||
const [showCreateCustom, setShowCreateCustom] = useState(false)
|
||||
const [showDelete, setShowDelete] = useState(false)
|
||||
|
||||
const giftCard = products?.[0]
|
||||
|
||||
@@ -36,6 +37,20 @@ const Overview = () => {
|
||||
const updateGiftCard = useAdminUpdateProduct(giftCard?.id!)
|
||||
const deleteGiftCard = useAdminDeleteProduct(giftCard?.id!)
|
||||
|
||||
const {
|
||||
state: stateCustom,
|
||||
close: closeCustom,
|
||||
open: openCustom,
|
||||
} = useToggleState()
|
||||
|
||||
const { state: stateNew, close: closeNew, open: openNew } = useToggleState()
|
||||
|
||||
const {
|
||||
state: stateDelete,
|
||||
close: closeDelete,
|
||||
open: openDelete,
|
||||
} = useToggleState()
|
||||
|
||||
const onUpdate = () => {
|
||||
let status: ProductStatus = ProductStatus.PUBLISHED
|
||||
if (giftCard?.status === "published") {
|
||||
@@ -43,7 +58,6 @@ const Overview = () => {
|
||||
}
|
||||
|
||||
updateGiftCard.mutate(
|
||||
// @ts-ignore
|
||||
{ status },
|
||||
{
|
||||
onSuccess: () =>
|
||||
@@ -64,7 +78,7 @@ const Overview = () => {
|
||||
const actionables = [
|
||||
{
|
||||
label: "Custom Gift Card",
|
||||
onClick: () => setShowCreateCustom(true),
|
||||
onClick: openCustom,
|
||||
icon: <PlusIcon size={20} />,
|
||||
},
|
||||
]
|
||||
@@ -74,69 +88,70 @@ const Overview = () => {
|
||||
return null
|
||||
}
|
||||
|
||||
return { ...giftCard, defaultCurrency: store.default_currency_code }
|
||||
return {
|
||||
...(giftCard as Product),
|
||||
defaultCurrency: store.default_currency_code,
|
||||
}
|
||||
}, [giftCard, store])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="pb-xlarge flex h-full grow flex-col">
|
||||
<div className="flex flex-col">
|
||||
<PageDescription
|
||||
title="Gift Cards"
|
||||
subtitle="Manage the Gift Cards of your Medusa store"
|
||||
/>
|
||||
{!isLoading ? (
|
||||
<>
|
||||
<div className="mb-base">
|
||||
{giftCardWithCurrency ? (
|
||||
<GiftCardBanner
|
||||
{...giftCardWithCurrency}
|
||||
onDelete={() => setShowDelete(true)}
|
||||
onEdit={() => navigate("/a/gift-cards/manage")}
|
||||
onUnpublish={onUpdate}
|
||||
/>
|
||||
) : (
|
||||
<BannerCard title="Are you ready to sell your first Gift Card?">
|
||||
<BannerCard.Description
|
||||
cta={{
|
||||
label: "Create Gift Card",
|
||||
onClick: () => setShowCreate(true),
|
||||
}}
|
||||
>
|
||||
No Gift Card has been added yet.
|
||||
</BannerCard.Description>
|
||||
</BannerCard>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex w-full grow flex-col">
|
||||
<BodyCard
|
||||
title="History"
|
||||
subtitle="See the history of purchased Gift Cards"
|
||||
actionables={actionables}
|
||||
className="h-fit"
|
||||
>
|
||||
<GiftCardTable />
|
||||
</BodyCard>
|
||||
</div>
|
||||
</>
|
||||
<div className="gap-y-xsmall flex flex-col">
|
||||
{giftCardWithCurrency ? (
|
||||
<GiftCardBanner
|
||||
{...giftCardWithCurrency}
|
||||
onDelete={openDelete}
|
||||
onEdit={() => navigate("/a/gift-cards/manage")}
|
||||
onUnpublish={onUpdate}
|
||||
/>
|
||||
) : (
|
||||
<BannerCard title="Are you ready to sell your first Gift Card?">
|
||||
<BannerCard.Description
|
||||
cta={{
|
||||
label: "Create Gift Card",
|
||||
onClick: () => setShowCreate(true),
|
||||
}}
|
||||
>
|
||||
No Gift Card has been added yet.
|
||||
</BannerCard.Description>
|
||||
</BannerCard>
|
||||
)}
|
||||
|
||||
<BodyCard
|
||||
title="History"
|
||||
subtitle="See the history of purchased Gift Cards"
|
||||
actionables={actionables}
|
||||
>
|
||||
<GiftCardTable />
|
||||
</BodyCard>
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-rounded border-grey-20 flex h-44 w-full items-center justify-center border">
|
||||
<Spinner variant="secondary" size="large" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{showCreateCustom && (
|
||||
<CustomGiftcard onDismiss={() => setShowCreateCustom(false)} />
|
||||
)}
|
||||
|
||||
<CustomGiftcard onClose={closeCustom} open={stateCustom} />
|
||||
|
||||
{showCreate && <NewGiftCard onClose={() => setShowCreate(!showCreate)} />}
|
||||
{showDelete && (
|
||||
|
||||
{stateDelete && (
|
||||
<DeletePrompt
|
||||
handleClose={() => setShowDelete(!showDelete)}
|
||||
handleClose={closeDelete}
|
||||
onDelete={async () => onDelete()}
|
||||
successText="Successfully deleted Gift Card"
|
||||
confirmText="Yes, delete"
|
||||
heading="Delete Gift Card"
|
||||
/>
|
||||
)}
|
||||
<Spacer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Spacer from "../../../components/atoms/spacer"
|
||||
import BodyCard from "../../../components/organisms/body-card"
|
||||
import InventoryTable from "../../../components/templates/inventory-table"
|
||||
import InventoryPageTableHeader from "../header"
|
||||
@@ -12,6 +13,7 @@ const InventoryView = () => {
|
||||
>
|
||||
<InventoryTable />
|
||||
</BodyCard>
|
||||
<Spacer />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -30,6 +30,7 @@ import moment from "moment"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useHotkeys } from "react-hotkeys-hook"
|
||||
import Avatar from "../../../components/atoms/avatar"
|
||||
import BackButton from "../../../components/atoms/back-button"
|
||||
import Spinner from "../../../components/atoms/spinner"
|
||||
import Tooltip from "../../../components/atoms/tooltip"
|
||||
import Button from "../../../components/fundamentals/button"
|
||||
@@ -42,7 +43,6 @@ import MailIcon from "../../../components/fundamentals/icons/mail-icon"
|
||||
import RefreshIcon from "../../../components/fundamentals/icons/refresh-icon"
|
||||
import TruckIcon from "../../../components/fundamentals/icons/truck-icon"
|
||||
import { ActionType } from "../../../components/molecules/actionables"
|
||||
import Breadcrumb from "../../../components/molecules/breadcrumb"
|
||||
import JSONView from "../../../components/molecules/json-view"
|
||||
import BodyCard from "../../../components/organisms/body-card"
|
||||
import RawJSON from "../../../components/organisms/raw-json"
|
||||
@@ -285,10 +285,10 @@ const OrderDetails = () => {
|
||||
return (
|
||||
<div>
|
||||
<OrderEditProvider orderId={id!}>
|
||||
<Breadcrumb
|
||||
currentPage={"Order Details"}
|
||||
previousBreadcrumb={"Orders"}
|
||||
previousRoute="/a/orders"
|
||||
<BackButton
|
||||
path="/a/orders"
|
||||
label="Back to Orders"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
{isLoading || !order ? (
|
||||
<BodyCard className="pt-2xlarge flex w-full items-center justify-center">
|
||||
|
||||
@@ -10,6 +10,7 @@ import moment from "moment"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useNavigate, useParams } from "react-router-dom"
|
||||
import Avatar from "../../../components/atoms/avatar"
|
||||
import BackButton from "../../../components/atoms/back-button"
|
||||
import CopyToClipboard from "../../../components/atoms/copy-to-clipboard"
|
||||
import Spinner from "../../../components/atoms/spinner"
|
||||
import Badge from "../../../components/fundamentals/badge"
|
||||
@@ -19,7 +20,6 @@ import DollarSignIcon from "../../../components/fundamentals/icons/dollar-sign-i
|
||||
import TruckIcon from "../../../components/fundamentals/icons/truck-icon"
|
||||
import ImagePlaceholder from "../../../components/fundamentals/image-placeholder"
|
||||
import StatusDot from "../../../components/fundamentals/status-indicator"
|
||||
import Breadcrumb from "../../../components/molecules/breadcrumb"
|
||||
import JSONView from "../../../components/molecules/json-view"
|
||||
import BodyCard from "../../../components/organisms/body-card"
|
||||
import ConfirmationPrompt from "../../../components/organisms/confirmation-prompt"
|
||||
@@ -125,10 +125,10 @@ const DraftOrderDetails = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Breadcrumb
|
||||
currentPage={"Draft Order Details"}
|
||||
previousBreadcrumb={"Draft Orders"}
|
||||
previousRoute="/a/draft-orders"
|
||||
<BackButton
|
||||
path="/a/draft-orders"
|
||||
label="Back to Draft Orders"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
{isLoading || !draft_order ? (
|
||||
<BodyCard className="pt-2xlarge flex w-full items-center justify-center">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useMemo, useState } from "react"
|
||||
import { Route, Routes, useNavigate } from "react-router-dom"
|
||||
import Spacer from "../../../components/atoms/spacer"
|
||||
|
||||
import PlusIcon from "../../../components/fundamentals/icons/plus-icon"
|
||||
import BodyCard from "../../../components/organisms/body-card"
|
||||
@@ -47,6 +48,7 @@ const DraftOrderIndex = () => {
|
||||
>
|
||||
<DraftOrderTable />
|
||||
</BodyCard>
|
||||
<Spacer />
|
||||
</div>
|
||||
{showNewOrder && (
|
||||
<NewOrderFormProvider>
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { Route, Routes, useNavigate } from "react-router-dom"
|
||||
import { useMemo, useState } from "react"
|
||||
import { Route, Routes, useNavigate } from "react-router-dom"
|
||||
|
||||
import BodyCard from "../../components/organisms/body-card"
|
||||
import { useAdminCreateBatchJob } from "medusa-react"
|
||||
import Spacer from "../../components/atoms/spacer"
|
||||
import Button from "../../components/fundamentals/button"
|
||||
import Details from "./details"
|
||||
import ExportIcon from "../../components/fundamentals/icons/export-icon"
|
||||
import BodyCard from "../../components/organisms/body-card"
|
||||
import TableViewHeader from "../../components/organisms/custom-table-header"
|
||||
import ExportModal from "../../components/organisms/export-modal"
|
||||
import OrderTable from "../../components/templates/order-table"
|
||||
import TableViewHeader from "../../components/organisms/custom-table-header"
|
||||
import { getErrorMessage } from "../../utils/error-messages"
|
||||
import { transformFiltersAsExportContext } from "./utils"
|
||||
import { useAdminCreateBatchJob } from "medusa-react"
|
||||
import useNotification from "../../hooks/use-notification"
|
||||
import { usePolling } from "../../providers/polling-provider"
|
||||
import useToggleState from "../../hooks/use-toggle-state"
|
||||
import { usePolling } from "../../providers/polling-provider"
|
||||
import { getErrorMessage } from "../../utils/error-messages"
|
||||
import Details from "./details"
|
||||
import { transformFiltersAsExportContext } from "./utils"
|
||||
|
||||
const VIEWS = ["orders", "drafts"]
|
||||
|
||||
@@ -37,6 +38,7 @@ const OrderIndex = () => {
|
||||
const actions = useMemo(() => {
|
||||
return [
|
||||
<Button
|
||||
key="export"
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={() => openExportModal()}
|
||||
@@ -90,6 +92,7 @@ const OrderIndex = () => {
|
||||
>
|
||||
<OrderTable setContextFilters={setContextFilters} />
|
||||
</BodyCard>
|
||||
<Spacer />
|
||||
</div>
|
||||
</div>
|
||||
{exportModalOpen && (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useAdminPriceList } from "medusa-react"
|
||||
import { useParams } from "react-router-dom"
|
||||
import Breadcrumb from "../../../components/molecules/breadcrumb"
|
||||
import BackButton from "../../../components/atoms/back-button"
|
||||
import RawJSON from "../../../components/organisms/raw-json"
|
||||
import { mapPriceListToFormValues } from "../pricing-form/form/mappers"
|
||||
import { PriceListFormProvider } from "../pricing-form/form/pricing-form-context"
|
||||
@@ -13,25 +13,21 @@ const PricingDetails = () => {
|
||||
const { price_list, isLoading } = useAdminPriceList(id!)
|
||||
|
||||
return (
|
||||
<div className="pb-xlarge">
|
||||
<Breadcrumb
|
||||
currentPage="Edit price list"
|
||||
previousBreadcrumb="Pricing"
|
||||
previousRoute="/a/pricing"
|
||||
<div className="pb-large">
|
||||
<BackButton
|
||||
label="Back to Pricing"
|
||||
path="/a/pricing"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
|
||||
{!isLoading && price_list ? (
|
||||
<PriceListFormProvider priceList={mapPriceListToFormValues(price_list)}>
|
||||
<Header priceList={price_list} />
|
||||
<div className="mt-4 w-full">
|
||||
<div className="gap-y-xsmall flex flex-col">
|
||||
<Header priceList={price_list} />
|
||||
|
||||
<PricesDetails id={price_list?.id} />
|
||||
</div>
|
||||
<div className="mt-xlarge">
|
||||
<RawJSON
|
||||
data={price_list}
|
||||
title="Raw price list"
|
||||
rootName="price_list"
|
||||
/>
|
||||
|
||||
<RawJSON data={price_list} title="Raw price list" />
|
||||
</div>
|
||||
</PriceListFormProvider>
|
||||
) : null}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Route, Routes, useNavigate } from "react-router-dom"
|
||||
import Spacer from "../../components/atoms/spacer"
|
||||
import PlusIcon from "../../components/fundamentals/icons/plus-icon"
|
||||
import BodyCard from "../../components/organisms/body-card"
|
||||
import TableViewHeader from "../../components/organisms/custom-table-header"
|
||||
@@ -27,6 +28,7 @@ const PricingIndex = () => {
|
||||
>
|
||||
<PricingTable />
|
||||
</BodyCard>
|
||||
<Spacer />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createContext, useState } from "react"
|
||||
import { ProductCategory } from "@medusajs/medusa"
|
||||
import { useAdminProductCategories } from "medusa-react"
|
||||
|
||||
import Spacer from "../../../components/atoms/spacer"
|
||||
import BodyCard from "../../../components/organisms/body-card"
|
||||
import useToggleState from "../../../hooks/use-toggle-state"
|
||||
import ProductCategoriesList from "../components/product-categories-list"
|
||||
@@ -99,6 +100,7 @@ function ProductCategoryPage() {
|
||||
<ProductCategoriesList categories={categories!} />
|
||||
)}
|
||||
</BodyCard>
|
||||
<Spacer />
|
||||
{isCreateModalVisible && (
|
||||
<CreateProductCategory
|
||||
parentCategory={activeCategory}
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
import { AdminPostProductsReq, ProductVariant } from "@medusajs/medusa"
|
||||
import { useAdminCreateProduct, useMedusa } from "medusa-react"
|
||||
import { useForm, useWatch } from "react-hook-form"
|
||||
import CustomsForm, { CustomsFormType } from "../../../components/forms/product/customs-form"
|
||||
import CustomsForm, {
|
||||
CustomsFormType,
|
||||
} from "../../../components/forms/product/customs-form"
|
||||
import DimensionsForm, {
|
||||
DimensionsFormType
|
||||
DimensionsFormType,
|
||||
} from "../../../components/forms/product/dimensions-form"
|
||||
import DiscountableForm, {
|
||||
DiscountableFormType
|
||||
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 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
|
||||
AddSalesChannelsFormType,
|
||||
} from "./add-sales-channels"
|
||||
import AddVariantsForm, { AddVariantsFormType } from "./add-variants"
|
||||
|
||||
@@ -247,8 +257,8 @@ const NewProduct = ({ onClose }: Props) => {
|
||||
</div>
|
||||
</div>
|
||||
</FocusModal.Header>
|
||||
<FocusModal.Main className="no-scrollbar flex w-full justify-center">
|
||||
<div className="small:w-4/5 medium:w-7/12 large:w-6/12 my-16 max-w-[700px]">
|
||||
<FocusModal.Main className="no-scrollbar flex w-full justify-center py-16">
|
||||
<div className="small:w-4/5 medium:w-7/12 large:w-6/12 max-w-[700px]">
|
||||
<Accordion defaultValue={["general"]} type="multiple">
|
||||
<Accordion.Item
|
||||
value={"general"}
|
||||
@@ -357,14 +367,14 @@ const createPayload = (
|
||||
mid_code: data.customs.mid_code || undefined,
|
||||
type: data.organize.type
|
||||
? {
|
||||
value: data.organize.type.label,
|
||||
id: data.organize.type.value,
|
||||
}
|
||||
value: data.organize.type.label,
|
||||
id: data.organize.type.value,
|
||||
}
|
||||
: undefined,
|
||||
tags: data.organize.tags
|
||||
? data.organize.tags.map((t) => ({
|
||||
value: t,
|
||||
}))
|
||||
value: t,
|
||||
}))
|
||||
: undefined,
|
||||
categories: data.organize.categories?.length
|
||||
? data.organize.categories.map((id) => ({ id }))
|
||||
@@ -435,6 +445,7 @@ const createBlank = (): NewProductForm => {
|
||||
images: [],
|
||||
},
|
||||
organize: {
|
||||
categories: null,
|
||||
collection: null,
|
||||
tags: null,
|
||||
type: null,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useAdminCreateBatchJob, useAdminCreateCollection } from "medusa-react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useLocation, useNavigate } from "react-router-dom"
|
||||
import Fade from "../../../components/atoms/fade-wrapper"
|
||||
import Spacer from "../../../components/atoms/spacer"
|
||||
import Button from "../../../components/fundamentals/button"
|
||||
import ExportIcon from "../../../components/fundamentals/icons/export-icon"
|
||||
import PlusIcon from "../../../components/fundamentals/icons/plus-icon"
|
||||
@@ -14,10 +15,10 @@ import CollectionsTable from "../../../components/templates/collections-table"
|
||||
import ProductTable from "../../../components/templates/product-table"
|
||||
import useNotification from "../../../hooks/use-notification"
|
||||
import useToggleState from "../../../hooks/use-toggle-state"
|
||||
import { usePolling } from "../../../providers/polling-provider"
|
||||
import { getErrorMessage } from "../../../utils/error-messages"
|
||||
import ImportProducts from "../batch-job/import"
|
||||
import NewProduct from "../new"
|
||||
import { usePolling } from "../../../providers/polling-provider"
|
||||
|
||||
const VIEWS = ["products", "collections"]
|
||||
|
||||
@@ -178,8 +179,10 @@ const Overview = () => {
|
||||
>
|
||||
<CurrentView />
|
||||
</BodyCard>
|
||||
<Spacer />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showNewCollection && (
|
||||
<AddCollectionModal
|
||||
onClose={() => setShowNewCollection(!showNewCollection)}
|
||||
|
||||
@@ -6,21 +6,22 @@ import {
|
||||
useAdminCreatePublishableApiKey,
|
||||
} from "medusa-react"
|
||||
|
||||
import Breadcrumb from "../../../components/molecules/breadcrumb"
|
||||
import BodyCard from "../../../components/organisms/body-card"
|
||||
import FocusModal from "../../../components/molecules/modal/focus-modal"
|
||||
import BackButton from "../../../components/atoms/back-button"
|
||||
import Fade from "../../../components/atoms/fade-wrapper"
|
||||
import useToggleState from "../../../hooks/use-toggle-state"
|
||||
import Spacer from "../../../components/atoms/spacer"
|
||||
import Button from "../../../components/fundamentals/button"
|
||||
import ChannelsIcon from "../../../components/fundamentals/icons/channels-icon"
|
||||
import CrossIcon from "../../../components/fundamentals/icons/cross-icon"
|
||||
import InputField from "../../../components/molecules/input"
|
||||
import FocusModal from "../../../components/molecules/modal/focus-modal"
|
||||
import SalesChannelsSummary from "../../../components/molecules/sales-channels-summary"
|
||||
import BodyCard from "../../../components/organisms/body-card"
|
||||
import useNotification from "../../../hooks/use-notification"
|
||||
import PublishableApiKeysTable from "../tables/publishable-api-keys-table"
|
||||
import useToggleState from "../../../hooks/use-toggle-state"
|
||||
import AddSalesChannelsSideModal from "../modals/add-sales-channels"
|
||||
import DetailsModal from "../modals/details"
|
||||
import ManageSalesChannelsSideModal from "../modals/manage-sales-channels"
|
||||
import ChannelsIcon from "../../../components/fundamentals/icons/channels-icon"
|
||||
import SalesChannelsSummary from "../../../components/molecules/sales-channels-summary"
|
||||
import AddSalesChannelsSideModal from "../modals/add-sales-channels"
|
||||
import PublishableApiKeysTable from "../tables/publishable-api-keys-table"
|
||||
|
||||
type AddSalesChannelsSectionProps = {
|
||||
setSelectedChannels: (arg: any) => void
|
||||
@@ -229,10 +230,10 @@ function Index() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Breadcrumb
|
||||
currentPage="Publishable API Keys"
|
||||
previousBreadcrumb="Settings"
|
||||
previousRoute="/a/settings"
|
||||
<BackButton
|
||||
label="Back to settings"
|
||||
path="/a/settings"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
<BodyCard
|
||||
title="Publishable API keys"
|
||||
@@ -256,6 +257,7 @@ function Index() {
|
||||
close={_closeChannelsModal}
|
||||
/>
|
||||
</BodyCard>
|
||||
<Spacer />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import clsx from "clsx"
|
||||
import { useEffect, useRef, useState, useMemo } from "react"
|
||||
import { useEffect, useMemo, useRef, useState } from "react"
|
||||
|
||||
import { SalesChannel } from "@medusajs/medusa"
|
||||
import {
|
||||
@@ -9,27 +9,27 @@ import {
|
||||
useAdminUpdateSalesChannel,
|
||||
} from "medusa-react"
|
||||
|
||||
import EditSalesChannel from "../form/edit-sales-channel"
|
||||
import AddSalesChannelModal from "../form/add-sales-channel"
|
||||
import { useNavigate, useParams } from "react-router-dom"
|
||||
import BackButton from "../../../components/atoms/back-button"
|
||||
import Fade from "../../../components/atoms/fade-wrapper"
|
||||
import Spacer from "../../../components/atoms/spacer"
|
||||
import CrossIcon from "../../../components/fundamentals/icons/cross-icon"
|
||||
import EditIcon from "../../../components/fundamentals/icons/edit-icon"
|
||||
import PlusIcon from "../../../components/fundamentals/icons/plus-icon"
|
||||
import SearchIcon from "../../../components/fundamentals/icons/search-icon"
|
||||
import TrashIcon from "../../../components/fundamentals/icons/trash-icon"
|
||||
import Actionables, {
|
||||
ActionType,
|
||||
} from "../../../components/molecules/actionables"
|
||||
import PlusIcon from "../../../components/fundamentals/icons/plus-icon"
|
||||
import EditIcon from "../../../components/fundamentals/icons/edit-icon"
|
||||
import TrashIcon from "../../../components/fundamentals/icons/trash-icon"
|
||||
import SearchIcon from "../../../components/fundamentals/icons/search-icon"
|
||||
import StatusSelector from "../../../components/molecules/status-selector"
|
||||
import useImperativeDialog from "../../../hooks/use-imperative-dialog"
|
||||
import useToggleState from "../../../hooks/use-toggle-state"
|
||||
import AddSalesChannelModal from "../form/add-sales-channel"
|
||||
import EditSalesChannel from "../form/edit-sales-channel"
|
||||
import {
|
||||
SalesChannelProductsSelectModal,
|
||||
SalesChannelProductsTable,
|
||||
} from "../tables/product"
|
||||
import CrossIcon from "../../../components/fundamentals/icons/cross-icon"
|
||||
import StatusSelector from "../../../components/molecules/status-selector"
|
||||
import TwoSplitPane from "../../../components/templates/two-split-pane"
|
||||
import Fade from "../../../components/atoms/fade-wrapper"
|
||||
import Breadcrumb from "../../../components/molecules/breadcrumb"
|
||||
import useToggleState from "../../../hooks/use-toggle-state"
|
||||
import { useNavigate, useParams } from "react-router-dom"
|
||||
import useImperativeDialog from "../../../hooks/use-imperative-dialog"
|
||||
|
||||
type ListIndicatorProps = { isActive: boolean }
|
||||
|
||||
@@ -101,10 +101,7 @@ function SalesChannelTile(props: SalesChannelTileProps) {
|
||||
<h3 className="text-grey-90 mb-1 font-semibold leading-5">
|
||||
{salesChannel.name}
|
||||
</h3>
|
||||
<span
|
||||
title={salesChannel.description}
|
||||
className="text-small text-grey-50 "
|
||||
>
|
||||
<span className="text-small text-grey-50 ">
|
||||
{salesChannel.description}
|
||||
</span>
|
||||
</div>
|
||||
@@ -210,7 +207,7 @@ function SalesChannelsList(props: SalesChannelsListProps) {
|
||||
} = props
|
||||
|
||||
return (
|
||||
<div className="bg-grey-0 border-grey-20 col-span-1 h-[968px] rounded-lg border px-8 py-6">
|
||||
<div className="bg-grey-0 border-grey-20 col-span-1 grow rounded-lg border px-8 py-6">
|
||||
<SalesChannelsHeader
|
||||
filterText={filterText}
|
||||
setFilterText={setFilterText}
|
||||
@@ -338,7 +335,7 @@ function SalesChannelDetails(props: SalesChannelDetailsProps) {
|
||||
useToggleState(false)
|
||||
|
||||
return (
|
||||
<div className="rounded-rounded bg-grey-0 border-grey-20 col-span-2 h-[968px] border px-8 py-6">
|
||||
<div className="rounded-rounded bg-grey-0 border-grey-20 col-span-2 col-span-2 h-fit border px-8 py-6">
|
||||
<SalesChannelDetailsHeader
|
||||
isDefault={isDefault}
|
||||
resetDetails={resetDetails}
|
||||
@@ -449,13 +446,13 @@ function Details() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Breadcrumb
|
||||
currentPage={"Sales channels"}
|
||||
previousBreadcrumb={"Settings"}
|
||||
previousRoute="/a/settings"
|
||||
<BackButton
|
||||
path="/a/settings"
|
||||
label="Back to settings"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
|
||||
<TwoSplitPane threeCols>
|
||||
<div className="gap-x-xsmall grid grid-cols-3">
|
||||
<SalesChannelsList
|
||||
filterText={filterText}
|
||||
setFilterText={setFilterText}
|
||||
@@ -477,7 +474,8 @@ function Details() {
|
||||
resetDetails={resetDetails}
|
||||
/>
|
||||
)}
|
||||
</TwoSplitPane>
|
||||
</div>
|
||||
<Spacer />
|
||||
|
||||
<Fade isVisible={showCreateModal} isFullScreen={true}>
|
||||
<AddSalesChannelModal onClose={closeCreateModal} />
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Store } from "@medusajs/medusa"
|
||||
import { useAdminStore, useAdminUpdateStore } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import BreadCrumb from "../../components/molecules/breadcrumb"
|
||||
import BackButton from "../../components/atoms/back-button"
|
||||
import Input from "../../components/molecules/input"
|
||||
import BodyCard from "../../components/organisms/body-card"
|
||||
import useNotification from "../../hooks/use-notification"
|
||||
@@ -64,10 +64,10 @@ const AccountDetails = () => {
|
||||
return (
|
||||
<form className="flex-col py-5">
|
||||
<div className="max-w-[632px]">
|
||||
<BreadCrumb
|
||||
previousRoute="/a/settings/"
|
||||
previousBreadcrumb="Settings"
|
||||
currentPage="Store Details"
|
||||
<BackButton
|
||||
path="/a/settings/"
|
||||
label="Back to settings"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
<BodyCard
|
||||
events={[
|
||||
@@ -76,37 +76,41 @@ const AccountDetails = () => {
|
||||
type: "button",
|
||||
onClick: handleSubmit(onSubmit),
|
||||
},
|
||||
{ label: "Cancel changes", type: "button", onClick: handleCancel },
|
||||
{ label: "Cancel", type: "button", onClick: handleCancel },
|
||||
]}
|
||||
title="Store Details"
|
||||
subtitle="Manage your business details"
|
||||
>
|
||||
<h6 className="mt-large inter-base-semibold">General</h6>
|
||||
<Input
|
||||
className="mt-base"
|
||||
label="Store name"
|
||||
{...register("name")}
|
||||
placeholder="Medusa Store"
|
||||
/>
|
||||
<h6 className="mt-2xlarge inter-base-semibold">Advanced settings</h6>
|
||||
<Input
|
||||
className="mt-base"
|
||||
label="Swap link template"
|
||||
{...register("swap_link_template")}
|
||||
placeholder="https://acme.inc/swap={swap_id}"
|
||||
/>
|
||||
<Input
|
||||
className="mt-base"
|
||||
label="Draft order link template"
|
||||
{...register("payment_link_template")}
|
||||
placeholder="https://acme.inc/payment={payment_id}"
|
||||
/>
|
||||
<Input
|
||||
className="mt-base"
|
||||
label="Invite link template"
|
||||
{...register("invite_link_template")}
|
||||
placeholder="https://acme-admin.inc/invite?token={invite_token}"
|
||||
/>
|
||||
<div className="gap-y-xlarge mb-large flex flex-col">
|
||||
<div>
|
||||
<h2 className="inter-base-semibold mb-base">General</h2>
|
||||
<Input
|
||||
label="Store name"
|
||||
{...register("name")}
|
||||
placeholder="Medusa Store"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="inter-base-semibold mb-base">Advanced settings</h2>
|
||||
<Input
|
||||
label="Swap link template"
|
||||
{...register("swap_link_template")}
|
||||
placeholder="https://acme.inc/swap={swap_id}"
|
||||
/>
|
||||
<Input
|
||||
className="mt-base"
|
||||
label="Draft order link template"
|
||||
{...register("payment_link_template")}
|
||||
placeholder="https://acme.inc/payment={payment_id}"
|
||||
/>
|
||||
<Input
|
||||
className="mt-base"
|
||||
label="Invite link template"
|
||||
{...register("invite_link_template")}
|
||||
placeholder="https://acme-admin.inc/invite?token={invite_token}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</BodyCard>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -9,7 +9,7 @@ const Regions = () => {
|
||||
|
||||
return (
|
||||
<div className="gap-y-xsmall flex h-full flex-col">
|
||||
<BackButton label="Back to Settings" path="/a/settings" />
|
||||
<BackButton label="Back to settings" path="/a/settings" />
|
||||
<div className="medium:grid-cols-3 gap-xsmall pb-xlarge grid grid-cols-1">
|
||||
<div className="h-full w-full">
|
||||
<RegionOverview id={regId} />
|
||||
|
||||
@@ -104,7 +104,7 @@ const ReturnReasonDetail = ({ reason }: ReturnReasonDetailsProps) => {
|
||||
onClick: handleSubmit(onSave),
|
||||
},
|
||||
{
|
||||
label: "Cancel changes",
|
||||
label: "Cancel",
|
||||
onClick: handleCancel,
|
||||
},
|
||||
]}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useAdminReturnReasons } from "medusa-react"
|
||||
import { useState } from "react"
|
||||
import BackButton from "../../../components/atoms/back-button"
|
||||
import Spinner from "../../../components/atoms/spinner"
|
||||
import PlusIcon from "../../../components/fundamentals/icons/plus-icon"
|
||||
import BreadCrumb from "../../../components/molecules/breadcrumb"
|
||||
import BodyCard from "../../../components/organisms/body-card"
|
||||
import RadioGroup from "../../../components/organisms/radio-group"
|
||||
import TwoSplitPane from "../../../components/templates/two-split-pane"
|
||||
@@ -30,10 +30,10 @@ const ReturnReasons = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<BreadCrumb
|
||||
previousRoute="/a/settings"
|
||||
previousBreadcrumb="Settings"
|
||||
currentPage="Return Reasons"
|
||||
<BackButton
|
||||
path="/a/settings"
|
||||
label="Back to settings"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
<TwoSplitPane>
|
||||
<BodyCard
|
||||
@@ -65,6 +65,7 @@ const ReturnReasons = () => {
|
||||
>
|
||||
{return_reasons?.map((reason) => (
|
||||
<RadioGroup.Item
|
||||
key={reason.id}
|
||||
label={reason.label}
|
||||
description={reason.description}
|
||||
className="mt-xsmall"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useAdminRegions } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useNavigate, useSearchParams } from "react-router-dom"
|
||||
import BackButton from "../../../components/atoms/back-button"
|
||||
import Spinner from "../../../components/atoms/spinner"
|
||||
import GearIcon from "../../../components/fundamentals/icons/gear-icon"
|
||||
import BreadCrumb from "../../../components/molecules/breadcrumb"
|
||||
import BodyCard from "../../../components/organisms/body-card"
|
||||
import RadioGroup from "../../../components/organisms/radio-group"
|
||||
import TwoSplitPane from "../../../components/templates/two-split-pane"
|
||||
@@ -26,10 +26,10 @@ const Taxes = () => {
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<BreadCrumb
|
||||
previousRoute="/a/settings"
|
||||
previousBreadcrumb="Settings"
|
||||
currentPage="Taxes"
|
||||
<BackButton
|
||||
path="/a/settings"
|
||||
label="Back to settings"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
<TwoSplitPane threeCols>
|
||||
<BodyCard
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import BreadCrumb from "../../../components/molecules/breadcrumb"
|
||||
import Medusa from "../../../services/api"
|
||||
import BackButton from "../../../components/atoms/back-button"
|
||||
import PlusIcon from "../../../components/fundamentals/icons/plus-icon"
|
||||
import BodyCard from "../../../components/organisms/body-card"
|
||||
import InviteModal from "../../../components/organisms/invite-modal"
|
||||
import PlusIcon from "../../../components/fundamentals/icons/plus-icon"
|
||||
import UserTable from "../../../components/templates/user-table"
|
||||
import Medusa from "../../../services/api"
|
||||
|
||||
const Users: React.FC = () => {
|
||||
const [users, setUsers] = useState([])
|
||||
@@ -46,28 +46,27 @@ const Users: React.FC = () => {
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex w-full grow flex-col">
|
||||
<BreadCrumb
|
||||
previousRoute="/a/settings"
|
||||
previousBreadcrumb="Settings"
|
||||
currentPage="The Team"
|
||||
<BackButton
|
||||
path="/a/settings"
|
||||
label="Back to settings"
|
||||
className="mb-xsmall"
|
||||
/>
|
||||
<BodyCard
|
||||
title="The Team"
|
||||
subtitle="Manage users of your Medusa Store"
|
||||
actionables={actionables}
|
||||
>
|
||||
<div className="flex grow flex-col pt-2">
|
||||
<div className="flex grow flex-col justify-between">
|
||||
<UserTable
|
||||
users={users}
|
||||
invites={invites}
|
||||
triggerRefetch={triggerRefetch}
|
||||
/>
|
||||
<p className="inter-small-regular text-grey-50">
|
||||
{users.length} member
|
||||
{users.length === 1 ? "" : "s"}
|
||||
</p>
|
||||
</div>
|
||||
<div className="inter-small-regular text-grey-50">
|
||||
{users.length} member
|
||||
{users.length === 1 ? "" : "s"}
|
||||
</div>
|
||||
|
||||
{showInviteModal && (
|
||||
<InviteModal
|
||||
handleClose={() => {
|
||||
|
||||
@@ -48,6 +48,10 @@ const FormValidator = {
|
||||
`${name} must be less than or equal to ${getNormalizedAmount(currency)}.`
|
||||
)
|
||||
},
|
||||
email: (name: string) => ({
|
||||
value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
|
||||
message: `${name} must be a valid email address.`,
|
||||
}),
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
AdminGiftCardsDeleteRes,
|
||||
AdminGiftCardsRes,
|
||||
AdminPostGiftCardsGiftCardReq,
|
||||
AdminPostGiftCardsReq,
|
||||
} from "@medusajs/medusa"
|
||||
import { Response } from "@medusajs/medusa-js"
|
||||
@@ -33,14 +34,14 @@ export const useAdminUpdateGiftCard = (
|
||||
options?: UseMutationOptions<
|
||||
Response<AdminGiftCardsRes>,
|
||||
Error,
|
||||
AdminPostGiftCardsReq
|
||||
AdminPostGiftCardsGiftCardReq
|
||||
>
|
||||
) => {
|
||||
const { client } = useMedusa()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
return useMutation(
|
||||
(payload: AdminPostGiftCardsReq) =>
|
||||
(payload: AdminPostGiftCardsGiftCardReq) =>
|
||||
client.admin.giftCards.update(id, payload),
|
||||
buildOptions(
|
||||
queryClient,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { IsBoolean, IsDate, IsInt, IsOptional, IsString } from "class-validator"
|
||||
import { defaultAdminGiftCardFields, defaultAdminGiftCardRelations } from "."
|
||||
|
||||
import { GiftCardService } from "../../../../services"
|
||||
import { Type } from "class-transformer"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { EntityManager } from "typeorm"
|
||||
import { GiftCardService } from "../../../../services"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
|
||||
/**
|
||||
* @oas [post] /admin/gift-cards/{id}
|
||||
@@ -123,7 +123,7 @@ export class AdminPostGiftCardsGiftCardReq {
|
||||
@IsOptional()
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
ends_at?: Date
|
||||
ends_at?: Date | null
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
|
||||
@@ -11,7 +11,7 @@ export type CreateGiftCardInput = {
|
||||
|
||||
export type UpdateGiftCardInput = {
|
||||
balance?: number
|
||||
ends_at?: Date
|
||||
ends_at?: Date | null
|
||||
is_disabled?: boolean
|
||||
region_id?: string
|
||||
metadata?: Record<string, unknown>
|
||||
|
||||
Reference in New Issue
Block a user