feat(dashboard,types,js-sdk): Cleanup collection domain (#7502)

**What**
- Adds missing collection HttpTypes
- Adds missing sdk functions
- Adds usage of sdk to collection domain.
This commit is contained in:
Kasper Fabricius Kristensen
2024-05-28 20:53:55 +02:00
committed by GitHub
parent 75791f2cbd
commit 5a9922916a
23 changed files with 359 additions and 446 deletions
+2 -2
View File
@@ -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",
@@ -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<string, any>,
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<ProductCollectionDeleteRes, Error, void>
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({
@@ -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,
@@ -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<string, any>) {
return getRequest<ProductCollectionListRes>(`/admin/collections`, query)
}
async function retrieveProductCollection(
id: string,
query?: Record<string, any>
) {
return getRequest<ProductCollectionRes>(`/admin/collections/${id}`, query)
}
async function updateProductCollection(
id: string,
payload: UpdateProductCollectionReq
) {
return postRequest<ProductCollectionRes>(`/admin/collections/${id}`, payload)
}
async function createProductCollection(payload: CreateProductCollectionReq) {
return postRequest<ProductCollectionRes>(`/admin/collections`, payload)
}
async function updateProductCollectionProducts(
id: string,
payload: UpdateProductCollectionProductsReq
) {
return postRequest<ProductCollectionRes>(
`/admin/collections/${id}/products`,
payload
)
}
async function deleteProductCollection(id: string) {
return deleteRequest<ProductCollectionDeleteRes>(`/admin/collections/${id}`)
}
export const collections = {
list: listProductCollections,
retrieve: retrieveProductCollection,
update: updateProductCollection,
updateProducts: updateProductCollectionProducts,
create: createProductCollection,
delete: deleteProductCollection,
}
@@ -1,15 +1,11 @@
import {
ProductCollectionListRes,
ProductCollectionRes,
} from "../../types/api-responses"
import { getRequest } from "./common"
async function listProductTags(query?: Record<string, any>) {
return getRequest<ProductCollectionListRes>(`/admin/tags`, query)
return getRequest<any>(`/admin/tags`, query)
}
async function retrieveProductTag(id: string, query?: Record<string, any>) {
return getRequest<ProductCollectionRes>(`/admin/tags/${id}`, query)
return getRequest<any>(`/admin/tags/${id}`, query)
}
export const tags = {
@@ -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<zod.infer<typeof AddProductsToCollectionSchema>>({
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<PaginationState>({
pageIndex: 0,
pageSize: PAGE_SIZE,
})
const pagination = useMemo(
() => ({
pageIndex,
pageSize,
}),
[pageIndex, pageSize]
collection.id!
)
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
const updater: OnChangeFn<RowSelectionState> = (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 = ({
>
<RouteFocusModal.Header>
<div className="flex items-center justify-end gap-x-2">
{form.formState.errors.product_ids && (
<Hint variant="error">
{form.formState.errors.product_ids.message}
</Hint>
{form.formState.errors.add && (
<Hint variant="error">{form.formState.errors.add.message}</Hint>
)}
<RouteFocusModal.Close asChild>
<Button size="small" variant="secondary">
@@ -188,112 +165,28 @@ export const AddProductsToCollectionForm = ({
</Button>
</div>
</RouteFocusModal.Header>
<RouteFocusModal.Body className="flex h-full w-full flex-col items-center divide-y overflow-y-auto">
{!noRecords && (
<div className="flex w-full items-center justify-between px-6 py-4">
<div></div>
<div className="flex items-center gap-x-2">
<Query />
<OrderBy keys={["title"]} />
</div>
</div>
)}
{!noRecords ? (
<Fragment>
<div
ref={tableContainerRef}
className="w-full flex-1 overflow-y-auto"
onScroll={handleScroll}
>
{!isLoading && !products?.length ? (
<div className="flex h-full flex-1 items-center justify-center">
<NoResults />
</div>
) : (
<Table>
<Table.Header
className={clx(
"bg-ui-bg-base transition-fg sticky inset-x-0 top-0 z-10 border-t-0",
{
"shadow-elevation-card-hover": isScrolled,
}
)}
>
{table.getHeaderGroups().map((headerGroup) => {
return (
<Table.Row
key={headerGroup.id}
className="[&_th:first-of-type]:w-[1%] [&_th:first-of-type]:whitespace-nowrap [&_th]:w-1/5"
>
{headerGroup.headers.map((header) => {
return (
<Table.HeaderCell key={header.id}>
{flexRender(
header.column.columnDef.header,
header.getContext()
)}
</Table.HeaderCell>
)
})}
</Table.Row>
)
})}
</Table.Header>
<Table.Body className="border-b-0">
{table.getRowModel().rows.map((row) => (
<Table.Row
key={row.id}
className={clx(
"transition-fg",
{
"bg-ui-bg-highlight hover:bg-ui-bg-highlight-hover":
row.getIsSelected(),
},
{
"bg-ui-bg-disabled hover:bg-ui-bg-disabled":
row.original.collection_id === collection.id,
}
)}
>
{row.getVisibleCells().map((cell) => (
<Table.Cell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</Table.Cell>
))}
</Table.Row>
))}
</Table.Body>
</Table>
)}
</div>
<div className="w-full border-t">
<LocalizedTablePagination
canNextPage={table.getCanNextPage()}
canPreviousPage={table.getCanPreviousPage()}
nextPage={table.nextPage}
previousPage={table.previousPage}
count={count ?? 0}
pageIndex={pageIndex}
pageCount={table.getPageCount()}
pageSize={PAGE_SIZE}
/>
</div>
</Fragment>
) : (
<div className="flex flex-1 items-center justify-center">
<NoRecords />
</div>
)}
<RouteFocusModal.Body className="size-full overflow-hidden">
<DataTable
table={table}
columns={columns}
pageSize={PAGE_SIZE}
count={count}
queryObject={raw}
filters={filters}
orderBy={["title", "created_at", "updated_at"]}
prefix={PREFIX}
isLoading={isLoading}
layout="fill"
pagination
search
/>
</RouteFocusModal.Body>
</form>
</RouteFocusModal.Form>
)
}
const columnHelper = createColumnHelper<ProductDTO>()
const columnHelper = createColumnHelper<ExtendedProductDTO>()
const useColumns = () => {
const { t } = useTranslation()
@@ -32,7 +32,7 @@ export const CollectionDetail = () => {
}
return (
<div className="flex flex-col gap-y-2">
<div className="flex flex-col gap-y-3">
{before.widgets.map((w, i) => {
return (
<div key={i}>
@@ -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: <PencilSquare />,
label: t("actions.edit"),
to: `/collections/${collection.id}/edit`,
disabled: !collection.id,
},
],
},
@@ -53,6 +54,7 @@ export const CollectionGeneralSection = ({
icon: <Trash />,
label: t("actions.delete"),
onClick: handleDelete,
disabled: !collection.id,
},
],
},
@@ -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<string, boolean>) => {
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 (
@@ -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<ProductCollectionRes>(query.queryKey) ??
(await queryClient.fetchQuery(query))
queryClient.getQueryData<{ collection: HttpTypes.AdminCollection }>(
query.queryKey
) ?? (await queryClient.fetchQuery(query))
)
}
@@ -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,
})
@@ -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 (
<ActionMenu
groups={[
{
actions: [
{
label: t("actions.edit"),
to: `/collections/${collection.id}/edit`,
icon: <PencilSquare />,
},
],
},
{
actions: [
{
label: t("actions.delete"),
onClick: handleDeleteCollection,
icon: <Trash />,
disabled: !collection.id,
},
],
},
]}
/>
)
}
@@ -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<ProductCollectionDTO>()
import { CollectionRowActions } from "./collection-row-actions"
const columnHelper = createColumnHelper<HttpTypes.AdminCollection>()
export const useCollectionTableColumns = () => {
const { t } = useTranslation()
@@ -31,64 +29,9 @@ export const useCollectionTableColumns = () => {
}),
columnHelper.display({
id: "actions",
cell: ({ row }) => <CollectionActions collection={row.original} />,
cell: ({ row }) => <CollectionRowActions collection={row.original} />,
}),
],
[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 (
<ActionMenu
groups={[
{
actions: [
{
label: t("actions.edit"),
to: `/collections/${collection.id}/edit`,
icon: <PencilSquare />,
},
],
},
{
actions: [
{
label: t("actions.delete"),
onClick: handleDeleteCollection,
icon: <Trash />,
},
],
},
]}
/>
)
}
@@ -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,
@@ -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!,
})),
})
@@ -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
@@ -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
+82 -1
View File
@@ -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<DeleteResponse<"collection">>(
`/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,
}
)
},
}
}
@@ -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<string, any>
}
export interface AdminUpdateCollection extends Partial<AdminCreateCollection> {}
export interface AdminUpdateCollectionProducts {
add?: string[]
remove?: string[]
}
@@ -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<string, unknown> | null
}
export interface BaseProductCollectionFilters
extends BaseFilterable<BaseProductCollectionFilters> {
export interface BaseCollectionFilters
extends BaseFilterable<BaseCollectionFilters> {
q?: string
id?: string | string[]
handle?: string | string[]
title?: string | string[]
created_at?: OperatorMap<string>
updated_at?: OperatorMap<string>
deleted_at?: OperatorMap<string>
}
@@ -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 {}
@@ -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
+6 -13
View File
@@ -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