From 98ade6ce64dbf2a724746c0a90d7919ccd0093d4 Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 17 Jan 2024 15:28:13 +0100 Subject: [PATCH 1/6] init work on collections --- packages/admin-next/dashboard/package.json | 2 + .../public/locales/en/translation.json | 6 +- .../components/common/combobox/combobox.tsx | 374 +++++++++++++++++- .../router-provider/router-provider.tsx | 5 +- .../create-publishable-api-key-form.tsx | 2 +- .../collection-detail.tsx} | 8 +- .../collections/collection-detail/index.ts | 1 + .../collection-list/collection-list.tsx | 9 + .../collection-list-table.tsx | 171 ++++++++ .../components/collection-list-table/index.ts | 1 + .../collections/collection-list/index.ts | 1 + .../src/routes/collections/details/index.ts | 1 - .../src/routes/collections/list/index.ts | 1 - .../src/routes/collections/list/list.tsx | 11 - .../create-customer-form.tsx | 4 +- .../customer-list-table.tsx | 128 +++--- .../create-location-form.tsx | 2 +- .../edit-location-form/edit-location-form.tsx | 2 +- .../create-region-form/create-region-form.tsx | 205 ++++++++++ .../components/create-region-form/index.ts | 1 + .../regions/region-create/region-create.tsx | 193 +-------- .../region-shipping-option-section.tsx | 32 +- .../create-sales-channel-form.tsx | 2 +- .../admin-next/dashboard/tailwind.config.cjs | 13 +- .../ui/src/components/select/select.tsx | 2 +- yarn.lock | 50 +++ 26 files changed, 918 insertions(+), 309 deletions(-) rename packages/admin-next/dashboard/src/routes/collections/{details/details.tsx => collection-detail/collection-detail.tsx} (51%) create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-detail/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-list/collection-list.tsx create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/collection-list-table.tsx create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-list/index.ts delete mode 100644 packages/admin-next/dashboard/src/routes/collections/details/index.ts delete mode 100644 packages/admin-next/dashboard/src/routes/collections/list/index.ts delete mode 100644 packages/admin-next/dashboard/src/routes/collections/list/list.tsx create mode 100644 packages/admin-next/dashboard/src/routes/regions/region-create/components/create-region-form/create-region-form.tsx create mode 100644 packages/admin-next/dashboard/src/routes/regions/region-create/components/create-region-form/index.ts diff --git a/packages/admin-next/dashboard/package.json b/packages/admin-next/dashboard/package.json index 559d2a4a41abf..537059c726d30 100644 --- a/packages/admin-next/dashboard/package.json +++ b/packages/admin-next/dashboard/package.json @@ -16,6 +16,8 @@ "package.json" ], "dependencies": { + "@headlessui/react": "^1.7.18", + "@headlessui/tailwindcss": "^0.2.0", "@hookform/resolvers": "3.3.2", "@medusajs/icons": "workspace:^", "@medusajs/ui": "workspace:^", diff --git a/packages/admin-next/dashboard/public/locales/en/translation.json b/packages/admin-next/dashboard/public/locales/en/translation.json index c1642e35c493a..6f16cb23a2132 100644 --- a/packages/admin-next/dashboard/public/locales/en/translation.json +++ b/packages/admin-next/dashboard/public/locales/en/translation.json @@ -35,6 +35,7 @@ "areYouSure": "Are you sure?", "noRecordsFound": "No records found", "typeToConfirm": "Please type {val} to confirm:", + "noResults": "No results", "noResultsMessage": "Try changing the filters or search query", "noRecordsTitle": "No records", "noRecordsMessage": "There are no records to show", @@ -129,10 +130,13 @@ "regions": { "domain": "Regions", "createRegion": "Create Region", + "createRegionHint": "Manage tax rates and providers for a set of countries.", "editRegion": "Edit Region", "deleteRegionWarning": "You are about to delete the region {{name}}. This action cannot be undone.", "taxInclusiveHint": "When enabled all prices in the region will be tax inclusive.", - "providersHint": "The providers that are available in the region." + "providersHint": "The providers that are available in the region.", + "shippingOptions": "Shipping Options", + "returnShippingOptions": "Return Shipping Options" }, "locations": { "domain": "Locations", diff --git a/packages/admin-next/dashboard/src/components/common/combobox/combobox.tsx b/packages/admin-next/dashboard/src/components/common/combobox/combobox.tsx index b499a8e94386b..620537d55736a 100644 --- a/packages/admin-next/dashboard/src/components/common/combobox/combobox.tsx +++ b/packages/admin-next/dashboard/src/components/common/combobox/combobox.tsx @@ -1,5 +1,23 @@ +import { Combobox as Primitive } from "@headlessui/react" +import { EllipseMiniSolid, TrianglesMini } from "@medusajs/icons" +import { Product } from "@medusajs/medusa" import { clx } from "@medusajs/ui" import * as Popover from "@radix-ui/react-popover" +import { useAdminProducts } from "medusa-react" +import { + ComponentPropsWithoutRef, + ElementRef, + ReactNode, + createContext, + forwardRef, + useContext, + useEffect, + useImperativeHandle, + useMemo, + useRef, + useState, +} from "react" +import { useTranslation } from "react-i18next" type ComboboxOption = { value: string @@ -13,25 +31,363 @@ type ComboboxProps = { } export const Combobox = ({ size = "base" }: ComboboxProps) => { + const [product, setProduct] = useState(null) + const [query, setQuery] = useState("") + const { products, count, isLoading } = useAdminProducts( + { + q: query, + }, + { + keepPreviousData: true, + } + ) + + return ( + +
+
+ setQuery(e.target.value)} + displayValue={(value: Product) => value?.title} + className={clx( + "bg-ui-bg-field shadow-buttons-neutral transition-fg flex w-full select-none items-center justify-between rounded-md outline-none", + "placeholder:text-ui-fg-muted text-ui-fg-base", + "hover:bg-ui-bg-field-hover", + "focus-visible:shadow-borders-interactive-with-active data-[state=open]:!shadow-borders-interactive-with-active", + "aria-[invalid=true]:border-ui-border-error aria-[invalid=true]:shadow-borders-error", + "invalid:border-ui-border-error invalid:shadow-borders-error", + "disabled:!bg-ui-bg-disabled disabled:!text-ui-fg-disabled", + { + "h-8 px-2 py-1.5 txt-compact-small": size === "base", + "h-7 px-2 py-1 txt-compact-small": size === "small", + } + )} + /> + + +
+ + {products?.map((p) => ( + + + + {p.title} + + + ))} + +
+
+ ) +} + +type ComboboxContextValue = { + size: "base" | "small" + open: boolean + setOpen: (open: boolean) => void +} + +const ComboboxContext = createContext(null) + +const useComboboxContext = () => { + const context = useContext(ComboboxContext) + + if (!context) { + throw new Error( + "Combobox compound components cannot be rendered outside the Combobox component" + ) + } + + return context +} + +const Root = forwardRef< + ElementRef, + Omit, "children"> & { + className?: string + size?: "base" | "small" + children: ReactNode + } +>(({ children, className, size = "base", ...props }, ref) => { + const [open, setOpen] = useState(false) + + const value = useMemo(() => ({ size, open, setOpen }), [size, open]) + return ( - + + + + {children} + + + + ) +}) +Root.displayName = "Combobox" + +const Trigger = forwardRef>( + ({ className, children, ...props }, ref) => { + const { size } = useComboboxContext() + + return ( - + {...props} + ref={ref} + > + {children} + + + - + ) + } +) +Trigger.displayName = "Combobox.Trigger" + +const Value = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>(({ className, ...props }, ref) => { + const { size } = useComboboxContext() + + return ( + + ) +}) +Value.displayName = "Combobox.Value" + +const Item = forwardRef< + ElementRef, + Omit, "children"> & { + children?: ReactNode + } +>(({ children, className, ...props }, ref) => { + const { size } = useComboboxContext() + + return ( + + + {children} + + ) +}) +Item.displayName = "Combobox.Item" + +const NoResults = forwardRef< + ElementRef<"span">, + ComponentPropsWithoutRef<"span"> +>(({ children, className, ...props }, ref) => { + const { size } = useComboboxContext() + const { t } = useTranslation() + + return ( + + {children ?? t("general.noResults")} + + ) +}) +Item.displayName = "Combobox.NoResults" + +const Content = forwardRef< + ElementRef, + ComponentPropsWithoutRef +>( + ( + { + children, + className, + side = "bottom", + sideOffset = 8, + collisionPadding = 24, + ...props + }, + ref + ) => { + return ( + + + + {children} + + + + ) + } +) +Content.displayName = "Combobox.Content" + +const Pagination = forwardRef< + ElementRef<"div">, + { + isLoading?: boolean + hasNext?: boolean + onPaginate: () => void + className?: string + } +>(({ isLoading, hasNext, onPaginate, className, ...props }, ref) => { + const observerRef = useRef(null) + const innerRef = useRef(null) + + // Merge innerRef and ref + useImperativeHandle(ref, () => { + return innerRef.current + }) + + useEffect(() => { + if (innerRef.current) { + observerRef.current = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting) { + onPaginate() + } + }) + + observerRef.current.observe(innerRef.current) + } + + return () => { + if (observerRef.current && innerRef.current) { + observerRef.current.unobserve(innerRef.current) + } + } + }, [isLoading, hasNext, onPaginate, ref]) + + return
+}) +Pagination.displayName = "Combobox.Pagination" + +const Combo = Object.assign(Root, { + Trigger, + Value, + Item, + NoResults, + Pagination, + Content, +}) + +export const TestCombobox = () => { + const [product, setProduct] = useState([]) + const [query, setQuery] = useState("") + const { products, count, isLoading } = useAdminProducts( + { + q: query, + }, + { + keepPreviousData: true, + } + ) + + return ( + + + setQuery(e.target.value)} + displayValue={(value: Product[]) => `${value?.length}`} + /> + + + {!products?.length && } + console.log("Heyo!")} /> + {products?.map((p) => ( + + {p.title} + + ))} + + ) } diff --git a/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx b/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx index 4c30503aaf496..aae48c3e4dd6b 100644 --- a/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx +++ b/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx @@ -136,11 +136,12 @@ const router = createBrowserRouter([ children: [ { index: true, - lazy: () => import("../../routes/collections/list"), + lazy: () => import("../../routes/collections/collection-list"), }, { path: ":id", - lazy: () => import("../../routes/collections/details"), + lazy: () => + import("../../routes/collections/collection-detail"), }, ], }, diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-create/components/create-publishable-api-key-form/create-publishable-api-key-form.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-create/components/create-publishable-api-key-form/create-publishable-api-key-form.tsx index 654550fec5ffb..dd5c196b8325d 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-create/components/create-publishable-api-key-form/create-publishable-api-key-form.tsx +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-create/components/create-publishable-api-key-form/create-publishable-api-key-form.tsx @@ -62,7 +62,7 @@ export const CreatePublishableApiKeyForm = ({
-
+
{t("apiKeyManagement.createPublishableApiKey")} diff --git a/packages/admin-next/dashboard/src/routes/collections/details/details.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-detail/collection-detail.tsx similarity index 51% rename from packages/admin-next/dashboard/src/routes/collections/details/details.tsx rename to packages/admin-next/dashboard/src/routes/collections/collection-detail/collection-detail.tsx index eaa4c0692f717..e215257460252 100644 --- a/packages/admin-next/dashboard/src/routes/collections/details/details.tsx +++ b/packages/admin-next/dashboard/src/routes/collections/collection-detail/collection-detail.tsx @@ -1,11 +1,11 @@ -import { Container, Heading } from "@medusajs/ui"; +import { Container, Heading } from "@medusajs/ui" -export const CollectionDetails = () => { +export const CollectionDetail = () => { return (
Collection
- ); -}; + ) +} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-detail/index.ts b/packages/admin-next/dashboard/src/routes/collections/collection-detail/index.ts new file mode 100644 index 0000000000000..114231925f7ad --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-detail/index.ts @@ -0,0 +1 @@ +export { CollectionDetail as Component } from "./collection-detail" diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-list/collection-list.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-list/collection-list.tsx new file mode 100644 index 0000000000000..59558d4dc7b26 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-list/collection-list.tsx @@ -0,0 +1,9 @@ +import { CollectionListTable } from "./components/collection-list-table" + +export const CollectionList = () => { + return ( +
+ +
+ ) +} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/collection-list-table.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/collection-list-table.tsx new file mode 100644 index 0000000000000..facb7d2bc8eb3 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/collection-list-table.tsx @@ -0,0 +1,171 @@ +import { ProductCollection } from "@medusajs/medusa" +import { Button, Container, Heading, Table, clx } from "@medusajs/ui" +import { + PaginationState, + createColumnHelper, + flexRender, + getCoreRowModel, + useReactTable, +} from "@tanstack/react-table" +import { useAdminCollections } from "medusa-react" +import { useMemo, useState } from "react" +import { useTranslation } from "react-i18next" +import { Link, useNavigate } from "react-router-dom" + +import { NoRecords } from "../../../../../components/common/empty-table-content" +import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" +import { useQueryParams } from "../../../../../hooks/use-query-params" + +const PAGE_SIZE = 50 + +export const CollectionListTable = () => { + const { t } = useTranslation() + const navigate = useNavigate() + + const [{ pageIndex, pageSize }, setPagination] = useState({ + pageIndex: 0, + pageSize: PAGE_SIZE, + }) + + const pagination = useMemo( + () => ({ + pageIndex, + pageSize, + }), + [pageIndex, pageSize] + ) + + const params = useQueryParams(["q"]) + const { collections, count, isError, error, isLoading } = useAdminCollections( + { + limit: PAGE_SIZE, + offset: pageIndex * PAGE_SIZE, + ...params, + } + ) + + const columns = useColumns() + + const table = useReactTable({ + data: collections ?? [], + columns, + pageCount: Math.ceil((count ?? 0) / PAGE_SIZE), + state: { + pagination, + }, + onPaginationChange: setPagination, + getCoreRowModel: getCoreRowModel(), + manualPagination: true, + }) + + const noRecords = + !isLoading && + (!collections || collections.length === 0) && + !Object.values(params).filter(Boolean).length + + if (isError) { + throw error + } + + return ( + +
+ {t("collections.domain")} + + + +
+ {noRecords ? ( + + ) : ( +
+ + + {table.getHeaderGroups().map((headerGroup) => { + return ( + + {headerGroup.headers.map((header) => { + return ( + + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ) + })} + + + {table.getRowModel().rows.map((row) => ( + navigate(`/collections/${row.original.id}`)} + > + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + ))} + +
+ +
+ )} +
+ ) +} + +const CollectionActions = ({ + collection, +}: { + collection: ProductCollection +}) => { + const { t } = useTranslation() + + return
+} + +const columnHelper = createColumnHelper() + +const useColumns = () => { + const { t } = useTranslation() + + return useMemo( + () => [ + columnHelper.accessor("title", { + header: t("fields.title"), + cell: ({ getValue }) => getValue(), + }), + ], + [t] + ) +} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/index.ts b/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/index.ts new file mode 100644 index 0000000000000..cd5c7323f8bbf --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/index.ts @@ -0,0 +1 @@ +export * from "./collection-list-table" diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-list/index.ts b/packages/admin-next/dashboard/src/routes/collections/collection-list/index.ts new file mode 100644 index 0000000000000..7be7aef36db1a --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-list/index.ts @@ -0,0 +1 @@ +export { CollectionList as Component } from "./collection-list" diff --git a/packages/admin-next/dashboard/src/routes/collections/details/index.ts b/packages/admin-next/dashboard/src/routes/collections/details/index.ts deleted file mode 100644 index 3b15dda2e499f..0000000000000 --- a/packages/admin-next/dashboard/src/routes/collections/details/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { CollectionDetails as Component } from "./details"; diff --git a/packages/admin-next/dashboard/src/routes/collections/list/index.ts b/packages/admin-next/dashboard/src/routes/collections/list/index.ts deleted file mode 100644 index aed84bc76a712..0000000000000 --- a/packages/admin-next/dashboard/src/routes/collections/list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { CollectionsList as Component } from "./list"; diff --git a/packages/admin-next/dashboard/src/routes/collections/list/list.tsx b/packages/admin-next/dashboard/src/routes/collections/list/list.tsx deleted file mode 100644 index ec9bda189e159..0000000000000 --- a/packages/admin-next/dashboard/src/routes/collections/list/list.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { Container, Heading } from "@medusajs/ui"; - -export const CollectionsList = () => { - return ( -
- - Collections - -
- ); -}; diff --git a/packages/admin-next/dashboard/src/routes/customers/customer-create/components/create-customer-form/create-customer-form.tsx b/packages/admin-next/dashboard/src/routes/customers/customer-create/components/create-customer-form/create-customer-form.tsx index 067da3b031fa2..bbbc7898db25d 100644 --- a/packages/admin-next/dashboard/src/routes/customers/customer-create/components/create-customer-form/create-customer-form.tsx +++ b/packages/admin-next/dashboard/src/routes/customers/customer-create/components/create-customer-form/create-customer-form.tsx @@ -93,8 +93,8 @@ export const CreateCustomerForm = ({ subscribe }: CreateCustomerFormProps) => {
- -
+ +
{t("customers.createCustomer")} diff --git a/packages/admin-next/dashboard/src/routes/customers/customer-list/components/customer-list-table/customer-list-table.tsx b/packages/admin-next/dashboard/src/routes/customers/customer-list/components/customer-list-table/customer-list-table.tsx index 1d0a8137ad379..f9d24739889e3 100644 --- a/packages/admin-next/dashboard/src/routes/customers/customer-list/components/customer-list-table/customer-list-table.tsx +++ b/packages/admin-next/dashboard/src/routes/customers/customer-list/components/customer-list-table/customer-list-table.tsx @@ -12,7 +12,6 @@ import { } from "@medusajs/ui" import { PaginationState, - RowSelectionState, createColumnHelper, flexRender, getCoreRowModel, @@ -22,6 +21,7 @@ import { useAdminCustomers } from "medusa-react" import { useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { Link, useNavigate } from "react-router-dom" +import { NoRecords } from "../../../../../components/common/empty-table-content" import { Query } from "../../../../../components/filtering/query" import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" import { useQueryParams } from "../../../../../hooks/use-query-params" @@ -45,13 +45,11 @@ export const CustomerListTable = () => { [pageIndex, pageSize] ) - const [rowSelection, setRowSelection] = useState({}) - - const { q } = useQueryParams(["q"]) + const params = useQueryParams(["q"]) const { customers, count, isLoading, isError, error } = useAdminCustomers({ - q, limit: PAGE_SIZE, offset: pageIndex * PAGE_SIZE, + ...params, }) const columns = useColumns() @@ -62,14 +60,17 @@ export const CustomerListTable = () => { pageCount: Math.ceil((count ?? 0) / PAGE_SIZE), state: { pagination, - rowSelection, }, onPaginationChange: setPagination, - onRowSelectionChange: setRowSelection, getCoreRowModel: getCoreRowModel(), manualPagination: true, }) + const noRecords = + !isLoading && + (!customers || customers.length === 0) && + !Object.values(params).filter(Boolean).length + if (isError) { throw error } @@ -90,62 +91,69 @@ export const CustomerListTable = () => {
-
- - - {table.getHeaderGroups().map((headerGroup) => { - return ( + {noRecords ? ( + + ) : ( +
+
+ + {table.getHeaderGroups().map((headerGroup) => { + return ( + + {headerGroup.headers.map((header) => { + return ( + + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ) + })} + + + {table.getRowModel().rows.map((row) => ( navigate(`/customers/${row.original.id}`)} > - {headerGroup.headers.map((header) => { - return ( - - {flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ) - })} + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} - ) - })} - - - {table.getRowModel().rows.map((row) => ( - navigate(`/customers/${row.original.id}`)} - > - {row.getVisibleCells().map((cell) => ( - - {flexRender(cell.column.columnDef.cell, cell.getContext())} - - ))} - - ))} - -
- -
+ ))} + + + +
+ )} ) } diff --git a/packages/admin-next/dashboard/src/routes/locations/location-create/components/create-location-form/create-location-form.tsx b/packages/admin-next/dashboard/src/routes/locations/location-create/components/create-location-form/create-location-form.tsx index 5c8c6fff37375..79bcee6d89add 100644 --- a/packages/admin-next/dashboard/src/routes/locations/location-create/components/create-location-form/create-location-form.tsx +++ b/packages/admin-next/dashboard/src/routes/locations/location-create/components/create-location-form/create-location-form.tsx @@ -75,7 +75,7 @@ export const CreateLocationForm = () => {
-
+
{t("locations.createLocation")} diff --git a/packages/admin-next/dashboard/src/routes/locations/location-edit/components/edit-location-form/edit-location-form.tsx b/packages/admin-next/dashboard/src/routes/locations/location-edit/components/edit-location-form/edit-location-form.tsx index 3591059a734f7..f5ee175ff8072 100644 --- a/packages/admin-next/dashboard/src/routes/locations/location-edit/components/edit-location-form/edit-location-form.tsx +++ b/packages/admin-next/dashboard/src/routes/locations/location-edit/components/edit-location-form/edit-location-form.tsx @@ -61,7 +61,7 @@ export const EditLocationForm = ({ location }: EditLocationFormProps) => { onSubmit={handleSubmit} className="flex flex-1 flex-col overflow-hidden" > - +
void +} + +const CreateRegionSchema = zod.object({ + name: zod.string().min(1), + currency_code: zod.string(), + includes_tax: zod.boolean(), + countries: zod.array(zod.string()), + fulfillment_providers: zod.array(zod.string()).min(1), + payment_providers: zod.array(zod.string()).min(1), + tax_rate: zod.number().min(0).max(1), + tax_code: zod.string().optional(), +}) + +export const CreateRegionForm = ({ subscribe }: CreateRegionFormProps) => { + const form = useForm>({ + defaultValues: { + name: "", + currency_code: "", + includes_tax: false, + countries: [], + fulfillment_providers: [], + payment_providers: [], + tax_code: "", + }, + resolver: zodResolver(CreateRegionSchema), + }) + + const { + formState: { isDirty }, + } = form + + useEffect(() => { + subscribe(isDirty) + }, [isDirty]) + + const { t } = useTranslation() + const navigate = useNavigate() + + const { mutateAsync, isLoading } = useAdminCreateRegion() + + const handleSubmit = form.handleSubmit(async (values) => { + await mutateAsync( + { + name: values.name, + countries: values.countries, + currency_code: values.currency_code, + fulfillment_providers: values.fulfillment_providers, + payment_providers: values.payment_providers, + tax_rate: values.tax_rate, + tax_code: values.tax_code, + includes_tax: values.includes_tax, + }, + { + onSuccess: ({ region }) => { + navigate(`../${region.id}`) + }, + } + ) + }) + + return ( +
+ + +
+ + + + +
+
+ +
+
+ {t("regions.createRegion")} + + {t("regions.createRegionHint")} + +
+
+
+ { + return ( + + {t("fields.name")} + + + + + + ) + }} + /> + { + return ( + + {t("fields.currency")} + + + + ) + }} + /> +
+
+ { + return ( + + {t("fields.taxRate")} + + + + + + ) + }} + /> + { + return ( + + {t("fields.taxCode")} + + + + + + ) + }} + /> +
+
+ { + return ( + +
+
+ + {t("fields.taxInclusivePricing")} + + + + +
+ {t("regions.taxInclusiveHint")} + +
+
+ ) + }} + /> +
+
+ + {t("fields.providers")} + + + {t("regions.providersHint")} + +
+
+
+
+
+
+ + ) +} diff --git a/packages/admin-next/dashboard/src/routes/regions/region-create/components/create-region-form/index.ts b/packages/admin-next/dashboard/src/routes/regions/region-create/components/create-region-form/index.ts new file mode 100644 index 0000000000000..115d05c6efe29 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/regions/region-create/components/create-region-form/index.ts @@ -0,0 +1 @@ +export * from "./create-region-form" diff --git a/packages/admin-next/dashboard/src/routes/regions/region-create/region-create.tsx b/packages/admin-next/dashboard/src/routes/regions/region-create/region-create.tsx index 191e96b1d428a..c313c1d915c09 100644 --- a/packages/admin-next/dashboard/src/routes/regions/region-create/region-create.tsx +++ b/packages/admin-next/dashboard/src/routes/regions/region-create/region-create.tsx @@ -1,200 +1,15 @@ -import { zodResolver } from "@hookform/resolvers/zod" -import { Button, FocusModal, Heading, Input, Switch, Text } from "@medusajs/ui" -import { useAdminCreateRegion } from "medusa-react" -import { useForm } from "react-hook-form" -import { useTranslation } from "react-i18next" -import * as zod from "zod" +import { FocusModal } from "@medusajs/ui" -import { useNavigate } from "react-router-dom" -import { Form } from "../../../components/common/form" import { useRouteModalState } from "../../../hooks/use-route-modal-state" - -const CreateRegionFormSchema = zod.object({ - name: zod.string().min(1), - currency_code: zod.string(), - includes_tax: zod.boolean(), - countries: zod.array(zod.string()), - fulfillment_providers: zod.array(zod.string()).min(1), - payment_providers: zod.array(zod.string()).min(1), - tax_rate: zod.number().min(0).max(100), - tax_code: zod.string().optional(), -}) +import { CreateRegionForm } from "./components/create-region-form" export const RegionCreate = () => { - const [open, onOpenChange] = useRouteModalState() - const navigate = useNavigate() - - const { t } = useTranslation() - - const form = useForm>({ - defaultValues: { - name: "", - currency_code: "", - includes_tax: false, - countries: [], - fulfillment_providers: [], - payment_providers: [], - tax_code: "", - }, - resolver: zodResolver(CreateRegionFormSchema), - }) - - const { mutateAsync, isLoading } = useAdminCreateRegion() - - const handleSubmit = form.handleSubmit(async (values) => { - await mutateAsync( - { - name: values.name, - countries: values.countries, - currency_code: values.currency_code, - fulfillment_providers: values.fulfillment_providers, - payment_providers: values.payment_providers, - tax_rate: values.tax_rate, - tax_code: values.tax_code, - includes_tax: values.includes_tax, - }, - { - onSuccess: ({ region }) => { - navigate(`../${region.id}`) - }, - } - ) - }) + const [open, onOpenChange, subscribe] = useRouteModalState() return ( -
- - -
- - - - -
-
- -
- -
-
- { - return ( - - {t("fields.name")} - - - - - - ) - }} - /> - { - return ( - - {t("fields.currency")} - - - - ) - }} - /> -
-
- { - return ( - - {t("fields.taxRate")} - - - - - - ) - }} - /> - { - return ( - - - {t("fields.taxCode")} - - - - - - - ) - }} - /> -
-
- { - return ( - -
-
- - {t("fields.taxInclusivePricing")} - - - - -
- {t("regions.taxInclusiveHint")} - -
-
- ) - }} - /> -
-
- - {t("fields.providers")} - - - {t("regions.providersHint")} - -
-
-
-
-
- - {t("fields.metadata")} - -
-
-
-
-
- +
) diff --git a/packages/admin-next/dashboard/src/routes/regions/region-detail/components/region-shipping-option-section/region-shipping-option-section.tsx b/packages/admin-next/dashboard/src/routes/regions/region-detail/components/region-shipping-option-section/region-shipping-option-section.tsx index 488fe486a51b5..ddebc331673d4 100644 --- a/packages/admin-next/dashboard/src/routes/regions/region-detail/components/region-shipping-option-section/region-shipping-option-section.tsx +++ b/packages/admin-next/dashboard/src/routes/regions/region-detail/components/region-shipping-option-section/region-shipping-option-section.tsx @@ -1,5 +1,5 @@ import { Region, ShippingOption } from "@medusajs/medusa" -import { Container, StatusBadge, Table, clx } from "@medusajs/ui" +import { Container, Heading, StatusBadge, Table, clx } from "@medusajs/ui" import { PaginationState, RowSelectionState, @@ -17,15 +17,18 @@ type RegionShippingOptionSectionProps = { region: Region } -const PAGE_SIZE = 20 - -// TODO: Need to fix pagination and search for shipping options 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({ pageIndex: 0, - pageSize: PAGE_SIZE, + pageSize: count || 0, }) const pagination = useMemo( @@ -36,11 +39,6 @@ export const RegionShippingOptionSection = ({ [pageIndex, pageSize] ) - const { shipping_options, count, isError, error, isLoading } = - useAdminShippingOptions({ - region_id: region.id, - }) - const [rowSelection, setRowSelection] = useState({}) const columns = useShippingOptionColumns() @@ -48,7 +46,7 @@ export const RegionShippingOptionSection = ({ const table = useReactTable({ data: shipping_options ?? [], columns, - pageCount: Math.ceil((count ?? 0) / PAGE_SIZE), + pageCount: count ? 1 : 0, state: { pagination, rowSelection, @@ -59,17 +57,17 @@ export const RegionShippingOptionSection = ({ onRowSelectionChange: setRowSelection, }) - if (isLoading) { - return
Loading...
- } + const { t } = useTranslation() if (isError) { throw error } return ( - -
{/* Filters go here */}
+ +
+ {t("regions.shippingOptions")} +
{table.getHeaderGroups().map((headerGroup) => { @@ -121,7 +119,7 @@ export const RegionShippingOptionSection = ({ count={count ?? 0} pageIndex={pageIndex} pageCount={table.getPageCount()} - pageSize={PAGE_SIZE} + pageSize={count ?? 0} /> ) diff --git a/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-create/components/create-sales-channel-form/create-sales-channel-form.tsx b/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-create/components/create-sales-channel-form/create-sales-channel-form.tsx index 48b4375dc369b..8e537c6b4beea 100644 --- a/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-create/components/create-sales-channel-form/create-sales-channel-form.tsx +++ b/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-create/components/create-sales-channel-form/create-sales-channel-form.tsx @@ -86,7 +86,7 @@ export const CreateSalesChannelForm = ({
-
+
{t("salesChannels.createSalesChannel")} diff --git a/packages/admin-next/dashboard/tailwind.config.cjs b/packages/admin-next/dashboard/tailwind.config.cjs index 4fb53b7c80287..14a34c0c7194f 100644 --- a/packages/admin-next/dashboard/tailwind.config.cjs +++ b/packages/admin-next/dashboard/tailwind.config.cjs @@ -1,20 +1,19 @@ -import preset from "@medusajs/ui-preset"; -import path from "path"; +import path from "path" const rootProject = path.join( process.cwd(), "../../apps/server/src/admin/**/*.{js,jsx,ts,tsx}" -); +) // get the path of the dependency "@medusajs/ui" const medusaUI = path.join( path.dirname(require.resolve("@medusajs/ui")), "**/*.{js,jsx,ts,tsx}" -); +) /** @type {import('tailwindcss').Config} */ module.exports = { - presets: [preset], + presets: [require("@medusajs/ui-preset")], content: [ "./index.html", "./src/**/*.{js,ts,jsx,tsx}", @@ -25,5 +24,5 @@ module.exports = { theme: { extend: {}, }, - plugins: [], -}; + plugins: [require("@headlessui/tailwindcss")], +} diff --git a/packages/design-system/ui/src/components/select/select.tsx b/packages/design-system/ui/src/components/select/select.tsx index bc613ea3a0d7e..054f3f05cd2ee 100644 --- a/packages/design-system/ui/src/components/select/select.tsx +++ b/packages/design-system/ui/src/components/select/select.tsx @@ -185,7 +185,7 @@ const Item = React.forwardRef< Date: Wed, 17 Jan 2024 23:04:46 +0100 Subject: [PATCH 2/6] finalize pcol domain --- .../public/locales/en/translation.json | 20 +- .../action-menu.tsx} | 35 +- .../components/common/action-menu/index.ts | 1 + .../src/components/common/form/form.tsx | 16 +- .../common/table-row-actions/index.ts | 1 - .../router-provider/router-provider.tsx | 27 +- .../api-key-management-list-table.tsx | 62 ++- .../collection-add-products.tsx | 34 ++ .../add-products-to-collection-form.tsx | 374 +++++++++++++++++ .../add-products-to-collection-form/index.ts | 1 + .../collection-add-products/index.ts | 1 + .../collection-create/collection-create.tsx | 15 + .../create-collection-form.tsx | 138 +++++++ .../create-collection-form/index.ts | 1 + .../collections/collection-create/index.ts | 1 + .../collection-detail/collection-detail.tsx | 37 +- .../collection-general-section.tsx | 76 ++++ .../collection-general-section/index.ts | 1 + .../collection-product-section.tsx | 385 ++++++++++++++++++ .../collection-product-section/index.ts | 1 + .../collections/collection-detail/index.ts | 1 + .../collections/collection-detail/loader.ts | 21 + .../collection-edit/collection-add-edit.tsx | 39 ++ .../edit-collection-form.tsx | 121 ++++++ .../components/edit-collection-form/index.ts | 1 + .../collections/collection-edit/index.ts | 1 + .../collection-list/collection-list.tsx | 2 + .../collection-list-table.tsx | 53 ++- .../customer-group-customer-section.tsx | 4 +- .../customer-group-general-section.tsx | 52 +-- .../customer-group-list-table.tsx | 4 +- .../customer-order-section.tsx | 45 +- .../customer-list-table.tsx | 36 +- .../location-sales-channel-section.tsx | 88 ++-- .../locations-list-table.tsx | 62 ++- .../product-list-table/product-list-table.tsx | 43 +- .../region-general-section.tsx | 65 ++- .../routes/regions/region-detail/loader.ts | 2 - .../regions/region-detail/region-detail.tsx | 21 +- .../region-list-table/region-list-table.tsx | 81 ++-- .../regions/region-list/region-list.tsx | 2 - .../sales-channel-general-section.tsx | 59 ++- .../sales-channel-product-section.tsx | 47 +-- .../sales-channel-list-table.tsx | 76 ++-- .../store-currency-section.tsx | 44 +- .../ui/src/components/drawer/drawer.tsx | 2 +- 46 files changed, 1775 insertions(+), 424 deletions(-) rename packages/admin-next/dashboard/src/components/common/{table-row-actions/table-row-actions.tsx => action-menu/action-menu.tsx} (67%) create mode 100644 packages/admin-next/dashboard/src/components/common/action-menu/index.ts delete mode 100644 packages/admin-next/dashboard/src/components/common/table-row-actions/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-add-products/collection-add-products.tsx create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-add-products/components/add-products-to-collection-form/add-products-to-collection-form.tsx create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-add-products/components/add-products-to-collection-form/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-add-products/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-create/collection-create.tsx create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-create/components/create-collection-form/create-collection-form.tsx create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-create/components/create-collection-form/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-create/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-general-section/collection-general-section.tsx create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-general-section/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-detail/loader.ts create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-edit/collection-add-edit.tsx create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-edit/components/edit-collection-form/edit-collection-form.tsx create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-edit/components/edit-collection-form/index.ts create mode 100644 packages/admin-next/dashboard/src/routes/collections/collection-edit/index.ts diff --git a/packages/admin-next/dashboard/public/locales/en/translation.json b/packages/admin-next/dashboard/public/locales/en/translation.json index d16355dfb6dd4..18d019cc09b87 100644 --- a/packages/admin-next/dashboard/public/locales/en/translation.json +++ b/packages/admin-next/dashboard/public/locales/en/translation.json @@ -56,7 +56,17 @@ } }, "collections": { - "domain": "Collections" + "domain": "Collections", + "createCollection": "Create Collection", + "createCollectionHint": "Create a new collection to organize your products.", + "editCollection": "Edit Collection", + "handleTooltip": "The handle is used to reference the collection in your storefront. If not specified, the handle will be generated from the collection title.", + "deleteWarning_one": "You are about to delete {{count}} collection. This action cannot be undone.", + "deleteWarning_other": "You are about to delete {{count}} collections. This action cannot be undone.", + "viewProduct": "View product", + "removeSingleProductWarning": "You are about to remove the product {{title}} from the collection. This action cannot be undone.", + "removeProductsWarning_one": "You are about to remove {{count}} product from the collection. This action cannot be undone.", + "removeProductsWarning_other": "You are about to remove {{count}} products from the collection. This action cannot be undone." }, "categories": { "domain": "Categories" @@ -78,7 +88,8 @@ "changePasswordPromptDescription": "You are about to change the password for {{email}}. Make sure that you have communicated the new password to the customer before proceeding.", "guest": "Guest", "registered": "Registered", - "firstSeen": "First seen" + "firstSeen": "First seen", + "viewOrder": "View order" }, "customerGroups": { "domain": "Customer Groups", @@ -153,7 +164,9 @@ "addSalesChannels": "Add sales channels", "detailsHint": "Specify the details of the location.", "noLocationsFound": "No locations found", - "deleteLocationWarning": "You are about to delete the location {{name}}. This action cannot be undone." + "deleteLocationWarning": "You are about to delete the location {{name}}. This action cannot be undone.", + "removeSalesChannelsWarning_one": "You are about to remove {{count}} sales channel from the location.", + "removeSalesChannelsWarning_other": "You are about to remove {{count}} sales channels from the location." }, "salesChannels": { "domain": "Sales Channels", @@ -218,6 +231,7 @@ "phone": "Phone", "metadata": "Metadata", "selectCountry": "Select country", + "products": "Products", "variants": "Variants", "orders": "Orders", "account": "Account", diff --git a/packages/admin-next/dashboard/src/components/common/table-row-actions/table-row-actions.tsx b/packages/admin-next/dashboard/src/components/common/action-menu/action-menu.tsx similarity index 67% rename from packages/admin-next/dashboard/src/components/common/table-row-actions/table-row-actions.tsx rename to packages/admin-next/dashboard/src/components/common/action-menu/action-menu.tsx index 99e5d6adfacd0..57949865be929 100644 --- a/packages/admin-next/dashboard/src/components/common/table-row-actions/table-row-actions.tsx +++ b/packages/admin-next/dashboard/src/components/common/action-menu/action-menu.tsx @@ -3,7 +3,7 @@ import { DropdownMenu, IconButton } from "@medusajs/ui" import { ReactNode } from "react" import { Link } from "react-router-dom" -type TableRowAction = { +type Action = { icon: ReactNode label: string } & ( @@ -17,15 +17,15 @@ type TableRowAction = { } ) -type TableRowActionGroup = { - actions: TableRowAction[] +type ActionGroup = { + actions: Action[] } -type TableRowActionsProps = { - groups: TableRowActionGroup[] +type ActionMenuProps = { + groups: ActionGroup[] } -export const TableRowActions = ({ groups }: TableRowActionsProps) => { +export const ActionMenu = ({ groups }: ActionMenuProps) => { return ( @@ -42,7 +42,7 @@ export const TableRowActions = ({ groups }: TableRowActionsProps) => { const isLast = index === groups.length - 1 return ( -
+ {group.actions.map((action, index) => { if (action.onClick) { return ( @@ -61,21 +61,18 @@ export const TableRowActions = ({ groups }: TableRowActionsProps) => { } return ( - - { - e.stopPropagation() - }} - className="[&_svg]:text-ui-fg-subtle flex items-center gap-x-2" - > - {action.icon} - {action.label} - - +
+ e.stopPropagation()}> + + {action.icon} + {action.label} + + +
) })} {!isLast && } -
+ ) })} diff --git a/packages/admin-next/dashboard/src/components/common/action-menu/index.ts b/packages/admin-next/dashboard/src/components/common/action-menu/index.ts new file mode 100644 index 0000000000000..a3a827d6173da --- /dev/null +++ b/packages/admin-next/dashboard/src/components/common/action-menu/index.ts @@ -0,0 +1 @@ +export * from "./action-menu" diff --git a/packages/admin-next/dashboard/src/components/common/form/form.tsx b/packages/admin-next/dashboard/src/components/common/form/form.tsx index b4e385c431a0d..d6c1d5201f10f 100644 --- a/packages/admin-next/dashboard/src/components/common/form/form.tsx +++ b/packages/admin-next/dashboard/src/components/common/form/form.tsx @@ -1,12 +1,14 @@ +import { InformationCircleSolid } from "@medusajs/icons" import { Hint as HintComponent, Label as LabelComponent, Text, + Tooltip, clx, } from "@medusajs/ui" import * as LabelPrimitives from "@radix-ui/react-label" import { Slot } from "@radix-ui/react-slot" -import { createContext, forwardRef, useContext, useId } from "react" +import { ReactNode, createContext, forwardRef, useContext, useId } from "react" import { Controller, ControllerProps, @@ -22,7 +24,7 @@ const Provider = FormProvider type FormFieldContextValue< TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath + TName extends FieldPath = FieldPath, > = { name: TName } @@ -33,7 +35,7 @@ const FormFieldContext = createContext( const Field = < TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath + TName extends FieldPath = FieldPath, >({ ...props }: ControllerProps) => { @@ -97,8 +99,9 @@ const Label = forwardRef< React.ElementRef, React.ComponentPropsWithoutRef & { optional?: boolean + tooltip?: ReactNode } ->(({ className, optional = false, ...props }, ref) => { +>(({ className, optional = false, tooltip, ...props }, ref) => { const { formItemId } = useFormField() const { t } = useTranslation() @@ -112,6 +115,11 @@ const Label = forwardRef< weight="plus" {...props} /> + {tooltip && ( + + + + )} {optional && ( ({t("fields.optional")}) diff --git a/packages/admin-next/dashboard/src/components/common/table-row-actions/index.ts b/packages/admin-next/dashboard/src/components/common/table-row-actions/index.ts deleted file mode 100644 index 155d370c4a28c..0000000000000 --- a/packages/admin-next/dashboard/src/components/common/table-row-actions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./table-row-actions" diff --git a/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx b/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx index 4ba30b696b20f..6ccd1178e7ba3 100644 --- a/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx +++ b/packages/admin-next/dashboard/src/providers/router-provider/router-provider.tsx @@ -1,4 +1,5 @@ import type { + AdminCollectionsRes, AdminCustomerGroupsRes, AdminCustomersRes, AdminProductsRes, @@ -136,13 +137,37 @@ const router = createBrowserRouter([ }, children: [ { - index: true, + path: "", lazy: () => import("../../routes/collections/collection-list"), + children: [ + { + path: "create", + lazy: () => + import("../../routes/collections/collection-create"), + }, + ], }, { path: ":id", + handle: { + crumb: (data: AdminCollectionsRes) => data.collection.title, + }, lazy: () => import("../../routes/collections/collection-detail"), + children: [ + { + path: "edit", + lazy: () => + import("../../routes/collections/collection-edit"), + }, + { + path: "add-products", + lazy: () => + import( + "../../routes/collections/collection-add-products" + ), + }, + ], }, ], }, diff --git a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx index cdbf3d8adb689..4edd5e6a1c16b 100644 --- a/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx +++ b/packages/admin-next/dashboard/src/routes/api-key-management/api-key-management-list/components/api-key-management-list-table/api-key-management-list-table.tsx @@ -1,15 +1,6 @@ -import { EllipsisHorizontal, Trash, XCircle } from "@medusajs/icons" +import { PencilSquare, Trash, XCircle } from "@medusajs/icons" import { PublishableApiKey } from "@medusajs/medusa" -import { - Button, - Container, - DropdownMenu, - Heading, - IconButton, - Table, - clx, - usePrompt, -} from "@medusajs/ui" +import { Button, Container, Heading, Table, clx, usePrompt } from "@medusajs/ui" import { PaginationState, RowSelectionState, @@ -26,6 +17,7 @@ import { import { useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { Link, useNavigate } from "react-router-dom" +import { ActionMenu } from "../../../../../components/common/action-menu" import { NoRecords } from "../../../../../components/common/empty-table-content" import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" @@ -212,27 +204,33 @@ const KeyActions = ({ apiKey }: { apiKey: PublishableApiKey }) => { } return ( - - - - - - - - -
- - {t("apiKeyManagement.revoke")} -
-
- -
- - {t("general.delete")} -
-
-
-
+ , + label: t("general.edit"), + to: `/settings/api-key-management/${apiKey.id}`, + }, + ], + }, + { + actions: [ + { + icon: , + label: t("apiKeyManagement.revoke"), + onClick: handleRevoke, + }, + { + icon: , + label: t("general.delete"), + onClick: handleDelete, + }, + ], + }, + ]} + /> ) } diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-add-products/collection-add-products.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-add-products/collection-add-products.tsx new file mode 100644 index 0000000000000..9832460d6bf06 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-add-products/collection-add-products.tsx @@ -0,0 +1,34 @@ +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 { 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 ( + + + {!isLoading && collection && ( + + )} + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-add-products/components/add-products-to-collection-form/add-products-to-collection-form.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-add-products/components/add-products-to-collection-form/add-products-to-collection-form.tsx new file mode 100644 index 0000000000000..225b103071d28 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-add-products/components/add-products-to-collection-form/add-products-to-collection-form.tsx @@ -0,0 +1,374 @@ +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 { + PaginationState, + RowSelectionState, + createColumnHelper, + flexRender, + getCoreRowModel, + useReactTable, +} from "@tanstack/react-table" +import { + adminProductKeys, + useAdminAddProductsToCollection, + useAdminProducts, +} from "medusa-react" +import { useEffect, useMemo, useState } from "react" +import { useForm } from "react-hook-form" +import { useTranslation } from "react-i18next" +import * as zod from "zod" +import { NoRecords } from "../../../../../components/common/empty-table-content" +import { Form } from "../../../../../components/common/form" +import { + ProductAvailabilityCell, + ProductCollectionCell, + ProductStatusCell, + ProductTitleCell, + ProductVariantCell, +} from "../../../../../components/common/product-table-cells" +import { OrderBy } from "../../../../../components/filtering/order-by" +import { Query } from "../../../../../components/filtering/query" +import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" +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({ + product_ids: zod.array(zod.string()).min(1), +}) + +const PAGE_SIZE = 50 + +export const AddProductsToCollectionForm = ({ + collection, + subscribe, + onSuccessfulSubmit, +}: AddProductsToCollectionFormProps) => { + const { t } = useTranslation() + + const form = useForm>({ + defaultValues: { + product_ids: [], + }, + resolver: zodResolver(AddProductsToSalesChannelSchema), + }) + + const { + formState: { isDirty }, + } = form + + useEffect(() => { + subscribe(isDirty) + }, [isDirty]) + + const { mutateAsync, isLoading: isMutating } = + useAdminAddProductsToCollection(collection.id) + + const [{ pageIndex, pageSize }, setPagination] = useState({ + pageIndex: 0, + pageSize: PAGE_SIZE, + }) + + const pagination = useMemo( + () => ({ + pageIndex, + pageSize, + }), + [pageIndex, pageSize] + ) + + const [rowSelection, setRowSelection] = useState({}) + + useEffect(() => { + form.setValue( + "product_ids", + Object.keys(rowSelection).filter((k) => rowSelection[k]) + ) + }, [rowSelection]) + + const params = useQueryParams(["q", "order"]) + + const { products, count, isLoading, isError, error } = useAdminProducts( + { + expand: "variants,sales_channels", + ...params, + }, + { + keepPreviousData: true, + } + ) + + const columns = useColumns() + + const table = useReactTable({ + data: (products ?? []) as Product[], + columns, + pageCount: Math.ceil((count ?? 0) / PAGE_SIZE), + state: { + pagination, + rowSelection, + }, + onPaginationChange: setPagination, + onRowSelectionChange: setRowSelection, + getCoreRowModel: getCoreRowModel(), + manualPagination: true, + getRowId: (row) => row.id, + enableRowSelection(row) { + return row.original.collection_id !== collection.id + }, + meta: { + collectionId: collection.id, + }, + }) + + const handleSubmit = form.handleSubmit(async (values) => { + await mutateAsync( + { + product_ids: values.product_ids.map((p) => p), + }, + { + onSuccess: () => { + /** + * Invalidate the products list query to refetch products and + * determine if they are added to the collection or not. + */ + queryClient.invalidateQueries(adminProductKeys.lists()) + onSuccessfulSubmit() + }, + } + ) + }) + + const noRecords = + !isLoading && + products?.length === 0 && + !Object.values(params).filter((v) => v).length + + if (isError) { + throw error + } + + return ( +
+ + +
+ {form.formState.errors.product_ids && ( + + {form.formState.errors.product_ids.message} + + )} + + + + +
+
+ + {!noRecords && ( +
+
+
+ + +
+
+ )} +
+ {!noRecords ? ( +
+ + {table.getHeaderGroups().map((headerGroup) => { + return ( + + {headerGroup.headers.map((header) => { + return ( + + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ) + })} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + ))} + +
+ ) : ( +
+ +
+ )} +
+
+ +
+ + + + ) +} + +const columnHelper = createColumnHelper() + +const useColumns = () => { + const { t } = useTranslation() + + return useMemo( + () => [ + columnHelper.display({ + id: "select", + header: ({ table }) => { + return ( + + table.toggleAllPageRowsSelected(!!value) + } + /> + ) + }, + cell: ({ row, table }) => { + const { collectionId } = table.options.meta as { + collectionId: string + } + + const isAdded = row.original.collection_id === collectionId + + const isSelected = row.getIsSelected() || isAdded + + const Component = ( + row.toggleSelected(!!value)} + onClick={(e) => { + e.stopPropagation() + }} + /> + ) + + if (isAdded) { + return ( + + {Component} + + ) + } + + return Component + }, + }), + columnHelper.accessor("title", { + header: t("fields.title"), + cell: ({ row }) => { + const product = row.original + + return + }, + }), + columnHelper.accessor("collection", { + header: t("fields.collection"), + cell: ({ getValue }) => { + const collection = getValue() + + return + }, + }), + columnHelper.accessor("sales_channels", { + header: t("fields.availability"), + cell: ({ getValue }) => { + const salesChannels = getValue() + + return + }, + }), + columnHelper.accessor("variants", { + header: t("fields.inventory"), + cell: (cell) => { + const variants = cell.getValue() + + return + }, + }), + columnHelper.accessor("status", { + header: t("fields.status"), + cell: ({ getValue }) => { + const status = getValue() + + return + }, + }), + ], + [t] + ) +} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-add-products/components/add-products-to-collection-form/index.ts b/packages/admin-next/dashboard/src/routes/collections/collection-add-products/components/add-products-to-collection-form/index.ts new file mode 100644 index 0000000000000..4fbbd8d08c1a2 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-add-products/components/add-products-to-collection-form/index.ts @@ -0,0 +1 @@ +export * from "./add-products-to-collection-form" diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-add-products/index.ts b/packages/admin-next/dashboard/src/routes/collections/collection-add-products/index.ts new file mode 100644 index 0000000000000..5553acfeb1bf5 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-add-products/index.ts @@ -0,0 +1 @@ +export { CollectionAddProducts as Component } from "./collection-add-products" diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-create/collection-create.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-create/collection-create.tsx new file mode 100644 index 0000000000000..2a43c7eb9b39b --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-create/collection-create.tsx @@ -0,0 +1,15 @@ +import { FocusModal } from "@medusajs/ui" +import { useRouteModalState } from "../../../hooks/use-route-modal-state" +import { CreateCollectionForm } from "./components/create-collection-form" + +export const CollectionCreate = () => { + const [open, onOpenChange, subscribe] = useRouteModalState() + + return ( + + + + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-create/components/create-collection-form/create-collection-form.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-create/components/create-collection-form/create-collection-form.tsx new file mode 100644 index 0000000000000..78b50dcad4dfa --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-create/components/create-collection-form/create-collection-form.tsx @@ -0,0 +1,138 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { Button, FocusModal, 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 +} + +const CreateCollectionSchema = zod.object({ + title: zod.string().min(1), + handle: zod.string().optional(), +}) + +export const CreateCollectionForm = ({ + subscribe, +}: CreateCollectionFormProps) => { + const { t } = useTranslation() + const navigate = useNavigate() + + const form = useForm>({ + defaultValues: { + title: "", + handle: "", + }, + 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}`) + }, + }) + }) + + return ( +
+ + +
+ + + + +
+
+ +
+
+ {t("collections.createCollection")} + + {t("collections.createCollectionHint")} + +
+
+ { + return ( + + {t("fields.title")} + + + + + + ) + }} + /> + { + return ( + + + {t("fields.handle")} + + +
+
+ + / + +
+ +
+
+ +
+ ) + }} + /> +
+
+
+
+ + ) +} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-create/components/create-collection-form/index.ts b/packages/admin-next/dashboard/src/routes/collections/collection-create/components/create-collection-form/index.ts new file mode 100644 index 0000000000000..19ae8e208b8d9 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-create/components/create-collection-form/index.ts @@ -0,0 +1 @@ +export * from "./create-collection-form" diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-create/index.ts b/packages/admin-next/dashboard/src/routes/collections/collection-create/index.ts new file mode 100644 index 0000000000000..184998a7767aa --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-create/index.ts @@ -0,0 +1 @@ +export { CollectionCreate as Component } from "./collection-create" diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-detail/collection-detail.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-detail/collection-detail.tsx index e215257460252..82a02a1c7f84c 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-detail/collection-detail.tsx +++ b/packages/admin-next/dashboard/src/routes/collections/collection-detail/collection-detail.tsx @@ -1,11 +1,38 @@ -import { Container, Heading } from "@medusajs/ui" +import { useAdminCollection } from "medusa-react" +import { Outlet, json, useLoaderData, useParams } from "react-router-dom" +import { JsonViewSection } from "../../../components/common/json-view-section" +import { CollectionGeneralSection } from "./components/collection-general-section" +import { CollectionProductSection } from "./components/collection-product-section" +import { collectionLoader } from "./loader" export const CollectionDetail = () => { + const initialData = useLoaderData() as Awaited< + ReturnType + > + + const { id } = useParams() + const { collection, isLoading, isError, error } = useAdminCollection(id!, { + initialData, + }) + + if (isLoading) { + return
Loading...
+ } + + if (isError || !collection) { + if (error) { + throw error + } + + throw json("An unknown error occurred", 500) + } + return ( -
- - Collection - +
+ + + +
) } diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-general-section/collection-general-section.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-general-section/collection-general-section.tsx new file mode 100644 index 0000000000000..c9ed9ee84f70a --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-general-section/collection-general-section.tsx @@ -0,0 +1,76 @@ +import { PencilSquare, Trash } from "@medusajs/icons" +import type { ProductCollection } from "@medusajs/medusa" +import { Container, Heading, Text, usePrompt } from "@medusajs/ui" +import { useAdminDeleteCollection } from "medusa-react" +import { useTranslation } from "react-i18next" +import { ActionMenu } from "../../../../../components/common/action-menu" + +type CollectionGeneralSectionProps = { + collection: ProductCollection +} + +export const CollectionGeneralSection = ({ + collection, +}: CollectionGeneralSectionProps) => { + const { t } = useTranslation() + const prompt = usePrompt() + + const { mutateAsync } = useAdminDeleteCollection(collection.id) + + const handleDelete = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("collections.deleteWarning", { + count: 1, + }), + }) + + if (!res) { + return + } + + await mutateAsync() + } + + return ( + +
+ {collection.title} + , + label: t("general.edit"), + to: `/collections/${collection.id}/edit`, + }, + ], + }, + { + actions: [ + { + icon: , + label: t("general.delete"), + onClick: handleDelete, + }, + ], + }, + ]} + /> +
+
+ + {t("fields.handle")} + + /{collection.handle} +
+
+ + {t("fields.products")} + + {collection.products?.length || "-"} +
+
+ ) +} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-general-section/index.ts b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-general-section/index.ts new file mode 100644 index 0000000000000..6c2173aea4846 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-general-section/index.ts @@ -0,0 +1 @@ +export * from "./collection-general-section" diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx new file mode 100644 index 0000000000000..f3c33b1fc2128 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx @@ -0,0 +1,385 @@ +import { Tag, Trash } from "@medusajs/icons" +import type { Product, ProductCollection } from "@medusajs/medusa" +import { + Button, + Checkbox, + CommandBar, + Container, + Heading, + Table, + clx, + usePrompt, +} from "@medusajs/ui" +import { + PaginationState, + RowSelectionState, + createColumnHelper, + flexRender, + getCoreRowModel, + useReactTable, +} from "@tanstack/react-table" +import { + adminProductKeys, + useAdminProducts, + useAdminRemoveProductsFromCollection, +} from "medusa-react" +import { useMemo, useState } from "react" +import { useTranslation } from "react-i18next" +import { Link } from "react-router-dom" +import { ActionMenu } from "../../../../../components/common/action-menu" +import { NoRecords } from "../../../../../components/common/empty-table-content" +import { + ProductAvailabilityCell, + ProductCollectionCell, + ProductStatusCell, + ProductTitleCell, + ProductVariantCell, +} from "../../../../../components/common/product-table-cells" +import { OrderBy } from "../../../../../components/filtering/order-by" +import { Query } from "../../../../../components/filtering/query" +import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" +import { useQueryParams } from "../../../../../hooks/use-query-params" +import { queryClient } from "../../../../../lib/medusa" + +type CollectionProductSectionProps = { + collection: ProductCollection +} + +const PAGE_SIZE = 10 + +export const CollectionProductSection = ({ + collection, +}: CollectionProductSectionProps) => { + const { t } = useTranslation() + + const [{ pageIndex, pageSize }, setPagination] = useState({ + pageIndex: 0, + pageSize: PAGE_SIZE, + }) + + const pagination = useMemo( + () => ({ + pageIndex, + pageSize, + }), + [pageIndex, pageSize] + ) + + const [rowSelection, setRowSelection] = useState({}) + + const params = useQueryParams(["q", "order"]) + const { products, count, isLoading, isError, error } = useAdminProducts({ + limit: PAGE_SIZE, + offset: pageIndex * PAGE_SIZE, + collection_id: [collection.id], + }) + + const columns = useColumns() + + const table = useReactTable({ + data: (products ?? []) as Product[], + columns, + pageCount: Math.ceil((count ?? 0) / PAGE_SIZE), + state: { + pagination, + rowSelection, + }, + getRowId: (row) => row.id, + onPaginationChange: setPagination, + onRowSelectionChange: setRowSelection, + getCoreRowModel: getCoreRowModel(), + manualPagination: true, + meta: { + collectionId: collection.id, + }, + }) + + const prompt = usePrompt() + const { mutateAsync } = useAdminRemoveProductsFromCollection(collection.id) + + const handleRemove = async () => { + const ids = Object.keys(rowSelection) + + const res = await prompt({ + title: t("general.areYouSure"), + description: t("collections.removeProductsWarning", { + count: ids.length, + }), + confirmText: t("general.confirm"), + cancelText: t("general.cancel"), + }) + + if (!res) { + return + } + + await mutateAsync( + { + product_ids: ids, + }, + { + onSuccess: () => { + queryClient.invalidateQueries(adminProductKeys.lists()) + setRowSelection({}) + }, + } + ) + } + + const noRecords = + !isLoading && + products?.length === 0 && + !Object.values(params).filter((v) => v).length + + if (isError) { + throw error + } + + return ( + +
+ {t("products.domain")} + + + +
+ {!noRecords && ( +
+
+
+ + +
+
+ )} + {noRecords ? ( + + ) : ( +
+ + + {table.getHeaderGroups().map((headerGroup) => { + return ( + + {headerGroup.headers.map((header) => { + return ( + + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ) + })} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + + ))} + +
+ + + + + {t("general.countSelected", { + count: Object.keys(rowSelection).length, + })} + + + + + +
+ )} +
+ ) +} + +const ProductActions = ({ + product, + collectionId, +}: { + product: Product + collectionId: string +}) => { + const { t } = useTranslation() + const prompt = usePrompt() + const { mutateAsync } = useAdminRemoveProductsFromCollection(collectionId) + + const handleRemove = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("collections.removeSingleProductWarning", { + title: product.title, + }), + confirmText: t("general.confirm"), + cancelText: t("general.cancel"), + }) + + if (!res) { + return + } + + await mutateAsync({ + product_ids: [product.id], + }) + } + + return ( + , + label: t("collections.viewProduct"), + to: `/products/${product.id}`, + }, + ], + }, + { + actions: [ + { + icon: , + label: t("general.remove"), + onClick: handleRemove, + }, + ], + }, + ]} + /> + ) +} + +const columnHelper = createColumnHelper() + +const useColumns = () => { + const { t } = useTranslation() + + return useMemo( + () => [ + columnHelper.display({ + id: "select", + header: ({ table }) => { + return ( + + table.toggleAllPageRowsSelected(!!value) + } + /> + ) + }, + cell: ({ row }) => { + return ( + row.toggleSelected(!!value)} + onClick={(e) => { + e.stopPropagation() + }} + /> + ) + }, + }), + columnHelper.accessor("title", { + header: t("fields.title"), + cell: ({ row }) => { + return + }, + }), + columnHelper.accessor("collection", { + header: t("fields.collection"), + cell: (cell) => { + const collection = cell.getValue() + + return + }, + }), + columnHelper.accessor("sales_channels", { + header: t("fields.availability"), + cell: (cell) => { + const salesChannels = cell.getValue() + + return + }, + }), + columnHelper.accessor("variants", { + header: t("fields.variants"), + cell: (cell) => { + const variants = cell.getValue() + + return + }, + }), + columnHelper.accessor("status", { + header: t("fields.status"), + cell: (cell) => { + const value = cell.getValue() + + return + }, + }), + columnHelper.display({ + id: "actions", + cell: ({ row, table }) => { + const { collectionId } = table.options.meta as { + collectionId: string + } + + return ( + + ) + }, + }), + ], + [t] + ) +} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/index.ts b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/index.ts new file mode 100644 index 0000000000000..7f00be2947100 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/index.ts @@ -0,0 +1 @@ +export * from "./collection-product-section" diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-detail/index.ts b/packages/admin-next/dashboard/src/routes/collections/collection-detail/index.ts index 114231925f7ad..d993cc2b01ec0 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-detail/index.ts +++ b/packages/admin-next/dashboard/src/routes/collections/collection-detail/index.ts @@ -1 +1,2 @@ export { CollectionDetail as Component } from "./collection-detail" +export { collectionLoader as loader } from "./loader" diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-detail/loader.ts b/packages/admin-next/dashboard/src/routes/collections/collection-detail/loader.ts new file mode 100644 index 0000000000000..05d1a929f0f5d --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-detail/loader.ts @@ -0,0 +1,21 @@ +import { AdminCollectionsRes } from "@medusajs/medusa" +import { Response } from "@medusajs/medusa-js" +import { adminProductKeys } from "medusa-react" +import { LoaderFunctionArgs } from "react-router-dom" + +import { medusa, queryClient } from "../../../lib/medusa" + +const collectionDetailQuery = (id: string) => ({ + queryKey: adminProductKeys.detail(id), + queryFn: async () => medusa.admin.collections.retrieve(id), +}) + +export const collectionLoader = async ({ params }: LoaderFunctionArgs) => { + const id = params.id + const query = collectionDetailQuery(id!) + + return ( + queryClient.getQueryData>(query.queryKey) ?? + (await queryClient.fetchQuery(query)) + ) +} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-edit/collection-add-edit.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-edit/collection-add-edit.tsx new file mode 100644 index 0000000000000..0496ec12c32c7 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-edit/collection-add-edit.tsx @@ -0,0 +1,39 @@ +import { Drawer, 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 { EditCollectionForm } from "./components/edit-collection-form" + +export const CollectionEdit = () => { + const { id } = useParams() + 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 ( + + + + {t("collections.editCollection")} + + {!isLoading && collection && ( + + )} + + + ) +} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-edit/components/edit-collection-form/edit-collection-form.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-edit/components/edit-collection-form/edit-collection-form.tsx new file mode 100644 index 0000000000000..3b720a8db37a6 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-edit/components/edit-collection-form/edit-collection-form.tsx @@ -0,0 +1,121 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import type { ProductCollection } from "@medusajs/medusa" +import { Button, Drawer, 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" + +type EditCollectionFormProps = { + collection: ProductCollection + subscribe: (state: boolean) => void + onSuccessfulSubmit: () => void +} + +const EditCollectionSchema = zod.object({ + title: zod.string().min(1), + handle: zod.string().min(1), +}) + +export const EditCollectionForm = ({ + collection, + onSuccessfulSubmit, + subscribe, +}: EditCollectionFormProps) => { + const { t } = useTranslation() + + const form = useForm>({ + defaultValues: { + title: collection.title, + handle: collection.handle, + }, + 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() + }, + }) + }) + + return ( +
+ + +
+ { + return ( + + {t("fields.title")} + + + + + + ) + }} + /> + { + return ( + + + {t("fields.handle")} + + +
+
+ + / + +
+ +
+
+ +
+ ) + }} + /> +
+
+ +
+ + + + +
+
+
+ + ) +} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-edit/components/edit-collection-form/index.ts b/packages/admin-next/dashboard/src/routes/collections/collection-edit/components/edit-collection-form/index.ts new file mode 100644 index 0000000000000..abcdae3f665cd --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-edit/components/edit-collection-form/index.ts @@ -0,0 +1 @@ +export * from "./edit-collection-form" diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-edit/index.ts b/packages/admin-next/dashboard/src/routes/collections/collection-edit/index.ts new file mode 100644 index 0000000000000..069cdd1eee59f --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-edit/index.ts @@ -0,0 +1 @@ +export { CollectionEdit as Component } from "./collection-add-edit" diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-list/collection-list.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-list/collection-list.tsx index 59558d4dc7b26..fa50d1e886078 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-list/collection-list.tsx +++ b/packages/admin-next/dashboard/src/routes/collections/collection-list/collection-list.tsx @@ -1,9 +1,11 @@ +import { Outlet } from "react-router-dom" import { CollectionListTable } from "./components/collection-list-table" export const CollectionList = () => { return (
+
) } diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/collection-list-table.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/collection-list-table.tsx index facb7d2bc8eb3..0d6966c7f7ab9 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/collection-list-table.tsx +++ b/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/collection-list-table.tsx @@ -1,5 +1,5 @@ import { ProductCollection } from "@medusajs/medusa" -import { Button, Container, Heading, Table, clx } from "@medusajs/ui" +import { Button, Container, Heading, Table, clx, usePrompt } from "@medusajs/ui" import { PaginationState, createColumnHelper, @@ -7,11 +7,13 @@ import { getCoreRowModel, useReactTable, } from "@tanstack/react-table" -import { useAdminCollections } from "medusa-react" +import { useAdminCollections, useAdminDeleteCollection } from "medusa-react" import { useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { Link, useNavigate } from "react-router-dom" +import { PencilSquare, Trash } from "@medusajs/icons" +import { ActionMenu } from "../../../../../components/common/action-menu" import { NoRecords } from "../../../../../components/common/empty-table-content" import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" import { useQueryParams } from "../../../../../hooks/use-query-params" @@ -150,8 +152,49 @@ const CollectionActions = ({ collection: ProductCollection }) => { const { t } = useTranslation() + const prompt = usePrompt() - return
+ const { mutateAsync } = useAdminDeleteCollection(collection.id) + + const handleDelete = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("collections.deleteWarning", { + count: 1, + }), + }) + + if (!res) { + return + } + + await mutateAsync() + } + + return ( + , + label: t("general.edit"), + to: `/collections/${collection.id}/edit`, + }, + ], + }, + { + actions: [ + { + icon: , + label: t("general.delete"), + onClick: handleDelete, + }, + ], + }, + ]} + /> + ) } const columnHelper = createColumnHelper() @@ -165,6 +208,10 @@ const useColumns = () => { header: t("fields.title"), cell: ({ getValue }) => getValue(), }), + columnHelper.display({ + id: "actions", + cell: ({ row }) => , + }), ], [t] ) diff --git a/packages/admin-next/dashboard/src/routes/customer-groups/customer-group-detail/components/customer-group-customer-section/customer-group-customer-section.tsx b/packages/admin-next/dashboard/src/routes/customer-groups/customer-group-detail/components/customer-group-customer-section/customer-group-customer-section.tsx index 04eb0e4bcb69b..ff21d6c43c2d0 100644 --- a/packages/admin-next/dashboard/src/routes/customer-groups/customer-group-detail/components/customer-group-customer-section/customer-group-customer-section.tsx +++ b/packages/admin-next/dashboard/src/routes/customer-groups/customer-group-detail/components/customer-group-customer-section/customer-group-customer-section.tsx @@ -26,11 +26,11 @@ import { import { useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { Link, useNavigate } from "react-router-dom" +import { ActionMenu } from "../../../../../components/common/action-menu" import { NoRecords, NoResults, } from "../../../../../components/common/empty-table-content" -import { TableRowActions } from "../../../../../components/common/table-row-actions" import { Query } from "../../../../../components/filtering/query" import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" import { useQueryParams } from "../../../../../hooks/use-query-params" @@ -279,7 +279,7 @@ const CustomerActions = ({ } return ( - {group.name} - - - - - - - - - - - {t("general.edit")} - - - - - - {t("general.delete")} - - - + , + label: t("general.edit"), + to: `/customer-groups/${group.id}/edit`, + }, + ], + }, + { + actions: [ + { + icon: , + label: t("general.delete"), + onClick: handleDelete, + }, + ], + }, + ]} + /> ) } diff --git a/packages/admin-next/dashboard/src/routes/customer-groups/customer-group-list/components/customer-group-list-table/customer-group-list-table.tsx b/packages/admin-next/dashboard/src/routes/customer-groups/customer-group-list/components/customer-group-list-table/customer-group-list-table.tsx index b090df3679a68..aa5c71bb7301c 100644 --- a/packages/admin-next/dashboard/src/routes/customer-groups/customer-group-list/components/customer-group-list-table/customer-group-list-table.tsx +++ b/packages/admin-next/dashboard/src/routes/customer-groups/customer-group-list/components/customer-group-list-table/customer-group-list-table.tsx @@ -16,11 +16,11 @@ import { import { useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { Link, useNavigate } from "react-router-dom" +import { ActionMenu } from "../../../../../components/common/action-menu" import { NoRecords, NoResults, } from "../../../../../components/common/empty-table-content" -import { TableRowActions } from "../../../../../components/common/table-row-actions" import { OrderBy } from "../../../../../components/filtering/order-by" import { Query } from "../../../../../components/filtering/query" import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" @@ -205,7 +205,7 @@ const CustomerGroupActions = ({ group }: { group: CustomerGroup }) => { } return ( - { + const { t } = useTranslation() + return ( - - - - - - - - - - - Go to order - - - - + , + label: t("customers.viewOrder"), + to: `/orders/${order.id}/edit`, + }, + ], + }, + ]} + /> ) } diff --git a/packages/admin-next/dashboard/src/routes/customers/customer-list/components/customer-list-table/customer-list-table.tsx b/packages/admin-next/dashboard/src/routes/customers/customer-list/components/customer-list-table/customer-list-table.tsx index 447d5fa1ac596..2ffab56dc879d 100644 --- a/packages/admin-next/dashboard/src/routes/customers/customer-list/components/customer-list-table/customer-list-table.tsx +++ b/packages/admin-next/dashboard/src/routes/customers/customer-list/components/customer-list-table/customer-list-table.tsx @@ -1,11 +1,9 @@ -import { EllipsisHorizontal, PencilSquare } from "@medusajs/icons" +import { PencilSquare } from "@medusajs/icons" import { Customer } from "@medusajs/medusa" import { Button, Container, - DropdownMenu, Heading, - IconButton, StatusBadge, Table, clx, @@ -21,6 +19,7 @@ import { useAdminCustomers } from "medusa-react" import { useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { Link, useNavigate } from "react-router-dom" +import { ActionMenu } from "../../../../../components/common/action-menu" import { NoRecords } from "../../../../../components/common/empty-table-content" import { Query } from "../../../../../components/filtering/query" import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" @@ -162,24 +161,19 @@ const CustomerActions = ({ customer }: { customer: Customer }) => { const { t } = useTranslation() return ( - - - - - - - - - e.stopPropagation()} - > - - {t("general.edit")} - - - - + , + label: t("general.edit"), + to: `/customers/${customer.id}/edit`, + }, + ], + }, + ]} + /> ) } diff --git a/packages/admin-next/dashboard/src/routes/locations/location-detail/components/location-sales-channel-section/location-sales-channel-section.tsx b/packages/admin-next/dashboard/src/routes/locations/location-detail/components/location-sales-channel-section/location-sales-channel-section.tsx index 83c4f5f18abee..cf0e17529974b 100644 --- a/packages/admin-next/dashboard/src/routes/locations/location-detail/components/location-sales-channel-section/location-sales-channel-section.tsx +++ b/packages/admin-next/dashboard/src/routes/locations/location-detail/components/location-sales-channel-section/location-sales-channel-section.tsx @@ -1,15 +1,14 @@ -import { EllipsisHorizontal, PencilSquare, Trash } from "@medusajs/icons" +import { PencilSquare, Trash } from "@medusajs/icons" import { SalesChannel } from "@medusajs/medusa" import { StockLocationExpandedDTO } from "@medusajs/types" import { Button, Container, - DropdownMenu, Heading, - IconButton, StatusBadge, Table, clx, + usePrompt, } from "@medusajs/ui" import { PaginationState, @@ -19,9 +18,11 @@ import { getCoreRowModel, useReactTable, } from "@tanstack/react-table" +import { useAdminRemoveLocationFromSalesChannel } from "medusa-react" import { useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { Link, useNavigate } from "react-router-dom" +import { ActionMenu } from "../../../../../components/common/action-menu" import { NoRecords } from "../../../../../components/common/empty-table-content/empty-table-content" type LocationSalesChannelSectionProps = { @@ -67,6 +68,9 @@ export const LocationSalesChannelSection = ({ onRowSelectionChange: setRowSelection, getCoreRowModel: getCoreRowModel(), manualPagination: true, + meta: { + locationId: location.id, + }, }) return ( @@ -143,28 +147,55 @@ export const LocationSalesChannelSection = ({ ) } -const SalesChannelActions = ({ id }: { id: string }) => { +const SalesChannelActions = ({ + salesChannel, + locationId, +}: { + salesChannel: SalesChannel + locationId: string +}) => { const { t } = useTranslation() + const prompt = usePrompt() + + const { mutateAsync } = useAdminRemoveLocationFromSalesChannel() + + const handleDelete = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("locations.removeSalesChannelsWarning", { count: 1 }), + confirmText: t("general.delete"), + cancelText: t("general.cancel"), + }) + + if (!res) { + return + } + + await mutateAsync({ + location_id: locationId, + sales_channel_id: salesChannel.id, + }) + } return ( - - - - - - - - - - {t("general.edit")} - - - - - {t("general.delete")} - - - + , + label: t("general.edit"), + to: `/settings/sales-channels/${salesChannel.id}/edit`, + }, + { + icon: , + label: t("general.delete"), + onClick: handleDelete, + }, + ], + }, + ]} + /> ) } @@ -198,8 +229,17 @@ const useColumns = () => { }), columnHelper.display({ id: "actions", - cell: ({ row }) => { - return + cell: ({ row, table }) => { + const { locationId } = table.options.meta as { + locationId: string + } + + return ( + + ) }, }), ], diff --git a/packages/admin-next/dashboard/src/routes/locations/location-list/components/locations-list-table/locations-list-table.tsx b/packages/admin-next/dashboard/src/routes/locations/location-list/components/locations-list-table/locations-list-table.tsx index 795c81ecfdc00..dff87638a4203 100644 --- a/packages/admin-next/dashboard/src/routes/locations/location-list/components/locations-list-table/locations-list-table.tsx +++ b/packages/admin-next/dashboard/src/routes/locations/location-list/components/locations-list-table/locations-list-table.tsx @@ -1,15 +1,6 @@ -import { EllipsisHorizontal, PencilSquare, Trash } from "@medusajs/icons" +import { PencilSquare, Trash } from "@medusajs/icons" import { StockLocationExpandedDTO } from "@medusajs/types" -import { - Button, - Container, - DropdownMenu, - Heading, - IconButton, - Table, - clx, - usePrompt, -} from "@medusajs/ui" +import { Button, Container, Heading, Table, clx, usePrompt } from "@medusajs/ui" import { PaginationState, RowSelectionState, @@ -22,10 +13,11 @@ import { useAdminDeleteStockLocation, useAdminStockLocations, } from "medusa-react" -import { MouseEvent, useMemo, useState } from "react" +import { useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { Link, useNavigate, useSearchParams } from "react-router-dom" +import { ActionMenu } from "../../../../../components/common/action-menu" import { NoRecords, NoResults, @@ -176,9 +168,7 @@ const LocationActions = ({ const prompt = usePrompt() const { mutateAsync } = useAdminDeleteStockLocation(location.id) - const handleDelete = async (e: MouseEvent) => { - e.stopPropagation() - + const handleDelete = async () => { const res = await prompt({ title: t("general.areYouSure"), description: t("locations.deleteLocationWarning", { @@ -198,30 +188,24 @@ const LocationActions = ({ } return ( - - - - - - - - - e.stopPropagation()}> -
- - {t("general.edit")} -
-
- - - -
- - {t("general.delete")} -
-
-
-
+ , + label: t("general.edit"), + to: `/settings/locations/${location.id}/edit`, + }, + { + icon: , + label: t("general.delete"), + onClick: handleDelete, + }, + ], + }, + ]} + /> ) } diff --git a/packages/admin-next/dashboard/src/routes/products/product-list/components/product-list-table/product-list-table.tsx b/packages/admin-next/dashboard/src/routes/products/product-list/components/product-list-table/product-list-table.tsx index 0114f4ffe9508..22a1d722bb5cb 100644 --- a/packages/admin-next/dashboard/src/routes/products/product-list/components/product-list-table/product-list-table.tsx +++ b/packages/admin-next/dashboard/src/routes/products/product-list/components/product-list-table/product-list-table.tsx @@ -1,13 +1,11 @@ -import { EllipsisHorizontal, Trash } from "@medusajs/icons" +import { PencilSquare, Trash } from "@medusajs/icons" import type { Product } from "@medusajs/medusa" import { Button, Checkbox, CommandBar, Container, - DropdownMenu, Heading, - IconButton, Table, clx, } from "@medusajs/ui" @@ -32,6 +30,7 @@ import { ProductVariantCell, } from "../../../../../components/common/product-table-cells" +import { ActionMenu } from "../../../../../components/common/action-menu" import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" import { productsLoader } from "../../loader" @@ -172,6 +171,7 @@ export const ProductListTable = () => { } const ProductActions = ({ id }: { id: string }) => { + const { t } = useTranslation() const { mutateAsync } = useAdminDeleteProduct(id) const handleDelete = async () => { @@ -179,21 +179,28 @@ const ProductActions = ({ id }: { id: string }) => { } return ( - - - - - - - - -
- - Delete -
-
-
-
+ , + label: t("general.edit"), + to: `/products/${id}/edit`, + }, + ], + }, + { + actions: [ + { + icon: , + label: t("general.delete"), + onClick: handleDelete, + }, + ], + }, + ]} + /> ) } diff --git a/packages/admin-next/dashboard/src/routes/regions/region-detail/components/region-general-section/region-general-section.tsx b/packages/admin-next/dashboard/src/routes/regions/region-detail/components/region-general-section/region-general-section.tsx index 860ef29da6dbb..e375dd72a1446 100644 --- a/packages/admin-next/dashboard/src/routes/regions/region-detail/components/region-general-section/region-general-section.tsx +++ b/packages/admin-next/dashboard/src/routes/regions/region-detail/components/region-general-section/region-general-section.tsx @@ -1,18 +1,11 @@ -import { - BuildingTax, - EllipsisHorizontal, - PencilSquare, - Trash, -} from "@medusajs/icons" +import { PencilSquare, Trash } from "@medusajs/icons" import { Country, Region } from "@medusajs/medusa" import { Badge, Button, Container, Drawer, - DropdownMenu, Heading, - IconButton, StatusBadge, Text, Tooltip, @@ -21,8 +14,8 @@ import { import { useAdminDeleteRegion, useAdminUpdateRegion } from "medusa-react" import { useForm } from "react-hook-form" import { useTranslation } from "react-i18next" -import { Link } from "react-router-dom" import * as zod from "zod" +import { ActionMenu } from "../../../../../components/common/action-menu" type RegionGeneralSectionProps = { region: Region @@ -113,38 +106,28 @@ const RegionActions = ({ region }: { region: Region }) => { } return ( - - - - - - - - - -
- - {t("general.edit")} -
-
- - - -
- - Tax settings -
-
- - - -
- - Delete -
-
-
-
+ , + label: t("general.edit"), + to: `/settings/regions/${region.id}/edit`, + }, + ], + }, + { + actions: [ + { + icon: , + label: t("general.delete"), + onClick: handleDelete, + }, + ], + }, + ]} + /> ) } diff --git a/packages/admin-next/dashboard/src/routes/regions/region-detail/loader.ts b/packages/admin-next/dashboard/src/routes/regions/region-detail/loader.ts index e8c97d2216f74..2e00f4fc67957 100644 --- a/packages/admin-next/dashboard/src/routes/regions/region-detail/loader.ts +++ b/packages/admin-next/dashboard/src/routes/regions/region-detail/loader.ts @@ -14,8 +14,6 @@ export const regionLoader = async ({ params }: LoaderFunctionArgs) => { const id = params.id const query = regionQuery(id!) - console.log("regionLoader", query) - return ( queryClient.getQueryData>(query.queryKey) ?? (await queryClient.fetchQuery(query)) diff --git a/packages/admin-next/dashboard/src/routes/regions/region-detail/region-detail.tsx b/packages/admin-next/dashboard/src/routes/regions/region-detail/region-detail.tsx index eeca1c0a65e53..b1ba0f700726e 100644 --- a/packages/admin-next/dashboard/src/routes/regions/region-detail/region-detail.tsx +++ b/packages/admin-next/dashboard/src/routes/regions/region-detail/region-detail.tsx @@ -1,5 +1,5 @@ import { useAdminRegion } from "medusa-react" -import { Outlet, useNavigate, useParams } from "react-router-dom" +import { Outlet, json, useParams } from "react-router-dom" import { JsonViewSection } from "../../../components/common/json-view-section" import { RegionGeneralSection } from "./components/region-general-section" @@ -8,28 +8,19 @@ import { RegionShippingOptionSection } from "./components/region-shipping-option export const RegionDetail = () => { const { id } = useParams() const { region, isLoading, isError, error } = useAdminRegion(id!) - const navigate = useNavigate() // TODO: Move to loading.tsx and set as Suspense fallback for the route if (isLoading) { return
Loading
} - // TODO: Move to error.tsx and set as ErrorBoundary for the route if (isError || !region) { - const err = error ? JSON.parse(JSON.stringify(error)) : null - return ( -
- {(err as Error & { status: number })?.status === 404 ? ( -
Not found
- ) : ( -
Something went wrong!
- )} -
- ) - } + if (error) { + throw error + } - console.log("RegionDetail") + throw json("An unknown error occurred", 500) + } return (
diff --git a/packages/admin-next/dashboard/src/routes/regions/region-list/components/region-list-table/region-list-table.tsx b/packages/admin-next/dashboard/src/routes/regions/region-list/components/region-list-table/region-list-table.tsx index e90b7b7987833..d7f63271a9a34 100644 --- a/packages/admin-next/dashboard/src/routes/regions/region-list/components/region-list-table/region-list-table.tsx +++ b/packages/admin-next/dashboard/src/routes/regions/region-list/components/region-list-table/region-list-table.tsx @@ -1,27 +1,26 @@ -import { EllipsisHorizontal, PencilSquare, Trash } from "@medusajs/icons" +import { PencilSquare, Trash } from "@medusajs/icons" import { Region } from "@medusajs/medusa" import { Button, Container, - DropdownMenu, Heading, - IconButton, Table, Tooltip, clx, + usePrompt, } from "@medusajs/ui" import { PaginationState, - RowSelectionState, createColumnHelper, flexRender, getCoreRowModel, useReactTable, } from "@tanstack/react-table" -import { useAdminRegions } from "medusa-react" +import { useAdminDeleteRegion, useAdminRegions } from "medusa-react" import { useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { Link, useNavigate } from "react-router-dom" +import { ActionMenu } from "../../../../../components/common/action-menu" import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" const PAGE_SIZE = 50 @@ -43,8 +42,6 @@ export const RegionListTable = () => { [pageIndex, pageSize] ) - const [rowSelection, setRowSelection] = useState({}) - const { regions, count, isLoading, isError, error } = useAdminRegions({ limit: PAGE_SIZE, offset: pageIndex * PAGE_SIZE, @@ -58,10 +55,8 @@ export const RegionListTable = () => { pageCount: Math.ceil((count ?? 0) / PAGE_SIZE), state: { pagination, - rowSelection, }, onPaginationChange: setPagination, - onRowSelectionChange: setRowSelection, getCoreRowModel: getCoreRowModel(), manualPagination: true, }) @@ -140,32 +135,52 @@ export const RegionListTable = () => { const RegionActions = ({ region }: { region: Region }) => { const { t } = useTranslation() + const prompt = usePrompt() + + const { mutateAsync } = useAdminDeleteRegion(region.id) + + const handleDelete = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("regions.deleteRegionWarning", { + name: region.name, + }), + verificationText: region.name, + verificationInstruction: t("general.typeToConfirm"), + confirmText: t("general.confirm"), + cancelText: t("general.cancel"), + }) + + if (!res) { + return + } + + await mutateAsync(undefined) + } return ( - - e.stopPropagation()}> - - - - - - - e.stopPropagation()}> -
- - {t("general.edit")} -
-
- - - e.stopPropagation()}> -
- - {t("general.delete")} -
-
-
-
+ , + }, + ], + }, + { + actions: [ + { + label: t("general.delete"), + onClick: handleDelete, + icon: , + }, + ], + }, + ]} + /> ) } diff --git a/packages/admin-next/dashboard/src/routes/regions/region-list/region-list.tsx b/packages/admin-next/dashboard/src/routes/regions/region-list/region-list.tsx index fb22082bb6d2c..02139e2a9d628 100644 --- a/packages/admin-next/dashboard/src/routes/regions/region-list/region-list.tsx +++ b/packages/admin-next/dashboard/src/routes/regions/region-list/region-list.tsx @@ -2,8 +2,6 @@ import { Outlet } from "react-router-dom" import { RegionListTable } from "./components/region-list-table" export const RegionList = () => { - console.log("RegionList") - return (
diff --git a/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/components/sales-channel-general-section/sales-channel-general-section.tsx b/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/components/sales-channel-general-section/sales-channel-general-section.tsx index 15435794f3f18..f348bd9cf6d38 100644 --- a/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/components/sales-channel-general-section/sales-channel-general-section.tsx +++ b/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/components/sales-channel-general-section/sales-channel-general-section.tsx @@ -1,18 +1,11 @@ -import { EllipsisHorizontal, PencilSquare, Trash } from "@medusajs/icons" +import { PencilSquare, Trash } from "@medusajs/icons" import { SalesChannel } from "@medusajs/medusa" -import { - Container, - DropdownMenu, - Heading, - IconButton, - StatusBadge, - Text, - usePrompt, -} from "@medusajs/ui" +import { Container, Heading, StatusBadge, Text, usePrompt } from "@medusajs/ui" import { useAdminDeleteSalesChannel } from "medusa-react" import { useTranslation } from "react-i18next" -import { Link, useNavigate } from "react-router-dom" +import { useNavigate } from "react-router-dom" +import { ActionMenu } from "../../../../../components/common/action-menu" type SalesChannelGeneralSection = { salesChannel: SalesChannel @@ -33,6 +26,8 @@ export const SalesChannelGeneralSection = ({ description: t("salesChannels.deleteSalesChannelWarning", { name: salesChannel.name, }), + verificationInstruction: t("general.typeToConfirm"), + verificationText: salesChannel.name, confirmText: t("general.delete"), cancelText: t("general.cancel"), }) @@ -61,26 +56,28 @@ export const SalesChannelGeneralSection = ({ {t(`general.${salesChannel.is_disabled ? "disabled" : "enabled"}`)} - - - - - - - - - - - {t("general.edit")} - - - - - - {t("general.delete")} - - - + , + label: t("general.edit"), + to: `/settings/sales-channels/${salesChannel.id}/edit`, + }, + ], + }, + { + actions: [ + { + icon: , + label: t("general.delete"), + onClick: handleDelete, + }, + ], + }, + ]} + />
diff --git a/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/components/sales-channel-product-section/sales-channel-product-section.tsx b/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/components/sales-channel-product-section/sales-channel-product-section.tsx index 48838ffd9157b..045698f8ddd6b 100644 --- a/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/components/sales-channel-product-section/sales-channel-product-section.tsx +++ b/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-detail/components/sales-channel-product-section/sales-channel-product-section.tsx @@ -1,13 +1,11 @@ -import { EllipsisHorizontal, PencilSquare, Trash } from "@medusajs/icons" +import { PencilSquare, Trash } from "@medusajs/icons" import { Product, SalesChannel } from "@medusajs/medusa" import { Button, Checkbox, CommandBar, Container, - DropdownMenu, Heading, - IconButton, Table, clx, usePrompt, @@ -36,6 +34,7 @@ import { } from "../../../../../components/common/product-table-cells" import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" +import { ActionMenu } from "../../../../../components/common/action-menu" import { FilterGroup } from "../../../../../components/filtering/filter-group" import { OrderBy } from "../../../../../components/filtering/order-by" import { Query } from "../../../../../components/filtering/query" @@ -324,25 +323,27 @@ const ProductListCellActions = ({ } return ( - - - - - - - - - - - Edit - - - - - - {t("general.remove")} - - - + , + label: t("general.edit"), + to: `/products/${productId}`, + }, + ], + }, + { + actions: [ + { + icon: , + label: t("general.remove"), + onClick: onRemove, + }, + ], + }, + ]} + /> ) } diff --git a/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-list/components/sales-channel-list-table/sales-channel-list-table.tsx b/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-list/components/sales-channel-list-table/sales-channel-list-table.tsx index c45b80c0a691f..66db8824ef894 100644 --- a/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-list/components/sales-channel-list-table/sales-channel-list-table.tsx +++ b/packages/admin-next/dashboard/src/routes/sales-channels/sales-channel-list/components/sales-channel-list-table/sales-channel-list-table.tsx @@ -1,14 +1,13 @@ -import { EllipsisHorizontal, PencilSquare, Trash } from "@medusajs/icons" +import { PencilSquare, Trash } from "@medusajs/icons" import { SalesChannel } from "@medusajs/medusa" import { Button, Container, - DropdownMenu, Heading, - IconButton, StatusBadge, Table, clx, + usePrompt, } from "@medusajs/ui" import { PaginationState, @@ -22,6 +21,7 @@ import { useAdminDeleteSalesChannel, useAdminSalesChannels } from "medusa-react" import { useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { Link, useNavigate } from "react-router-dom" +import { ActionMenu } from "../../../../../components/common/action-menu" import { OrderBy } from "../../../../../components/filtering/order-by" import { Query } from "../../../../../components/filtering/query" import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" @@ -162,29 +162,57 @@ export const SalesChannelListTable = () => { ) } -const SalesChannelActions = ({ id }: { id: string }) => { - const { mutateAsync } = useAdminDeleteSalesChannel(id) +const SalesChannelActions = ({ + salesChannel, +}: { + salesChannel: SalesChannel +}) => { const { t } = useTranslation() + const prompt = usePrompt() + const { mutateAsync } = useAdminDeleteSalesChannel(salesChannel.id) + + const handleDelete = async () => { + const confirm = await prompt({ + title: t("general.areYouSure"), + description: t("salesChannels.deleteSalesChannelWarning", { + name: salesChannel.name, + }), + verificationInstruction: t("general.typeToConfirm"), + verificationText: salesChannel.name, + confirmText: t("general.delete"), + cancelText: t("general.cancel"), + }) + + if (!confirm) { + return + } + + await mutateAsync() + } return ( - - - - - - - - - - {t("general.edit")} - - - - - {t("general.delete")} - - - + , + label: t("general.edit"), + to: `/settings/sales-channels/${salesChannel.id}/edit`, + }, + ], + }, + { + actions: [ + { + icon: , + label: t("general.delete"), + onClick: handleDelete, + }, + ], + }, + ]} + /> ) } @@ -223,7 +251,7 @@ const useColumns = () => { columnHelper.display({ id: "actions", cell: ({ row }) => { - return + return }, }), ], diff --git a/packages/admin-next/dashboard/src/routes/store/store-detail/components/store-currency-section/store-currencies-section.tsx/store-currency-section.tsx b/packages/admin-next/dashboard/src/routes/store/store-detail/components/store-currency-section/store-currencies-section.tsx/store-currency-section.tsx index e9be43d95c9ef..1f472c5a5a0e0 100644 --- a/packages/admin-next/dashboard/src/routes/store/store-detail/components/store-currency-section/store-currencies-section.tsx/store-currency-section.tsx +++ b/packages/admin-next/dashboard/src/routes/store/store-detail/components/store-currency-section/store-currencies-section.tsx/store-currency-section.tsx @@ -1,13 +1,11 @@ -import { BuildingTax, EllipsisHorizontal, Trash } from "@medusajs/icons" +import { Trash } from "@medusajs/icons" import { Currency, Store } from "@medusajs/medusa" import { Button, Checkbox, CommandBar, Container, - DropdownMenu, Heading, - IconButton, StatusBadge, Table, clx, @@ -25,6 +23,7 @@ import { useAdminUpdateStore } from "medusa-react" import { useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { Link } from "react-router-dom" +import { ActionMenu } from "../../../../../../components/common/action-menu" import { LocalizedTablePagination } from "../../../../../../components/localization/localized-table-pagination" type StoreCurrencySectionProps = { @@ -180,12 +179,14 @@ const CurrencyActions = ({ const { t } = useTranslation() const prompt = usePrompt() - const handleDeleteCurrency = async () => { + const handleRemove = async () => { const result = await prompt({ title: t("general.areYouSure"), description: t("store.removeCurrencyWarning", { count: 1, }), + verificationInstruction: t("general.typeToConfirm"), + verificationText: currency.name, confirmText: t("general.remove"), cancelText: t("general.cancel"), }) @@ -200,28 +201,19 @@ const CurrencyActions = ({ } return ( - - - - - - - - -
- - {t("general.remove")} -
-
- - -
- - {t("general.remove")} -
-
-
-
+ , + label: t("general.remove"), + onClick: handleRemove, + }, + ], + }, + ]} + /> ) } diff --git a/packages/design-system/ui/src/components/drawer/drawer.tsx b/packages/design-system/ui/src/components/drawer/drawer.tsx index dcd59512285d2..08b26a5aa054c 100644 --- a/packages/design-system/ui/src/components/drawer/drawer.tsx +++ b/packages/design-system/ui/src/components/drawer/drawer.tsx @@ -73,7 +73,7 @@ const DrawerContent = React.forwardRef< Date: Wed, 17 Jan 2024 23:07:17 +0100 Subject: [PATCH 3/6] add changeset --- .changeset/metal-turtles-cover.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/metal-turtles-cover.md diff --git a/.changeset/metal-turtles-cover.md b/.changeset/metal-turtles-cover.md new file mode 100644 index 0000000000000..f8726b5ed020a --- /dev/null +++ b/.changeset/metal-turtles-cover.md @@ -0,0 +1,5 @@ +--- +"@medusajs/ui": patch +--- + +fix(ui): Fix broken responsive style of Drawer between `sm` and `md`. From c09d73798d848baee4a8a2f3d946a4f28b22d240 Mon Sep 17 00:00:00 2001 From: Kasper Date: Wed, 17 Jan 2024 23:15:04 +0100 Subject: [PATCH 4/6] fix: active style of nested nav items --- .../components/layout/nav-item/nav-item.tsx | 2 +- .../collection-product-section.tsx | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/admin-next/dashboard/src/components/layout/nav-item/nav-item.tsx b/packages/admin-next/dashboard/src/components/layout/nav-item/nav-item.tsx index 55f7cc720464b..16b4e6e5f08d4 100644 --- a/packages/admin-next/dashboard/src/components/layout/nav-item/nav-item.tsx +++ b/packages/admin-next/dashboard/src/components/layout/nav-item/nav-item.tsx @@ -122,7 +122,7 @@ export const NavItem = ({ "text-ui-fg-subtle hover:text-ui-fg-base transition-fg hover:bg-ui-bg-subtle-hover flex h-8 flex-1 items-center gap-x-2 rounded-md px-2 py-2.5 outline-none first-of-type:mt-1 last-of-type:mb-2 md:py-1.5", { "bg-ui-bg-base text-ui-fg-base hover:bg-ui-bg-base shadow-elevation-card-rest": - location.pathname === item.to, + location.pathname.startsWith(item.to), } )} > diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx index f3c33b1fc2128..dddaeb571912a 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx +++ b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx @@ -68,11 +68,17 @@ export const CollectionProductSection = ({ const [rowSelection, setRowSelection] = useState({}) const params = useQueryParams(["q", "order"]) - const { products, count, isLoading, isError, error } = useAdminProducts({ - limit: PAGE_SIZE, - offset: pageIndex * PAGE_SIZE, - collection_id: [collection.id], - }) + const { products, count, isLoading, isError, error } = useAdminProducts( + { + limit: PAGE_SIZE, + offset: pageIndex * PAGE_SIZE, + collection_id: [collection.id], + ...params, + }, + { + keepPreviousData: true, + } + ) const columns = useColumns() @@ -150,7 +156,7 @@ export const CollectionProductSection = ({
- +
)} From 345e79dcd9694eb7d90689f542b7090ae6211cf2 Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 18 Jan 2024 10:01:14 +0100 Subject: [PATCH 5/6] add no results screen on tables --- .../src/hooks/use-handle-table-scroll.tsx | 24 +++ .../add-products-to-collection-form.tsx | 154 ++++++++++-------- .../collection-product-section.tsx | 97 ++++++----- .../collection-list-table.tsx | 111 ++++++++----- .../add-currencies-form.tsx | 22 +-- 5 files changed, 236 insertions(+), 172 deletions(-) create mode 100644 packages/admin-next/dashboard/src/hooks/use-handle-table-scroll.tsx diff --git a/packages/admin-next/dashboard/src/hooks/use-handle-table-scroll.tsx b/packages/admin-next/dashboard/src/hooks/use-handle-table-scroll.tsx new file mode 100644 index 0000000000000..3efb437e7b45f --- /dev/null +++ b/packages/admin-next/dashboard/src/hooks/use-handle-table-scroll.tsx @@ -0,0 +1,24 @@ +import { useRef, useState } from "react" + +export const useHandleTableScroll = () => { + const tableContainerRef = useRef(null) + + // Listen for if the table container that has overflow-y: auto is scrolled, and if true set some state + const [isScrolled, setIsScrolled] = useState(false) + + const handleScroll = () => { + if (tableContainerRef.current) { + setIsScrolled( + tableContainerRef.current.scrollTop > 0 && + tableContainerRef.current.scrollTop < + tableContainerRef.current.scrollHeight + ) + } + } + + return { + tableContainerRef, + isScrolled, + handleScroll, + } +} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-add-products/components/add-products-to-collection-form/add-products-to-collection-form.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-add-products/components/add-products-to-collection-form/add-products-to-collection-form.tsx index 225b103071d28..ff7576e8c9e1e 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-add-products/components/add-products-to-collection-form/add-products-to-collection-form.tsx +++ b/packages/admin-next/dashboard/src/routes/collections/collection-add-products/components/add-products-to-collection-form/add-products-to-collection-form.tsx @@ -22,11 +22,14 @@ import { useAdminAddProductsToCollection, useAdminProducts, } from "medusa-react" -import { useEffect, useMemo, useState } from "react" +import { Fragment, useEffect, useMemo, useState } from "react" import { useForm } from "react-hook-form" import { useTranslation } from "react-i18next" import * as zod from "zod" -import { NoRecords } from "../../../../../components/common/empty-table-content" +import { + NoRecords, + NoResults, +} from "../../../../../components/common/empty-table-content" import { Form } from "../../../../../components/common/form" import { ProductAvailabilityCell, @@ -38,6 +41,7 @@ import { import { OrderBy } from "../../../../../components/filtering/order-by" import { Query } from "../../../../../components/filtering/query" import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination" +import { useHandleTableScroll } from "../../../../../hooks/use-handle-table-scroll" import { useQueryParams } from "../../../../../hooks/use-query-params" import { queryClient } from "../../../../../lib/medusa" @@ -153,6 +157,8 @@ export const AddProductsToCollectionForm = ({ ) }) + const { handleScroll, isScrolled, tableContainerRef } = useHandleTableScroll() + const noRecords = !isLoading && products?.length === 0 && @@ -195,76 +201,96 @@ export const AddProductsToCollectionForm = ({
)} -
- {!noRecords ? ( - - - {table.getHeaderGroups().map((headerGroup) => { - return ( - - {headerGroup.headers.map((header) => { - return ( - - {flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ) - })} - - ) - })} - - - {table.getRowModel().rows.map((row) => ( - +
+ {!isLoading && !products?.length ? ( +
+ +
+ ) : ( +
+ - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() + {table.getHeaderGroups().map((headerGroup) => { + return ( + + {headerGroup.headers.map((header) => { + return ( + + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ) + })} + + + {table.getRowModel().rows.map((row) => ( + + > + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} + ))} - - ))} - -
- ) : ( -
- + + + )}
- )} -
-
- -
+
+ +
+ + ) : ( +
+ + {/* TODO: fix this, and add NoRecords as well */} +
+ )} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx index dddaeb571912a..37f7a3c2896a0 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx +++ b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx @@ -27,7 +27,10 @@ import { useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { Link } from "react-router-dom" import { ActionMenu } from "../../../../../components/common/action-menu" -import { NoRecords } from "../../../../../components/common/empty-table-content" +import { + NoRecords, + NoResults, +} from "../../../../../components/common/empty-table-content" import { ProductAvailabilityCell, ProductCollectionCell, @@ -164,52 +167,58 @@ export const CollectionProductSection = ({ ) : (
- - - {table.getHeaderGroups().map((headerGroup) => { - return ( + {!isLoading && !products?.length ? ( +
+ +
+ ) : ( +
+ + {table.getHeaderGroups().map((headerGroup) => { + return ( + + {headerGroup.headers.map((header) => { + return ( + + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ) + })} + + + {table.getRowModel().rows.map((row) => ( - {headerGroup.headers.map((header) => { - return ( - - {flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ) - })} + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} - ) - })} - - - {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ))} - - ))} - -
+ ))} + + + )} { limit: PAGE_SIZE, offset: pageIndex * PAGE_SIZE, ...params, + }, + { + keepPreviousData: true, } ) @@ -79,57 +86,71 @@ export const CollectionListTable = () => {
+ {!noRecords && ( +
+
+
+ +
+
+ )} {noRecords ? ( ) : (
- - - {table.getHeaderGroups().map((headerGroup) => { - return ( + {!isLoading && !collections?.length ? ( +
+ +
+ ) : ( +
+ + {table.getHeaderGroups().map((headerGroup) => { + return ( + + {headerGroup.headers.map((header) => { + return ( + + {flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ) + })} + + + {table.getRowModel().rows.map((row) => ( navigate(`/collections/${row.original.id}`)} > - {headerGroup.headers.map((header) => { - return ( - - {flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ) - })} + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ))} - ) - })} - - - {table.getRowModel().rows.map((row) => ( - navigate(`/collections/${row.original.id}`)} - > - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ))} - - ))} - -
+ ))} + + + )} { const { mutateAsync, isLoading: isMutating } = useAdminUpdateStore() - const tableContainerRef = useRef(null) - - // Listen for if the table container that has overflow-y: auto is scrolled, and if true set some state - const [isScrolled, setIsScrolled] = useState(false) - - const handleScroll = () => { - if (tableContainerRef.current) { - setIsScrolled( - tableContainerRef.current.scrollTop > 0 && - tableContainerRef.current.scrollTop < - tableContainerRef.current.scrollHeight - ) - } - } + const { handleScroll, isScrolled, tableContainerRef } = useHandleTableScroll() const handleSubmit = async (e: FormEvent) => { e.preventDefault() @@ -129,10 +117,6 @@ export const AddCurrenciesForm = ({ store }: AddCurrenciesFormProps) => { }) } - if (isLoading) { - return
Loading...
- } - if (isError) { throw error } From b9877d9f915a54d5f2be1e3e3c1e5d83c06d31f6 Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 18 Jan 2024 14:40:30 +0100 Subject: [PATCH 6/6] add link to products table --- .../dashboard/public/locales/en/translation.json | 1 - .../collection-product-section.tsx | 14 ++++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/admin-next/dashboard/public/locales/en/translation.json b/packages/admin-next/dashboard/public/locales/en/translation.json index 18d019cc09b87..57dfc15fafc57 100644 --- a/packages/admin-next/dashboard/public/locales/en/translation.json +++ b/packages/admin-next/dashboard/public/locales/en/translation.json @@ -63,7 +63,6 @@ "handleTooltip": "The handle is used to reference the collection in your storefront. If not specified, the handle will be generated from the collection title.", "deleteWarning_one": "You are about to delete {{count}} collection. This action cannot be undone.", "deleteWarning_other": "You are about to delete {{count}} collections. This action cannot be undone.", - "viewProduct": "View product", "removeSingleProductWarning": "You are about to remove the product {{title}} from the collection. This action cannot be undone.", "removeProductsWarning_one": "You are about to remove {{count}} product from the collection. This action cannot be undone.", "removeProductsWarning_other": "You are about to remove {{count}} products from the collection. This action cannot be undone." diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx index 37f7a3c2896a0..fd0edc6a4b1fb 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx +++ b/packages/admin-next/dashboard/src/routes/collections/collection-detail/components/collection-product-section/collection-product-section.tsx @@ -1,4 +1,4 @@ -import { Tag, Trash } from "@medusajs/icons" +import { PencilSquare, Trash } from "@medusajs/icons" import type { Product, ProductCollection } from "@medusajs/medusa" import { Button, @@ -25,7 +25,7 @@ import { } from "medusa-react" import { useMemo, useState } from "react" import { useTranslation } from "react-i18next" -import { Link } from "react-router-dom" +import { Link, useNavigate } from "react-router-dom" import { ActionMenu } from "../../../../../components/common/action-menu" import { NoRecords, @@ -54,6 +54,7 @@ export const CollectionProductSection = ({ collection, }: CollectionProductSectionProps) => { const { t } = useTranslation() + const navigate = useNavigate() const [{ pageIndex, pageSize }, setPagination] = useState({ pageIndex: 0, @@ -199,12 +200,13 @@ export const CollectionProductSection = ({ navigate(`/products/${row.original.id}`)} > {row.getVisibleCells().map((cell) => ( @@ -286,9 +288,9 @@ const ProductActions = ({ { actions: [ { - icon: , - label: t("collections.viewProduct"), - to: `/products/${product.id}`, + icon: , + label: t("general.edit"), + to: `/products/${product.id}/edit`, }, ], },