feat(dashboard): Rework route modals (#6459)
**What** - Reworks how RouteModals are setup. **Why** - With the current implementation it was possible to create a race-condition in the logic that handled displaying a prompt if the user tried to close a modal, while a child form was dirty. The race condition would cause a new prompt to spawn each time the user clicked the screen, making it impossible to dismiss the prompt. This only occurred in a few specific cases. **How** - Creates two new components: RouteFocusModal and RouteDrawer. The component shares logic for handling their own open/closed state, and now accept a form prop, that allows the modals to keep track of whether their child form is dirty. This change ensures that race conditions cannot occur, and that the prompt always only renders once when needed.
This commit is contained in:
committed by
GitHub
parent
add861d205
commit
72a17d6cd7
@@ -83,6 +83,7 @@
|
||||
"gallery": "Gallery",
|
||||
"titleHint": "Give your product a short and clear title.<0/>50-60 characters is the recommended length for search engines.",
|
||||
"descriptionHint": "Give your product a short and clear description.<0/>120-160 characters is the recommended length for search engines.",
|
||||
"discountableHint": "When unchecked discounts will not be applied to this product.",
|
||||
"handleTooltip": "The handle is used to reference the product in your storefront. If not specified, the handle will be generated from the product title.",
|
||||
"availableInSalesChannels": "Available in <0>{{x}}</0> of <1>{{y}}</1> sales channels",
|
||||
"noSalesChannels": "Not available in any sales channels",
|
||||
@@ -249,7 +250,12 @@
|
||||
"taxInclusiveHint": "When enabled all prices in the region will be tax inclusive.",
|
||||
"providersHint": "The providers that are available in the region.",
|
||||
"shippingOptions": "Shipping Options",
|
||||
"returnShippingOptions": "Return Shipping Options"
|
||||
"returnShippingOptions": "Return Shipping Options",
|
||||
"return": "Return",
|
||||
"outbound": "Outbound",
|
||||
"priceType": "Price Type",
|
||||
"flatRate": "Flat Rate",
|
||||
"calculated": "Calculated"
|
||||
},
|
||||
"locations": {
|
||||
"domain": "Locations",
|
||||
@@ -399,6 +405,7 @@
|
||||
"dateIssued": "Date issued",
|
||||
"issuedDate": "Issued date",
|
||||
"expiryDate": "Expiry date",
|
||||
"price": "Price",
|
||||
"height": "Height",
|
||||
"width": "Width",
|
||||
"length": "Length",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export { RouteDrawer } from "./route-drawer"
|
||||
export { RouteFocusModal } from "./route-focus-modal"
|
||||
export { useRouteModal } from "./route-modal-provider"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./route-drawer"
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Drawer } from "@medusajs/ui"
|
||||
import { PropsWithChildren, useEffect, useState } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { RouteForm } from "../route-form"
|
||||
import { RouteModalProvider } from "../route-modal-provider/route-provider"
|
||||
|
||||
type RouteDrawerProps = PropsWithChildren<{
|
||||
prev?: string
|
||||
}>
|
||||
|
||||
const Root = ({ prev = "..", children }: RouteDrawerProps) => {
|
||||
const navigate = useNavigate()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
/**
|
||||
* Open the modal when the component mounts. This
|
||||
* ensures that the entry animation is played.
|
||||
*/
|
||||
useEffect(() => {
|
||||
setOpen(true)
|
||||
}, [])
|
||||
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
if (!open) {
|
||||
document.body.style.pointerEvents = "auto"
|
||||
navigate(prev, { replace: true })
|
||||
return
|
||||
}
|
||||
|
||||
setOpen(open)
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={handleOpenChange}>
|
||||
<RouteModalProvider prev={prev}>
|
||||
<Drawer.Content>{children}</Drawer.Content>
|
||||
</RouteModalProvider>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
const Header = Drawer.Header
|
||||
const Body = Drawer.Body
|
||||
const Footer = Drawer.Footer
|
||||
const Close = Drawer.Close
|
||||
const Form = RouteForm
|
||||
|
||||
/**
|
||||
* Drawer that is used to render a form on a separate route.
|
||||
*
|
||||
* Typically used for forms editing a resource.
|
||||
*/
|
||||
export const RouteDrawer = Object.assign(Root, {
|
||||
Header,
|
||||
Body,
|
||||
Footer,
|
||||
Close,
|
||||
Form,
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./route-focus-modal"
|
||||
@@ -0,0 +1,58 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { PropsWithChildren, useEffect, useState } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { RouteForm } from "../route-form"
|
||||
import { RouteModalProvider } from "../route-modal-provider/route-provider"
|
||||
|
||||
type RouteFocusModalProps = PropsWithChildren<{
|
||||
prev?: string
|
||||
}>
|
||||
|
||||
const Root = ({ prev = "..", children }: RouteFocusModalProps) => {
|
||||
const navigate = useNavigate()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
/**
|
||||
* Open the modal when the component mounts. This
|
||||
* ensures that the entry animation is played.
|
||||
*/
|
||||
useEffect(() => {
|
||||
setOpen(true)
|
||||
}, [])
|
||||
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
if (!open) {
|
||||
document.body.style.pointerEvents = "auto"
|
||||
navigate(prev, { replace: true })
|
||||
return
|
||||
}
|
||||
|
||||
setOpen(open)
|
||||
}
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={handleOpenChange}>
|
||||
<RouteModalProvider prev={prev}>
|
||||
<FocusModal.Content>{children}</FocusModal.Content>
|
||||
</RouteModalProvider>
|
||||
</FocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
const Header = FocusModal.Header
|
||||
const Body = FocusModal.Body
|
||||
const Close = FocusModal.Close
|
||||
const Form = RouteForm
|
||||
|
||||
/**
|
||||
* FocusModal that is used to render a form on a separate route.
|
||||
*
|
||||
* Typically used for forms creating a resource or forms that require
|
||||
* a lot of space.
|
||||
*/
|
||||
export const RouteFocusModal = Object.assign(Root, {
|
||||
Header,
|
||||
Body,
|
||||
Close,
|
||||
Form,
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./route-form"
|
||||
@@ -0,0 +1,63 @@
|
||||
import { Prompt } from "@medusajs/ui"
|
||||
import { PropsWithChildren } from "react"
|
||||
import { FieldValues, UseFormReturn } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useBlocker } from "react-router-dom"
|
||||
import { Form } from "../../common/form"
|
||||
|
||||
type RouteFormProps<TFieldValues extends FieldValues> = PropsWithChildren<{
|
||||
form: UseFormReturn<TFieldValues>
|
||||
}>
|
||||
|
||||
export const RouteForm = <TFieldValues extends FieldValues = any>({
|
||||
form,
|
||||
children,
|
||||
}: RouteFormProps<TFieldValues>) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
const blocker = useBlocker(({ currentLocation, nextLocation }) => {
|
||||
const { isSubmitSuccessful } = nextLocation.state || {}
|
||||
|
||||
if (isSubmitSuccessful) {
|
||||
return false
|
||||
}
|
||||
|
||||
return isDirty && currentLocation.pathname !== nextLocation.pathname
|
||||
})
|
||||
|
||||
const handleCancel = () => {
|
||||
blocker?.reset?.()
|
||||
}
|
||||
|
||||
const handleContinue = () => {
|
||||
blocker?.proceed?.()
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
{children}
|
||||
<Prompt open={blocker.state === "blocked"} variant="confirmation">
|
||||
<Prompt.Content>
|
||||
<Prompt.Header>
|
||||
<Prompt.Title>{t("general.unsavedChangesTitle")}</Prompt.Title>
|
||||
<Prompt.Description>
|
||||
{t("general.unsavedChangesDescription")}
|
||||
</Prompt.Description>
|
||||
</Prompt.Header>
|
||||
<Prompt.Footer>
|
||||
<Prompt.Cancel onClick={handleCancel} type="button">
|
||||
{t("actions.cancel")}
|
||||
</Prompt.Cancel>
|
||||
<Prompt.Action onClick={handleContinue} type="button">
|
||||
{t("actions.continue")}
|
||||
</Prompt.Action>
|
||||
</Prompt.Footer>
|
||||
</Prompt.Content>
|
||||
</Prompt>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./route-provider"
|
||||
@@ -0,0 +1,41 @@
|
||||
import { PropsWithChildren, createContext, useContext } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
|
||||
type RouteModalProviderContextType = {
|
||||
handleSuccess: (path?: string) => void
|
||||
}
|
||||
|
||||
const RouteModalProviderContext =
|
||||
createContext<RouteModalProviderContextType | null>(null)
|
||||
|
||||
export const useRouteModal = () => {
|
||||
const context = useContext(RouteModalProviderContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useRouteModal must be used within a RouteModalProvider")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
type RouteModalProviderProps = PropsWithChildren<{
|
||||
prev: string
|
||||
}>
|
||||
|
||||
export const RouteModalProvider = ({
|
||||
prev,
|
||||
children,
|
||||
}: RouteModalProviderProps) => {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleSuccess = (path?: string) => {
|
||||
const to = path || prev
|
||||
navigate(to, { replace: true, state: { isSubmitSuccessful: true } })
|
||||
}
|
||||
|
||||
return (
|
||||
<RouteModalProviderContext.Provider value={{ handleSuccess }}>
|
||||
{children}
|
||||
</RouteModalProviderContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Country } from "@medusajs/medusa"
|
||||
import { Tooltip } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { PlaceholderCell } from "../../common/placeholder-cell"
|
||||
|
||||
type CountriesCellProps = {
|
||||
countries?: Country[] | null
|
||||
}
|
||||
|
||||
export const CountriesCell = ({ countries }: CountriesCellProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (!countries || countries.length === 0) {
|
||||
return <PlaceholderCell />
|
||||
}
|
||||
|
||||
const displayValue = countries
|
||||
.slice(0, 2)
|
||||
.map((c) => c.display_name)
|
||||
.join(", ")
|
||||
|
||||
const additionalCountries = countries.slice(2).map((c) => c.display_name)
|
||||
|
||||
return (
|
||||
<div className="flex size-full items-center gap-x-1">
|
||||
<span>{displayValue}</span>
|
||||
{additionalCountries.length > 0 && (
|
||||
<Tooltip
|
||||
content={
|
||||
<ul>
|
||||
{additionalCountries.map((c) => (
|
||||
<li key={c}>{c}</li>
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{t("general.plusCountMore", {
|
||||
count: additionalCountries.length,
|
||||
})}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const CountriesHeader = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="flex size-full items-center">
|
||||
<span>{t("fields.countries")}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./countries-cell"
|
||||
@@ -0,0 +1,58 @@
|
||||
import { FulfillmentProvider } from "@medusajs/medusa"
|
||||
import { Tooltip } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { PlaceholderCell } from "../../common/placeholder-cell"
|
||||
|
||||
type FulfillmentProvidersCellProps = {
|
||||
fulfillmentProviders?: FulfillmentProvider[] | null
|
||||
}
|
||||
|
||||
export const FulfillmentProvidersCell = ({
|
||||
fulfillmentProviders,
|
||||
}: FulfillmentProvidersCellProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (!fulfillmentProviders || fulfillmentProviders.length === 0) {
|
||||
return <PlaceholderCell />
|
||||
}
|
||||
|
||||
const displayValue = fulfillmentProviders
|
||||
.slice(0, 2)
|
||||
.map((p) => p.id)
|
||||
.join(", ")
|
||||
|
||||
const additionalProviders = fulfillmentProviders.slice(2).map((c) => c.id)
|
||||
|
||||
return (
|
||||
<div className="flex size-full items-center gap-x-1">
|
||||
<span>{displayValue}</span>
|
||||
{additionalProviders.length > 0 && (
|
||||
<Tooltip
|
||||
content={
|
||||
<ul>
|
||||
{additionalProviders.map((c) => (
|
||||
<li key={c}>{c}</li>
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{t("general.plusCountMore", {
|
||||
count: additionalProviders.length,
|
||||
})}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const FulfillmentProvidersHeader = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="flex size-full items-center">
|
||||
<span>{t("fields.fulfillmentProviders")}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./fulfillment-providers-cell"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./payment-providers-cell"
|
||||
@@ -0,0 +1,58 @@
|
||||
import { PaymentProvider } from "@medusajs/medusa"
|
||||
import { Tooltip } from "@medusajs/ui"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { PlaceholderCell } from "../../common/placeholder-cell"
|
||||
|
||||
type PaymentProvidersCellProps = {
|
||||
paymentProviders?: PaymentProvider[] | null
|
||||
}
|
||||
|
||||
export const PaymentProvidersCell = ({
|
||||
paymentProviders,
|
||||
}: PaymentProvidersCellProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (!paymentProviders || paymentProviders.length === 0) {
|
||||
return <PlaceholderCell />
|
||||
}
|
||||
|
||||
const displayValue = paymentProviders
|
||||
.slice(0, 2)
|
||||
.map((p) => p.id)
|
||||
.join(", ")
|
||||
|
||||
const additionalProviders = paymentProviders.slice(2).map((c) => c.id)
|
||||
|
||||
return (
|
||||
<div className="flex size-full items-center gap-x-1">
|
||||
<span>{displayValue}</span>
|
||||
{additionalProviders.length > 0 && (
|
||||
<Tooltip
|
||||
content={
|
||||
<ul>
|
||||
{additionalProviders.map((c) => (
|
||||
<li key={c}>{c}</li>
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{t("general.plusCountMore", {
|
||||
count: additionalProviders.length,
|
||||
})}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const PaymentProvidersHeader = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="flex size-full items-center">
|
||||
<span>{t("fields.paymentProviders")}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./region-cell"
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
type RegionCellProps = {
|
||||
name: string
|
||||
}
|
||||
|
||||
export const RegionCell = ({ name }: RegionCellProps) => {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center overflow-hidden">
|
||||
<span className="truncate">{name}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const RegionHeader = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full items-center">
|
||||
<span className="truncate">{t("fields.name")}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Region } from "@medusajs/medusa"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
|
||||
import {
|
||||
CountriesCell,
|
||||
CountriesHeader,
|
||||
} from "../../../components/table/table-cells/region/countries-cell"
|
||||
import {
|
||||
FulfillmentProvidersCell,
|
||||
FulfillmentProvidersHeader,
|
||||
} from "../../../components/table/table-cells/region/fulfillment-providers-cell"
|
||||
import {
|
||||
PaymentProvidersCell,
|
||||
PaymentProvidersHeader,
|
||||
} from "../../../components/table/table-cells/region/payment-providers-cell"
|
||||
import {
|
||||
RegionCell,
|
||||
RegionHeader,
|
||||
} from "../../../components/table/table-cells/region/region-cell"
|
||||
|
||||
const columnHelper = createColumnHelper<Region>()
|
||||
|
||||
export const useRegionTableColumns = () => {
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.accessor("name", {
|
||||
header: () => <RegionHeader />,
|
||||
cell: ({ getValue }) => <RegionCell name={getValue()} />,
|
||||
}),
|
||||
columnHelper.accessor("countries", {
|
||||
header: () => <CountriesHeader />,
|
||||
cell: ({ getValue }) => <CountriesCell countries={getValue()} />,
|
||||
}),
|
||||
columnHelper.accessor("payment_providers", {
|
||||
header: () => <PaymentProvidersHeader />,
|
||||
cell: ({ getValue }) => (
|
||||
<PaymentProvidersCell paymentProviders={getValue()} />
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("fulfillment_providers", {
|
||||
header: () => <FulfillmentProvidersHeader />,
|
||||
cell: ({ getValue }) => (
|
||||
<FulfillmentProvidersCell fulfillmentProviders={getValue()} />
|
||||
),
|
||||
}),
|
||||
],
|
||||
[]
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Filter } from "../../../components/table/data-table"
|
||||
|
||||
export const useRegionTableFilters = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const dateFilters: Filter[] = [
|
||||
{ label: t("fields.createdAt"), key: "created_at" },
|
||||
{ label: t("fields.updatedAt"), key: "updated_at" },
|
||||
].map((f) => ({
|
||||
key: f.key,
|
||||
label: f.label,
|
||||
type: "date",
|
||||
}))
|
||||
|
||||
const filters = [...dateFilters]
|
||||
|
||||
return filters
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { AdminGetRegionsParams } from "@medusajs/medusa"
|
||||
import { useQueryParams } from "../../use-query-params"
|
||||
|
||||
type UseRegionTableQueryProps = {
|
||||
prefix?: string
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
export const useRegionTableQuery = ({
|
||||
prefix,
|
||||
pageSize = 20,
|
||||
}: UseRegionTableQueryProps) => {
|
||||
const queryObject = useQueryParams(
|
||||
["offset", "q", "order", "created_at", "updated_at"],
|
||||
prefix
|
||||
)
|
||||
|
||||
const { offset, q, order, created_at, updated_at } = queryObject
|
||||
|
||||
const searchParams: AdminGetRegionsParams = {
|
||||
limit: pageSize,
|
||||
offset: offset ? Number(offset) : 0,
|
||||
order,
|
||||
created_at: created_at ? JSON.parse(created_at) : undefined,
|
||||
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
|
||||
q,
|
||||
}
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
raw: queryObject,
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import { usePrompt } from "@medusajs/ui"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
|
||||
/**
|
||||
* Hook for managing the state of route modals.
|
||||
*/
|
||||
export const useRouteModalState = (): [
|
||||
open: boolean,
|
||||
onOpenChange: (open: boolean, ignore?: boolean) => void,
|
||||
/**
|
||||
* Subscribe to the dirty state of the form.
|
||||
* If the form is dirty, the modal will prompt
|
||||
* the user before closing.
|
||||
*/
|
||||
subscribe: (value: boolean) => void,
|
||||
] => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [shouldPrompt, subscribe] = useState(false)
|
||||
|
||||
const navigate = useNavigate()
|
||||
const prompt = usePrompt()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const promptValues = {
|
||||
title: t("general.unsavedChangesTitle"),
|
||||
description: t("general.unsavedChangesDescription"),
|
||||
cancelText: t("actions.cancel"),
|
||||
confirmText: t("actions.continue"),
|
||||
variant: "confirmation" as const,
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(true)
|
||||
}, [])
|
||||
|
||||
const onOpenChange = async (open: boolean, ignore = false) => {
|
||||
if (!open) {
|
||||
if (shouldPrompt && !ignore) {
|
||||
const confirmed = await prompt(promptValues)
|
||||
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
navigate("..", { replace: true })
|
||||
}, 200)
|
||||
}
|
||||
|
||||
setOpen(open)
|
||||
}
|
||||
|
||||
return [open, onOpenChange, subscribe]
|
||||
}
|
||||
@@ -1,36 +1,26 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { useAdminPublishableApiKeySalesChannels } from "medusa-react"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { AddSalesChannelsToApiKeyForm } from "./components"
|
||||
|
||||
export const ApiKeyManagementAddSalesChannels = () => {
|
||||
const { id } = useParams()
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
const { sales_channels, isLoading, isError, error } =
|
||||
useAdminPublishableApiKeySalesChannels(id!)
|
||||
|
||||
const handleSuccessfulSubmit = () => {
|
||||
onOpenChange(false, true)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content>
|
||||
{!isLoading && sales_channels && (
|
||||
<AddSalesChannelsToApiKeyForm
|
||||
apiKey={id!}
|
||||
preSelected={sales_channels.map((sc) => sc.id)}
|
||||
onSuccessfulSubmit={handleSuccessfulSubmit}
|
||||
subscribe={subscribe}
|
||||
/>
|
||||
)}
|
||||
</FocusModal.Content>
|
||||
</FocusModal>
|
||||
<RouteFocusModal>
|
||||
{!isLoading && sales_channels && (
|
||||
<AddSalesChannelsToApiKeyForm
|
||||
apiKey={id!}
|
||||
preSelected={sales_channels.map((sc) => sc.id)}
|
||||
/>
|
||||
)}
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { SalesChannel } from "@medusajs/medusa"
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
FocusModal,
|
||||
Hint,
|
||||
StatusBadge,
|
||||
Table,
|
||||
@@ -26,17 +25,18 @@ import { useEffect, useMemo, useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
import { Form } from "../../../../components/common/form"
|
||||
import { OrderBy } from "../../../../components/filtering/order-by"
|
||||
import { Query } from "../../../../components/filtering/query"
|
||||
import { LocalizedTablePagination } from "../../../../components/localization/localized-table-pagination"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../components/route-modal"
|
||||
import { useQueryParams } from "../../../../hooks/use-query-params"
|
||||
|
||||
type AddSalesChannelsToApiKeyFormProps = {
|
||||
apiKey: string
|
||||
preSelected: string[]
|
||||
subscribe: (state: boolean) => void
|
||||
onSuccessfulSubmit: () => void
|
||||
}
|
||||
|
||||
const AddSalesChannelsToApiKeySchema = zod.object({
|
||||
@@ -48,10 +48,9 @@ const PAGE_SIZE = 50
|
||||
export const AddSalesChannelsToApiKeyForm = ({
|
||||
apiKey,
|
||||
preSelected,
|
||||
subscribe,
|
||||
onSuccessfulSubmit,
|
||||
}: AddSalesChannelsToApiKeyFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof AddSalesChannelsToApiKeySchema>>({
|
||||
defaultValues: {
|
||||
@@ -60,13 +59,7 @@ export const AddSalesChannelsToApiKeyForm = ({
|
||||
resolver: zodResolver(AddSalesChannelsToApiKeySchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty])
|
||||
const { setValue } = form
|
||||
|
||||
const { mutateAsync, isLoading: isMutating } =
|
||||
useAdminAddPublishableKeySalesChannelsBatch(apiKey)
|
||||
@@ -87,11 +80,15 @@ export const AddSalesChannelsToApiKeyForm = ({
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||
|
||||
useEffect(() => {
|
||||
form.setValue(
|
||||
setValue(
|
||||
"sales_channel_ids",
|
||||
Object.keys(rowSelection).filter((k) => rowSelection[k])
|
||||
Object.keys(rowSelection).filter((k) => rowSelection[k]),
|
||||
{
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
}
|
||||
)
|
||||
}, [rowSelection])
|
||||
}, [rowSelection, setValue])
|
||||
|
||||
const params = useQueryParams(["q", "order"])
|
||||
const { sales_channels, count } = useAdminSalesChannels(
|
||||
@@ -135,36 +132,36 @@ export const AddSalesChannelsToApiKeyForm = ({
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onSuccessfulSubmit()
|
||||
handleSuccess()
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex h-full flex-col overflow-hidden"
|
||||
>
|
||||
<FocusModal.Header>
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
{form.formState.errors.sales_channel_ids && (
|
||||
<Hint variant="error">
|
||||
{form.formState.errors.sales_channel_ids.message}
|
||||
</Hint>
|
||||
)}
|
||||
<FocusModal.Close asChild>
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</FocusModal.Close>
|
||||
</RouteFocusModal.Close>
|
||||
<Button size="small" type="submit" isLoading={isMutating}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</FocusModal.Header>
|
||||
<FocusModal.Body className="flex h-full w-full flex-col items-center divide-y overflow-y-auto">
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex h-full w-full flex-col items-center divide-y overflow-y-auto">
|
||||
<div className="flex w-full items-center justify-between px-6 py-4">
|
||||
<div></div>
|
||||
<div className="flex items-center gap-x-2">
|
||||
@@ -236,9 +233,9 @@ export const AddSalesChannelsToApiKeyForm = ({
|
||||
pageSize={PAGE_SIZE}
|
||||
/>
|
||||
</div>
|
||||
</FocusModal.Body>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { CreatePublishableApiKeyForm } from "./components/create-publishable-api-key-form"
|
||||
|
||||
export const ApiKeyManagementCreate = () => {
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content>
|
||||
<CreatePublishableApiKeyForm subscribe={subscribe} />
|
||||
</FocusModal.Content>
|
||||
</FocusModal>
|
||||
<RouteFocusModal>
|
||||
<CreatePublishableApiKeyForm />
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Button, FocusModal, Heading, Input, Text } from "@medusajs/ui"
|
||||
import { Button, Heading, Input, Text } from "@medusajs/ui"
|
||||
import { useAdminCreatePublishableApiKey } from "medusa-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { useEffect } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
|
||||
type CreatePublishableApiKeyFormProps = {
|
||||
subscribe: (state: boolean) => void
|
||||
}
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
|
||||
const CreatePublishableApiKeySchema = zod.object({
|
||||
title: zod.string().min(1),
|
||||
})
|
||||
|
||||
export const CreatePublishableApiKeyForm = ({
|
||||
subscribe,
|
||||
}: CreatePublishableApiKeyFormProps) => {
|
||||
const { mutateAsync, isLoading } = useAdminCreatePublishableApiKey()
|
||||
export const CreatePublishableApiKeyForm = () => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof CreatePublishableApiKeySchema>>({
|
||||
defaultValues: {
|
||||
@@ -29,46 +26,35 @@ export const CreatePublishableApiKeyForm = ({
|
||||
resolver: zodResolver(CreatePublishableApiKeySchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty])
|
||||
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const { mutateAsync, isLoading } = useAdminCreatePublishableApiKey()
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
await mutateAsync(values, {
|
||||
onSuccess: ({ publishable_api_key }) => {
|
||||
navigate(`/settings/api-key-management/${publishable_api_key.id}`, {
|
||||
replace: true,
|
||||
})
|
||||
handleSuccess(`/settings/api-key-management/${publishable_api_key.id}`)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form
|
||||
className="flex h-full flex-col overflow-hidden"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<FocusModal.Header>
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<FocusModal.Close asChild>
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</FocusModal.Close>
|
||||
</RouteFocusModal.Close>
|
||||
<Button size="small" type="submit" isLoading={isLoading}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</FocusModal.Header>
|
||||
<FocusModal.Body className="flex flex-1 flex-col overflow-hidden">
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex flex-1 flex-col overflow-hidden">
|
||||
<div className="flex flex-1 flex-col items-center overflow-y-auto">
|
||||
<div className="flex w-full max-w-[720px] flex-col gap-y-8 px-2 py-16">
|
||||
<div>
|
||||
@@ -88,7 +74,7 @@ export const CreatePublishableApiKeyForm = ({
|
||||
<Form.Item>
|
||||
<Form.Label>{t("fields.title")}</Form.Label>
|
||||
<Form.Control>
|
||||
<Input size="small" {...field} />
|
||||
<Input {...field} />
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
@@ -98,8 +84,8 @@ export const CreatePublishableApiKeyForm = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FocusModal.Body>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,40 +1,29 @@
|
||||
import { Drawer, Heading } from "@medusajs/ui"
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminPublishableApiKey } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { EditApiKeyForm } from "./components/edit-api-key-form"
|
||||
|
||||
export const ApiKeyManagementEdit = () => {
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
const { id } = useParams()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { publishable_api_key, isLoading, isError, error } =
|
||||
useAdminPublishableApiKey(id!)
|
||||
|
||||
const handleSuccessfulSubmit = () => {
|
||||
onOpenChange(false, true)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={onOpenChange}>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Heading>{t("apiKeyManagement.editKey")}</Heading>
|
||||
</Drawer.Header>
|
||||
{!isLoading && publishable_api_key && (
|
||||
<EditApiKeyForm
|
||||
apiKey={publishable_api_key}
|
||||
onSuccessfulSubmit={handleSuccessfulSubmit}
|
||||
subscribe={subscribe}
|
||||
/>
|
||||
)}
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<Heading>{t("apiKeyManagement.editKey")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
{!isLoading && publishable_api_key && (
|
||||
<EditApiKeyForm apiKey={publishable_api_key} />
|
||||
)}
|
||||
</RouteDrawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import type { PublishableApiKey } from "@medusajs/medusa"
|
||||
import { Button, Drawer, Input } from "@medusajs/ui"
|
||||
import { Button, Input } from "@medusajs/ui"
|
||||
import { useAdminUpdatePublishableApiKey } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
|
||||
type EditApiKeyFormProps = {
|
||||
apiKey: PublishableApiKey
|
||||
onSuccessfulSubmit: () => void
|
||||
subscribe: (state: boolean) => void
|
||||
}
|
||||
|
||||
const EditApiKeySchema = zod.object({
|
||||
title: zod.string().min(1),
|
||||
})
|
||||
|
||||
export const EditApiKeyForm = ({
|
||||
apiKey,
|
||||
onSuccessfulSubmit,
|
||||
subscribe,
|
||||
}: EditApiKeyFormProps) => {
|
||||
export const EditApiKeyForm = ({ apiKey }: EditApiKeyFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof EditApiKeySchema>>({
|
||||
defaultValues: {
|
||||
@@ -32,28 +31,23 @@ export const EditApiKeyForm = ({
|
||||
resolver: zodResolver(EditApiKeySchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty])
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminUpdatePublishableApiKey(apiKey.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
await mutateAsync(data, {
|
||||
onSuccess: () => {
|
||||
onSuccessfulSubmit()
|
||||
handleSuccess()
|
||||
},
|
||||
onError: (error) => {
|
||||
console.log(error)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form onSubmit={handleSubmit} className="flex flex-1 flex-col">
|
||||
<Drawer.Body>
|
||||
<RouteDrawer.Body>
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
@@ -71,20 +65,20 @@ export const EditApiKeyForm = ({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Drawer.Body>
|
||||
<Drawer.Footer>
|
||||
</RouteDrawer.Body>
|
||||
<RouteDrawer.Footer>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Drawer.Close asChild>
|
||||
<RouteDrawer.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</Drawer.Close>
|
||||
</RouteDrawer.Close>
|
||||
<Button size="small" type="submit" isLoading={isLoading}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</Drawer.Footer>
|
||||
</RouteDrawer.Footer>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteDrawer.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,34 +1,22 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { useAdminCollection } from "medusa-react"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { AddProductsToCollectionForm } from "./components/add-products-to-collection-form"
|
||||
|
||||
export const CollectionAddProducts = () => {
|
||||
const { id } = useParams()
|
||||
const { collection, isLoading, isError, error } = useAdminCollection(id!)
|
||||
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
const handleSuccessfulSubmit = () => {
|
||||
onOpenChange(false, true)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content>
|
||||
{!isLoading && collection && (
|
||||
<AddProductsToCollectionForm
|
||||
collection={collection}
|
||||
onSuccessfulSubmit={handleSuccessfulSubmit}
|
||||
subscribe={subscribe}
|
||||
/>
|
||||
)}
|
||||
</FocusModal.Content>
|
||||
</FocusModal>
|
||||
<RouteFocusModal>
|
||||
{!isLoading && collection && (
|
||||
<AddProductsToCollectionForm collection={collection} />
|
||||
)}
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import type { Product, ProductCollection } from "@medusajs/medusa"
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
FocusModal,
|
||||
Hint,
|
||||
Table,
|
||||
Tooltip,
|
||||
clx,
|
||||
} from "@medusajs/ui"
|
||||
import { Button, Checkbox, Hint, Table, Tooltip, clx } from "@medusajs/ui"
|
||||
import {
|
||||
PaginationState,
|
||||
RowSelectionState,
|
||||
@@ -30,7 +22,6 @@ import {
|
||||
NoRecords,
|
||||
NoResults,
|
||||
} from "../../../../../components/common/empty-table-content"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
ProductAvailabilityCell,
|
||||
ProductCollectionCell,
|
||||
@@ -41,14 +32,16 @@ import {
|
||||
import { OrderBy } from "../../../../../components/filtering/order-by"
|
||||
import { Query } from "../../../../../components/filtering/query"
|
||||
import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { useHandleTableScroll } from "../../../../../hooks/use-handle-table-scroll"
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
import { queryClient } from "../../../../../lib/medusa"
|
||||
|
||||
type AddProductsToCollectionFormProps = {
|
||||
collection: ProductCollection
|
||||
subscribe: (state: boolean) => void
|
||||
onSuccessfulSubmit: () => void
|
||||
}
|
||||
|
||||
const AddProductsToSalesChannelSchema = zod.object({
|
||||
@@ -59,10 +52,9 @@ const PAGE_SIZE = 50
|
||||
|
||||
export const AddProductsToCollectionForm = ({
|
||||
collection,
|
||||
subscribe,
|
||||
onSuccessfulSubmit,
|
||||
}: AddProductsToCollectionFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof AddProductsToSalesChannelSchema>>({
|
||||
defaultValues: {
|
||||
@@ -71,13 +63,7 @@ export const AddProductsToCollectionForm = ({
|
||||
resolver: zodResolver(AddProductsToSalesChannelSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty])
|
||||
const { setValue } = form
|
||||
|
||||
const { mutateAsync, isLoading: isMutating } =
|
||||
useAdminAddProductsToCollection(collection.id)
|
||||
@@ -98,11 +84,15 @@ export const AddProductsToCollectionForm = ({
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||
|
||||
useEffect(() => {
|
||||
form.setValue(
|
||||
setValue(
|
||||
"product_ids",
|
||||
Object.keys(rowSelection).filter((k) => rowSelection[k])
|
||||
Object.keys(rowSelection).filter((k) => rowSelection[k]),
|
||||
{
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
}
|
||||
)
|
||||
}, [rowSelection])
|
||||
}, [rowSelection, setValue])
|
||||
|
||||
const params = useQueryParams(["q", "order"])
|
||||
|
||||
@@ -151,7 +141,7 @@ export const AddProductsToCollectionForm = ({
|
||||
* determine if they are added to the collection or not.
|
||||
*/
|
||||
queryClient.invalidateQueries(adminProductKeys.lists())
|
||||
onSuccessfulSubmit()
|
||||
handleSuccess()
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -169,29 +159,29 @@ export const AddProductsToCollectionForm = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex h-full flex-col overflow-hidden"
|
||||
>
|
||||
<FocusModal.Header>
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
{form.formState.errors.product_ids && (
|
||||
<Hint variant="error">
|
||||
{form.formState.errors.product_ids.message}
|
||||
</Hint>
|
||||
)}
|
||||
<FocusModal.Close asChild>
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</FocusModal.Close>
|
||||
</RouteFocusModal.Close>
|
||||
<Button size="small" type="submit" isLoading={isMutating}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</FocusModal.Header>
|
||||
<FocusModal.Body className="flex h-full w-full flex-col items-center divide-y overflow-y-auto">
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex h-full w-full flex-col items-center divide-y overflow-y-auto">
|
||||
{!noRecords && (
|
||||
<div className="flex w-full items-center justify-between px-6 py-4">
|
||||
<div></div>
|
||||
@@ -291,9 +281,9 @@ export const AddProductsToCollectionForm = ({
|
||||
{/* TODO: fix this, and add NoRecords as well */}
|
||||
</div>
|
||||
)}
|
||||
</FocusModal.Body>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { CreateCollectionForm } from "./components/create-collection-form"
|
||||
|
||||
export const CollectionCreate = () => {
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content>
|
||||
<CreateCollectionForm subscribe={subscribe} />
|
||||
</FocusModal.Content>
|
||||
</FocusModal>
|
||||
<RouteFocusModal>
|
||||
<CreateCollectionForm />
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Button, FocusModal, Heading, Input, Text } from "@medusajs/ui"
|
||||
import { Button, Heading, Input, Text } from "@medusajs/ui"
|
||||
import { useAdminCreateCollection } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import * as zod from "zod"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
|
||||
type CreateCollectionFormProps = {
|
||||
subscribe: (state: boolean) => void
|
||||
}
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
|
||||
const CreateCollectionSchema = zod.object({
|
||||
title: zod.string().min(1),
|
||||
handle: zod.string().optional(),
|
||||
})
|
||||
|
||||
export const CreateCollectionForm = ({
|
||||
subscribe,
|
||||
}: CreateCollectionFormProps) => {
|
||||
export const CreateCollectionForm = () => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof CreateCollectionSchema>>({
|
||||
defaultValues: {
|
||||
@@ -31,34 +28,26 @@ export const CreateCollectionForm = ({
|
||||
resolver: zodResolver(CreateCollectionSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty])
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminCreateCollection()
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
await mutateAsync(data, {
|
||||
onSuccess: ({ collection }) => {
|
||||
navigate(`/collections/${collection.id}`)
|
||||
handleSuccess(`/collections/${collection.id}`)
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<FocusModal.Header>
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<FocusModal.Close asChild>
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</FocusModal.Close>
|
||||
</RouteFocusModal.Close>
|
||||
<Button
|
||||
size="small"
|
||||
variant="primary"
|
||||
@@ -68,8 +57,8 @@ export const CreateCollectionForm = ({
|
||||
{t("actions.create")}
|
||||
</Button>
|
||||
</div>
|
||||
</FocusModal.Header>
|
||||
<FocusModal.Body className="flex flex-col items-center py-16">
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex flex-col items-center py-16">
|
||||
<div className="flex w-full max-w-[720px] flex-col gap-y-8">
|
||||
<div>
|
||||
<Heading>{t("collections.createCollection")}</Heading>
|
||||
@@ -131,8 +120,8 @@ export const CreateCollectionForm = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</FocusModal.Body>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Drawer, Heading } from "@medusajs/ui"
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminCollection } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { EditCollectionForm } from "./components/edit-collection-form"
|
||||
|
||||
export const CollectionEdit = () => {
|
||||
@@ -10,30 +10,18 @@ export const CollectionEdit = () => {
|
||||
const { t } = useTranslation()
|
||||
const { collection, isLoading, isError, error } = useAdminCollection(id!)
|
||||
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
const handleSuccessfulSubmit = () => {
|
||||
onOpenChange(false, true)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={onOpenChange}>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Heading>{t("collections.editCollection")}</Heading>
|
||||
</Drawer.Header>
|
||||
{!isLoading && collection && (
|
||||
<EditCollectionForm
|
||||
collection={collection}
|
||||
onSuccessfulSubmit={handleSuccessfulSubmit}
|
||||
subscribe={subscribe}
|
||||
/>
|
||||
)}
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<Heading>{t("collections.editCollection")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
{!isLoading && collection && (
|
||||
<EditCollectionForm collection={collection} />
|
||||
)}
|
||||
</RouteDrawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import type { ProductCollection } from "@medusajs/medusa"
|
||||
import { Button, Drawer, Input, Text } from "@medusajs/ui"
|
||||
import { Button, Input, Text } from "@medusajs/ui"
|
||||
import { useAdminUpdateCollection } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
|
||||
type EditCollectionFormProps = {
|
||||
collection: ProductCollection
|
||||
subscribe: (state: boolean) => void
|
||||
onSuccessfulSubmit: () => void
|
||||
}
|
||||
|
||||
const EditCollectionSchema = zod.object({
|
||||
@@ -19,12 +21,9 @@ const EditCollectionSchema = zod.object({
|
||||
handle: zod.string().min(1),
|
||||
})
|
||||
|
||||
export const EditCollectionForm = ({
|
||||
collection,
|
||||
onSuccessfulSubmit,
|
||||
subscribe,
|
||||
}: EditCollectionFormProps) => {
|
||||
export const EditCollectionForm = ({ collection }: EditCollectionFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof EditCollectionSchema>>({
|
||||
defaultValues: {
|
||||
@@ -34,28 +33,20 @@ export const EditCollectionForm = ({
|
||||
resolver: zodResolver(EditCollectionSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty])
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminUpdateCollection(collection.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
await mutateAsync(data, {
|
||||
onSuccess: () => {
|
||||
onSuccessfulSubmit()
|
||||
handleSuccess()
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form onSubmit={handleSubmit} className="flex flex-1 flex-col">
|
||||
<Drawer.Body>
|
||||
<RouteDrawer.Body>
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
@@ -102,20 +93,20 @@ export const EditCollectionForm = ({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Drawer.Body>
|
||||
<Drawer.Footer>
|
||||
</RouteDrawer.Body>
|
||||
<RouteDrawer.Footer>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Drawer.Close asChild>
|
||||
<RouteDrawer.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</Drawer.Close>
|
||||
</RouteDrawer.Close>
|
||||
<Button size="small" type="submit" isLoading={isLoading}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</Drawer.Footer>
|
||||
</RouteDrawer.Footer>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteDrawer.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Customer } from "@medusajs/medusa"
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
FocusModal,
|
||||
Hint,
|
||||
Table,
|
||||
Tooltip,
|
||||
clx,
|
||||
} from "@medusajs/ui"
|
||||
import { Button, Checkbox, Hint, Table, Tooltip, clx } from "@medusajs/ui"
|
||||
import {
|
||||
PaginationState,
|
||||
RowSelectionState,
|
||||
@@ -25,21 +17,23 @@ import {
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import * as zod from "zod"
|
||||
|
||||
import {
|
||||
NoRecords,
|
||||
NoResults,
|
||||
} from "../../../../../components/common/empty-table-content"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { Query } from "../../../../../components/filtering/query"
|
||||
import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
import { queryClient } from "../../../../../lib/medusa"
|
||||
|
||||
type AddCustomersFormProps = {
|
||||
customerGroupId: string
|
||||
subscribe: (state: boolean) => void
|
||||
}
|
||||
|
||||
const AddCustomersSchema = zod.object({
|
||||
@@ -50,10 +44,9 @@ const PAGE_SIZE = 10
|
||||
|
||||
export const AddCustomersForm = ({
|
||||
customerGroupId,
|
||||
subscribe,
|
||||
}: AddCustomersFormProps) => {
|
||||
const navigate = useNavigate()
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof AddCustomersSchema>>({
|
||||
defaultValues: {
|
||||
@@ -62,13 +55,7 @@ export const AddCustomersForm = ({
|
||||
resolver: zodResolver(AddCustomersSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty])
|
||||
const { setValue } = form
|
||||
|
||||
const [{ pageIndex, pageSize }, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
@@ -86,11 +73,15 @@ export const AddCustomersForm = ({
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||
|
||||
useEffect(() => {
|
||||
form.setValue(
|
||||
setValue(
|
||||
"customer_ids",
|
||||
Object.keys(rowSelection).filter((k) => rowSelection[k])
|
||||
Object.keys(rowSelection).filter((k) => rowSelection[k]),
|
||||
{
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
}
|
||||
)
|
||||
}, [rowSelection])
|
||||
}, [rowSelection, setValue])
|
||||
|
||||
const params = useQueryParams(["q"])
|
||||
const { customers, count, isLoading, isError, error } = useAdminCustomers({
|
||||
@@ -131,7 +122,7 @@ export const AddCustomersForm = ({
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(adminCustomerKeys.lists())
|
||||
navigate(`/customer-groups/${customerGroupId}`)
|
||||
handleSuccess(`/customer-groups/${customerGroupId}`)
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -147,23 +138,23 @@ export const AddCustomersForm = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form
|
||||
className="flex h-full flex-col overflow-hidden"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<FocusModal.Header>
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
{form.formState.errors.customer_ids && (
|
||||
<Hint variant="error">
|
||||
{form.formState.errors.customer_ids.message}
|
||||
</Hint>
|
||||
)}
|
||||
<FocusModal.Close asChild>
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button variant="secondary" size="small">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</FocusModal.Close>
|
||||
</RouteFocusModal.Close>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
@@ -173,8 +164,8 @@ export const AddCustomersForm = ({
|
||||
{t("general.add")}
|
||||
</Button>
|
||||
</div>
|
||||
</FocusModal.Header>
|
||||
<FocusModal.Body className="flex h-full w-full flex-col items-center divide-y overflow-y-auto">
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex h-full w-full flex-col items-center divide-y overflow-y-auto">
|
||||
{noRecords ? (
|
||||
<div className="flex w-full flex-1 items-center justify-center">
|
||||
<NoRecords />
|
||||
@@ -259,9 +250,9 @@ export const AddCustomersForm = ({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</FocusModal.Body>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { AddCustomersForm } from "./components/add-customers-form"
|
||||
|
||||
export const CustomerGroupAddCustomers = () => {
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
const { id } = useParams()
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content>
|
||||
<AddCustomersForm customerGroupId={id!} subscribe={subscribe} />
|
||||
</FocusModal.Content>
|
||||
</FocusModal>
|
||||
<RouteFocusModal>
|
||||
<AddCustomersForm customerGroupId={id!} />
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,26 +1,23 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Button, FocusModal, Heading, Input, Text } from "@medusajs/ui"
|
||||
import { Button, Heading, Input, Text } from "@medusajs/ui"
|
||||
import { useAdminCreateCustomerGroup } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import * as zod from "zod"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
|
||||
type CreateCustomerGroupFormProps = {
|
||||
subscribe: (state: boolean) => void
|
||||
}
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
|
||||
const CreateCustomerGroupSchema = zod.object({
|
||||
name: zod.string().min(1),
|
||||
})
|
||||
|
||||
export const CreateCustomerGroupForm = ({
|
||||
subscribe,
|
||||
}: CreateCustomerGroupFormProps) => {
|
||||
export const CreateCustomerGroupForm = () => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof CreateCustomerGroupSchema>>({
|
||||
defaultValues: {
|
||||
@@ -29,14 +26,6 @@ export const CreateCustomerGroupForm = ({
|
||||
resolver: zodResolver(CreateCustomerGroupSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty])
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminCreateCustomerGroup()
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
@@ -46,22 +35,22 @@ export const CreateCustomerGroupForm = ({
|
||||
},
|
||||
{
|
||||
onSuccess: ({ customer_group }) => {
|
||||
navigate(`/customer-groups/${customer_group.id}`)
|
||||
handleSuccess(`/customer-groups/${customer_group.id}`)
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<FocusModal.Header>
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<FocusModal.Close asChild>
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button variant="secondary" size="small">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</FocusModal.Close>
|
||||
</RouteFocusModal.Close>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="primary"
|
||||
@@ -71,8 +60,8 @@ export const CreateCustomerGroupForm = ({
|
||||
{t("actions.create")}
|
||||
</Button>
|
||||
</div>
|
||||
</FocusModal.Header>
|
||||
<FocusModal.Body className="flex flex-col items-center pt-[72px]">
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex flex-col items-center pt-[72px]">
|
||||
<div className="flex w-full max-w-[720px] flex-col gap-y-8">
|
||||
<div>
|
||||
<Heading>{t("customerGroups.createCustomerGroup")}</Heading>
|
||||
@@ -98,8 +87,8 @@ export const CreateCustomerGroupForm = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</FocusModal.Body>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { CreateCustomerGroupForm } from "./components/create-customer-group-form"
|
||||
|
||||
export const CustomerGroupCreate = () => {
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content>
|
||||
<CreateCustomerGroupForm subscribe={subscribe} />
|
||||
</FocusModal.Content>
|
||||
</FocusModal>
|
||||
<RouteFocusModal>
|
||||
<CreateCustomerGroupForm />
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { CustomerGroup } from "@medusajs/medusa"
|
||||
import { Button, Drawer, Input } from "@medusajs/ui"
|
||||
import { Button, Input } from "@medusajs/ui"
|
||||
import { useAdminUpdateCustomerGroup } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as z from "zod"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
|
||||
type EditCustomerGroupFormProps = {
|
||||
group: CustomerGroup
|
||||
onSuccessfulSubmit: () => void
|
||||
subscribe: (state: boolean) => void
|
||||
}
|
||||
|
||||
const EditCustomerGroupSchema = z.object({
|
||||
@@ -20,10 +21,9 @@ const EditCustomerGroupSchema = z.object({
|
||||
|
||||
export const EditCustomerGroupForm = ({
|
||||
group,
|
||||
onSuccessfulSubmit,
|
||||
subscribe,
|
||||
}: EditCustomerGroupFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<z.infer<typeof EditCustomerGroupSchema>>({
|
||||
defaultValues: {
|
||||
@@ -32,31 +32,23 @@ export const EditCustomerGroupForm = ({
|
||||
resolver: zodResolver(EditCustomerGroupSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty])
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminUpdateCustomerGroup(group.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
await mutateAsync(data, {
|
||||
onSuccess: () => {
|
||||
onSuccessfulSubmit()
|
||||
handleSuccess()
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex flex-1 flex-col overflow-hidden"
|
||||
>
|
||||
<Drawer.Body className="flex max-w-full flex-1 flex-col gap-y-8 overflow-y-auto">
|
||||
<RouteDrawer.Body className="flex max-w-full flex-1 flex-col gap-y-8 overflow-y-auto">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="name"
|
||||
@@ -72,20 +64,20 @@ export const EditCustomerGroupForm = ({
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Drawer.Body>
|
||||
<Drawer.Footer>
|
||||
</RouteDrawer.Body>
|
||||
<RouteDrawer.Footer>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<Drawer.Close asChild>
|
||||
<RouteDrawer.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</Drawer.Close>
|
||||
</RouteDrawer.Close>
|
||||
<Button size="small" type="submit" isLoading={isLoading}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</Drawer.Footer>
|
||||
</RouteDrawer.Footer>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteDrawer.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { Drawer, Heading } from "@medusajs/ui"
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminCustomerGroup } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { EditCustomerGroupForm } from "./components/edit-customer-group-form"
|
||||
|
||||
export const CustomerGroupEdit = () => {
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
const { id } = useParams()
|
||||
const { customer_group, isLoading, isError, error } = useAdminCustomerGroup(
|
||||
id!
|
||||
@@ -15,28 +13,18 @@ export const CustomerGroupEdit = () => {
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleSuccessfulSubmit = () => {
|
||||
onOpenChange(false, true)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={onOpenChange}>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Heading>{t("customerGroups.editCustomerGroup")}</Heading>
|
||||
</Drawer.Header>
|
||||
{!isLoading && customer_group && (
|
||||
<EditCustomerGroupForm
|
||||
subscribe={subscribe}
|
||||
onSuccessfulSubmit={handleSuccessfulSubmit}
|
||||
group={customer_group}
|
||||
/>
|
||||
)}
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<Heading>{t("customerGroups.editCustomerGroup")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
{!isLoading && customer_group && (
|
||||
<EditCustomerGroupForm group={customer_group} />
|
||||
)}
|
||||
</RouteDrawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Button, FocusModal, Heading, Input, Text } from "@medusajs/ui"
|
||||
import { Button, Heading, Input, Text } from "@medusajs/ui"
|
||||
import { useAdminCreateCustomer } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import * as zod from "zod"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
|
||||
type CreateCustomerFormProps = {
|
||||
subscribe: (state: boolean) => void
|
||||
}
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
|
||||
const CreateCustomerSchema = zod
|
||||
.object({
|
||||
@@ -31,9 +30,9 @@ const CreateCustomerSchema = zod
|
||||
}
|
||||
})
|
||||
|
||||
export const CreateCustomerForm = ({ subscribe }: CreateCustomerFormProps) => {
|
||||
export const CreateCustomerForm = () => {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof CreateCustomerSchema>>({
|
||||
defaultValues: {
|
||||
@@ -46,13 +45,6 @@ export const CreateCustomerForm = ({ subscribe }: CreateCustomerFormProps) => {
|
||||
},
|
||||
resolver: zodResolver(CreateCustomerSchema),
|
||||
})
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty])
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminCreateCustomer()
|
||||
|
||||
@@ -67,22 +59,22 @@ export const CreateCustomerForm = ({ subscribe }: CreateCustomerFormProps) => {
|
||||
},
|
||||
{
|
||||
onSuccess: ({ customer }) => {
|
||||
navigate(`/customers/${customer.id}`, { replace: true })
|
||||
handleSuccess(`/customers/${customer.id}`)
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<FocusModal.Header>
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<FocusModal.Close asChild>
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</FocusModal.Close>
|
||||
</RouteFocusModal.Close>
|
||||
<Button
|
||||
size="small"
|
||||
variant="primary"
|
||||
@@ -92,8 +84,8 @@ export const CreateCustomerForm = ({ subscribe }: CreateCustomerFormProps) => {
|
||||
{t("actions.create")}
|
||||
</Button>
|
||||
</div>
|
||||
</FocusModal.Header>
|
||||
<FocusModal.Body className="flex flex-col items-center py-16">
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex flex-col items-center py-16">
|
||||
<div className="flex w-full max-w-[720px] flex-col gap-y-8">
|
||||
<div>
|
||||
<Heading>{t("customers.createCustomer")}</Heading>
|
||||
@@ -214,8 +206,8 @@ export const CreateCustomerForm = ({ subscribe }: CreateCustomerFormProps) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FocusModal.Body>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { CreateCustomerForm } from "./components/create-customer-form"
|
||||
|
||||
export const CustomerCreate = () => {
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content>
|
||||
<CreateCustomerForm subscribe={subscribe} />
|
||||
</FocusModal.Content>
|
||||
</FocusModal>
|
||||
<RouteFocusModal>
|
||||
<CreateCustomerForm />
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Customer } from "@medusajs/medusa"
|
||||
import { Button, Drawer, Input } from "@medusajs/ui"
|
||||
import { Button, Input } from "@medusajs/ui"
|
||||
import { useAdminUpdateCustomer } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
|
||||
type EditCustomerFormProps = {
|
||||
customer: Customer
|
||||
subscribe: (state: boolean) => void
|
||||
onSuccessfulSubmit: () => void
|
||||
}
|
||||
|
||||
const EditCustomerSchema = zod.object({
|
||||
@@ -21,12 +23,9 @@ const EditCustomerSchema = zod.object({
|
||||
phone: zod.string().optional(),
|
||||
})
|
||||
|
||||
export const EditCustomerForm = ({
|
||||
customer,
|
||||
subscribe,
|
||||
onSuccessfulSubmit,
|
||||
}: EditCustomerFormProps) => {
|
||||
export const EditCustomerForm = ({ customer }: EditCustomerFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof EditCustomerSchema>>({
|
||||
defaultValues: {
|
||||
@@ -38,14 +37,6 @@ export const EditCustomerForm = ({
|
||||
resolver: zodResolver(EditCustomerSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty])
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminUpdateCustomer(customer.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
@@ -58,16 +49,16 @@ export const EditCustomerForm = ({
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onSuccessfulSubmit()
|
||||
handleSuccess()
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form onSubmit={handleSubmit} className="flex flex-1 flex-col">
|
||||
<Drawer.Body>
|
||||
<RouteDrawer.Body>
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
@@ -130,14 +121,14 @@ export const EditCustomerForm = ({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Drawer.Body>
|
||||
<Drawer.Footer>
|
||||
</RouteDrawer.Body>
|
||||
<RouteDrawer.Footer>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<Drawer.Close asChild>
|
||||
<RouteDrawer.Close asChild>
|
||||
<Button variant="secondary" size="small">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</Drawer.Close>
|
||||
</RouteDrawer.Close>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
type="submit"
|
||||
@@ -147,8 +138,8 @@ export const EditCustomerForm = ({
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</Drawer.Footer>
|
||||
</RouteDrawer.Footer>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteDrawer.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,39 +1,26 @@
|
||||
import { Drawer, Heading } from "@medusajs/ui"
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminCustomer } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { EditCustomerForm } from "./components/edit-customer-form"
|
||||
|
||||
export const CustomerEdit = () => {
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { id } = useParams()
|
||||
const { customer, isLoading, isError, error } = useAdminCustomer(id!)
|
||||
|
||||
const handleSuccessfulSubmit = () => {
|
||||
onOpenChange(false, true)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={onOpenChange}>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Heading>{t("customers.editCustomer")}</Heading>
|
||||
</Drawer.Header>
|
||||
{!isLoading && customer && (
|
||||
<EditCustomerForm
|
||||
customer={customer}
|
||||
onSuccessfulSubmit={handleSuccessfulSubmit}
|
||||
subscribe={subscribe}
|
||||
/>
|
||||
)}
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<Heading>{t("customers.editCustomer")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
{!isLoading && customer && <EditCustomerForm customer={customer} />}
|
||||
</RouteDrawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
Button,
|
||||
CurrencyInput,
|
||||
DatePicker,
|
||||
FocusModal,
|
||||
Heading,
|
||||
Input,
|
||||
Select,
|
||||
@@ -14,20 +13,19 @@ import {
|
||||
} from "@medusajs/ui"
|
||||
import * as Collapsible from "@radix-ui/react-collapsible"
|
||||
import { useAdminCreateGiftCard, useAdminRegions } from "medusa-react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useState } from "react"
|
||||
import { useForm, useWatch } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { currencies } from "../../../../../lib/currencies"
|
||||
import { getDbAmount } from "../../../../../lib/money-amount-helpers"
|
||||
|
||||
type CreateGiftCardFormProps = {
|
||||
subscribe: (state: boolean) => void
|
||||
}
|
||||
|
||||
const CreateGiftCardSchema = zod.object({
|
||||
region_id: zod.string(),
|
||||
value: zod.string(),
|
||||
@@ -37,7 +35,7 @@ const CreateGiftCardSchema = zod.object({
|
||||
personal_message: zod.string().optional(),
|
||||
})
|
||||
|
||||
export const CreateGiftCardForm = ({ subscribe }: CreateGiftCardFormProps) => {
|
||||
export const CreateGiftCardForm = () => {
|
||||
const [showDateFields, setShowDateFields] = useState(false)
|
||||
|
||||
const { regions } = useAdminRegions({
|
||||
@@ -45,7 +43,7 @@ export const CreateGiftCardForm = ({ subscribe }: CreateGiftCardFormProps) => {
|
||||
fields: "id,name,currency_code",
|
||||
})
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof CreateGiftCardSchema>>({
|
||||
defaultValues: {
|
||||
@@ -59,11 +57,7 @@ export const CreateGiftCardForm = ({ subscribe }: CreateGiftCardFormProps) => {
|
||||
resolver: zodResolver(CreateGiftCardSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
setValue,
|
||||
setError,
|
||||
} = form
|
||||
const { setValue, setError } = form
|
||||
|
||||
const regionId = useWatch({
|
||||
control: form.control,
|
||||
@@ -75,10 +69,6 @@ export const CreateGiftCardForm = ({ subscribe }: CreateGiftCardFormProps) => {
|
||||
? currencies[currencyCode.toUpperCase()].symbol_native
|
||||
: undefined
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty, subscribe])
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminCreateGiftCard()
|
||||
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
@@ -95,7 +85,7 @@ export const CreateGiftCardForm = ({ subscribe }: CreateGiftCardFormProps) => {
|
||||
if (!currencyCode) {
|
||||
setError("region_id", {
|
||||
type: "manual",
|
||||
message: "Region not found",
|
||||
message: t("giftCards.selectRegionFirst"),
|
||||
})
|
||||
|
||||
return
|
||||
@@ -114,31 +104,31 @@ export const CreateGiftCardForm = ({ subscribe }: CreateGiftCardFormProps) => {
|
||||
},
|
||||
{
|
||||
onSuccess: ({ gift_card }) => {
|
||||
navigate(`../${gift_card.id}`, { replace: true })
|
||||
handleSuccess(`../${gift_card.id}`)
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form
|
||||
className="flex h-full flex-col overflow-hidden"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<FocusModal.Header>
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<FocusModal.Close asChild>
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</FocusModal.Close>
|
||||
</RouteFocusModal.Close>
|
||||
<Button size="small" type="submit" isLoading={isLoading}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</FocusModal.Header>
|
||||
<FocusModal.Body className="flex h-full w-full flex-col items-center overflow-y-auto py-16">
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex h-full w-full flex-col items-center overflow-y-auto py-16">
|
||||
<div className="flex w-full max-w-[720px] flex-col gap-y-8">
|
||||
<div>
|
||||
<Heading>{t("giftCards.createGiftCard")}</Heading>
|
||||
@@ -304,8 +294,8 @@ export const CreateGiftCardForm = ({ subscribe }: CreateGiftCardFormProps) => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</FocusModal.Body>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { CreateGiftCardForm } from "./components/create-gift-card-form"
|
||||
|
||||
export const GiftCardCreate = () => {
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content>
|
||||
<CreateGiftCardForm subscribe={subscribe} />
|
||||
</FocusModal.Content>
|
||||
</FocusModal>
|
||||
<RouteFocusModal>
|
||||
<CreateGiftCardForm />
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,19 +4,22 @@ import {
|
||||
Button,
|
||||
CurrencyInput,
|
||||
DatePicker,
|
||||
Drawer,
|
||||
Select,
|
||||
Switch,
|
||||
Text,
|
||||
} from "@medusajs/ui"
|
||||
import * as Collapsible from "@radix-ui/react-collapsible"
|
||||
import { useAdminRegions, useAdminUpdateGiftCard } from "medusa-react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { currencies } from "../../../../../lib/currencies"
|
||||
import { isAxiosError } from "../../../../../lib/is-axios-error"
|
||||
import {
|
||||
@@ -26,8 +29,6 @@ import {
|
||||
|
||||
type EditGiftCardFormProps = {
|
||||
giftCard: GiftCard
|
||||
onSuccessfulSubmit: () => void
|
||||
subscribe: (state: boolean) => void
|
||||
}
|
||||
|
||||
const EditGiftCardSchema = zod.object({
|
||||
@@ -37,13 +38,12 @@ const EditGiftCardSchema = zod.object({
|
||||
ends_at: zod.date().nullable(),
|
||||
})
|
||||
|
||||
export const EditGiftCardForm = ({
|
||||
giftCard,
|
||||
onSuccessfulSubmit,
|
||||
subscribe,
|
||||
}: EditGiftCardFormProps) => {
|
||||
export const EditGiftCardForm = ({ giftCard }: EditGiftCardFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const [showDateFields, setShowDateFields] = useState(!!giftCard.ends_at)
|
||||
|
||||
const form = useForm<zod.infer<typeof EditGiftCardSchema>>({
|
||||
defaultValues: {
|
||||
region_id: giftCard.region_id,
|
||||
@@ -57,14 +57,7 @@ export const EditGiftCardForm = ({
|
||||
resolver: zodResolver(EditGiftCardSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
setValue,
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty, subscribe])
|
||||
const { setValue } = form
|
||||
|
||||
const { regions } = useAdminRegions({
|
||||
limit: 1000,
|
||||
@@ -117,7 +110,7 @@ export const EditGiftCardForm = ({
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onSuccessfulSubmit()
|
||||
handleSuccess()
|
||||
},
|
||||
onError: (error) => {
|
||||
if (isAxiosError(error)) {
|
||||
@@ -132,19 +125,19 @@ export const EditGiftCardForm = ({
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex flex-1 flex-col overflow-hidden"
|
||||
>
|
||||
<Drawer.Body className="flex flex-1 flex-col gap-y-8 overflow-auto">
|
||||
<RouteDrawer.Body className="flex flex-1 flex-col gap-y-8 overflow-auto">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="balance"
|
||||
render={({ field: { onChange, ...field } }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>{t("fields.balance")}</Form.Label>
|
||||
<Form.Label>{t("giftCards.balance")}</Form.Label>
|
||||
<Form.Control>
|
||||
<CurrencyInput
|
||||
code={giftCard.region.currency_code.toUpperCase()}
|
||||
@@ -258,20 +251,20 @@ export const EditGiftCardForm = ({
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Drawer.Body>
|
||||
<Drawer.Footer>
|
||||
</RouteDrawer.Body>
|
||||
<RouteDrawer.Footer>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Drawer.Close asChild>
|
||||
<RouteDrawer.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</Drawer.Close>
|
||||
</RouteDrawer.Close>
|
||||
<Button size="small" type="submit" isLoading={isLoading}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</Drawer.Footer>
|
||||
</RouteDrawer.Footer>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteDrawer.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,39 +1,26 @@
|
||||
import { Drawer, Heading } from "@medusajs/ui"
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminGiftCard } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { EditGiftCardForm } from "./components/edit-gift-card-form"
|
||||
|
||||
export const GiftCardEdit = () => {
|
||||
const { id } = useParams()
|
||||
const { t } = useTranslation()
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
const { gift_card, isLoading, isError, error } = useAdminGiftCard(id!)
|
||||
|
||||
const handleSuccessfulSubmit = () => {
|
||||
onOpenChange(false, true)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={onOpenChange}>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Heading>{t("giftCards.editGiftCard")}</Heading>
|
||||
</Drawer.Header>
|
||||
{!isLoading && gift_card && (
|
||||
<EditGiftCardForm
|
||||
giftCard={gift_card}
|
||||
onSuccessfulSubmit={handleSuccessfulSubmit}
|
||||
subscribe={subscribe}
|
||||
/>
|
||||
)}
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<Heading>{t("giftCards.editGiftCard")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
{!isLoading && gift_card && <EditGiftCardForm giftCard={gift_card} />}
|
||||
</RouteDrawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { useAdminAddLocationToSalesChannel } from "medusa-react"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
|
||||
export const LocationAddSalesChannels = () => {
|
||||
const [open, onOpenChange] = useRouteModalState()
|
||||
|
||||
const { mutateAsync } = useAdminAddLocationToSalesChannel() // TODO: We need a batch mutation instead of this to avoid multiple requests
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content></FocusModal.Content>
|
||||
</FocusModal>
|
||||
)
|
||||
return <RouteFocusModal></RouteFocusModal>
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Button, FocusModal, Heading, Input, Text } from "@medusajs/ui"
|
||||
import { Button, Heading, Input, Text } from "@medusajs/ui"
|
||||
import { useAdminCreateStockLocation } from "medusa-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
import { CountrySelect } from "../../../../../components/common/country-select"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { RouteFocusModal } from "../../../../../components/route-modal"
|
||||
|
||||
const CreateLocationSchema = zod.object({
|
||||
name: zod.string().min(1),
|
||||
@@ -22,7 +23,7 @@ const CreateLocationSchema = zod.object({
|
||||
})
|
||||
|
||||
export const CreateLocationForm = () => {
|
||||
const { mutateAsync, isLoading } = useAdminCreateStockLocation()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const form = useForm<zod.infer<typeof CreateLocationSchema>>({
|
||||
defaultValues: {
|
||||
@@ -41,7 +42,7 @@ export const CreateLocationForm = () => {
|
||||
resolver: zodResolver(CreateLocationSchema),
|
||||
})
|
||||
|
||||
const { t } = useTranslation()
|
||||
const { mutateAsync, isLoading } = useAdminCreateStockLocation()
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
mutateAsync(
|
||||
@@ -56,24 +57,24 @@ export const CreateLocationForm = () => {
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex h-full flex-col overflow-hidden"
|
||||
>
|
||||
<FocusModal.Header>
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<FocusModal.Close asChild>
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</FocusModal.Close>
|
||||
</RouteFocusModal.Close>
|
||||
<Button type="submit" size="small" isLoading={isLoading}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</FocusModal.Header>
|
||||
<FocusModal.Body className="flex flex-1 flex-col overflow-hidden">
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex flex-1 flex-col overflow-hidden">
|
||||
<div className="flex flex-1 flex-col items-center overflow-y-auto">
|
||||
<div className="flex w-full max-w-[720px] flex-col gap-y-8 px-2 py-16">
|
||||
<div>
|
||||
@@ -227,8 +228,8 @@ export const CreateLocationForm = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FocusModal.Body>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { CreateLocationForm } from "./components/create-location-form"
|
||||
|
||||
export const LocationCreate = () => {
|
||||
const [open, onOpenChange] = useRouteModalState()
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content>
|
||||
<CreateLocationForm />
|
||||
</FocusModal.Content>
|
||||
</FocusModal>
|
||||
<RouteFocusModal>
|
||||
<CreateLocationForm />
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { StockLocationExpandedDTO } from "@medusajs/types"
|
||||
import { Button, Drawer, Input } from "@medusajs/ui"
|
||||
import { Button, Input } from "@medusajs/ui"
|
||||
import { useAdminUpdateStockLocation } from "medusa-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { CountrySelect } from "../../../../../components/common/country-select"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { RouteDrawer } from "../../../../../components/route-modal"
|
||||
|
||||
type EditLocationFormProps = {
|
||||
location: StockLocationExpandedDTO
|
||||
@@ -56,12 +58,12 @@ export const EditLocationForm = ({ location }: EditLocationFormProps) => {
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex flex-1 flex-col overflow-hidden"
|
||||
>
|
||||
<Drawer.Body className="flex flex-col gap-y-8 overflow-y-auto">
|
||||
<RouteDrawer.Body className="flex flex-col gap-y-8 overflow-y-auto">
|
||||
<div>
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
@@ -201,18 +203,20 @@ export const EditLocationForm = ({ location }: EditLocationFormProps) => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Drawer.Body>
|
||||
<Drawer.Footer>
|
||||
</RouteDrawer.Body>
|
||||
<RouteDrawer.Footer>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<Drawer.Close asChild>
|
||||
<RouteDrawer.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</Drawer.Close>
|
||||
<Button size="small">{t("actions.save")}</Button>
|
||||
</RouteDrawer.Close>
|
||||
<Button size="small" type="submit" isLoading={isLoading}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</Drawer.Footer>
|
||||
</RouteDrawer.Footer>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteDrawer.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { Drawer, Heading } from "@medusajs/ui"
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminStockLocations } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { json, useParams } from "react-router-dom"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { EditLocationForm } from "./components/edit-location-form/edit-location-form"
|
||||
|
||||
export const LocationEdit = () => {
|
||||
const [open, onOpenChange] = useRouteModalState()
|
||||
const { id } = useParams()
|
||||
|
||||
const { stock_locations, isLoading, isError, error } = useAdminStockLocations(
|
||||
@@ -24,24 +23,14 @@ export const LocationEdit = () => {
|
||||
|
||||
const stock_location = stock_locations?.[0]
|
||||
|
||||
if (!isLoading && !stock_location) {
|
||||
throw json({ message: "Not found" }, 404)
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={onOpenChange}>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Heading className="capitalize">
|
||||
{t("locations.editLocation")}
|
||||
</Heading>
|
||||
</Drawer.Header>
|
||||
{isLoading || !stock_location ? (
|
||||
<div>Loading...</div>
|
||||
) : (
|
||||
<EditLocationForm location={stock_location} />
|
||||
)}
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<Heading className="capitalize">{t("locations.editLocation")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
{!isLoading && stock_location && (
|
||||
<EditLocationForm location={stock_location} />
|
||||
)}
|
||||
</RouteDrawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import { Button, Drawer, Input } from "@medusajs/ui"
|
||||
import { Button, Input } from "@medusajs/ui"
|
||||
import { useAdminUpdateProduct } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { CountrySelect } from "../../../../../components/common/country-select"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
|
||||
type ProductAttributesFormProps = {
|
||||
product: Product
|
||||
subscribe: (state: boolean) => void
|
||||
onSuccessfulSubmit: () => void
|
||||
}
|
||||
|
||||
const dimension = zod
|
||||
@@ -39,9 +40,10 @@ const ProductAttributesSchema = zod.object({
|
||||
|
||||
export const ProductAttributesForm = ({
|
||||
product,
|
||||
subscribe,
|
||||
onSuccessfulSubmit,
|
||||
}: ProductAttributesFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof ProductAttributesSchema>>({
|
||||
defaultValues: {
|
||||
height: product.height ? product.height : null,
|
||||
@@ -55,15 +57,6 @@ export const ProductAttributesForm = ({
|
||||
resolver: zodResolver(ProductAttributesSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty, subscribe])
|
||||
|
||||
const { t } = useTranslation()
|
||||
const { mutateAsync, isLoading } = useAdminUpdateProduct(product.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
@@ -79,16 +72,16 @@ export const ProductAttributesForm = ({
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onSuccessfulSubmit()
|
||||
handleSuccess()
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form onSubmit={handleSubmit} className="flex h-full flex-col">
|
||||
<Drawer.Body>
|
||||
<RouteDrawer.Body>
|
||||
<div className="flex h-full flex-col gap-y-8">
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<Form.Field
|
||||
@@ -254,20 +247,20 @@ export const ProductAttributesForm = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Drawer.Body>
|
||||
<Drawer.Footer>
|
||||
</RouteDrawer.Body>
|
||||
<RouteDrawer.Footer>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<Drawer.Close asChild>
|
||||
<RouteDrawer.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</Drawer.Close>
|
||||
</RouteDrawer.Close>
|
||||
<Button size="small" type="submit" isLoading={isLoading}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</Drawer.Footer>
|
||||
</RouteDrawer.Footer>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteDrawer.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,41 +1,27 @@
|
||||
import { Drawer, Heading } from "@medusajs/ui"
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminProduct } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { ProductAttributesForm } from "./components/product-attributes-form"
|
||||
|
||||
export const ProductAttributes = () => {
|
||||
const { id } = useParams()
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { product, isLoading, isError, error } = useAdminProduct(id!)
|
||||
|
||||
const handleSuccessfulSubmit = () => {
|
||||
onOpenChange(false, true)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={onOpenChange}>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Heading>{t("products.editAttributes")}</Heading>
|
||||
</Drawer.Header>
|
||||
{!isLoading && product && (
|
||||
<ProductAttributesForm
|
||||
product={product}
|
||||
onSuccessfulSubmit={handleSuccessfulSubmit}
|
||||
subscribe={subscribe}
|
||||
/>
|
||||
)}
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<Heading>{t("products.editAttributes")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
{!isLoading && product && <ProductAttributesForm product={product} />}
|
||||
</RouteDrawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Button, FocusModal } from "@medusajs/ui"
|
||||
import { Button } from "@medusajs/ui"
|
||||
import { useAdminCreateProduct } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { UseFormReturn, useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { CreateProductDetails } from "./create-product-details"
|
||||
|
||||
type CreateProductFormProps = {
|
||||
subscribe: (state: boolean) => void
|
||||
}
|
||||
|
||||
const CreateProductSchema = zod.object({
|
||||
title: zod.string(),
|
||||
subtitle: zod.string().optional(),
|
||||
@@ -38,8 +36,10 @@ const CreateProductSchema = zod.object({
|
||||
type Schema = zod.infer<typeof CreateProductSchema>
|
||||
export type CreateProductFormReturn = UseFormReturn<Schema>
|
||||
|
||||
export const CreateProductForm = ({ subscribe }: CreateProductFormProps) => {
|
||||
export const CreateProductForm = () => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<Schema>({
|
||||
defaultValues: {
|
||||
title: "",
|
||||
@@ -61,14 +61,6 @@ export const CreateProductForm = ({ subscribe }: CreateProductFormProps) => {
|
||||
resolver: zodResolver(CreateProductSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [subscribe, isDirty])
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminCreateProduct()
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
@@ -83,34 +75,36 @@ export const CreateProductForm = ({ subscribe }: CreateProductFormProps) => {
|
||||
weight: values.weight ? parseFloat(values.weight) : undefined,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {},
|
||||
onSuccess: ({ product }) => {
|
||||
handleSuccess(`../${product.id}`)
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form onSubmit={handleSubmit} className="flex h-full flex-col">
|
||||
<FocusModal.Header>
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<FocusModal.Close asChild>
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button variant="secondary" size="small">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</FocusModal.Close>
|
||||
</RouteFocusModal.Close>
|
||||
<Button type="submit" size="small" isLoading={isLoading}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</FocusModal.Header>
|
||||
<FocusModal.Body className="flex flex-1 flex-col overflow-hidden">
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex flex-1 flex-col overflow-hidden">
|
||||
<div className="flex flex-1 flex-col items-center overflow-y-auto">
|
||||
<div className="flex h-full w-full">
|
||||
<CreateProductDetails form={form} />
|
||||
</div>
|
||||
</div>
|
||||
</FocusModal.Body>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { CreateProductForm } from "./components/create-product-form"
|
||||
|
||||
export const ProductCreate = () => {
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content>
|
||||
<CreateProductForm subscribe={subscribe} />
|
||||
</FocusModal.Content>
|
||||
</FocusModal>
|
||||
<RouteFocusModal>
|
||||
<CreateProductForm />
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,27 +1,20 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import { ProductStatus } from "@medusajs/types"
|
||||
import {
|
||||
Button,
|
||||
Drawer,
|
||||
Input,
|
||||
Select,
|
||||
Switch,
|
||||
Text,
|
||||
Textarea,
|
||||
} from "@medusajs/ui"
|
||||
import { Button, Input, Select, Switch, Text, Textarea } from "@medusajs/ui"
|
||||
import { useAdminUpdateProduct } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { Trans, useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
|
||||
type EditProductFormProps = {
|
||||
product: Product
|
||||
subscribe: (state: boolean) => void
|
||||
onSuccessfulSubmit: () => void
|
||||
}
|
||||
|
||||
const EditProductSchema = zod.object({
|
||||
@@ -34,11 +27,10 @@ const EditProductSchema = zod.object({
|
||||
discountable: zod.boolean(),
|
||||
})
|
||||
|
||||
export const EditProductForm = ({
|
||||
product,
|
||||
subscribe,
|
||||
onSuccessfulSubmit,
|
||||
}: EditProductFormProps) => {
|
||||
export const EditProductForm = ({ product }: EditProductFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof EditProductSchema>>({
|
||||
defaultValues: {
|
||||
status: product.status,
|
||||
@@ -52,15 +44,6 @@ export const EditProductForm = ({
|
||||
resolver: zodResolver(EditProductSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty, subscribe])
|
||||
|
||||
const { t } = useTranslation()
|
||||
const { mutateAsync, isLoading } = useAdminUpdateProduct(product.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
@@ -71,16 +54,16 @@ export const EditProductForm = ({
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onSuccessfulSubmit()
|
||||
handleSuccess()
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form onSubmit={handleSubmit} className="flex h-full flex-col">
|
||||
<Drawer.Body>
|
||||
<RouteDrawer.Body>
|
||||
<div className="flex h-full flex-col gap-y-8">
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<Form.Field
|
||||
@@ -247,8 +230,7 @@ export const EditProductForm = ({
|
||||
</Form.Control>
|
||||
</div>
|
||||
<Form.Hint className="!mt-1">
|
||||
When unchecked discounts will not be applied to this
|
||||
product.
|
||||
{t("products.discountableHint")}
|
||||
</Form.Hint>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
@@ -256,20 +238,20 @@ export const EditProductForm = ({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Drawer.Body>
|
||||
<Drawer.Footer>
|
||||
</RouteDrawer.Body>
|
||||
<RouteDrawer.Footer>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<Drawer.Close asChild>
|
||||
<RouteDrawer.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</Drawer.Close>
|
||||
</RouteDrawer.Close>
|
||||
<Button size="small" type="submit" isLoading={isLoading}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</Drawer.Footer>
|
||||
</RouteDrawer.Footer>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteDrawer.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,41 +1,27 @@
|
||||
import { Drawer, Heading } from "@medusajs/ui"
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminProduct } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { EditProductForm } from "./components/edit-product-form"
|
||||
|
||||
export const ProductEdit = () => {
|
||||
const { id } = useParams()
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { product, isLoading, isError, error } = useAdminProduct(id!)
|
||||
|
||||
const handleSuccessfulSubmit = () => {
|
||||
onOpenChange(false, true)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={onOpenChange}>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Heading>{t("products.editProduct")}</Heading>
|
||||
</Drawer.Header>
|
||||
{!isLoading && product && (
|
||||
<EditProductForm
|
||||
product={product}
|
||||
onSuccessfulSubmit={handleSuccessfulSubmit}
|
||||
subscribe={subscribe}
|
||||
/>
|
||||
)}
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<Heading>{t("products.editProduct")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
{!isLoading && product && <EditProductForm product={product} />}
|
||||
</RouteDrawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,14 +11,16 @@ import { Button, IconButton, Kbd, Tooltip } from "@medusajs/ui"
|
||||
import * as Dialog from "@radix-ui/react-dialog"
|
||||
import { Variants, motion } from "framer-motion"
|
||||
import { useAdminProduct } from "medusa-react"
|
||||
import { useCallback, useEffect, useMemo } from "react"
|
||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link, useParams, useSearchParams } from "react-router-dom"
|
||||
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
|
||||
export const ProductGallery = () => {
|
||||
const [open, onOpenChange] = useRouteModalState()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(true)
|
||||
}, [])
|
||||
|
||||
const { id } = useParams()
|
||||
const [searchParams, setSearchParams] = useSearchParams()
|
||||
@@ -78,7 +80,7 @@ export const ProductGallery = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog.Root modal open={open} onOpenChange={onOpenChange}>
|
||||
<Dialog.Root modal open={open} onOpenChange={setOpen}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Content className="bg-ui-bg-subtle dark fixed inset-0 grid-rows-[32px_1fr_6px] pb-16 pt-4 outline-none">
|
||||
<div className="flex items-center justify-between px-4">
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Product } from "@medusajs/medusa"
|
||||
import { Button, Drawer } from "@medusajs/ui"
|
||||
import { Button } from "@medusajs/ui"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { RouteDrawer } from "../../../../../components/route-modal"
|
||||
|
||||
type EditProductOptionsFormProps = {
|
||||
product: Product
|
||||
handleSuccess: () => void
|
||||
subscribe: (state: boolean) => void
|
||||
}
|
||||
|
||||
const EditProductOptionsSchema = zod.object({})
|
||||
@@ -22,22 +20,22 @@ export const EditProductOptionsForm = (props: EditProductOptionsFormProps) => {
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form className="flex flex-1 flex-col overflow-hidden">
|
||||
<Drawer.Body className="flex flex-1 flex-col gap-y-8 overflow-auto"></Drawer.Body>
|
||||
<Drawer.Footer>
|
||||
<RouteDrawer.Body className="flex flex-1 flex-col gap-y-8 overflow-auto"></RouteDrawer.Body>
|
||||
<RouteDrawer.Footer>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<Drawer.Close asChild>
|
||||
<RouteDrawer.Close asChild>
|
||||
<Button variant="secondary" size="small">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</Drawer.Close>
|
||||
</RouteDrawer.Close>
|
||||
<Button type="submit" size="small">
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</Drawer.Footer>
|
||||
</RouteDrawer.Footer>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteDrawer.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,39 +1,26 @@
|
||||
import { Drawer, Heading } from "@medusajs/ui"
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminProduct } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { EditProductOptionsForm } from "./components/edit-product-options-form"
|
||||
|
||||
export const ProductOptions = () => {
|
||||
const { id } = useParams()
|
||||
const { t } = useTranslation()
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
const { product, isLoading, isError, error } = useAdminProduct(id!)
|
||||
|
||||
const handleSuccess = () => {
|
||||
onOpenChange(false, true)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={onOpenChange}>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Heading>{t("products.editOptions")}</Heading>
|
||||
</Drawer.Header>
|
||||
{!isLoading && product && (
|
||||
<EditProductOptionsForm
|
||||
product={product}
|
||||
handleSuccess={handleSuccess}
|
||||
subscribe={subscribe}
|
||||
/>
|
||||
)}
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<Heading>{t("products.editOptions")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
{!isLoading && product && <EditProductOptionsForm product={product} />}
|
||||
</RouteDrawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import { Product, SalesChannel } from "@medusajs/medusa"
|
||||
import { Button, Checkbox, FocusModal } from "@medusajs/ui"
|
||||
import { Button, Checkbox } from "@medusajs/ui"
|
||||
import { RowSelectionState, createColumnHelper } from "@tanstack/react-table"
|
||||
import { useAdminSalesChannels, useAdminUpdateProduct } from "medusa-react"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useForm } from "react-hook-form"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useSalesChannelTableColumns } from "../../../../../hooks/table/columns/use-sales-channel-table-columns"
|
||||
import { useSalesChannelTableFilters } from "../../../../../hooks/table/filters/use-sales-channel-table-filters"
|
||||
@@ -12,18 +20,28 @@ import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
|
||||
type EditSalesChannelsFormProps = {
|
||||
product: Product
|
||||
subscribe: (state: boolean) => void
|
||||
onSuccessfulSubmit: () => void
|
||||
}
|
||||
|
||||
const EditSalesChannelsSchema = zod.object({
|
||||
sales_channels: zod.array(zod.string()).optional(),
|
||||
})
|
||||
|
||||
const PAGE_SIZE = 50
|
||||
|
||||
export const EditSalesChannelsForm = ({
|
||||
product,
|
||||
subscribe,
|
||||
onSuccessfulSubmit,
|
||||
}: EditSalesChannelsFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof EditSalesChannelsSchema>>({
|
||||
defaultValues: {
|
||||
sales_channels: product.sales_channels?.map((sc) => sc.id) ?? [],
|
||||
},
|
||||
resolver: zodResolver(EditSalesChannelsSchema),
|
||||
})
|
||||
|
||||
const { setValue } = form
|
||||
|
||||
const initialState =
|
||||
product.sales_channels?.reduce((acc, curr) => {
|
||||
@@ -34,13 +52,13 @@ export const EditSalesChannelsForm = ({
|
||||
const [rowSelection, setRowSelection] =
|
||||
useState<RowSelectionState>(initialState)
|
||||
|
||||
const isDirty = Object.entries(initialState).some(
|
||||
([key, value]) => value !== rowSelection[key]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty, subscribe])
|
||||
const ids = Object.keys(rowSelection)
|
||||
setValue("sales_channels", ids, {
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
})
|
||||
}, [rowSelection, setValue])
|
||||
|
||||
const { searchParams, raw } = useSalesChannelTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
@@ -76,12 +94,10 @@ export const EditSalesChannelsForm = ({
|
||||
product.id
|
||||
)
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const selected = Object.keys(rowSelection).filter((key) => {
|
||||
return rowSelection[key]
|
||||
})
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
const arr = data.sales_channels ?? []
|
||||
|
||||
const sales_channels = selected.map((id) => {
|
||||
const sales_channels = arr.map((id) => {
|
||||
return {
|
||||
id,
|
||||
}
|
||||
@@ -93,46 +109,48 @@ export const EditSalesChannelsForm = ({
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onSuccessfulSubmit()
|
||||
handleSuccess()
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col overflow-hidden">
|
||||
<FocusModal.Header>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<FocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<div className="flex h-full flex-col overflow-hidden">
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</RouteFocusModal.Close>
|
||||
<Button size="small" isLoading={isMutating} onClick={handleSubmit}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</FocusModal.Close>
|
||||
<Button size="small" isLoading={isMutating} onClick={handleSubmit}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</FocusModal.Header>
|
||||
<FocusModal.Body>
|
||||
<DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
isLoading={isLoading}
|
||||
count={count}
|
||||
filters={filters}
|
||||
search
|
||||
pagination
|
||||
orderBy={["name", "created_at", "updated_at"]}
|
||||
queryObject={raw}
|
||||
layout="fill"
|
||||
/>
|
||||
</FocusModal.Body>
|
||||
</div>
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body>
|
||||
<DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
isLoading={isLoading}
|
||||
count={count}
|
||||
filters={filters}
|
||||
search
|
||||
pagination
|
||||
orderBy={["name", "created_at", "updated_at"]}
|
||||
queryObject={raw}
|
||||
layout="fill"
|
||||
/>
|
||||
</RouteFocusModal.Body>
|
||||
</div>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,35 +1,20 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { useAdminProduct } from "medusa-react"
|
||||
import { useParams } from "react-router-dom"
|
||||
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { EditSalesChannelsForm } from "./components/edit-sales-channels-form"
|
||||
|
||||
export const ProductSalesChannels = () => {
|
||||
const { id } = useParams()
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
const { product, isLoading, isError, error } = useAdminProduct(id!)
|
||||
|
||||
const handleSuccessfulSubmit = () => {
|
||||
onOpenChange(false, true)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content>
|
||||
{!isLoading && product && (
|
||||
<EditSalesChannelsForm
|
||||
product={product}
|
||||
onSuccessfulSubmit={handleSuccessfulSubmit}
|
||||
subscribe={subscribe}
|
||||
/>
|
||||
)}
|
||||
</FocusModal.Content>
|
||||
</FocusModal>
|
||||
<RouteFocusModal>
|
||||
{!isLoading && product && <EditSalesChannelsForm product={product} />}
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import { User } from "@medusajs/medusa"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Button, Drawer, Input, Select, Switch } from "@medusajs/ui"
|
||||
import { User } from "@medusajs/medusa"
|
||||
import { Button, Input, Select, Switch } from "@medusajs/ui"
|
||||
import { adminAuthKeys, useAdminUpdateUser } from "medusa-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { Trans, useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { languages } from "../../../../../i18n/config"
|
||||
import { queryClient } from "../../../../../lib/medusa"
|
||||
|
||||
type EditProfileProps = {
|
||||
user: Omit<User, "password_hash">
|
||||
usageInsights: boolean
|
||||
onSuccess: () => void
|
||||
}
|
||||
|
||||
const EditProfileSchema = zod.object({
|
||||
@@ -23,13 +26,9 @@ const EditProfileSchema = zod.object({
|
||||
usage_insights: zod.boolean(),
|
||||
})
|
||||
|
||||
export const EditProfileForm = ({
|
||||
user,
|
||||
usageInsights,
|
||||
onSuccess,
|
||||
}: EditProfileProps) => {
|
||||
export const EditProfileForm = ({ user, usageInsights }: EditProfileProps) => {
|
||||
const { t, i18n } = useTranslation()
|
||||
const { mutateAsync, isLoading } = useAdminUpdateUser(user.id)
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof EditProfileSchema>>({
|
||||
defaultValues: {
|
||||
@@ -49,6 +48,8 @@ export const EditProfileForm = ({
|
||||
a.display_name.localeCompare(b.display_name)
|
||||
)
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminUpdateUser(user.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
await mutateAsync(
|
||||
{
|
||||
@@ -56,12 +57,7 @@ export const EditProfileForm = ({
|
||||
last_name: values.last_name,
|
||||
},
|
||||
{
|
||||
onSuccess: ({ user }) => {
|
||||
form.reset({
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
})
|
||||
|
||||
onSuccess: () => {
|
||||
// Invalidate the current user session.
|
||||
queryClient.invalidateQueries(adminAuthKeys.details())
|
||||
},
|
||||
@@ -73,13 +69,13 @@ export const EditProfileForm = ({
|
||||
|
||||
changeLanguage(values.language)
|
||||
|
||||
onSuccess()
|
||||
handleSuccess()
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form onSubmit={handleSubmit} className="flex flex-1 flex-col">
|
||||
<Drawer.Body>
|
||||
<RouteDrawer.Body>
|
||||
<div className="flex flex-col gap-y-8">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Form.Field
|
||||
@@ -122,9 +118,6 @@ export const EditProfileForm = ({
|
||||
<Form.Control>
|
||||
<Select
|
||||
{...field}
|
||||
// open={selectOpen}
|
||||
// onOpenChange={setSelectOpen}
|
||||
value={field.value}
|
||||
onValueChange={field.onChange}
|
||||
size="small"
|
||||
>
|
||||
@@ -175,6 +168,7 @@ export const EditProfileForm = ({
|
||||
i18nKey="profile.userInsightsHint"
|
||||
components={[
|
||||
<a
|
||||
key="hint-link"
|
||||
className="text-ui-fg-interactive hover:text-ui-fg-interactive-hover transition-fg underline"
|
||||
href="https://docs.medusajs.com/usage#admin-analytics"
|
||||
target="_blank"
|
||||
@@ -189,20 +183,20 @@ export const EditProfileForm = ({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Drawer.Body>
|
||||
<Drawer.Footer>
|
||||
</RouteDrawer.Body>
|
||||
<RouteDrawer.Footer>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Drawer.Close asChild>
|
||||
<RouteDrawer.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</Drawer.Close>
|
||||
</RouteDrawer.Close>
|
||||
<Button size="small" type="submit" isLoading={isLoading}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</Drawer.Footer>
|
||||
</RouteDrawer.Footer>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteDrawer.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,52 +1,26 @@
|
||||
import { Drawer, Heading } from "@medusajs/ui"
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminGetSession } from "medusa-react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { EditProfileForm } from "./components/edit-profile-form/edit-profile-form"
|
||||
|
||||
export const ProfileEdit = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { user, isLoading, isError, error } = useAdminGetSession()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(true)
|
||||
}, [])
|
||||
|
||||
const onOpenChange = (open: boolean) => {
|
||||
if (!open) {
|
||||
setTimeout(() => {
|
||||
navigate(`/settings/profile`, { replace: true })
|
||||
}, 200)
|
||||
}
|
||||
|
||||
setOpen(open)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={onOpenChange}>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header className="capitalize">
|
||||
<Heading>{t("profile.editProfile")}</Heading>
|
||||
</Drawer.Header>
|
||||
{isLoading || !user ? (
|
||||
<div>Loading...</div>
|
||||
) : (
|
||||
<EditProfileForm
|
||||
user={user}
|
||||
usageInsights={false}
|
||||
onSuccess={() => onOpenChange(false)}
|
||||
/>
|
||||
)}
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header className="capitalize">
|
||||
<Heading>{t("profile.editProfile")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
{!isLoading && user && (
|
||||
<EditProfileForm user={user} usageInsights={false} />
|
||||
)}
|
||||
</RouteDrawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Button, FocusModal, Heading, Input, Switch, Text } from "@medusajs/ui"
|
||||
import { useAdminCreateRegion } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { Button, Heading, Input, Select, Switch, Text } from "@medusajs/ui"
|
||||
import { useAdminCreateRegion, useAdminStore } from "medusa-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import * as zod from "zod"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
|
||||
type CreateRegionFormProps = {
|
||||
subscribe: (state: boolean) => void
|
||||
}
|
||||
import { RouteFocusModal } from "../../../../../components/route-modal/route-focus-modal"
|
||||
|
||||
const CreateRegionSchema = zod.object({
|
||||
name: zod.string().min(1),
|
||||
@@ -23,7 +19,7 @@ const CreateRegionSchema = zod.object({
|
||||
tax_code: zod.string().optional(),
|
||||
})
|
||||
|
||||
export const CreateRegionForm = ({ subscribe }: CreateRegionFormProps) => {
|
||||
export const CreateRegionForm = () => {
|
||||
const form = useForm<zod.infer<typeof CreateRegionSchema>>({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
@@ -33,23 +29,19 @@ export const CreateRegionForm = ({ subscribe }: CreateRegionFormProps) => {
|
||||
fulfillment_providers: [],
|
||||
payment_providers: [],
|
||||
tax_code: "",
|
||||
tax_rate: 0,
|
||||
},
|
||||
resolver: zodResolver(CreateRegionSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty, subscribe])
|
||||
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminCreateRegion()
|
||||
|
||||
const { store } = useAdminStore()
|
||||
const storeCurrencies = store?.currencies ?? []
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
await mutateAsync(
|
||||
{
|
||||
@@ -71,24 +63,24 @@ export const CreateRegionForm = ({ subscribe }: CreateRegionFormProps) => {
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form
|
||||
className="flex h-full flex-col overflow-hidden"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<FocusModal.Header>
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<FocusModal.Close asChild>
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</FocusModal.Close>
|
||||
</RouteFocusModal.Close>
|
||||
<Button size="small" type="submit" isLoading={isLoading}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</FocusModal.Header>
|
||||
<FocusModal.Body className="flex h-full w-full flex-col items-center overflow-y-auto py-16">
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex h-full w-full flex-col items-center overflow-y-auto py-16">
|
||||
<div className="flex w-full max-w-[720px] flex-col gap-y-8">
|
||||
<div>
|
||||
<Heading>{t("regions.createRegion")}</Heading>
|
||||
@@ -116,11 +108,27 @@ export const CreateRegionForm = ({ subscribe }: CreateRegionFormProps) => {
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="currency_code"
|
||||
render={({ field }) => {
|
||||
render={({ field: { onChange, ref, ...field } }) => {
|
||||
return (
|
||||
<Form.Item>
|
||||
<Form.Label>{t("fields.currency")}</Form.Label>
|
||||
<Form.Control></Form.Control>
|
||||
<Form.Control>
|
||||
<Select {...field} onValueChange={onChange}>
|
||||
<Select.Trigger ref={ref}>
|
||||
<Select.Value />
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{storeCurrencies.map((currency) => (
|
||||
<Select.Item
|
||||
value={currency.code}
|
||||
key={currency.code}
|
||||
>
|
||||
{currency.name}
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Content>
|
||||
</Select>
|
||||
</Form.Control>
|
||||
<Form.ErrorMessage />
|
||||
</Form.Item>
|
||||
)
|
||||
@@ -198,8 +206,8 @@ export const CreateRegionForm = ({ subscribe }: CreateRegionFormProps) => {
|
||||
<div className="grid grid-cols-2 gap-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
</FocusModal.Body>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteFocusModal } from "../../../components/route-modal/route-focus-modal"
|
||||
import { CreateRegionForm } from "./components/create-region-form"
|
||||
|
||||
export const RegionCreate = () => {
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content>
|
||||
<CreateRegionForm subscribe={subscribe} />
|
||||
</FocusModal.Content>
|
||||
</FocusModal>
|
||||
<RouteFocusModal>
|
||||
<CreateRegionForm />
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./region-shipping-option-section"
|
||||
@@ -1,60 +1,48 @@
|
||||
import { Region, ShippingOption } from "@medusajs/medusa"
|
||||
import { Container, Heading, StatusBadge, Table, clx } from "@medusajs/ui"
|
||||
import {
|
||||
PaginationState,
|
||||
RowSelectionState,
|
||||
createColumnHelper,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table"
|
||||
import { Region } from "@medusajs/medusa"
|
||||
import { Container, Heading } from "@medusajs/ui"
|
||||
import { useAdminShippingOptions } from "medusa-react"
|
||||
import { useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination"
|
||||
|
||||
import { PricedShippingOption } from "@medusajs/medusa/dist/types/pricing"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useShippingOptionColumns } from "./use-shipping-option-table-columns"
|
||||
import { useShippingOptionTableFilters } from "./use-shipping-option-table-filters"
|
||||
import { useShippingOptionTableQuery } from "./use-shipping-option-table-query"
|
||||
|
||||
type RegionShippingOptionSectionProps = {
|
||||
region: Region
|
||||
}
|
||||
|
||||
const PAGE_SIZE = 10
|
||||
|
||||
export const RegionShippingOptionSection = ({
|
||||
region,
|
||||
}: RegionShippingOptionSectionProps) => {
|
||||
const { shipping_options, count, isError, error, isLoading } =
|
||||
useAdminShippingOptions({
|
||||
region_id: region.id,
|
||||
is_return: false,
|
||||
})
|
||||
|
||||
const [{ pageIndex, pageSize }, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: count || 0,
|
||||
const { searchParams, raw } = useShippingOptionTableQuery({
|
||||
pageSize: PAGE_SIZE,
|
||||
regionId: region.id,
|
||||
})
|
||||
const { shipping_options, count, isError, error, isLoading } =
|
||||
useAdminShippingOptions(
|
||||
{
|
||||
...searchParams,
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
}
|
||||
)
|
||||
|
||||
const pagination = useMemo(
|
||||
() => ({
|
||||
pageIndex,
|
||||
pageSize,
|
||||
}),
|
||||
[pageIndex, pageSize]
|
||||
)
|
||||
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||
|
||||
const filters = useShippingOptionTableFilters()
|
||||
const columns = useShippingOptionColumns()
|
||||
|
||||
const table = useReactTable({
|
||||
data: shipping_options ?? [],
|
||||
const { table } = useDataTable({
|
||||
data: (shipping_options ?? []) as unknown as PricedShippingOption[],
|
||||
columns,
|
||||
pageCount: count ? 1 : 0,
|
||||
state: {
|
||||
pagination,
|
||||
rowSelection,
|
||||
},
|
||||
manualPagination: true,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
onPaginationChange: setPagination,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
count,
|
||||
enablePagination: true,
|
||||
getRowId: (row) => row.id!,
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
|
||||
const { t } = useTranslation()
|
||||
@@ -64,91 +52,30 @@ export const RegionShippingOptionSection = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="p-0 divide-y">
|
||||
<Container className="divide-y p-0">
|
||||
<div className="px-6 py-4">
|
||||
<Heading level="h2">{t("regions.shippingOptions")}</Heading>
|
||||
</div>
|
||||
<Table>
|
||||
<Table.Header>
|
||||
{table.getHeaderGroups().map((headerGroup) => {
|
||||
return (
|
||||
<Table.Row
|
||||
key={headerGroup.id}
|
||||
className="[&_th:first-of-type]:w-[1%] [&_th:first-of-type]:whitespace-nowrap [&_th:last-of-type]:w-[1%] [&_th:last-of-type]:whitespace-nowrap"
|
||||
>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<Table.HeaderCell key={header.id}>
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</Table.HeaderCell>
|
||||
)
|
||||
})}
|
||||
</Table.Row>
|
||||
)
|
||||
})}
|
||||
</Table.Header>
|
||||
<Table.Body className="border-b-0">
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<Table.Row
|
||||
key={row.id}
|
||||
className={clx(
|
||||
"transition-fg [&_td:last-of-type]:w-[1%] [&_td:last-of-type]:whitespace-nowrap",
|
||||
{
|
||||
"bg-ui-bg-highlight hover:bg-ui-bg-highlight-hover":
|
||||
row.getIsSelected(),
|
||||
}
|
||||
)}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<Table.Cell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</Table.Cell>
|
||||
))}
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
<LocalizedTablePagination
|
||||
canNextPage={table.getCanNextPage()}
|
||||
canPreviousPage={table.getCanPreviousPage()}
|
||||
nextPage={table.nextPage}
|
||||
previousPage={table.previousPage}
|
||||
count={count ?? 0}
|
||||
pageIndex={pageIndex}
|
||||
pageCount={table.getPageCount()}
|
||||
pageSize={count ?? 0}
|
||||
<DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
count={count}
|
||||
filters={filters}
|
||||
orderBy={[
|
||||
"name",
|
||||
"price_type",
|
||||
"price_incl_tax",
|
||||
"is_return",
|
||||
"admin_only",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]}
|
||||
isLoading={isLoading}
|
||||
rowCount={PAGE_SIZE}
|
||||
pagination
|
||||
search
|
||||
queryObject={raw}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<ShippingOption>()
|
||||
|
||||
const useShippingOptionColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.accessor("name", {
|
||||
header: t("fields.name"),
|
||||
cell: (cell) => cell.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("admin_only", {
|
||||
header: t("fields.availability"),
|
||||
cell: (cell) => {
|
||||
const value = cell.getValue()
|
||||
|
||||
return (
|
||||
<StatusBadge color={value ? "blue" : "green"}>
|
||||
{value ? t("general.admin") : t("general.store")}
|
||||
</StatusBadge>
|
||||
)
|
||||
},
|
||||
}),
|
||||
],
|
||||
[t]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { PricedShippingOption } from "@medusajs/medusa/dist/types/pricing"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { MoneyAmountCell } from "../../../../../components/table/table-cells/common/money-amount-cell"
|
||||
import { PlaceholderCell } from "../../../../../components/table/table-cells/common/placeholder-cell"
|
||||
import { StatusCell } from "../../../../../components/table/table-cells/common/status-cell"
|
||||
|
||||
const columnHelper = createColumnHelper<PricedShippingOption>()
|
||||
|
||||
export const useShippingOptionColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.accessor("name", {
|
||||
header: t("fields.name"),
|
||||
cell: ({ getValue }) => (
|
||||
<div className="flex size-full items-center overflow-hidden">
|
||||
<span className="truncate">{getValue()}</span>
|
||||
</div>
|
||||
),
|
||||
}),
|
||||
columnHelper.accessor("price_type", {
|
||||
header: t("regions.priceType"),
|
||||
cell: ({ getValue }) => {
|
||||
const type = getValue()
|
||||
|
||||
return (
|
||||
<StatusCell color={type === "flat_rate" ? "green" : "blue"}>
|
||||
{type === "flat_rate"
|
||||
? t("regions.flatRate")
|
||||
: t("regions.calculated")}
|
||||
</StatusCell>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("price_incl_tax", {
|
||||
header: t("fields.price"),
|
||||
cell: ({ getValue, row }) => {
|
||||
const isCalculated = row.original.price_type === "calculated"
|
||||
|
||||
if (isCalculated) {
|
||||
return <PlaceholderCell />
|
||||
}
|
||||
|
||||
const amount = getValue()
|
||||
const currencyCode = row.original.region!.currency_code
|
||||
|
||||
return <MoneyAmountCell currencyCode={currencyCode} amount={amount} />
|
||||
},
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "min_amount",
|
||||
header: "Min.",
|
||||
cell: ({ row }) => {
|
||||
const minAmountReq = row.original.requirements?.find(
|
||||
(r) => r.type === "min_subtotal"
|
||||
)
|
||||
|
||||
if (!minAmountReq) {
|
||||
return <PlaceholderCell />
|
||||
}
|
||||
|
||||
const amount = minAmountReq.amount
|
||||
const currencyCode = row.original.region!.currency_code
|
||||
|
||||
return <MoneyAmountCell currencyCode={currencyCode} amount={amount} />
|
||||
},
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: "max_amount",
|
||||
header: "Max.",
|
||||
cell: ({ row }) => {
|
||||
const maxAmountReq = row.original.requirements?.find(
|
||||
(r) => r.type === "max_subtotal"
|
||||
)
|
||||
|
||||
if (!maxAmountReq) {
|
||||
return <PlaceholderCell />
|
||||
}
|
||||
|
||||
const amount = maxAmountReq.amount
|
||||
const currencyCode = row.original.region!.currency_code
|
||||
|
||||
return <MoneyAmountCell currencyCode={currencyCode} amount={amount} />
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("admin_only", {
|
||||
header: t("fields.availability"),
|
||||
cell: (cell) => {
|
||||
const value = cell.getValue()
|
||||
|
||||
return (
|
||||
<StatusCell color={value ? "blue" : "green"}>
|
||||
{value ? t("general.admin") : t("general.store")}
|
||||
</StatusCell>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("is_return", {
|
||||
header: t("fields.type"),
|
||||
cell: (cell) => {
|
||||
const value = cell.getValue()
|
||||
|
||||
return (
|
||||
<StatusCell color={value ? "blue" : "green"}>
|
||||
{value ? t("regions.return") : t("regions.outbound")}
|
||||
</StatusCell>
|
||||
)
|
||||
},
|
||||
}),
|
||||
],
|
||||
[t]
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Filter } from "../../../../../components/table/data-table"
|
||||
|
||||
export const useShippingOptionTableFilters = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const isReturnFilter: Filter = {
|
||||
key: "is_return",
|
||||
label: t("fields.type"),
|
||||
type: "select",
|
||||
options: [
|
||||
{ label: t("regions.return"), value: "true" },
|
||||
{ label: t("regions.outbound"), value: "false" },
|
||||
],
|
||||
}
|
||||
|
||||
const isAdminFilter: Filter = {
|
||||
key: "admin_only",
|
||||
label: t("fields.availability"),
|
||||
type: "select",
|
||||
options: [
|
||||
{ label: t("general.admin"), value: "true" },
|
||||
{ label: t("general.store"), value: "false" },
|
||||
],
|
||||
}
|
||||
|
||||
const dateFilters: Filter[] = [
|
||||
{ label: t("fields.createdAt"), key: "created_at" },
|
||||
{ label: t("fields.updatedAt"), key: "updated_at" },
|
||||
].map((f) => ({
|
||||
key: f.key,
|
||||
label: f.label,
|
||||
type: "date",
|
||||
}))
|
||||
|
||||
const filters = [isReturnFilter, isAdminFilter, ...dateFilters]
|
||||
|
||||
return filters
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { AdminGetShippingOptionsParams } from "@medusajs/medusa"
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
|
||||
type UseShippingOptionTableQueryProps = {
|
||||
regionId: string
|
||||
isReturn?: boolean
|
||||
pageSize?: number
|
||||
prefix?: string
|
||||
}
|
||||
|
||||
export const useShippingOptionTableQuery = ({
|
||||
regionId,
|
||||
pageSize = 10,
|
||||
prefix,
|
||||
}: UseShippingOptionTableQueryProps) => {
|
||||
const queryObject = useQueryParams(
|
||||
[
|
||||
"offset",
|
||||
"q",
|
||||
"order",
|
||||
"admin_only",
|
||||
"is_return",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
],
|
||||
prefix
|
||||
)
|
||||
|
||||
const { offset, order, q, admin_only, is_return, created_at, updated_at } =
|
||||
queryObject
|
||||
|
||||
const searchParams: AdminGetShippingOptionsParams = {
|
||||
limit: pageSize,
|
||||
offset: offset ? Number(offset) : 0,
|
||||
region_id: regionId,
|
||||
is_return: is_return ? is_return === "true" : undefined,
|
||||
admin_only: admin_only ? admin_only === "true" : undefined,
|
||||
q,
|
||||
order,
|
||||
created_at: created_at ? JSON.parse(created_at) : undefined,
|
||||
updated_at: updated_at ? JSON.parse(updated_at) : undefined,
|
||||
}
|
||||
|
||||
return {
|
||||
searchParams,
|
||||
raw: queryObject,
|
||||
}
|
||||
}
|
||||
@@ -1,64 +1,43 @@
|
||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { Region } from "@medusajs/medusa"
|
||||
import {
|
||||
Button,
|
||||
Container,
|
||||
Heading,
|
||||
Table,
|
||||
Tooltip,
|
||||
clx,
|
||||
usePrompt,
|
||||
} from "@medusajs/ui"
|
||||
import {
|
||||
PaginationState,
|
||||
createColumnHelper,
|
||||
flexRender,
|
||||
getCoreRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table"
|
||||
import { Button, Container, Heading, usePrompt } from "@medusajs/ui"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useAdminDeleteRegion, useAdminRegions } from "medusa-react"
|
||||
import { useMemo, useState } from "react"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link, useNavigate } from "react-router-dom"
|
||||
import { Link } from "react-router-dom"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useRegionTableColumns } from "../../../../../hooks/table/columns/use-region-table-columns"
|
||||
import { useRegionTableFilters } from "../../../../../hooks/table/filters/use-region-table-filters"
|
||||
import { useRegionTableQuery } from "../../../../../hooks/table/query/use-region-table-query"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
|
||||
const PAGE_SIZE = 50
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
export const RegionListTable = () => {
|
||||
const navigate = useNavigate()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [{ pageIndex, pageSize }, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
|
||||
const pagination = useMemo(
|
||||
() => ({
|
||||
pageIndex,
|
||||
pageSize,
|
||||
}),
|
||||
[pageIndex, pageSize]
|
||||
const { searchParams, raw } = useRegionTableQuery({ pageSize: PAGE_SIZE })
|
||||
const { regions, count, isLoading, isError, error } = useAdminRegions(
|
||||
{
|
||||
...searchParams,
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
}
|
||||
)
|
||||
|
||||
const { regions, count, isLoading, isError, error } = useAdminRegions({
|
||||
limit: PAGE_SIZE,
|
||||
offset: pageIndex * PAGE_SIZE,
|
||||
})
|
||||
|
||||
const filters = useRegionTableFilters()
|
||||
const columns = useColumns()
|
||||
|
||||
const table = useReactTable({
|
||||
const { table } = useDataTable({
|
||||
data: regions ?? [],
|
||||
columns,
|
||||
pageCount: Math.ceil((count ?? 0) / PAGE_SIZE),
|
||||
state: {
|
||||
pagination,
|
||||
},
|
||||
onPaginationChange: setPagination,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
count,
|
||||
enablePagination: true,
|
||||
getRowId: (row) => row.id,
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
|
||||
if (isError) {
|
||||
@@ -66,7 +45,7 @@ export const RegionListTable = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="p-0">
|
||||
<Container className="divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<Heading level="h2">{t("regions.domain")}</Heading>
|
||||
<Link to="/settings/regions/create">
|
||||
@@ -75,59 +54,18 @@ export const RegionListTable = () => {
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
<Table>
|
||||
<Table.Header>
|
||||
{table.getHeaderGroups().map((headerGroup) => {
|
||||
return (
|
||||
<Table.Row
|
||||
key={headerGroup.id}
|
||||
className=" [&_th:last-of-type]:w-[1%] [&_th:last-of-type]:whitespace-nowrap"
|
||||
>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<Table.HeaderCell key={header.id}>
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</Table.HeaderCell>
|
||||
)
|
||||
})}
|
||||
</Table.Row>
|
||||
)
|
||||
})}
|
||||
</Table.Header>
|
||||
<Table.Body className="border-b-0">
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<Table.Row
|
||||
key={row.id}
|
||||
className={clx(
|
||||
"transition-fg cursor-pointer [&_td:last-of-type]:w-[1%] [&_td:last-of-type]:whitespace-nowrap",
|
||||
{
|
||||
"bg-ui-bg-highlight hover:bg-ui-bg-highlight-hover":
|
||||
row.getIsSelected(),
|
||||
}
|
||||
)}
|
||||
onClick={() => navigate(`/settings/regions/${row.original.id}`)}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<Table.Cell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</Table.Cell>
|
||||
))}
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
<LocalizedTablePagination
|
||||
canNextPage={table.getCanNextPage()}
|
||||
canPreviousPage={table.getCanPreviousPage()}
|
||||
nextPage={table.nextPage}
|
||||
previousPage={table.previousPage}
|
||||
count={count ?? 0}
|
||||
pageIndex={pageIndex}
|
||||
pageCount={table.getPageCount()}
|
||||
pageSize={PAGE_SIZE}
|
||||
<DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
count={count}
|
||||
rowCount={PAGE_SIZE}
|
||||
isLoading={isLoading}
|
||||
filters={filters}
|
||||
orderBy={["name", "created_at", "updated_at"]}
|
||||
navigateTo={(row) => `${row.original.id}`}
|
||||
pagination
|
||||
search
|
||||
queryObject={raw}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
@@ -187,124 +125,11 @@ const RegionActions = ({ region }: { region: Region }) => {
|
||||
const columnHelper = createColumnHelper<Region>()
|
||||
|
||||
const useColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
const base = useRegionTableColumns()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.accessor("name", {
|
||||
header: t("fields.name"),
|
||||
cell: (cell) => cell.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("countries", {
|
||||
header: t("fields.countries"),
|
||||
cell: (cell) => {
|
||||
const countries = cell.getValue()
|
||||
|
||||
const displayValue = countries
|
||||
.slice(0, 2)
|
||||
.map((c) => c.display_name)
|
||||
.join(", ")
|
||||
|
||||
const additionalCountries = countries
|
||||
.slice(2)
|
||||
.map((c) => c.display_name)
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-x-1">
|
||||
<span>{displayValue}</span>
|
||||
{additionalCountries.length > 0 && (
|
||||
<Tooltip
|
||||
content={
|
||||
<ul>
|
||||
{additionalCountries.map((c) => (
|
||||
<li key={c}>{c}</li>
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{t("general.plusCountMore", {
|
||||
count: additionalCountries.length,
|
||||
})}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("payment_providers", {
|
||||
header: t("fields.paymentProviders"),
|
||||
cell: (cell) => {
|
||||
const providers = cell.getValue()
|
||||
|
||||
const displayValue = providers
|
||||
.slice(0, 2)
|
||||
.map((p) => p.id)
|
||||
.join(", ")
|
||||
|
||||
const additionalProviders = providers.slice(2).map((c) => c.id)
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-x-1">
|
||||
<span>{displayValue}</span>
|
||||
{additionalProviders.length > 0 && (
|
||||
<Tooltip
|
||||
content={
|
||||
<ul>
|
||||
{additionalProviders.map((c) => (
|
||||
<li key={c}>{c}</li>
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{t("general.plusCountMore", {
|
||||
count: additionalProviders.length,
|
||||
})}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}),
|
||||
columnHelper.accessor("fulfillment_providers", {
|
||||
header: t("fields.fulfillmentProviders"),
|
||||
cell: (cell) => {
|
||||
const providers = cell.getValue()
|
||||
|
||||
const displayValue = providers
|
||||
.slice(0, 2)
|
||||
.map((p) => p.id)
|
||||
.join(", ")
|
||||
|
||||
const additionalProviders = providers.slice(2).map((c) => c.id)
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-x-1">
|
||||
<span>{displayValue}</span>
|
||||
{additionalProviders.length > 0 && (
|
||||
<Tooltip
|
||||
content={
|
||||
<ul>
|
||||
{additionalProviders.map((c) => (
|
||||
<li key={c}>{c}</li>
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{t("general.plusCountMore", {
|
||||
count: additionalProviders.length,
|
||||
})}
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}),
|
||||
...base,
|
||||
columnHelper.display({
|
||||
id: "actions",
|
||||
cell: ({ row }) => {
|
||||
@@ -312,6 +137,6 @@ const useColumns = () => {
|
||||
},
|
||||
}),
|
||||
],
|
||||
[t]
|
||||
[base]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Product, SalesChannel } from "@medusajs/medusa"
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
FocusModal,
|
||||
Hint,
|
||||
Table,
|
||||
Tooltip,
|
||||
clx,
|
||||
} from "@medusajs/ui"
|
||||
import { Button, Checkbox, Hint, Table, Tooltip, clx } from "@medusajs/ui"
|
||||
import {
|
||||
PaginationState,
|
||||
RowSelectionState,
|
||||
@@ -26,7 +18,6 @@ import { useEffect, useMemo, useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
import { Form } from "../../../../components/common/form"
|
||||
import {
|
||||
ProductAvailabilityCell,
|
||||
ProductCollectionCell,
|
||||
@@ -37,13 +28,15 @@ import {
|
||||
import { OrderBy } from "../../../../components/filtering/order-by"
|
||||
import { Query } from "../../../../components/filtering/query"
|
||||
import { LocalizedTablePagination } from "../../../../components/localization/localized-table-pagination"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../components/route-modal"
|
||||
import { useQueryParams } from "../../../../hooks/use-query-params"
|
||||
import { queryClient } from "../../../../lib/medusa"
|
||||
|
||||
type AddProductsToSalesChannelFormProps = {
|
||||
salesChannel: SalesChannel
|
||||
subscribe: (state: boolean) => void
|
||||
onSuccess: () => void
|
||||
}
|
||||
|
||||
const AddProductsToSalesChannelSchema = zod.object({
|
||||
@@ -54,10 +47,9 @@ const PAGE_SIZE = 50
|
||||
|
||||
export const AddProductsToSalesChannelForm = ({
|
||||
salesChannel,
|
||||
subscribe,
|
||||
onSuccess,
|
||||
}: AddProductsToSalesChannelFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof AddProductsToSalesChannelSchema>>({
|
||||
defaultValues: {
|
||||
@@ -66,13 +58,7 @@ export const AddProductsToSalesChannelForm = ({
|
||||
resolver: zodResolver(AddProductsToSalesChannelSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty])
|
||||
const { setValue } = form
|
||||
|
||||
const { mutateAsync, isLoading: isMutating } =
|
||||
useAdminAddProductsToSalesChannel(salesChannel.id)
|
||||
@@ -93,15 +79,19 @@ export const AddProductsToSalesChannelForm = ({
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||
|
||||
useEffect(() => {
|
||||
form.setValue(
|
||||
setValue(
|
||||
"product_ids",
|
||||
Object.keys(rowSelection).filter((k) => rowSelection[k])
|
||||
Object.keys(rowSelection).filter((k) => rowSelection[k]),
|
||||
{
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
}
|
||||
)
|
||||
}, [rowSelection])
|
||||
}, [rowSelection, setValue])
|
||||
|
||||
const params = useQueryParams(["q", "order"])
|
||||
|
||||
const { products, count, isLoading } = useAdminProducts(
|
||||
const { products, count } = useAdminProducts(
|
||||
{
|
||||
expand: "variants,sales_channels",
|
||||
...params,
|
||||
@@ -148,36 +138,36 @@ export const AddProductsToSalesChannelForm = ({
|
||||
* determine if they are added to the sales channel or not.
|
||||
*/
|
||||
queryClient.invalidateQueries(adminProductKeys.lists())
|
||||
onSuccess()
|
||||
handleSuccess()
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex h-full flex-col overflow-hidden"
|
||||
>
|
||||
<FocusModal.Header>
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
{form.formState.errors.product_ids && (
|
||||
<Hint variant="error">
|
||||
{form.formState.errors.product_ids.message}
|
||||
</Hint>
|
||||
)}
|
||||
<FocusModal.Close asChild>
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</FocusModal.Close>
|
||||
</RouteFocusModal.Close>
|
||||
<Button size="small" type="submit" isLoading={isMutating}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</FocusModal.Header>
|
||||
<FocusModal.Body className="flex h-full w-full flex-col items-center divide-y overflow-y-auto">
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex h-full w-full flex-col items-center divide-y overflow-y-auto">
|
||||
<div className="flex w-full items-center justify-between px-6 py-4">
|
||||
<div></div>
|
||||
<div className="flex items-center gap-x-2">
|
||||
@@ -251,9 +241,9 @@ export const AddProductsToSalesChannelForm = ({
|
||||
pageSize={PAGE_SIZE}
|
||||
/>
|
||||
</div>
|
||||
</FocusModal.Body>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +1,21 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { useAdminSalesChannel } from "medusa-react"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { AddProductsToSalesChannelForm } from "./components"
|
||||
|
||||
export const SalesChannelAddProducts = () => {
|
||||
const { id } = useParams()
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
const { sales_channel, isLoading, isError, error } = useAdminSalesChannel(id!)
|
||||
|
||||
const handleSuccess = () => {
|
||||
onOpenChange(false, true)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content>
|
||||
{isLoading || !sales_channel ? (
|
||||
<div>Loading...</div>
|
||||
) : (
|
||||
<AddProductsToSalesChannelForm
|
||||
salesChannel={sales_channel}
|
||||
onSuccess={handleSuccess}
|
||||
subscribe={subscribe}
|
||||
/>
|
||||
)}
|
||||
</FocusModal.Content>
|
||||
</FocusModal>
|
||||
<RouteFocusModal>
|
||||
{!isLoading && sales_channel && (
|
||||
<AddProductsToSalesChannelForm salesChannel={sales_channel} />
|
||||
)}
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,25 +1,15 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import {
|
||||
Button,
|
||||
FocusModal,
|
||||
Heading,
|
||||
Input,
|
||||
Switch,
|
||||
Text,
|
||||
Textarea,
|
||||
} from "@medusajs/ui"
|
||||
import { Button, Heading, Input, Switch, Text, Textarea } from "@medusajs/ui"
|
||||
import { useAdminCreateSalesChannel } from "medusa-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { useEffect } from "react"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
|
||||
type CreateSalesChannelFormProps = {
|
||||
subscribe: (state: boolean) => void
|
||||
}
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
|
||||
const CreateSalesChannelSchema = zod.object({
|
||||
name: zod.string().min(1),
|
||||
@@ -27,9 +17,10 @@ const CreateSalesChannelSchema = zod.object({
|
||||
enabled: zod.boolean(),
|
||||
})
|
||||
|
||||
export const CreateSalesChannelForm = ({
|
||||
subscribe,
|
||||
}: CreateSalesChannelFormProps) => {
|
||||
export const CreateSalesChannelForm = () => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof CreateSalesChannelSchema>>({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
@@ -40,17 +31,6 @@ export const CreateSalesChannelForm = ({
|
||||
})
|
||||
const { mutateAsync, isLoading } = useAdminCreateSalesChannel()
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty])
|
||||
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
await mutateAsync(
|
||||
{
|
||||
@@ -60,31 +40,31 @@ export const CreateSalesChannelForm = ({
|
||||
},
|
||||
{
|
||||
onSuccess: ({ sales_channel }) => {
|
||||
navigate(`../${sales_channel.id}`)
|
||||
handleSuccess(`../${sales_channel.id}`)
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex h-full flex-col overflow-hidden"
|
||||
>
|
||||
<FocusModal.Header>
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<FocusModal.Close asChild>
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</FocusModal.Close>
|
||||
</RouteFocusModal.Close>
|
||||
<Button size="small" type="submit" isLoading={isLoading}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</FocusModal.Header>
|
||||
<FocusModal.Body className="flex flex-1 flex-col overflow-hidden">
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex flex-1 flex-col overflow-hidden">
|
||||
<div className="flex flex-1 flex-col items-center overflow-y-auto">
|
||||
<div className="flex w-full max-w-[720px] flex-col gap-y-8 px-2 py-16">
|
||||
<div>
|
||||
@@ -153,8 +133,8 @@ export const CreateSalesChannelForm = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</FocusModal.Body>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { CreateSalesChannelForm } from "./components/create-sales-channel-form"
|
||||
|
||||
export const SalesChannelCreate = () => {
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content>
|
||||
<CreateSalesChannelForm subscribe={subscribe} />
|
||||
</FocusModal.Content>
|
||||
</FocusModal>
|
||||
<RouteFocusModal>
|
||||
<CreateSalesChannelForm />
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { SalesChannel } from "@medusajs/medusa"
|
||||
import { Button, Drawer, Input, Switch, Textarea } from "@medusajs/ui"
|
||||
import { Button, Input, Switch, Textarea } from "@medusajs/ui"
|
||||
import { useAdminUpdateSalesChannel } from "medusa-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { useEffect } from "react"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
|
||||
type EditSalesChannelFormProps = {
|
||||
salesChannel: SalesChannel
|
||||
subscribe: (state: boolean) => void
|
||||
onSuccess: () => void
|
||||
}
|
||||
|
||||
const EditSalesChannelSchema = zod.object({
|
||||
@@ -23,9 +24,10 @@ const EditSalesChannelSchema = zod.object({
|
||||
|
||||
export const EditSalesChannelForm = ({
|
||||
salesChannel,
|
||||
subscribe,
|
||||
onSuccess,
|
||||
}: EditSalesChannelFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof EditSalesChannelSchema>>({
|
||||
defaultValues: {
|
||||
name: salesChannel.name,
|
||||
@@ -35,16 +37,6 @@ export const EditSalesChannelForm = ({
|
||||
resolver: zodResolver(EditSalesChannelSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty])
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminUpdateSalesChannel(salesChannel.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
@@ -56,19 +48,19 @@ export const EditSalesChannelForm = ({
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onSuccess()
|
||||
handleSuccess()
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex flex-1 flex-col overflow-hidden"
|
||||
>
|
||||
<Drawer.Body className="flex max-w-full flex-1 flex-col gap-y-8 overflow-y-auto">
|
||||
<RouteDrawer.Body className="flex max-w-full flex-1 flex-col gap-y-8 overflow-y-auto">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="name"
|
||||
@@ -121,20 +113,20 @@ export const EditSalesChannelForm = ({
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Drawer.Body>
|
||||
<Drawer.Footer>
|
||||
</RouteDrawer.Body>
|
||||
<RouteDrawer.Footer>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<Drawer.Close asChild>
|
||||
<RouteDrawer.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</Drawer.Close>
|
||||
</RouteDrawer.Close>
|
||||
<Button size="small" type="submit" isLoading={isLoading}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</Drawer.Footer>
|
||||
</RouteDrawer.Footer>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteDrawer.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,41 +1,31 @@
|
||||
import { Drawer, Heading } from "@medusajs/ui"
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminSalesChannel } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { EditSalesChannelForm } from "./components/edit-sales-channel-form"
|
||||
|
||||
export const SalesChannelEdit = () => {
|
||||
const { id } = useParams()
|
||||
const { t } = useTranslation()
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
const { sales_channel, isLoading, isError, error } = useAdminSalesChannel(id!)
|
||||
|
||||
const onSuccess = () => {
|
||||
onOpenChange(false, true)
|
||||
}
|
||||
const { sales_channel, isLoading, isError, error } = useAdminSalesChannel(id!)
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={onOpenChange}>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Heading className="capitalize">
|
||||
{t("salesChannels.editSalesChannel")}
|
||||
</Heading>
|
||||
</Drawer.Header>
|
||||
{!isLoading && sales_channel && (
|
||||
<EditSalesChannelForm
|
||||
salesChannel={sales_channel}
|
||||
subscribe={subscribe}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
)}
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<Heading className="capitalize">
|
||||
{t("salesChannels.editSalesChannel")}
|
||||
</Heading>
|
||||
</RouteDrawer.Header>
|
||||
{!isLoading && sales_channel && (
|
||||
<EditSalesChannelForm salesChannel={sales_channel} />
|
||||
)}
|
||||
</RouteDrawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
Badge,
|
||||
Button,
|
||||
Checkbox,
|
||||
FocusModal,
|
||||
Hint,
|
||||
StatusBadge,
|
||||
Table,
|
||||
@@ -19,11 +18,18 @@ import {
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table"
|
||||
import { useAdminCurrencies, useAdminUpdateStore } from "medusa-react"
|
||||
import { FormEvent, useMemo, useState } from "react"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { OrderBy } from "../../../../../components/filtering/order-by"
|
||||
import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination"
|
||||
import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { useHandleTableScroll } from "../../../../../hooks/use-handle-table-scroll"
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
|
||||
@@ -38,9 +44,18 @@ const AddCurrenciesSchema = zod.object({
|
||||
const PAGE_SIZE = 50
|
||||
|
||||
export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
|
||||
const [errorMessage, setErrorMessage] = useState<{
|
||||
currencies?: string | undefined
|
||||
}>({})
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof AddCurrenciesSchema>>({
|
||||
defaultValues: {
|
||||
currencies: [],
|
||||
},
|
||||
resolver: zodResolver(AddCurrenciesSchema),
|
||||
})
|
||||
|
||||
const { setValue } = form
|
||||
|
||||
const [{ pageIndex, pageSize }, setPagination] = useState<PaginationState>({
|
||||
pageIndex: 0,
|
||||
pageSize: PAGE_SIZE,
|
||||
@@ -56,8 +71,16 @@ export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
|
||||
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||
|
||||
useEffect(() => {
|
||||
const ids = Object.keys(rowSelection)
|
||||
setValue("currencies", ids, {
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
})
|
||||
}, [rowSelection, setValue])
|
||||
|
||||
const params = useQueryParams(["order"])
|
||||
const { currencies, count, isLoading, isError, error } = useAdminCurrencies({
|
||||
const { currencies, count, isError, error } = useAdminCurrencies({
|
||||
limit: PAGE_SIZE,
|
||||
offset: pageIndex * PAGE_SIZE,
|
||||
...params,
|
||||
@@ -83,152 +106,143 @@ export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => {
|
||||
manualPagination: true,
|
||||
})
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { mutateAsync, isLoading: isMutating } = useAdminUpdateStore()
|
||||
|
||||
const { handleScroll, isScrolled, tableContainerRef } = useHandleTableScroll()
|
||||
|
||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
|
||||
const ids = Object.keys(rowSelection)
|
||||
|
||||
try {
|
||||
AddCurrenciesSchema.parse({
|
||||
currencies: ids,
|
||||
})
|
||||
|
||||
setErrorMessage({})
|
||||
} catch (err) {
|
||||
if (err instanceof zod.ZodError) {
|
||||
setErrorMessage(err.flatten().fieldErrors)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
const currencies = Array.from(
|
||||
new Set([...ids, ...preSelectedRows])
|
||||
new Set([...data.currencies, ...preSelectedRows])
|
||||
) as string[]
|
||||
|
||||
await mutateAsync({
|
||||
currencies,
|
||||
})
|
||||
}
|
||||
await mutateAsync(
|
||||
{
|
||||
currencies,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
handleSuccess()
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex h-full flex-col overflow-hidden"
|
||||
>
|
||||
<FocusModal.Header>
|
||||
<div className="flex flex-1 items-center justify-between">
|
||||
<div>
|
||||
{errorMessage.currencies && (
|
||||
<Hint variant="error">{errorMessage.currencies}</Hint>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<FocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</FocusModal.Close>
|
||||
<Button size="small" type="submit" isLoading={isMutating}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</FocusModal.Header>
|
||||
<FocusModal.Body className="flex flex-1 flex-col overflow-hidden">
|
||||
<div className="flex items-center justify-between border-b px-6 py-4">
|
||||
<div></div>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<OrderBy keys={["code"]} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="flex-1 overflow-y-auto"
|
||||
ref={tableContainerRef}
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
<Table className="relative">
|
||||
<Table.Header
|
||||
className={clx(
|
||||
"bg-ui-bg-base transition-fg sticky inset-x-0 top-0 z-10 border-t-0",
|
||||
{
|
||||
"shadow-elevation-card-hover": isScrolled,
|
||||
}
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex h-full flex-col overflow-hidden"
|
||||
>
|
||||
<RouteFocusModal.Header>
|
||||
<div className="flex flex-1 items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
{form.formState.errors.currencies && (
|
||||
<Hint variant="error">
|
||||
{form.formState.errors.currencies.message}
|
||||
</Hint>
|
||||
)}
|
||||
>
|
||||
{table.getHeaderGroups().map((headerGroup) => {
|
||||
return (
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<RouteFocusModal.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</RouteFocusModal.Close>
|
||||
<Button size="small" type="submit" isLoading={isMutating}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</RouteFocusModal.Header>
|
||||
<RouteFocusModal.Body className="flex flex-1 flex-col overflow-hidden">
|
||||
<div className="flex items-center justify-between border-b px-6 py-4">
|
||||
<div></div>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<OrderBy keys={["code"]} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="flex-1 overflow-y-auto"
|
||||
ref={tableContainerRef}
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
<Table className="relative">
|
||||
<Table.Header
|
||||
className={clx(
|
||||
"bg-ui-bg-base transition-fg sticky inset-x-0 top-0 z-10 border-t-0",
|
||||
{
|
||||
"shadow-elevation-card-hover": isScrolled,
|
||||
}
|
||||
)}
|
||||
>
|
||||
{table.getHeaderGroups().map((headerGroup) => {
|
||||
return (
|
||||
<Table.Row
|
||||
key={headerGroup.id}
|
||||
className="[&_th:first-of-type]:w-[1%] [&_th:first-of-type]:whitespace-nowrap [&_th]:w-1/3"
|
||||
>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<Table.HeaderCell key={header.id}>
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</Table.HeaderCell>
|
||||
)
|
||||
})}
|
||||
</Table.Row>
|
||||
)
|
||||
})}
|
||||
</Table.Header>
|
||||
<Table.Body className="border-b-0">
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<Table.Row
|
||||
key={headerGroup.id}
|
||||
className="[&_th:first-of-type]:w-[1%] [&_th:first-of-type]:whitespace-nowrap [&_th]:w-1/3"
|
||||
key={row.id}
|
||||
className={clx(
|
||||
"transition-fg last-of-type:border-b-0",
|
||||
{
|
||||
"bg-ui-bg-highlight hover:bg-ui-bg-highlight-hover":
|
||||
row.getIsSelected(),
|
||||
},
|
||||
{
|
||||
"bg-ui-bg-disabled hover:bg-ui-bg-disabled":
|
||||
!row.getCanSelect(),
|
||||
}
|
||||
)}
|
||||
>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<Table.HeaderCell key={header.id}>
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</Table.HeaderCell>
|
||||
)
|
||||
})}
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<Table.Cell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
)}
|
||||
</Table.Cell>
|
||||
))}
|
||||
</Table.Row>
|
||||
)
|
||||
})}
|
||||
</Table.Header>
|
||||
<Table.Body className="border-b-0">
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<Table.Row
|
||||
key={row.id}
|
||||
className={clx(
|
||||
"transition-fg last-of-type:border-b-0",
|
||||
{
|
||||
"bg-ui-bg-highlight hover:bg-ui-bg-highlight-hover":
|
||||
row.getIsSelected(),
|
||||
},
|
||||
{
|
||||
"bg-ui-bg-disabled hover:bg-ui-bg-disabled":
|
||||
!row.getCanSelect(),
|
||||
}
|
||||
)}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<Table.Cell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
)}
|
||||
</Table.Cell>
|
||||
))}
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</div>
|
||||
<div className="w-full border-t">
|
||||
<LocalizedTablePagination
|
||||
canNextPage={table.getCanNextPage()}
|
||||
canPreviousPage={table.getCanPreviousPage()}
|
||||
nextPage={table.nextPage}
|
||||
previousPage={table.previousPage}
|
||||
count={count ?? 0}
|
||||
pageIndex={pageIndex}
|
||||
pageCount={table.getPageCount()}
|
||||
pageSize={PAGE_SIZE}
|
||||
/>
|
||||
</div>
|
||||
</FocusModal.Body>
|
||||
</form>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
</div>
|
||||
<div className="w-full border-t">
|
||||
<LocalizedTablePagination
|
||||
canNextPage={table.getCanNextPage()}
|
||||
canPreviousPage={table.getCanPreviousPage()}
|
||||
nextPage={table.nextPage}
|
||||
previousPage={table.previousPage}
|
||||
count={count ?? 0}
|
||||
pageIndex={pageIndex}
|
||||
pageCount={table.getPageCount()}
|
||||
pageSize={PAGE_SIZE}
|
||||
/>
|
||||
</div>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,42 +1,17 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { useAdminStore } from "medusa-react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { AddCurrenciesForm } from "./components/add-currencies-form/add-currencies-form"
|
||||
|
||||
export const StoreAddCurrencies = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { store, isLoading, isError, error } = useAdminStore()
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(true)
|
||||
}, [])
|
||||
|
||||
const onOpenChange = (open: boolean) => {
|
||||
if (!open) {
|
||||
setTimeout(() => {
|
||||
navigate(`/settings/store`, { replace: true })
|
||||
}, 200)
|
||||
}
|
||||
|
||||
setOpen(open)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content>
|
||||
{isLoading || !store ? (
|
||||
<div>Loading...</div>
|
||||
) : (
|
||||
<AddCurrenciesForm store={store} />
|
||||
)}
|
||||
</FocusModal.Content>
|
||||
</FocusModal>
|
||||
<RouteFocusModal>
|
||||
{!isLoading && store && <AddCurrenciesForm store={store} />}
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import type { Store } from "@medusajs/medusa"
|
||||
import { Button, Drawer, Input } from "@medusajs/ui"
|
||||
import { Button, Input } from "@medusajs/ui"
|
||||
import { useAdminUpdateStore } from "medusa-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
|
||||
type EditStoreFormProps = {
|
||||
store: Store
|
||||
onSuccess: () => void
|
||||
}
|
||||
|
||||
const EditStoreSchema = zod.object({
|
||||
@@ -22,8 +25,9 @@ const EditStoreSchema = zod.object({
|
||||
invite_link_template: zod.union([zod.literal(""), zod.string().trim().url()]),
|
||||
})
|
||||
|
||||
export const EditStoreForm = ({ store, onSuccess }: EditStoreFormProps) => {
|
||||
const { mutateAsync, isLoading } = useAdminUpdateStore()
|
||||
export const EditStoreForm = ({ store }: EditStoreFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof EditStoreSchema>>({
|
||||
defaultValues: {
|
||||
@@ -35,7 +39,7 @@ export const EditStoreForm = ({ store, onSuccess }: EditStoreFormProps) => {
|
||||
resolver: zodResolver(EditStoreSchema),
|
||||
})
|
||||
|
||||
const { t } = useTranslation()
|
||||
const { mutateAsync, isLoading } = useAdminUpdateStore()
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
mutateAsync(
|
||||
@@ -47,16 +51,16 @@ export const EditStoreForm = ({ store, onSuccess }: EditStoreFormProps) => {
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
onSuccess()
|
||||
handleSuccess()
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form onSubmit={handleSubmit} className="flex h-full flex-col">
|
||||
<Drawer.Body>
|
||||
<RouteDrawer.Body>
|
||||
<div className="flex flex-col gap-y-8">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
@@ -123,20 +127,20 @@ export const EditStoreForm = ({ store, onSuccess }: EditStoreFormProps) => {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Drawer.Body>
|
||||
<Drawer.Footer>
|
||||
</RouteDrawer.Body>
|
||||
<RouteDrawer.Footer>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<Drawer.Close asChild>
|
||||
<RouteDrawer.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</Drawer.Close>
|
||||
</RouteDrawer.Close>
|
||||
<Button size="small" isLoading={isLoading} type="submit">
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</Drawer.Footer>
|
||||
</RouteDrawer.Footer>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteDrawer.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,30 +1,13 @@
|
||||
import { Drawer, Heading } from "@medusajs/ui"
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminStore } from "medusa-react"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { json, useNavigate } from "react-router-dom"
|
||||
import { json } from "react-router-dom"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { EditStoreForm } from "./components/edit-store-form/edit-store-form"
|
||||
|
||||
export const StoreEdit = () => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const { store, isLoading, isError, error } = useAdminStore()
|
||||
|
||||
const navigate = useNavigate()
|
||||
const { t } = useTranslation()
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(true)
|
||||
}, [])
|
||||
|
||||
const onOpenChange = (open: boolean) => {
|
||||
if (!open) {
|
||||
setTimeout(() => {
|
||||
navigate(`/settings/store`, { replace: true })
|
||||
}, 200)
|
||||
}
|
||||
|
||||
setOpen(open)
|
||||
}
|
||||
const { store, isLoading, isError, error } = useAdminStore()
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
@@ -35,15 +18,11 @@ export const StoreEdit = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={onOpenChange}>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Heading className="capitalize">{t("store.editStore")}</Heading>
|
||||
</Drawer.Header>
|
||||
{store && (
|
||||
<EditStoreForm store={store} onSuccess={() => onOpenChange(false)} />
|
||||
)}
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<Heading className="capitalize">{t("store.editStore")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
{store && <EditStoreForm store={store} />}
|
||||
</RouteDrawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { User } from "@medusajs/medusa"
|
||||
import { Button, Drawer, Input } from "@medusajs/ui"
|
||||
import { Button, Input } from "@medusajs/ui"
|
||||
import { useAdminUpdateUser } from "medusa-react"
|
||||
import { useEffect } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
|
||||
type EditUserFormProps = {
|
||||
user: Omit<User, "password_hash">
|
||||
subscribe: (state: boolean) => void
|
||||
onSuccessfulSubmit: () => void
|
||||
}
|
||||
|
||||
const EditUserFormSchema = zod.object({
|
||||
@@ -19,11 +21,10 @@ const EditUserFormSchema = zod.object({
|
||||
last_name: zod.string().optional(),
|
||||
})
|
||||
|
||||
export const EditUserForm = ({
|
||||
user,
|
||||
subscribe,
|
||||
onSuccessfulSubmit,
|
||||
}: EditUserFormProps) => {
|
||||
export const EditUserForm = ({ user }: EditUserFormProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { handleSuccess } = useRouteModal()
|
||||
|
||||
const form = useForm<zod.infer<typeof EditUserFormSchema>>({
|
||||
defaultValues: {
|
||||
first_name: user.first_name || "",
|
||||
@@ -32,33 +33,23 @@ export const EditUserForm = ({
|
||||
resolver: zodResolver(EditUserFormSchema),
|
||||
})
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty])
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminUpdateUser(user.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
await mutateAsync(values, {
|
||||
onSuccess: () => {
|
||||
onSuccessfulSubmit()
|
||||
handleSuccess()
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteDrawer.Form form={form}>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex flex-1 flex-col overflow-hidden"
|
||||
>
|
||||
<Drawer.Body className="flex max-w-full flex-1 flex-col gap-y-8 overflow-y-auto">
|
||||
<RouteDrawer.Body className="flex max-w-full flex-1 flex-col gap-y-8 overflow-y-auto">
|
||||
<Form.Field
|
||||
control={form.control}
|
||||
name="first_name"
|
||||
@@ -89,20 +80,20 @@ export const EditUserForm = ({
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Drawer.Body>
|
||||
<Drawer.Footer>
|
||||
</RouteDrawer.Body>
|
||||
<RouteDrawer.Footer>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<Drawer.Close asChild>
|
||||
<RouteDrawer.Close asChild>
|
||||
<Button size="small" variant="secondary">
|
||||
{t("actions.cancel")}
|
||||
</Button>
|
||||
</Drawer.Close>
|
||||
</RouteDrawer.Close>
|
||||
<Button size="small" type="submit" isLoading={isLoading}>
|
||||
{t("actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</Drawer.Footer>
|
||||
</RouteDrawer.Footer>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteDrawer.Form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,39 +1,25 @@
|
||||
import { Drawer, Heading } from "@medusajs/ui"
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminUser } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { EditUserForm } from "./components/edit-user-form"
|
||||
|
||||
export const UserEdit = () => {
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
const { t } = useTranslation()
|
||||
const { id } = useParams()
|
||||
const { user, isLoading, isError, error } = useAdminUser(id!)
|
||||
|
||||
const handleSuccessfulSubmit = () => {
|
||||
onOpenChange(false, true)
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer open={open} onOpenChange={onOpenChange}>
|
||||
<Drawer.Content>
|
||||
<Drawer.Header>
|
||||
<Heading>{t("users.editUser")}</Heading>
|
||||
</Drawer.Header>
|
||||
{!isLoading && user && (
|
||||
<EditUserForm
|
||||
user={user}
|
||||
subscribe={subscribe}
|
||||
onSuccessfulSubmit={handleSuccessfulSubmit}
|
||||
/>
|
||||
)}
|
||||
</Drawer.Content>
|
||||
</Drawer>
|
||||
<RouteDrawer>
|
||||
<RouteDrawer.Header>
|
||||
<Heading>{t("users.editUser")}</Heading>
|
||||
</RouteDrawer.Header>
|
||||
{!isLoading && user && <EditUserForm user={user} />}
|
||||
</RouteDrawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { Invite } from "@medusajs/medusa"
|
||||
import {
|
||||
Button,
|
||||
Container,
|
||||
FocusModal,
|
||||
Heading,
|
||||
Input,
|
||||
Select,
|
||||
@@ -30,7 +29,7 @@ import {
|
||||
useAdminResendInvite,
|
||||
useAdminStore,
|
||||
} from "medusa-react"
|
||||
import { useEffect, useMemo } from "react"
|
||||
import { useMemo } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { Trans, useTranslation } from "react-i18next"
|
||||
import * as zod from "zod"
|
||||
@@ -38,10 +37,7 @@ import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { NoRecords } from "../../../../../components/common/empty-table-content"
|
||||
import { Form } from "../../../../../components/common/form"
|
||||
import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination"
|
||||
|
||||
type InviteUserFormProps = {
|
||||
subscribe: (state: boolean) => void
|
||||
}
|
||||
import { RouteFocusModal } from "../../../../../components/route-modal"
|
||||
|
||||
enum UserRole {
|
||||
MEMBER = "member",
|
||||
@@ -56,7 +52,9 @@ const InviteUserSchema = zod.object({
|
||||
|
||||
const PAGE_SIZE = 10
|
||||
|
||||
export const InviteUserForm = ({ subscribe }: InviteUserFormProps) => {
|
||||
export const InviteUserForm = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const form = useForm<zod.infer<typeof InviteUserSchema>>({
|
||||
defaultValues: {
|
||||
user: "",
|
||||
@@ -64,15 +62,6 @@ export const InviteUserForm = ({ subscribe }: InviteUserFormProps) => {
|
||||
},
|
||||
resolver: zodResolver(InviteUserSchema),
|
||||
})
|
||||
const { mutateAsync, isLoading: isMutating } = useAdminCreateInvite()
|
||||
|
||||
const {
|
||||
formState: { isDirty },
|
||||
} = form
|
||||
|
||||
useEffect(() => {
|
||||
subscribe(isDirty)
|
||||
}, [isDirty, subscribe])
|
||||
|
||||
const { invites, isLoading, isError, error } = useAdminInvites()
|
||||
const count = invites?.length ?? 0
|
||||
@@ -89,7 +78,7 @@ export const InviteUserForm = ({ subscribe }: InviteUserFormProps) => {
|
||||
getPaginationRowModel: getPaginationRowModel(),
|
||||
})
|
||||
|
||||
const { t } = useTranslation()
|
||||
const { mutateAsync, isLoading: isMutating } = useAdminCreateInvite()
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (values) => {
|
||||
await mutateAsync(
|
||||
@@ -110,13 +99,13 @@ export const InviteUserForm = ({ subscribe }: InviteUserFormProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<RouteFocusModal.Form form={form}>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="flex h-full flex-col overflow-hidden"
|
||||
>
|
||||
<FocusModal.Header />
|
||||
<FocusModal.Body className="flex flex-1 flex-col overflow-hidden">
|
||||
<RouteFocusModal.Header />
|
||||
<RouteFocusModal.Body className="flex flex-1 flex-col overflow-hidden">
|
||||
<div className="flex flex-1 flex-col items-center overflow-y-auto">
|
||||
<div className="flex w-full max-w-[720px] flex-col gap-y-8 px-2 py-16">
|
||||
<div>
|
||||
@@ -249,9 +238,9 @@ export const InviteUserForm = ({ subscribe }: InviteUserFormProps) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FocusModal.Body>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</Form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { FocusModal } from "@medusajs/ui"
|
||||
import { useRouteModalState } from "../../../hooks/use-route-modal-state"
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { InviteUserForm } from "./components/invite-user-form/invite-user-form"
|
||||
|
||||
export const UserInvite = () => {
|
||||
const [open, onOpenChange, subscribe] = useRouteModalState()
|
||||
|
||||
return (
|
||||
<FocusModal open={open} onOpenChange={onOpenChange}>
|
||||
<FocusModal.Content>
|
||||
<InviteUserForm subscribe={subscribe} />
|
||||
</FocusModal.Content>
|
||||
</FocusModal>
|
||||
<RouteFocusModal>
|
||||
<InviteUserForm />
|
||||
</RouteFocusModal>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user