diff --git a/packages/admin-next/dashboard/package.json b/packages/admin-next/dashboard/package.json index 551144f119..4e06531359 100644 --- a/packages/admin-next/dashboard/package.json +++ b/packages/admin-next/dashboard/package.json @@ -31,7 +31,7 @@ "@ariakit/react": "^0.4.1", "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^8.0.0", - "@hookform/resolvers": "3.3.2", + "@hookform/resolvers": "3.4.2", "@medusajs/icons": "1.2.1", "@medusajs/js-sdk": "0.0.1", "@medusajs/ui": "3.0.0", @@ -58,7 +58,7 @@ "react-jwt": "^1.2.0", "react-resizable-panels": "^2.0.16", "react-router-dom": "6.20.1", - "zod": "^3.22.4" + "zod": "3.22.4" }, "devDependencies": { "@medusajs/admin-shared": "^0.0.1", diff --git a/packages/admin-next/dashboard/src/hooks/api/collections.tsx b/packages/admin-next/dashboard/src/hooks/api/collections.tsx index 655b5b3050..ece0dee78e 100644 --- a/packages/admin-next/dashboard/src/hooks/api/collections.tsx +++ b/packages/admin-next/dashboard/src/hooks/api/collections.tsx @@ -1,3 +1,5 @@ +import { FetchError } from "@medusajs/js-sdk" +import { FindParams, HttpTypes, PaginatedResponse } from "@medusajs/types" import { QueryKey, UseMutationOptions, @@ -5,19 +7,10 @@ import { useMutation, useQuery, } from "@tanstack/react-query" -import { client } from "../../lib/client" +import { sdk } from "../../lib/client" import { queryClient } from "../../lib/query-client" import { queryKeysFactory } from "../../lib/query-key-factory" -import { - CreateProductCollectionReq, - UpdateProductCollectionProductsReq, - UpdateProductCollectionReq, -} from "../../types/api-payloads" -import { - ProductCollectionDeleteRes, - ProductCollectionListRes, - ProductCollectionRes, -} from "../../types/api-responses" +import { productsQueryKeys } from "./products" const COLLECTION_QUERY_KEY = "collections" as const export const collectionsQueryKeys = queryKeysFactory(COLLECTION_QUERY_KEY) @@ -26,9 +19,9 @@ export const useCollection = ( id: string, options?: Omit< UseQueryOptions< - ProductCollectionRes, - Error, - ProductCollectionRes, + { collection: HttpTypes.AdminCollection }, + FetchError, + { collection: HttpTypes.AdminCollection }, QueryKey >, "queryFn" | "queryKey" @@ -36,7 +29,7 @@ export const useCollection = ( ) => { const { data, ...rest } = useQuery({ queryKey: collectionsQueryKeys.detail(id), - queryFn: async () => client.collections.retrieve(id), + queryFn: async () => sdk.admin.collection.retrieve(id), ...options, }) @@ -44,12 +37,12 @@ export const useCollection = ( } export const useCollections = ( - query?: Record, + query?: FindParams & HttpTypes.AdminCollectionFilters, options?: Omit< UseQueryOptions< - ProductCollectionListRes, - Error, - ProductCollectionListRes, + PaginatedResponse<{ collections: HttpTypes.AdminCollection[] }>, + FetchError, + PaginatedResponse<{ collections: HttpTypes.AdminCollection[] }>, QueryKey >, "queryFn" | "queryKey" @@ -57,7 +50,7 @@ export const useCollections = ( ) => { const { data, ...rest } = useQuery({ queryKey: collectionsQueryKeys.list(query), - queryFn: async () => client.collections.list(query), + queryFn: async () => sdk.admin.collection.list(query), ...options, }) @@ -67,14 +60,13 @@ export const useCollections = ( export const useUpdateCollection = ( id: string, options?: UseMutationOptions< - ProductCollectionRes, - Error, - UpdateProductCollectionReq + { collection: HttpTypes.AdminCollection }, + FetchError, + HttpTypes.AdminUpdateCollection > ) => { return useMutation({ - mutationFn: (payload: UpdateProductCollectionReq) => - client.collections.update(id, payload), + mutationFn: (payload) => sdk.admin.collection.update(id, payload), onSuccess: (data, variables, context) => { queryClient.invalidateQueries({ queryKey: collectionsQueryKeys.lists() }) queryClient.invalidateQueries({ @@ -90,19 +82,24 @@ export const useUpdateCollection = ( export const useUpdateCollectionProducts = ( id: string, options?: UseMutationOptions< - ProductCollectionRes, - Error, - UpdateProductCollectionProductsReq + { collection: HttpTypes.AdminCollection }, + FetchError, + HttpTypes.AdminUpdateCollectionProducts > ) => { return useMutation({ - mutationFn: (payload: UpdateProductCollectionProductsReq) => - client.collections.updateProducts(id, payload), + mutationFn: (payload) => sdk.admin.collection.updateProducts(id, payload), onSuccess: (data, variables, context) => { queryClient.invalidateQueries({ queryKey: collectionsQueryKeys.lists() }) queryClient.invalidateQueries({ queryKey: collectionsQueryKeys.detail(id), }) + /** + * Invalidate products list query to ensure that the products collections are updated. + */ + queryClient.invalidateQueries({ + queryKey: productsQueryKeys.lists(), + }) options?.onSuccess?.(data, variables, context) }, @@ -112,13 +109,13 @@ export const useUpdateCollectionProducts = ( export const useCreateCollection = ( options?: UseMutationOptions< - ProductCollectionRes, - Error, - CreateProductCollectionReq + { collection: HttpTypes.AdminCollection }, + FetchError, + HttpTypes.AdminCreateCollection > ) => { return useMutation({ - mutationFn: (payload) => client.collections.create(payload), + mutationFn: (payload) => sdk.admin.collection.create(payload), onSuccess: (data, variables, context) => { queryClient.invalidateQueries({ queryKey: collectionsQueryKeys.lists() }) @@ -130,10 +127,14 @@ export const useCreateCollection = ( export const useDeleteCollection = ( id: string, - options?: UseMutationOptions + options?: UseMutationOptions< + HttpTypes.DeleteResponse<"collection">, + FetchError, + void + > ) => { return useMutation({ - mutationFn: () => client.collections.delete(id), + mutationFn: () => sdk.admin.collection.delete(id), onSuccess: (data, variables, context) => { queryClient.invalidateQueries({ queryKey: collectionsQueryKeys.lists() }) queryClient.invalidateQueries({ diff --git a/packages/admin-next/dashboard/src/lib/client/client.ts b/packages/admin-next/dashboard/src/lib/client/client.ts index fc6cf8451e..0de119006b 100644 --- a/packages/admin-next/dashboard/src/lib/client/client.ts +++ b/packages/admin-next/dashboard/src/lib/client/client.ts @@ -2,7 +2,6 @@ import Medusa from "@medusajs/js-sdk" import { apiKeys } from "./api-keys" import { campaigns } from "./campaigns" import { categories } from "./categories" -import { collections } from "./collections" import { currencies } from "./currencies" import { customerGroups } from "./customer-groups" import { fulfillmentProviders } from "./fulfillment-providers" @@ -34,7 +33,6 @@ export const client = { categories: categories, customerGroups: customerGroups, currencies: currencies, - collections: collections, promotions: promotions, payments: payments, stores: stores, diff --git a/packages/admin-next/dashboard/src/lib/client/collections.ts b/packages/admin-next/dashboard/src/lib/client/collections.ts deleted file mode 100644 index 3bf418ec5c..0000000000 --- a/packages/admin-next/dashboard/src/lib/client/collections.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - CreateProductCollectionReq, - UpdateProductCollectionProductsReq, - UpdateProductCollectionReq, -} from "../../types/api-payloads" -import { - ProductCollectionDeleteRes, - ProductCollectionListRes, - ProductCollectionRes, -} from "../../types/api-responses" -import { deleteRequest, getRequest, postRequest } from "./common" - -async function listProductCollections(query?: Record) { - return getRequest(`/admin/collections`, query) -} - -async function retrieveProductCollection( - id: string, - query?: Record -) { - return getRequest(`/admin/collections/${id}`, query) -} - -async function updateProductCollection( - id: string, - payload: UpdateProductCollectionReq -) { - return postRequest(`/admin/collections/${id}`, payload) -} - -async function createProductCollection(payload: CreateProductCollectionReq) { - return postRequest(`/admin/collections`, payload) -} - -async function updateProductCollectionProducts( - id: string, - payload: UpdateProductCollectionProductsReq -) { - return postRequest( - `/admin/collections/${id}/products`, - payload - ) -} - -async function deleteProductCollection(id: string) { - return deleteRequest(`/admin/collections/${id}`) -} - -export const collections = { - list: listProductCollections, - retrieve: retrieveProductCollection, - update: updateProductCollection, - updateProducts: updateProductCollectionProducts, - create: createProductCollection, - delete: deleteProductCollection, -} diff --git a/packages/admin-next/dashboard/src/lib/client/tags.ts b/packages/admin-next/dashboard/src/lib/client/tags.ts index b3aa9ff759..70e92c55f6 100644 --- a/packages/admin-next/dashboard/src/lib/client/tags.ts +++ b/packages/admin-next/dashboard/src/lib/client/tags.ts @@ -1,15 +1,11 @@ -import { - ProductCollectionListRes, - ProductCollectionRes, -} from "../../types/api-responses" import { getRequest } from "./common" async function listProductTags(query?: Record) { - return getRequest(`/admin/tags`, query) + return getRequest(`/admin/tags`, query) } async function retrieveProductTag(id: string, query?: Record) { - return getRequest(`/admin/tags/${id}`, query) + return getRequest(`/admin/tags/${id}`, query) } export const tags = { 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 3120a8d3a7..f5ba727e30 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 @@ -1,59 +1,39 @@ import { zodResolver } from "@hookform/resolvers/zod" -import { ProductCollectionDTO, ProductDTO } from "@medusajs/types" -import { - Button, - Checkbox, - Hint, - Table, - Tooltip, - clx, - toast, -} from "@medusajs/ui" +import { HttpTypes } from "@medusajs/types" +import { Button, Checkbox, Hint, Tooltip, toast } from "@medusajs/ui" import { keepPreviousData } from "@tanstack/react-query" import { - PaginationState, + OnChangeFn, RowSelectionState, createColumnHelper, - flexRender, - getCoreRowModel, - useReactTable, } from "@tanstack/react-table" -import { Fragment, useEffect, useMemo, useState } from "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, - NoResults, -} from "../../../../../components/common/empty-table-content/index.ts" -import { OrderBy } from "../../../../../components/filtering/order-by/index.ts" -import { Query } from "../../../../../components/filtering/query/index.ts" -import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination/index.ts" import { RouteFocusModal, useRouteModal, } from "../../../../../components/route-modal/index.ts" +import { DataTable } from "../../../../../components/table/data-table/data-table.tsx" import { useUpdateCollectionProducts } from "../../../../../hooks/api/collections.tsx" -import { - productsQueryKeys, - useProducts, -} from "../../../../../hooks/api/products.tsx" +import { useProducts } from "../../../../../hooks/api/products.tsx" import { useProductTableColumns } from "../../../../../hooks/table/columns/use-product-table-columns.tsx" -import { useHandleTableScroll } from "../../../../../hooks/use-handle-table-scroll.tsx" -import { useQueryParams } from "../../../../../hooks/use-query-params.tsx" -import { queryClient } from "../../../../../lib/query-client.ts" - -// Re-add when supported on the backend +import { useProductTableFilters } from "../../../../../hooks/table/filters/use-product-table-filters.tsx" +import { useProductTableQuery } from "../../../../../hooks/table/query/use-product-table-query.tsx" +import { useDataTable } from "../../../../../hooks/use-data-table.tsx" +import { ExtendedProductDTO } from "../../../../../types/api-responses.ts" type AddProductsToCollectionFormProps = { - collection: ProductCollectionDTO + collection: HttpTypes.AdminCollection } const AddProductsToCollectionSchema = zod.object({ - product_ids: zod.array(zod.string()).min(1), + add: zod.array(zod.string()).min(1), }) const PAGE_SIZE = 50 +const PREFIX = "p" export const AddProductsToCollectionForm = ({ collection, @@ -63,7 +43,7 @@ export const AddProductsToCollectionForm = ({ const form = useForm>({ defaultValues: { - product_ids: [], + add: [], }, resolver: zodResolver(AddProductsToCollectionSchema), }) @@ -71,27 +51,32 @@ export const AddProductsToCollectionForm = ({ const { setValue } = form const { mutateAsync, isPending: isMutating } = useUpdateCollectionProducts( - collection.id - ) - - const [{ pageIndex, pageSize }, setPagination] = useState({ - pageIndex: 0, - pageSize: PAGE_SIZE, - }) - - const pagination = useMemo( - () => ({ - pageIndex, - pageSize, - }), - [pageIndex, pageSize] + collection.id! ) const [rowSelection, setRowSelection] = useState({}) + const updater: OnChangeFn = (newSelection) => { + const update = + typeof newSelection === "function" + ? newSelection(rowSelection) + : newSelection + + setValue( + "add", + Object.keys(update).filter((k) => update[k]), + { + shouldDirty: true, + shouldTouch: true, + } + ) + + setRowSelection(update) + } + useEffect(() => { setValue( - "product_ids", + "add", Object.keys(rowSelection).filter((k) => rowSelection[k]), { shouldDirty: true, @@ -100,12 +85,15 @@ export const AddProductsToCollectionForm = ({ ) }, [rowSelection, setValue]) - const params = useQueryParams(["q", "order"]) + const { searchParams, raw } = useProductTableQuery({ + prefix: PREFIX, + pageSize: PAGE_SIZE, + }) const { products, count, isLoading, isError, error } = useProducts( { fields: "*variants,*sales_channels", - ...params, + ...searchParams, }, { placeholderData: keepPreviousData, @@ -113,54 +101,45 @@ export const AddProductsToCollectionForm = ({ ) const columns = useColumns() + const filters = useProductTableFilters(["collections"]) - const table = useReactTable({ - data: (products ?? []) as ProductDTO[], + const { table } = useDataTable({ + data: (products ?? []) as ExtendedProductDTO[], columns, - pageCount: Math.ceil((count ?? 0) / PAGE_SIZE), - state: { - pagination, - rowSelection, - }, - onPaginationChange: setPagination, - onRowSelectionChange: setRowSelection, - getCoreRowModel: getCoreRowModel(), - manualPagination: true, + count, + pageSize: PAGE_SIZE, + prefix: PREFIX, getRowId: (row) => row.id, - enableRowSelection(row) { - return row.original.collection_id !== collection.id + enableRowSelection: true, + rowSelection: { + state: rowSelection, + updater, }, + enablePagination: true, meta: { collectionId: collection.id, }, }) const handleSubmit = form.handleSubmit(async (values) => { - try { - await mutateAsync({ - add: values.product_ids, - }) - /** - * Invalidate the products list query to refetch products and - * determine if they are added to the collection or not. - */ - queryClient.invalidateQueries(productsQueryKeys.lists()) - handleSuccess() - } catch (e) { - toast.error(t("general.error"), { - description: e.message, - dismissLabel: t("actions.close"), - }) - } + await mutateAsync( + { + add: values.add, + }, + { + onSuccess: () => { + handleSuccess() + }, + onError: (e) => { + toast.error(t("general.error"), { + description: e.message, + dismissLabel: t("actions.close"), + }) + }, + } + ) }) - const { handleScroll, isScrolled, tableContainerRef } = useHandleTableScroll() - - const noRecords = - !isLoading && - products?.length === 0 && - !Object.values(params).filter((v) => v).length - if (isError) { throw error } @@ -173,10 +152,8 @@ export const AddProductsToCollectionForm = ({ >
- {form.formState.errors.product_ids && ( - - {form.formState.errors.product_ids.message} - + {form.formState.errors.add && ( + {form.formState.errors.add.message} )}
- - {!noRecords && ( -
-
-
- - -
-
- )} - {!noRecords ? ( - -
- {!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) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ))} - - ))} - -
- )} -
-
- -
-
- ) : ( -
- -
- )} + + ) } -const columnHelper = createColumnHelper() +const columnHelper = createColumnHelper() const useColumns = () => { const { t } = useTranslation() 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 b9b9fe8cd5..5e5c97a75b 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 @@ -32,7 +32,7 @@ export const CollectionDetail = () => { } return ( -
+
{before.widgets.map((w, i) => { return (
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 index e1710c2dca..145c31c467 100644 --- 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 @@ -1,12 +1,12 @@ import { PencilSquare, Trash } from "@medusajs/icons" -import { ProductCollectionDTO } from "@medusajs/types" +import { HttpTypes } from "@medusajs/types" import { Container, Heading, Text, usePrompt } from "@medusajs/ui" import { useTranslation } from "react-i18next" import { ActionMenu } from "../../../../../components/common/action-menu" import { useDeleteCollection } from "../../../../../hooks/api/collections" type CollectionGeneralSectionProps = { - collection: ProductCollectionDTO + collection: HttpTypes.AdminCollection } export const CollectionGeneralSection = ({ @@ -15,7 +15,7 @@ export const CollectionGeneralSection = ({ const { t } = useTranslation() const prompt = usePrompt() - const { mutateAsync } = useDeleteCollection(collection.id) + const { mutateAsync } = useDeleteCollection(collection.id!) const handleDelete = async () => { const res = await prompt({ @@ -44,6 +44,7 @@ export const CollectionGeneralSection = ({ icon: , label: t("actions.edit"), to: `/collections/${collection.id}/edit`, + disabled: !collection.id, }, ], }, @@ -53,6 +54,7 @@ export const CollectionGeneralSection = ({ icon: , label: t("actions.delete"), onClick: handleDelete, + disabled: !collection.id, }, ], }, 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 39f0812f78..d036dc93c3 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,5 +1,5 @@ import { PencilSquare, Plus, Trash } from "@medusajs/icons" -import { ProductCollectionDTO } from "@medusajs/types" +import { HttpTypes } from "@medusajs/types" import { Checkbox, Container, Heading, toast, usePrompt } from "@medusajs/ui" import { keepPreviousData } from "@tanstack/react-query" import { createColumnHelper } from "@tanstack/react-table" @@ -8,19 +8,15 @@ import { useTranslation } from "react-i18next" import { ActionMenu } from "../../../../../components/common/action-menu" import { DataTable } from "../../../../../components/table/data-table" import { useUpdateCollectionProducts } from "../../../../../hooks/api/collections" -import { - productsQueryKeys, - useProducts, -} from "../../../../../hooks/api/products" +import { useProducts } from "../../../../../hooks/api/products" import { useProductTableColumns } from "../../../../../hooks/table/columns/use-product-table-columns" import { useProductTableFilters } from "../../../../../hooks/table/filters/use-product-table-filters" import { useProductTableQuery } from "../../../../../hooks/table/query/use-product-table-query" import { useDataTable } from "../../../../../hooks/use-data-table" -import { queryClient } from "../../../../../lib/query-client" import { ExtendedProductDTO } from "../../../../../types/api-responses" type CollectionProductSectionProps = { - collection: ProductCollectionDTO + collection: HttpTypes.AdminCollection } const PAGE_SIZE = 10 @@ -60,7 +56,7 @@ export const CollectionProductSection = ({ const prompt = usePrompt() - const { mutateAsync } = useUpdateCollectionProducts(collection.id) + const { mutateAsync } = useUpdateCollectionProducts(collection.id!) const handleRemove = async (selection: Record) => { const ids = Object.keys(selection) @@ -78,18 +74,19 @@ export const CollectionProductSection = ({ return } - try { - await mutateAsync({ + await mutateAsync( + { remove: ids, - }) - - queryClient.invalidateQueries(productsQueryKeys.lists()) - } catch (e) { - toast.error(t("general.error"), { - description: e.message, - dismissLabel: t("actions.close"), - }) - } + }, + { + onError: (e) => { + toast.error(t("general.error"), { + description: e.message, + dismissLabel: t("actions.close"), + }) + }, + } + ) } if (isError) { @@ -162,18 +159,20 @@ const ProductActions = ({ if (!res) { return } - try { - await mutateAsync({ - remove: [product.id], - }) - queryClient.invalidateQueries(productsQueryKeys.lists()) - } catch (e) { - toast.error(t("general.error"), { - description: e.message, - dismissLabel: t("actions.close"), - }) - } + await mutateAsync( + { + remove: [product.id], + }, + { + onError: (e) => { + toast.error(t("general.error"), { + description: e.message, + dismissLabel: t("actions.close"), + }) + }, + } + ) } return ( 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 index a17e99a84e..d462a09e0f 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-detail/loader.ts +++ b/packages/admin-next/dashboard/src/routes/collections/collection-detail/loader.ts @@ -1,13 +1,13 @@ +import { HttpTypes } from "@medusajs/types" import { LoaderFunctionArgs } from "react-router-dom" import { collectionsQueryKeys } from "../../../hooks/api/collections" -import { client } from "../../../lib/client" +import { sdk } from "../../../lib/client" import { queryClient } from "../../../lib/query-client" -import { ProductCollectionRes } from "../../../types/api-responses" const collectionDetailQuery = (id: string) => ({ queryKey: collectionsQueryKeys.detail(id), - queryFn: async () => client.collections.retrieve(id), + queryFn: async () => sdk.admin.collection.retrieve(id), }) export const collectionLoader = async ({ params }: LoaderFunctionArgs) => { @@ -15,7 +15,8 @@ export const collectionLoader = async ({ params }: LoaderFunctionArgs) => { const query = collectionDetailQuery(id!) return ( - queryClient.getQueryData(query.queryKey) ?? - (await queryClient.fetchQuery(query)) + queryClient.getQueryData<{ collection: HttpTypes.AdminCollection }>( + query.queryKey + ) ?? (await queryClient.fetchQuery(query)) ) } 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 369621ffa7..b90e25fac0 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 @@ -18,6 +18,7 @@ export const CollectionListTable = () => { const { collections, count, isError, error, isLoading } = useCollections( { ...searchParams, + fields: "+products.id", }, { placeholderData: keepPreviousData, @@ -32,7 +33,7 @@ export const CollectionListTable = () => { columns, count, enablePagination: true, - getRowId: (row) => row.id, + getRowId: (row, index) => row.id ?? `${index}`, pageSize: PAGE_SIZE, }) diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/collection-row-actions.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/collection-row-actions.tsx new file mode 100644 index 0000000000..b1f703b28a --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/collection-row-actions.tsx @@ -0,0 +1,63 @@ +import { PencilSquare, Trash } from "@medusajs/icons" +import { HttpTypes } from "@medusajs/types" +import { usePrompt } from "@medusajs/ui" +import { useTranslation } from "react-i18next" + +import { ActionMenu } from "../../../../../components/common/action-menu" +import { useDeleteCollection } from "../../../../../hooks/api/collections" + +export const CollectionRowActions = ({ + collection, +}: { + collection: HttpTypes.AdminCollection +}) => { + const { t } = useTranslation() + const prompt = usePrompt() + + const { mutateAsync } = useDeleteCollection(collection.id!) + + const handleDeleteCollection = async () => { + const res = await prompt({ + title: t("general.areYouSure"), + description: t("collections.deleteWarning", { + title: collection.title, + }), + verificationText: collection.title, + verificationInstruction: t("general.typeToConfirm"), + confirmText: t("actions.delete"), + cancelText: t("actions.cancel"), + }) + + if (!res) { + return + } + + await mutateAsync() + } + + return ( + , + }, + ], + }, + { + actions: [ + { + label: t("actions.delete"), + onClick: handleDeleteCollection, + icon: , + disabled: !collection.id, + }, + ], + }, + ]} + /> + ) +} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-columns.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-columns.tsx index 57e9615d24..2c531c4425 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-columns.tsx +++ b/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-columns.tsx @@ -1,13 +1,11 @@ -import { PencilSquare, Trash } from "@medusajs/icons" -import { ProductCollectionDTO } from "@medusajs/types" -import { usePrompt } from "@medusajs/ui" +import { HttpTypes } from "@medusajs/types" import { createColumnHelper } from "@tanstack/react-table" import { useMemo } from "react" import { useTranslation } from "react-i18next" -import { ActionMenu } from "../../../../../components/common/action-menu" -import { useDeleteCollection } from "../../../../../hooks/api/collections" -const columnHelper = createColumnHelper() +import { CollectionRowActions } from "./collection-row-actions" + +const columnHelper = createColumnHelper() export const useCollectionTableColumns = () => { const { t } = useTranslation() @@ -31,64 +29,9 @@ export const useCollectionTableColumns = () => { }), columnHelper.display({ id: "actions", - cell: ({ row }) => , + cell: ({ row }) => , }), ], [t] ) } - -const CollectionActions = ({ - collection, -}: { - collection: ProductCollectionDTO -}) => { - const { t } = useTranslation() - const prompt = usePrompt() - - const { mutateAsync } = useDeleteCollection(collection.id) - - const handleDeleteCollection = async () => { - const res = await prompt({ - title: t("general.areYouSure"), - description: t("collections.deleteWarning", { - title: collection.title, - }), - verificationText: collection.title, - verificationInstruction: t("general.typeToConfirm"), - confirmText: t("actions.delete"), - cancelText: t("actions.cancel"), - }) - - if (!res) { - return - } - - await mutateAsync() - } - - return ( - , - }, - ], - }, - { - actions: [ - { - label: t("actions.delete"), - onClick: handleDeleteCollection, - icon: , - }, - ], - }, - ]} - /> - ) -} diff --git a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-query.tsx b/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-query.tsx index 5ddcfac4dd..620c92ad3b 100644 --- a/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-query.tsx +++ b/packages/admin-next/dashboard/src/routes/collections/collection-list/components/collection-list-table/use-collection-table-query.tsx @@ -1,4 +1,4 @@ -import { AdminGetCollectionsParams } from "@medusajs/medusa" +import { AdminCollectionFilters, FindParams } from "@medusajs/types" import { useQueryParams } from "../../../../../hooks/use-query-params" type UseCollectionTableQueryProps = { @@ -17,7 +17,7 @@ export const useCollectionTableQuery = ({ const { offset, created_at, updated_at, q, order } = queryObject - const searchParams: AdminGetCollectionsParams = { + const searchParams: FindParams & AdminCollectionFilters = { limit: pageSize, offset: offset ? Number(offset) : 0, order, diff --git a/packages/admin-next/dashboard/src/routes/products/product-create/components/product-create-organize-form/components/product-create-organize-section/product-create-details-organize-section.tsx b/packages/admin-next/dashboard/src/routes/products/product-create/components/product-create-organize-form/components/product-create-organize-section/product-create-details-organize-section.tsx index e4b81b7138..3262c95983 100644 --- a/packages/admin-next/dashboard/src/routes/products/product-create/components/product-create-organize-form/components/product-create-organize-section/product-create-details-organize-section.tsx +++ b/packages/admin-next/dashboard/src/routes/products/product-create/components/product-create-organize-form/components/product-create-organize-section/product-create-details-organize-section.tsx @@ -6,7 +6,7 @@ import { ChipGroup } from "../../../../../../../components/common/chip-group" import { Form } from "../../../../../../../components/common/form" import { Combobox } from "../../../../../../../components/inputs/combobox" import { useComboboxData } from "../../../../../../../hooks/use-combobox-data" -import { client } from "../../../../../../../lib/client" +import { client, sdk } from "../../../../../../../lib/client" import { CategoryCombobox } from "../../../../../common/components/category-combobox" import { ProductCreateSchemaType } from "../../../../types" import { useProductCreateDetailsContext } from "../product-create-organize-context" @@ -23,11 +23,11 @@ export const ProductCreateOrganizationSection = ({ const collections = useComboboxData({ queryKey: ["product_collections"], - queryFn: client.collections.list, + queryFn: sdk.admin.collection.list, getOptions: (data) => data.collections.map((collection) => ({ - label: collection.title, - value: collection.id, + label: collection.title!, + value: collection.id!, })), }) diff --git a/packages/admin-next/dashboard/src/types/api-payloads.ts b/packages/admin-next/dashboard/src/types/api-payloads.ts index 6bc6b70845..471027fc36 100644 --- a/packages/admin-next/dashboard/src/types/api-payloads.ts +++ b/packages/admin-next/dashboard/src/types/api-payloads.ts @@ -8,7 +8,6 @@ import { CreateFulfillmentSetDTO, CreateInviteDTO, CreatePriceListDTO, - CreateProductCollectionDTO, CreatePromotionDTO, CreatePromotionRuleDTO, CreateSalesChannelDTO, @@ -20,7 +19,6 @@ import { UpdateApiKeyDTO, UpdateCampaignDTO, UpdatePriceListDTO, - UpdateProductCollectionDTO, UpdatePromotionDTO, UpdatePromotionRuleDTO, UpdateSalesChannelDTO, @@ -71,14 +69,6 @@ export type UpdateShippingOptionReq = UpdateShippingOptionDTO // Shipping Profile export type CreateShippingProfileReq = CreateShippingProfileDTO -// Product Collections -export type CreateProductCollectionReq = CreateProductCollectionDTO -export type UpdateProductCollectionReq = UpdateProductCollectionDTO -export type UpdateProductCollectionProductsReq = { - add?: string[] - remove?: string[] -} - // Price Lists export type CreatePriceListReq = CreatePriceListDTO export type UpdatePriceListReq = UpdatePriceListDTO diff --git a/packages/admin-next/dashboard/src/types/api-responses.ts b/packages/admin-next/dashboard/src/types/api-responses.ts index 3049e3c3d0..7639412e99 100644 --- a/packages/admin-next/dashboard/src/types/api-responses.ts +++ b/packages/admin-next/dashboard/src/types/api-responses.ts @@ -172,13 +172,6 @@ export type WorkflowExecutionListRes = { workflow_executions: WorkflowExecutionDTO[] } & ListRes -// Product Collections -export type ProductCollectionRes = { collection: ProductCollectionDTO } -export type ProductCollectionListRes = { - collections: ProductCollectionDTO[] -} & ListRes -export type ProductCollectionDeleteRes = DeleteRes - // Taxes export type TaxRegionDeleteRes = DeleteRes export type TaxRateDeleteRes = DeleteRes diff --git a/packages/core/js-sdk/src/admin/index.ts b/packages/core/js-sdk/src/admin/index.ts index 198f3db248..7f64ee1a96 100644 --- a/packages/core/js-sdk/src/admin/index.ts +++ b/packages/core/js-sdk/src/admin/index.ts @@ -203,7 +203,10 @@ export class Admin { } ) }, - list: async (queryParams?: FindParams, headers?: ClientHeaders) => { + list: async ( + queryParams?: FindParams & HttpTypes.AdminCollectionFilters, + headers?: ClientHeaders + ) => { return this.client.fetch< PaginatedResponse<{ customers: HttpTypes.AdminCustomer[] }> >(`/admin/customers`, { @@ -234,4 +237,82 @@ export class Admin { ) }, } + + public collection = { + create: async ( + body: HttpTypes.AdminCreateCollection, + query?: SelectParams, + headers?: ClientHeaders + ) => { + return this.client.fetch<{ collection: HttpTypes.AdminCollection }>( + `/admin/collections`, + { + method: "POST", + headers, + body, + query, + } + ) + }, + update: async ( + id: string, + body: HttpTypes.AdminUpdateCollection, + query?: SelectParams, + headers?: ClientHeaders + ) => { + return this.client.fetch<{ collection: HttpTypes.AdminCollection }>( + `/admin/collections/${id}`, + { + method: "POST", + headers, + body, + query, + } + ) + }, + list: async (queryParams?: FindParams, headers?: ClientHeaders) => { + return this.client.fetch< + PaginatedResponse<{ collections: HttpTypes.AdminCollection[] }> + >(`/admin/collections`, { + headers, + query: queryParams, + }) + }, + retrieve: async ( + id: string, + query?: SelectParams, + headers?: ClientHeaders + ) => { + return this.client.fetch<{ collection: HttpTypes.AdminCollection }>( + `/admin/collections/${id}`, + { + query, + headers, + } + ) + }, + delete: async (id: string, headers?: ClientHeaders) => { + return this.client.fetch>( + `/admin/collections/${id}`, + { + method: "DELETE", + headers, + } + ) + }, + updateProducts: async ( + id: string, + body: HttpTypes.AdminUpdateCollectionProducts, + headers?: ClientHeaders + ) => { + return this.client.fetch<{ collection: HttpTypes.AdminCollection }>( + `/admin/collections/${id}/products`, + { + method: "POST", + headers, + body, + } + ) + }, + } } diff --git a/packages/core/types/src/http/collection/admin.ts b/packages/core/types/src/http/collection/admin.ts index 74258fbb07..b419fbd50b 100644 --- a/packages/core/types/src/http/collection/admin.ts +++ b/packages/core/types/src/http/collection/admin.ts @@ -1,4 +1,16 @@ -import { BaseProductCollection, BaseProductCollectionFilters } from "./common" +import { BaseCollection, BaseCollectionFilters } from "./common" -export interface AdminCollection extends BaseProductCollection {} -export interface AdminCollectionFilters extends BaseProductCollectionFilters {} +export interface AdminCollection extends BaseCollection {} +export interface AdminCollectionFilters extends BaseCollectionFilters {} + +export interface AdminCreateCollection { + title: string + handle?: string + metadata?: Record +} +export interface AdminUpdateCollection extends Partial {} + +export interface AdminUpdateCollectionProducts { + add?: string[] + remove?: string[] +} diff --git a/packages/core/types/src/http/collection/common.ts b/packages/core/types/src/http/collection/common.ts index 286d6cf307..e48ad13b65 100644 --- a/packages/core/types/src/http/collection/common.ts +++ b/packages/core/types/src/http/collection/common.ts @@ -1,21 +1,24 @@ -import { BaseFilterable } from "../../dal" -import { BaseProduct } from "../product/common" +import { BaseFilterable, OperatorMap } from "../../dal" +import { AdminProduct } from "../product" -export interface BaseProductCollection { +export interface BaseCollection { id?: string title?: string handle?: string created_at?: string updated_at?: string deleted_at?: string | null - products?: BaseProduct[] + products?: AdminProduct[] metadata?: Record | null } -export interface BaseProductCollectionFilters - extends BaseFilterable { +export interface BaseCollectionFilters + extends BaseFilterable { q?: string id?: string | string[] handle?: string | string[] title?: string | string[] + created_at?: OperatorMap + updated_at?: OperatorMap + deleted_at?: OperatorMap } diff --git a/packages/core/types/src/http/collection/store.ts b/packages/core/types/src/http/collection/store.ts index b290ae4b8e..f653b0d91a 100644 --- a/packages/core/types/src/http/collection/store.ts +++ b/packages/core/types/src/http/collection/store.ts @@ -1,4 +1,4 @@ -import { BaseProductCollection, BaseProductCollectionFilters } from "./common" +import { BaseCollection, BaseCollectionFilters } from "./common" -export interface StoreCollection extends BaseProductCollection {} -export interface StoreCollectionFilters extends BaseProductCollectionFilters {} +export interface StoreCollection extends BaseCollection {} +export interface StoreCollectionFilters extends BaseCollectionFilters {} diff --git a/packages/core/types/src/http/product/common.ts b/packages/core/types/src/http/product/common.ts index f550ab439c..1b943131f5 100644 --- a/packages/core/types/src/http/product/common.ts +++ b/packages/core/types/src/http/product/common.ts @@ -1,5 +1,5 @@ import { BaseFilterable, OperatorMap } from "../../dal" -import { BaseProductCollection } from "../collection/common" +import { BaseCollection } from "../collection/common" export type ProductStatus = "draft" | "proposed" | "published" | "rejected" @@ -20,7 +20,7 @@ export interface BaseProduct { hs_code?: string mid_code?: string material?: string - collection?: BaseProductCollection + collection?: BaseCollection collection_id?: string | null categories?: BaseProductCategory[] type?: BaseProductType | null diff --git a/yarn.lock b/yarn.lock index 0c9735330e..ea55323708 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3908,12 +3908,12 @@ __metadata: languageName: node linkType: hard -"@hookform/resolvers@npm:3.3.2": - version: 3.3.2 - resolution: "@hookform/resolvers@npm:3.3.2" +"@hookform/resolvers@npm:3.4.2": + version: 3.4.2 + resolution: "@hookform/resolvers@npm:3.4.2" peerDependencies: react-hook-form: ^7.0.0 - checksum: 538c86a744c71735a5c3be7b450c704111fe53a52634463ed3c801681d57504d3717ab5f7bd20e99ee87d81a9da0aca640af0837eb807486d9d5c927269a9b37 + checksum: cb8a3e0cc1c9aeaf2adad585f29badf342a96da9cfdf4efa1168aadb7ef52f440cac5cc6fedf0fda6a52effd27a5c859692c89bb99ed5c76d03064aea1f0d684 languageName: node linkType: hard @@ -5175,7 +5175,7 @@ __metadata: "@ariakit/react": ^0.4.1 "@dnd-kit/core": ^6.1.0 "@dnd-kit/sortable": ^8.0.0 - "@hookform/resolvers": 3.3.2 + "@hookform/resolvers": 3.4.2 "@medusajs/admin-shared": ^0.0.1 "@medusajs/admin-vite-plugin": 0.0.1 "@medusajs/icons": 1.2.1 @@ -5217,7 +5217,7 @@ __metadata: tsup: ^8.0.2 typescript: 5.2.2 vite: ^5.2.11 - zod: ^3.22.4 + zod: 3.22.4 languageName: unknown linkType: soft @@ -33295,10 +33295,3 @@ __metadata: checksum: 7578ab283dac0eee66a0ad0fc4a7f28c43e6745aadb3a529f59a4b851aa10872b3890398b3160f257f4b6817b4ce643debdda4fb21a2c040adda7862cab0a587 languageName: node linkType: hard - -"zod@npm:^3.22.4": - version: 3.23.8 - resolution: "zod@npm:3.23.8" - checksum: 8f14c87d6b1b53c944c25ce7a28616896319d95bc46a9660fe441adc0ed0a81253b02b5abdaeffedbeb23bdd25a0bf1c29d2c12dd919aef6447652dd295e3e69 - languageName: node - linkType: hard