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:
Kasper Fabricius Kristensen
2023-04-02 19:04:32 +02:00
committed by GitHub
parent a5ad6c0542
commit 788ddc0f43
51 changed files with 997 additions and 709 deletions
@@ -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}