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
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user