feat: Admin V2 customer group (#7000)
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { ICustomerModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { createAdminUser } from "../../../../helpers/create-admin-user"
|
||||
import { ICustomerModuleService } from "@medusajs/types"
|
||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||
import { createAdminUser } from "../../../../helpers/create-admin-user"
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
@@ -13,7 +13,7 @@ const adminHeaders = {
|
||||
medusaIntegrationTestRunner({
|
||||
env,
|
||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||
describe("POST /admin/customer-groups/:id/customers/batch", () => {
|
||||
describe("POST /admin/customer-groups/:id/customers/batch/add", () => {
|
||||
let appContainer
|
||||
let customerModuleService: ICustomerModuleService
|
||||
|
||||
@@ -48,7 +48,7 @@ medusaIntegrationTestRunner({
|
||||
])
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/customer-groups/${group.id}/customers/batch`,
|
||||
`/admin/customer-groups/${group.id}/customers/batch/add`,
|
||||
{
|
||||
customer_ids: customers.map((c) => ({ id: c.id })),
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ICustomerModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { createAdminUser } from "../../../../helpers/create-admin-user"
|
||||
import { ICustomerModuleService } from "@medusajs/types"
|
||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||
import { createAdminUser } from "../../../../helpers/create-admin-user"
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
@@ -13,7 +13,7 @@ const adminHeaders = {
|
||||
medusaIntegrationTestRunner({
|
||||
env,
|
||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||
describe("DELETE /admin/customer-groups/:id/customers/remove", () => {
|
||||
describe("POST /admin/customer-groups/:id/customers/batch/remove", () => {
|
||||
let appContainer
|
||||
let customerModuleService: ICustomerModuleService
|
||||
|
||||
@@ -55,7 +55,7 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/customer-groups/${group.id}/customers/remove`,
|
||||
`/admin/customer-groups/${group.id}/customers/batch/remove`,
|
||||
{
|
||||
customer_ids: customers.map((c) => ({ id: c.id })),
|
||||
},
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
import {
|
||||
QueryKey,
|
||||
UseMutationOptions,
|
||||
UseQueryOptions,
|
||||
useMutation,
|
||||
useQuery,
|
||||
} from "@tanstack/react-query"
|
||||
import { client } from "../../lib/client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import {
|
||||
AdminCustomerGroupListResponse,
|
||||
AdminCustomerGroupResponse,
|
||||
} from "@medusajs/types"
|
||||
import { QueryKey, UseQueryOptions, useQuery } from "@tanstack/react-query"
|
||||
import { client } from "../../lib/client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import { z } from "zod"
|
||||
import { CreateCustomerGroupSchema } from "../../v2-routes/customer-groups/customer-group-create/components/create-customer-group-form"
|
||||
import { queryClient } from "../../lib/medusa"
|
||||
import { EditCustomerGroupSchema } from "../../v2-routes/customer-groups/customer-group-edit/components/edit-customer-group-form"
|
||||
import { customersQueryKeys } from "./customers"
|
||||
|
||||
const CUSTOMER_GROUPS_QUERY_KEY = "customer_groups" as const
|
||||
const customerGroupsQueryKeys = queryKeysFactory(CUSTOMER_GROUPS_QUERY_KEY)
|
||||
@@ -51,3 +62,124 @@ export const useCustomerGroups = (
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const useCreateCustomerGroup = (
|
||||
options?: UseMutationOptions<
|
||||
AdminCustomerGroupResponse,
|
||||
Error,
|
||||
z.infer<typeof CreateCustomerGroupSchema>
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (payload) => client.customerGroups.create(payload),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: customerGroupsQueryKeys.lists(),
|
||||
})
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useUpdateCustomerGroup = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
AdminCustomerGroupResponse,
|
||||
Error,
|
||||
z.infer<typeof EditCustomerGroupSchema>
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (payload) => client.customerGroups.update(id, payload),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: customerGroupsQueryKeys.lists(),
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: customerGroupsQueryKeys.detail(id),
|
||||
})
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useDeleteCustomerGroup = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
{ id: string; object: "customer-group"; deleted: boolean },
|
||||
Error,
|
||||
void
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: () => client.customerGroups.delete(id),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: customerGroupsQueryKeys.lists(),
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: customerGroupsQueryKeys.detail(id),
|
||||
})
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useAddCustomersToGroup = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
AdminCustomerGroupResponse,
|
||||
Error,
|
||||
{ customer_ids: { id: string }[] }
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (payload) => client.customerGroups.addCustomers(id, payload),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: customerGroupsQueryKeys.lists(),
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: customerGroupsQueryKeys.detail(id),
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: customersQueryKeys.lists(),
|
||||
})
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useRemoveCustomersFromGroup = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
AdminCustomerGroupResponse,
|
||||
Error,
|
||||
{ customer_ids: { id: string }[] }
|
||||
>
|
||||
) => {
|
||||
return useMutation({
|
||||
mutationFn: (payload) => client.customerGroups.removeCustomers(id, payload),
|
||||
onSuccess: (data, variables, context) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: customerGroupsQueryKeys.lists(),
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: customerGroupsQueryKeys.detail(id),
|
||||
})
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: customersQueryKeys.lists(),
|
||||
})
|
||||
|
||||
options?.onSuccess?.(data, variables, context)
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from "@medusajs/types"
|
||||
|
||||
const CUSTOMERS_QUERY_KEY = "customers" as const
|
||||
const customersQueryKeys = queryKeysFactory(CUSTOMERS_QUERY_KEY)
|
||||
export const customersQueryKeys = queryKeysFactory(CUSTOMERS_QUERY_KEY)
|
||||
|
||||
export const useCustomer = (
|
||||
id: string,
|
||||
|
||||
@@ -2,7 +2,10 @@ import {
|
||||
AdminCustomerGroupListResponse,
|
||||
AdminCustomerGroupResponse,
|
||||
} from "@medusajs/types"
|
||||
import { getRequest } from "./common"
|
||||
import { z } from "zod"
|
||||
import { CreateCustomerGroupSchema } from "../../v2-routes/customer-groups/customer-group-create/components/create-customer-group-form"
|
||||
import { EditCustomerGroupSchema } from "../../v2-routes/customer-groups/customer-group-edit/components/edit-customer-group-form"
|
||||
import { deleteRequest, getRequest, postRequest } from "./common"
|
||||
|
||||
async function retrieveCustomerGroup(id: string, query?: Record<string, any>) {
|
||||
return getRequest<AdminCustomerGroupResponse>(
|
||||
@@ -18,7 +21,59 @@ async function listCustomerGroups(query?: Record<string, any>) {
|
||||
)
|
||||
}
|
||||
|
||||
async function createCustomerGroup(
|
||||
payload: z.infer<typeof CreateCustomerGroupSchema>
|
||||
) {
|
||||
return postRequest<AdminCustomerGroupResponse>(
|
||||
`/admin/customer-groups`,
|
||||
payload
|
||||
)
|
||||
}
|
||||
|
||||
async function updateCustomerGroup(
|
||||
id: string,
|
||||
payload: z.infer<typeof EditCustomerGroupSchema>
|
||||
) {
|
||||
return postRequest<AdminCustomerGroupResponse>(
|
||||
`/admin/customer-groups/${id}`,
|
||||
payload
|
||||
)
|
||||
}
|
||||
|
||||
async function deleteCustomerGroup(id: string) {
|
||||
return deleteRequest<{
|
||||
id: string
|
||||
deleted: boolean
|
||||
object: "customer-group"
|
||||
}>(`/admin/customer-groups/${id}`)
|
||||
}
|
||||
|
||||
async function batchAddCustomers(
|
||||
id: string,
|
||||
payload: { customer_ids: { id: string }[] }
|
||||
) {
|
||||
return postRequest<AdminCustomerGroupResponse>(
|
||||
`/admin/customer-groups/${id}/customers/batch/add`,
|
||||
payload
|
||||
)
|
||||
}
|
||||
|
||||
async function batchRemoveCustomers(
|
||||
id: string,
|
||||
payload: { customer_ids: { id: string }[] }
|
||||
) {
|
||||
return postRequest<AdminCustomerGroupResponse>(
|
||||
`/admin/customer-groups/${id}/customers/batch/remove`,
|
||||
payload
|
||||
)
|
||||
}
|
||||
|
||||
export const customerGroups = {
|
||||
retrieve: retrieveCustomerGroup,
|
||||
list: listCustomerGroups,
|
||||
create: createCustomerGroup,
|
||||
update: updateCustomerGroup,
|
||||
delete: deleteCustomerGroup,
|
||||
addCustomers: batchAddCustomers,
|
||||
removeCustomers: batchRemoveCustomers,
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
AdminProductCategoryResponse,
|
||||
SalesChannelDTO,
|
||||
UserDTO,
|
||||
AdminCustomerGroupResponse,
|
||||
} from "@medusajs/types"
|
||||
import { Navigate, Outlet, RouteObject, useLocation } from "react-router-dom"
|
||||
import { ErrorBoundary } from "../../components/error/error-boundary"
|
||||
@@ -343,6 +344,55 @@ export const v2Routes: RouteObject[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/customer-groups",
|
||||
handle: {
|
||||
crumb: () => "Customer Groups",
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
lazy: () =>
|
||||
import("../../v2-routes/customer-groups/customer-group-list"),
|
||||
children: [
|
||||
{
|
||||
path: "create",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../v2-routes/customer-groups/customer-group-create"
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: ":id",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../v2-routes/customer-groups/customer-group-detail"
|
||||
),
|
||||
handle: {
|
||||
crumb: (data: AdminCustomerGroupResponse) =>
|
||||
data.customer_group.name,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "edit",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../v2-routes/customer-groups/customer-group-edit"
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "add-customers",
|
||||
lazy: () =>
|
||||
import(
|
||||
"../../v2-routes/customer-groups/customer-group-add-customers"
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -2,6 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { Customer } from "@medusajs/medusa"
|
||||
import { Button, Checkbox, Hint, Table, Tooltip, clx } from "@medusajs/ui"
|
||||
import {
|
||||
OnChangeFn,
|
||||
PaginationState,
|
||||
RowSelectionState,
|
||||
createColumnHelper,
|
||||
@@ -31,12 +32,19 @@ import {
|
||||
} from "../../../../../components/route-modal"
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
import { queryClient } from "../../../../../lib/medusa"
|
||||
import { useCustomers } from "../../../../../hooks/api/customers"
|
||||
import { useAddCustomersToGroup } from "../../../../../hooks/api/customer-groups"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useCustomerTableFilters } from "../../../../../hooks/table/filters/use-customer-table-filters"
|
||||
import { useCustomerTableQuery } from "../../../../../hooks/table/query/use-customer-table-query"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { AdminCustomerResponse } from "@medusajs/types"
|
||||
|
||||
type AddCustomersFormProps = {
|
||||
customerGroupId: string
|
||||
}
|
||||
|
||||
const AddCustomersSchema = zod.object({
|
||||
export const AddCustomersSchema = zod.object({
|
||||
customer_ids: zod.array(zod.string()).min(1),
|
||||
})
|
||||
|
||||
@@ -83,36 +91,47 @@ export const AddCustomersForm = ({
|
||||
)
|
||||
}, [rowSelection, setValue])
|
||||
|
||||
const params = useQueryParams(["q"])
|
||||
const { customers, count, isLoading, isError, error } = useAdminCustomers({
|
||||
expand: "groups",
|
||||
limit: PAGE_SIZE,
|
||||
offset: pageIndex * PAGE_SIZE,
|
||||
...params,
|
||||
const { searchParams, raw } = useCustomerTableQuery({ pageSize: PAGE_SIZE })
|
||||
const filters = useCustomerTableFilters()
|
||||
|
||||
const { customers, count, isLoading, isError, error } = useCustomers({
|
||||
fields: "id,email,first_name,last_name,*groups",
|
||||
...searchParams,
|
||||
})
|
||||
|
||||
const updater: OnChangeFn<RowSelectionState> = (fn) => {
|
||||
const state = typeof fn === "function" ? fn(rowSelection) : fn
|
||||
|
||||
const ids = Object.keys(state)
|
||||
|
||||
setValue("customer_ids", ids, {
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
})
|
||||
|
||||
setRowSelection(state)
|
||||
}
|
||||
|
||||
const columns = useColumns()
|
||||
|
||||
const table = useReactTable({
|
||||
const { table } = useDataTable({
|
||||
data: customers ?? [],
|
||||
columns,
|
||||
pageCount: Math.ceil((count ?? 0) / PAGE_SIZE),
|
||||
state: {
|
||||
pagination,
|
||||
rowSelection,
|
||||
count,
|
||||
enablePagination: true,
|
||||
enableRowSelection: (row) => {
|
||||
return !row.original.groups?.map((gc) => gc.id).includes(customerGroupId)
|
||||
},
|
||||
getRowId: (row) => row.id,
|
||||
onPaginationChange: setPagination,
|
||||
onRowSelectionChange: setRowSelection,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
meta: {
|
||||
customerGroupId,
|
||||
pageSize: PAGE_SIZE,
|
||||
rowSelection: {
|
||||
state: rowSelection,
|
||||
updater,
|
||||
},
|
||||
})
|
||||
|
||||
const { mutateAsync, isLoading: isMutating } =
|
||||
useAdminAddCustomersToCustomerGroup(customerGroupId)
|
||||
useAddCustomersToGroup(customerGroupId)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
await mutateAsync(
|
||||
@@ -121,18 +140,12 @@ export const AddCustomersForm = ({
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(adminCustomerKeys.lists())
|
||||
handleSuccess(`/customer-groups/${customerGroupId}`)
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
const noRecords =
|
||||
!isLoading &&
|
||||
!customers?.length &&
|
||||
!Object.values(params).filter(Boolean).length
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
@@ -165,98 +178,32 @@ export const AddCustomersForm = ({
|
||||
</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 flex-1 items-center justify-center">
|
||||
<NoRecords />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex w-full flex-1 flex-col divide-y">
|
||||
<div className="flex w-full items-center justify-between px-6 py-4">
|
||||
<div></div>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Query />
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full flex-1 overflow-y-auto">
|
||||
{(customers?.length || 0) > 0 ? (
|
||||
<Table>
|
||||
<Table.Header className="border-t-0">
|
||||
{table.getHeaderGroups().map((headerGroup) => {
|
||||
return (
|
||||
<Table.Row
|
||||
key={headerGroup.id}
|
||||
className="[&_th:first-of-type]:w-[1%] [&_th:first-of-type]:whitespace-nowrap"
|
||||
>
|
||||
{headerGroup.headers.map((header) => {
|
||||
return (
|
||||
<Table.HeaderCell key={header.id}>
|
||||
{flexRender(
|
||||
header.column.columnDef.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</Table.HeaderCell>
|
||||
)
|
||||
})}
|
||||
</Table.Row>
|
||||
)
|
||||
})}
|
||||
</Table.Header>
|
||||
<Table.Body className="border-b-0">
|
||||
{table.getRowModel().rows.map((row) => (
|
||||
<Table.Row
|
||||
key={row.id}
|
||||
className={clx(
|
||||
"transition-fg",
|
||||
{
|
||||
"bg-ui-bg-highlight hover:bg-ui-bg-highlight-hover":
|
||||
row.getIsSelected(),
|
||||
},
|
||||
{
|
||||
"bg-ui-bg-disabled hover:bg-ui-bg-disabled":
|
||||
row.original.groups
|
||||
?.map((cg) => cg.id)
|
||||
.includes(customerGroupId),
|
||||
}
|
||||
)}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => (
|
||||
<Table.Cell key={cell.id}>
|
||||
{flexRender(
|
||||
cell.column.columnDef.cell,
|
||||
cell.getContext()
|
||||
)}
|
||||
</Table.Cell>
|
||||
))}
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="flex min-h-full flex-1 items-center justify-center">
|
||||
<NoResults />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<LocalizedTablePagination
|
||||
canNextPage={table.getCanNextPage()}
|
||||
canPreviousPage={table.getCanPreviousPage()}
|
||||
nextPage={table.nextPage}
|
||||
previousPage={table.previousPage}
|
||||
count={count ?? 0}
|
||||
pageIndex={pageIndex}
|
||||
pageCount={table.getPageCount()}
|
||||
pageSize={PAGE_SIZE}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<RouteFocusModal.Body>
|
||||
<DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
count={count}
|
||||
filters={filters}
|
||||
orderBy={[
|
||||
"email",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"has_account",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]}
|
||||
isLoading={isLoading}
|
||||
search
|
||||
queryObject={raw}
|
||||
/>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
</RouteFocusModal.Form>
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<Customer>()
|
||||
const columnHelper = createColumnHelper<AdminCustomerResponse["customer"]>()
|
||||
|
||||
const useColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
@@ -279,21 +226,14 @@ const useColumns = () => {
|
||||
/>
|
||||
)
|
||||
},
|
||||
cell: ({ row, table }) => {
|
||||
const { customerGroupId } = table.options.meta as {
|
||||
customerGroupId: string
|
||||
}
|
||||
|
||||
const isAdded = row.original.groups
|
||||
?.map((gc) => gc.id)
|
||||
.includes(customerGroupId)
|
||||
|
||||
const isSelected = row.getIsSelected() || isAdded
|
||||
cell: ({ row }) => {
|
||||
const isPreSelected = !row.getCanSelect()
|
||||
const isSelected = row.getIsSelected() || isPreSelected
|
||||
|
||||
const Component = (
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={isAdded}
|
||||
disabled={isPreSelected}
|
||||
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
@@ -301,7 +241,7 @@ const useColumns = () => {
|
||||
/>
|
||||
)
|
||||
|
||||
if (isAdded) {
|
||||
if (isPreSelected) {
|
||||
return (
|
||||
<Tooltip
|
||||
content={t("customerGroups.customerAlreadyAdded")}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useParams } from "react-router-dom"
|
||||
|
||||
import { RouteFocusModal } from "../../../components/route-modal"
|
||||
import { AddCustomersForm } from "./components/add-customers-form"
|
||||
|
||||
@@ -10,8 +10,9 @@ import {
|
||||
RouteFocusModal,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { useCreateCustomerGroup } from "../../../../../hooks/api/customer-groups"
|
||||
|
||||
const CreateCustomerGroupSchema = zod.object({
|
||||
export const CreateCustomerGroupSchema = zod.object({
|
||||
name: zod.string().min(1),
|
||||
})
|
||||
|
||||
@@ -26,7 +27,7 @@ export const CreateCustomerGroupForm = () => {
|
||||
resolver: zodResolver(CreateCustomerGroupSchema),
|
||||
})
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminCreateCustomerGroup()
|
||||
const { mutateAsync, isLoading } = useCreateCustomerGroup()
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
await mutateAsync(
|
||||
@@ -1,23 +1,30 @@
|
||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { Customer, CustomerGroup } from "@medusajs/medusa"
|
||||
import { Button, Checkbox, Container, Heading, usePrompt } from "@medusajs/ui"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { Customer } from "@medusajs/medusa"
|
||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import {
|
||||
adminCustomerGroupKeys,
|
||||
useAdminCustomPost,
|
||||
useAdminCustomerGroupCustomers,
|
||||
useAdminRemoveCustomersFromCustomerGroup,
|
||||
} from "medusa-react"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { Link } from "react-router-dom"
|
||||
import { RowSelectionState, createColumnHelper } from "@tanstack/react-table"
|
||||
import { useCustomerTableColumns } from "../../../../../hooks/table/columns/use-customer-table-columns"
|
||||
import { useCustomerTableFilters } from "../../../../../hooks/table/filters/use-customer-table-filters"
|
||||
import { useCustomerTableQuery } from "../../../../../hooks/table/query/use-customer-table-query"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useMemo, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { AdminCustomerGroupResponse, AdminCustomerResponse } from "@medusajs/types"
|
||||
import { useCustomers } from "../../../../../hooks/api/customers"
|
||||
import {
|
||||
useRemoveCustomersFromGroup,
|
||||
useUpdateCustomerGroup,
|
||||
} from "../../../../../hooks/api/customer-groups"
|
||||
|
||||
type CustomerGroupCustomerSectionProps = {
|
||||
group: CustomerGroup
|
||||
group: AdminCustomerGroupResponse["customer_group"]
|
||||
}
|
||||
|
||||
const PAGE_SIZE = 10
|
||||
@@ -25,22 +32,18 @@ const PAGE_SIZE = 10
|
||||
export const CustomerGroupCustomerSection = ({
|
||||
group,
|
||||
}: CustomerGroupCustomerSectionProps) => {
|
||||
const [rowSelection, setRowSelection] = useState<RowSelectionState>({})
|
||||
const { t } = useTranslation()
|
||||
const prompt = usePrompt()
|
||||
|
||||
const { searchParams, raw } = useCustomerTableQuery({ pageSize: PAGE_SIZE })
|
||||
const { customers, count, isLoading, isError, error } =
|
||||
useAdminCustomerGroupCustomers(
|
||||
group.id,
|
||||
{
|
||||
...searchParams,
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
}
|
||||
)
|
||||
const { customers, count, isLoading, isError, error } = useCustomers({
|
||||
...searchParams,
|
||||
groups: group.id,
|
||||
})
|
||||
|
||||
const filters = useCustomerTableFilters(["groups"])
|
||||
const columns = useColumns()
|
||||
const filters = useCustomerTableFilters(["groups"])
|
||||
|
||||
const { table } = useDataTable({
|
||||
data: customers ?? [],
|
||||
@@ -50,21 +53,28 @@ export const CustomerGroupCustomerSection = ({
|
||||
enablePagination: true,
|
||||
enableRowSelection: true,
|
||||
pageSize: PAGE_SIZE,
|
||||
rowSelection: {
|
||||
state: rowSelection,
|
||||
updater: setRowSelection,
|
||||
},
|
||||
meta: {
|
||||
customerGroupId: group.id,
|
||||
},
|
||||
})
|
||||
|
||||
const { mutateAsync } = useAdminRemoveCustomersFromCustomerGroup(group.id)
|
||||
const prompt = usePrompt()
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
const handleRemoveCustomers = async (selection: Record<string, boolean>) => {
|
||||
const selected = Object.keys(selection).filter((k) => selection[k])
|
||||
const { mutateAsync } = useRemoveCustomersFromGroup(group.id)
|
||||
|
||||
const handleRemove = async () => {
|
||||
const keys = Object.keys(rowSelection)
|
||||
|
||||
const res = await prompt({
|
||||
title: t("general.areYouSure"),
|
||||
description: t("customerGroups.removeCustomersWarning", {
|
||||
count: selected.length,
|
||||
count: keys.length,
|
||||
}),
|
||||
confirmText: t("actions.continue"),
|
||||
cancelText: t("actions.cancel"),
|
||||
@@ -74,13 +84,16 @@ export const CustomerGroupCustomerSection = ({
|
||||
return
|
||||
}
|
||||
|
||||
await mutateAsync({
|
||||
customer_ids: selected.map((s) => ({ id: s })),
|
||||
})
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
throw error
|
||||
await mutateAsync(
|
||||
{
|
||||
customer_ids: keys.map((k) => ({ id: k })),
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
setRowSelection({})
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -111,14 +124,14 @@ export const CustomerGroupCustomerSection = ({
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]}
|
||||
queryObject={raw}
|
||||
commands={[
|
||||
{
|
||||
action: handleRemoveCustomers,
|
||||
action: handleRemove,
|
||||
label: t("actions.remove"),
|
||||
shortcut: "r",
|
||||
},
|
||||
]}
|
||||
queryObject={raw}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
@@ -128,12 +141,11 @@ const CustomerActions = ({
|
||||
customer,
|
||||
customerGroupId,
|
||||
}: {
|
||||
customer: Customer
|
||||
customer: AdminCustomerResponse["customer"]
|
||||
customerGroupId: string
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { mutateAsync } =
|
||||
useAdminRemoveCustomersFromCustomerGroup(customerGroupId)
|
||||
const { mutateAsync } = useRemoveCustomersFromGroup(customerGroupId)
|
||||
|
||||
const prompt = usePrompt()
|
||||
|
||||
@@ -182,7 +194,7 @@ const CustomerActions = ({
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper = createColumnHelper<Customer>()
|
||||
const columnHelper = createColumnHelper<AdminCustomerResponse["customer"]>()
|
||||
|
||||
const useColumns = () => {
|
||||
const columns = useCustomerTableColumns()
|
||||
@@ -1,13 +1,13 @@
|
||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import type { CustomerGroup } from "@medusajs/medusa"
|
||||
import { Container, Heading } from "@medusajs/ui"
|
||||
import { useAdminDeleteCustomerGroup } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { AdminCustomerGroupResponse } from "@medusajs/types"
|
||||
import { useDeleteCustomerGroup } from "../../../../../hooks/api/customer-groups"
|
||||
|
||||
type CustomerGroupGeneralSectionProps = {
|
||||
group: CustomerGroup
|
||||
group: AdminCustomerGroupResponse["customer_group"]
|
||||
}
|
||||
|
||||
export const CustomerGroupGeneralSection = ({
|
||||
@@ -16,7 +16,7 @@ export const CustomerGroupGeneralSection = ({
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
|
||||
const { mutateAsync } = useAdminDeleteCustomerGroup(group.id)
|
||||
const { mutateAsync } = useDeleteCustomerGroup(group.id)
|
||||
|
||||
const handleDelete = async () => {
|
||||
await mutateAsync(undefined, {
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useAdminCustomerGroup } from "medusa-react"
|
||||
import { Outlet, json, useLoaderData, useParams } from "react-router-dom"
|
||||
import { JsonViewSection } from "../../../components/common/json-view-section"
|
||||
import { CustomerGroupCustomerSection } from "./components/customer-group-customer-section"
|
||||
import { CustomerGroupGeneralSection } from "./components/customer-group-general-section"
|
||||
import { customerGroupLoader } from "./loader"
|
||||
import { useCustomerGroup } from "../../../hooks/api/customer-groups"
|
||||
|
||||
export const CustomerGroupDetail = () => {
|
||||
const initialData = useLoaderData() as Awaited<
|
||||
@@ -11,7 +11,7 @@ export const CustomerGroupDetail = () => {
|
||||
>
|
||||
|
||||
const { id } = useParams()
|
||||
const { customer_group, isLoading, isError, error } = useAdminCustomerGroup(
|
||||
const { customer_group, isLoading, isError, error } = useCustomerGroup(
|
||||
id!,
|
||||
undefined,
|
||||
{ initialData }
|
||||
@@ -1,8 +1,8 @@
|
||||
import { AdminCustomerGroupsRes } from "@medusajs/medusa"
|
||||
import { Response } from "@medusajs/medusa-js"
|
||||
import { adminProductKeys } from "medusa-react"
|
||||
import { LoaderFunctionArgs } from "react-router-dom"
|
||||
|
||||
import { AdminCustomerGroupResponse } from "@medusajs/types"
|
||||
import { medusa, queryClient } from "../../../lib/medusa"
|
||||
|
||||
const customerGroupDetailQuery = (id: string) => ({
|
||||
@@ -15,7 +15,7 @@ export const customerGroupLoader = async ({ params }: LoaderFunctionArgs) => {
|
||||
const query = customerGroupDetailQuery(id!)
|
||||
|
||||
return (
|
||||
queryClient.getQueryData<Response<AdminCustomerGroupsRes>>(
|
||||
queryClient.getQueryData<Response<AdminCustomerGroupResponse>>(
|
||||
query.queryKey
|
||||
) ?? (await queryClient.fetchQuery(query))
|
||||
)
|
||||
@@ -1,5 +1,4 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { CustomerGroup } from "@medusajs/medusa"
|
||||
import { Button, Input } from "@medusajs/ui"
|
||||
import { useAdminUpdateCustomerGroup } from "medusa-react"
|
||||
import { useForm } from "react-hook-form"
|
||||
@@ -10,12 +9,14 @@ import {
|
||||
RouteDrawer,
|
||||
useRouteModal,
|
||||
} from "../../../../../components/route-modal"
|
||||
import { AdminCustomerGroupResponse } from "@medusajs/types"
|
||||
import { useUpdateCustomerGroup } from "../../../../../hooks/api/customer-groups"
|
||||
|
||||
type EditCustomerGroupFormProps = {
|
||||
group: CustomerGroup
|
||||
group: AdminCustomerGroupResponse["customer_group"]
|
||||
}
|
||||
|
||||
const EditCustomerGroupSchema = z.object({
|
||||
export const EditCustomerGroupSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
})
|
||||
|
||||
@@ -32,7 +33,7 @@ export const EditCustomerGroupForm = ({
|
||||
resolver: zodResolver(EditCustomerGroupSchema),
|
||||
})
|
||||
|
||||
const { mutateAsync, isLoading } = useAdminUpdateCustomerGroup(group.id)
|
||||
const { mutateAsync, isLoading } = useUpdateCustomerGroup(group.id)
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
await mutateAsync(data, {
|
||||
@@ -1,15 +1,13 @@
|
||||
import { Heading } from "@medusajs/ui"
|
||||
import { useAdminCustomerGroup } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { RouteDrawer } from "../../../components/route-modal"
|
||||
import { EditCustomerGroupForm } from "./components/edit-customer-group-form"
|
||||
import { useCustomerGroup } from "../../../hooks/api/customer-groups"
|
||||
|
||||
export const CustomerGroupEdit = () => {
|
||||
const { id } = useParams()
|
||||
const { customer_group, isLoading, isError, error } = useAdminCustomerGroup(
|
||||
id!
|
||||
)
|
||||
const { customer_group, isLoading, isError, error } = useCustomerGroup(id!)
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Button, Container, Heading } from "@medusajs/ui"
|
||||
import { useAdminCustomerGroups } from "medusa-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link } from "react-router-dom"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
@@ -7,6 +6,7 @@ import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useCustomerGroupTableColumns } from "./use-customer-group-table-columns"
|
||||
import { useCustomerGroupTableFilters } from "./use-customer-group-table-filters"
|
||||
import { useCustomerGroupTableQuery } from "./use-customer-group-table-query"
|
||||
import { useCustomerGroups } from "../../../../../hooks/api/customer-groups"
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
@@ -17,16 +17,10 @@ export const CustomerGroupListTable = () => {
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
const { customer_groups, count, isLoading, isError, error } =
|
||||
useAdminCustomerGroups(
|
||||
{
|
||||
...searchParams,
|
||||
expand: "customers",
|
||||
fields: "id,name,customers,customers.id",
|
||||
},
|
||||
{
|
||||
keepPreviousData: true,
|
||||
}
|
||||
)
|
||||
useCustomerGroups({
|
||||
...searchParams,
|
||||
fields: "id,name,customers.id",
|
||||
})
|
||||
|
||||
const filters = useCustomerGroupTableFilters()
|
||||
const columns = useCustomerGroupTableColumns()
|
||||
@@ -1,13 +1,14 @@
|
||||
import { PencilSquare, Trash } from "@medusajs/icons"
|
||||
import { CustomerGroup } from "@medusajs/medusa"
|
||||
import { usePrompt } from "@medusajs/ui"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useAdminDeleteCustomerGroup } from "medusa-react"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ActionMenu } from "../../../../../components/common/action-menu"
|
||||
import { AdminCustomerGroupResponse } from "@medusajs/types"
|
||||
import { useDeleteCustomerGroup } from "../../../../../hooks/api/customer-groups"
|
||||
|
||||
const columnHelper = createColumnHelper<CustomerGroup>()
|
||||
const columnHelper =
|
||||
createColumnHelper<AdminCustomerGroupResponse["customer_group"]>()
|
||||
|
||||
export const useCustomerGroupTableColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
@@ -35,11 +36,15 @@ export const useCustomerGroupTableColumns = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const CustomerGroupActions = ({ group }: { group: CustomerGroup }) => {
|
||||
const CustomerGroupActions = ({
|
||||
group,
|
||||
}: {
|
||||
group: AdminCustomerGroupResponse["customer_group"]
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const prompt = usePrompt()
|
||||
|
||||
const { mutateAsync } = useAdminDeleteCustomerGroup(group.id)
|
||||
const { mutateAsync } = useDeleteCustomerGroup(group.id)
|
||||
|
||||
const handleDelete = async () => {
|
||||
const res = await prompt({
|
||||
@@ -1,4 +1,3 @@
|
||||
import { AdminGetCustomerGroupsParams } from "@medusajs/medusa"
|
||||
import { useQueryParams } from "../../../../../hooks/use-query-params"
|
||||
|
||||
type UseCustomerGroupTableQueryProps = {
|
||||
@@ -17,7 +16,7 @@ export const useCustomerGroupTableQuery = ({
|
||||
|
||||
const { offset, created_at, updated_at, q, order } = queryObject
|
||||
|
||||
const searchParams: AdminGetCustomerGroupsParams = {
|
||||
const searchParams = {
|
||||
limit: pageSize,
|
||||
offset: offset ? Number(offset) : 0,
|
||||
order,
|
||||
@@ -138,16 +138,6 @@ export const CreateCustomerForm = () => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<div>
|
||||
<Text size="small" leading="compact" weight="plus">
|
||||
{t("fields.password")}
|
||||
</Text>
|
||||
<Text size="small" className="text-ui-fg-subtle">
|
||||
{t("customers.passwordHint")}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RouteFocusModal.Body>
|
||||
</form>
|
||||
|
||||
@@ -1,54 +1,90 @@
|
||||
// TODO: Should be added with Customer Groups UI
|
||||
import { Customer, CustomerGroup } from "@medusajs/medusa"
|
||||
import { Container, Heading } from "@medusajs/ui"
|
||||
import { createColumnHelper } from "@tanstack/react-table"
|
||||
import { useMemo } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useCustomerGroups } from "../../../../../hooks/api/customer-groups"
|
||||
import {
|
||||
AdminCustomerGroupResponse,
|
||||
AdminCustomerResponse,
|
||||
} from "@medusajs/types"
|
||||
import { DataTable } from "../../../../../components/table/data-table"
|
||||
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||
import { useCustomerGroupTableFilters } from "../../../../customer-groups/customer-group-list/components/customer-group-list-table/use-customer-group-table-filters"
|
||||
import { t } from "i18next"
|
||||
|
||||
// import { Customer, CustomerGroup } from "@medusajs/medusa"
|
||||
// import { Container, Heading } from "@medusajs/ui"
|
||||
// import { createColumnHelper } from "@tanstack/react-table"
|
||||
// import { useAdminCustomerGroups } from "medusa-react"
|
||||
// import { useMemo } from "react"
|
||||
// import { useTranslation } from "react-i18next"
|
||||
type CustomerGroupSectionProps = {
|
||||
customer: AdminCustomerResponse["customer"]
|
||||
}
|
||||
|
||||
// // TODO: Continue working on this when there is a natural way to get customer groups related to a customer.
|
||||
// type CustomerGroupSectionProps = {
|
||||
// customer: Customer
|
||||
// }
|
||||
const PAGE_SIZE = 10
|
||||
|
||||
// export const CustomerGroupSection = ({
|
||||
// customer,
|
||||
// }: CustomerGroupSectionProps) => {
|
||||
// const { customer_groups, isLoading, isError, error } = useAdminCustomerGroups(
|
||||
// {
|
||||
// id: customer.groups.map((g) => g.id).join(","),
|
||||
// }
|
||||
// )
|
||||
export const CustomerGroupSection = ({
|
||||
customer,
|
||||
}: CustomerGroupSectionProps) => {
|
||||
const { customer_groups, count, isLoading, isError, error } =
|
||||
useCustomerGroups({
|
||||
customers: { id: customer.id },
|
||||
})
|
||||
|
||||
// if (isError) {
|
||||
// throw error
|
||||
// }
|
||||
const filters = useCustomerGroupTableFilters()
|
||||
const columns = useColumns()
|
||||
|
||||
// return (
|
||||
// <Container className="p-0 divide-y">
|
||||
// <div className="px-6 py-4">
|
||||
// <Heading level="h2">Groups</Heading>
|
||||
// </div>
|
||||
// </Container>
|
||||
// )
|
||||
// }
|
||||
const { table } = useDataTable({
|
||||
data: customer_groups ?? [],
|
||||
columns,
|
||||
count,
|
||||
getRowId: (row) => row.id,
|
||||
enablePagination: true,
|
||||
enableRowSelection: true,
|
||||
pageSize: PAGE_SIZE,
|
||||
})
|
||||
|
||||
// const columnHelper = createColumnHelper<CustomerGroup>()
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
// const useColumns = () => {
|
||||
// const { t } = useTranslation()
|
||||
if (isError) {
|
||||
throw error
|
||||
}
|
||||
|
||||
// return useMemo(
|
||||
// () => [
|
||||
// columnHelper.display({
|
||||
// id: "select",
|
||||
// }),
|
||||
// columnHelper.accessor("name", {
|
||||
// header: t("fields.name"),
|
||||
// cell: ({ getValue }) => getValue(),
|
||||
// }),
|
||||
// ],
|
||||
// [t]
|
||||
// )
|
||||
// }
|
||||
return (
|
||||
<Container className="divide-y p-0">
|
||||
<div className="flex items-center justify-between px-6 py-4">
|
||||
<Heading level="h2">{t("customerGroups.domain")}</Heading>
|
||||
</div>
|
||||
<DataTable
|
||||
table={table}
|
||||
columns={columns}
|
||||
pageSize={PAGE_SIZE}
|
||||
isLoading={isLoading}
|
||||
count={count}
|
||||
navigateTo={(row) => `/customer-groups/${row.id}`}
|
||||
filters={filters}
|
||||
search
|
||||
pagination
|
||||
orderBy={["name", "created_at", "updated_at"]}
|
||||
/>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const columnHelper =
|
||||
createColumnHelper<AdminCustomerGroupResponse["customer_group"]>()
|
||||
|
||||
const useColumns = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return useMemo(
|
||||
() => [
|
||||
columnHelper.display({
|
||||
id: "select",
|
||||
}),
|
||||
columnHelper.accessor("name", {
|
||||
header: t("fields.name"),
|
||||
cell: ({ getValue }) => getValue(),
|
||||
}),
|
||||
],
|
||||
[t]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { CustomerGeneralSection } from "./components/customer-general-section"
|
||||
import { JsonViewSection } from "../../../components/common/json-view-section"
|
||||
import { customerLoader } from "./loader"
|
||||
import { useCustomer } from "../../../hooks/api/customers"
|
||||
import { CustomerGroupSection } from "./components/customer-group-section"
|
||||
|
||||
export const CustomerDetail = () => {
|
||||
const { id } = useParams()
|
||||
@@ -32,7 +33,7 @@ export const CustomerDetail = () => {
|
||||
{/* <CustomerOrderSection customer={customer} />
|
||||
// TODO: re-add when order endpoints are added to api-v2
|
||||
*/}
|
||||
{/* <CustomerGroupSection customer={customer} /> */}
|
||||
<CustomerGroupSection customer={customer} />
|
||||
<JsonViewSection data={customer} />
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { createCustomerGroupCustomersWorkflow } from "@medusajs/core-flows"
|
||||
import { AdminCustomerGroupResponse } from "@medusajs/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../../../types/routing"
|
||||
|
||||
import { AdminPostCustomerGroupsGroupCustomersBatchReq } from "../../../validators"
|
||||
import { createCustomerGroupCustomersWorkflow } from "@medusajs/core-flows"
|
||||
} from "../../../../../../../types/routing"
|
||||
import { AdminPostCustomerGroupsGroupCustomersBatchReq } from "../../../../validators"
|
||||
|
||||
export const POST = async (
|
||||
// eslint-disable-next-line max-len
|
||||
req: AuthenticatedMedusaRequest<AdminPostCustomerGroupsGroupCustomersBatchReq>,
|
||||
res: MedusaResponse
|
||||
res: MedusaResponse<AdminCustomerGroupResponse>
|
||||
) => {
|
||||
const { id } = req.params
|
||||
const { customer_ids } = req.validatedBody
|
||||
@@ -30,5 +34,15 @@ export const POST = async (
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
res.status(200).json({ customer_group_customers: result })
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "customer_group",
|
||||
variables: { id },
|
||||
fields: req.remoteQueryConfig.fields,
|
||||
})
|
||||
|
||||
const [customer_group] = await remoteQuery(queryObject)
|
||||
|
||||
res.status(200).json({ customer_group })
|
||||
}
|
||||
@@ -1,15 +1,19 @@
|
||||
import { deleteCustomerGroupCustomersWorkflow } from "@medusajs/core-flows"
|
||||
import { AdminCustomerGroupResponse } from "@medusajs/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../../../types/routing"
|
||||
|
||||
import { AdminPostCustomerGroupsGroupCustomersBatchReq } from "../../../validators"
|
||||
import { deleteCustomerGroupCustomersWorkflow } from "@medusajs/core-flows"
|
||||
} from "../../../../../../../types/routing"
|
||||
import { AdminPostCustomerGroupsGroupCustomersBatchReq } from "../../../../validators"
|
||||
|
||||
export const POST = async (
|
||||
// eslint-disable-next-line max-len
|
||||
req: AuthenticatedMedusaRequest<AdminPostCustomerGroupsGroupCustomersBatchReq>,
|
||||
res: MedusaResponse
|
||||
res: MedusaResponse<AdminCustomerGroupResponse>
|
||||
) => {
|
||||
const { id } = req.params
|
||||
const { customer_ids } = req.validatedBody
|
||||
@@ -30,8 +34,15 @@ export const POST = async (
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
object: "customer_group_customers",
|
||||
deleted: true,
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "customer_group",
|
||||
variables: { id },
|
||||
fields: req.remoteQueryConfig.fields,
|
||||
})
|
||||
|
||||
const [customer_group] = await remoteQuery(queryObject)
|
||||
|
||||
res.status(200).json({ customer_group })
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as QueryConfig from "./query-config"
|
||||
|
||||
import { transformBody, transformQuery } from "../../../api/middlewares"
|
||||
import {
|
||||
AdminDeleteCustomerGroupsGroupCustomersBatchReq,
|
||||
AdminGetCustomerGroupsGroupCustomersParams,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
AdminPostCustomerGroupsGroupReq,
|
||||
AdminPostCustomerGroupsReq,
|
||||
} from "./validators"
|
||||
import { transformBody, transformQuery } from "../../../api/middlewares"
|
||||
|
||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||
@@ -63,14 +63,24 @@ export const adminCustomerGroupRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/customer-groups/:id/customers/batch",
|
||||
middlewares: [transformBody(AdminPostCustomerGroupsGroupCustomersBatchReq)],
|
||||
matcher: "/admin/customer-groups/:id/customers/batch/add",
|
||||
middlewares: [
|
||||
transformBody(AdminPostCustomerGroupsGroupCustomersBatchReq),
|
||||
transformQuery(
|
||||
AdminGetCustomerGroupsGroupParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/customer-groups/:id/customers/remove",
|
||||
matcher: "/admin/customer-groups/:id/customers/batch/remove",
|
||||
middlewares: [
|
||||
transformBody(AdminDeleteCustomerGroupsGroupCustomersBatchReq),
|
||||
transformQuery(
|
||||
AdminGetCustomerGroupsGroupParams,
|
||||
QueryConfig.retrieveTransformQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { OperatorMap } from "@medusajs/types"
|
||||
import { Transform, Type } from "class-transformer"
|
||||
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
|
||||
import {
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { FindParams, extendedFindParamsMixin } from "../../../types/common"
|
||||
import { OperatorMapValidator } from "../../../types/validators/operator-map"
|
||||
import { Transform, Type } from "class-transformer"
|
||||
|
||||
import { IsType } from "../../../utils"
|
||||
import { OperatorMap } from "@medusajs/types"
|
||||
import { OperatorMapValidator } from "../../../types/validators/operator-map"
|
||||
|
||||
export class AdminGetCustomerGroupsGroupParams extends FindParams {}
|
||||
|
||||
@@ -61,6 +62,10 @@ export class AdminGetCustomerGroupsParams extends extendedFindParamsMixin({
|
||||
limit: 100,
|
||||
offset: 0,
|
||||
}) {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
q?: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString({ each: true })
|
||||
id?: string | string[]
|
||||
@@ -120,6 +125,10 @@ export class AdminGetCustomerGroupsGroupCustomersParams extends extendedFindPara
|
||||
offset: 0,
|
||||
}
|
||||
) {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
q?: string
|
||||
|
||||
@IsOptional()
|
||||
@IsString({ each: true })
|
||||
id?: string | string[]
|
||||
|
||||
@@ -2,12 +2,11 @@ import {
|
||||
deleteCustomersWorkflow,
|
||||
updateCustomersWorkflow,
|
||||
} from "@medusajs/core-flows"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { AdminCustomerResponse, CustomerUpdatableFields } from "@medusajs/types"
|
||||
import {
|
||||
AdminCustomerResponse,
|
||||
CustomerUpdatableFields,
|
||||
ICustomerModuleService,
|
||||
} from "@medusajs/types"
|
||||
ContainerRegistrationKeys,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
@@ -17,24 +16,26 @@ export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse<AdminCustomerResponse>
|
||||
) => {
|
||||
const customerModuleService = req.scope.resolve<ICustomerModuleService>(
|
||||
ModuleRegistrationName.CUSTOMER
|
||||
)
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
|
||||
const customer = await customerModuleService.retrieve(req.params.id, {
|
||||
select: req.retrieveConfig.select,
|
||||
relations: req.retrieveConfig.relations,
|
||||
const variables = { id: req.params.id }
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "customer",
|
||||
variables,
|
||||
fields: req.remoteQueryConfig.fields,
|
||||
})
|
||||
|
||||
res.status(200).json({ customer: customer as AdminCustomerResponse["customer"] })
|
||||
const [customer] = await remoteQuery(queryObject)
|
||||
|
||||
res.status(200).json({ customer })
|
||||
}
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<CustomerUpdatableFields>,
|
||||
res: MedusaResponse<AdminCustomerResponse>
|
||||
) => {
|
||||
const updateCustomers = updateCustomersWorkflow(req.scope)
|
||||
const { result, errors } = await updateCustomers.run({
|
||||
const { errors } = await updateCustomersWorkflow(req.scope).run({
|
||||
input: {
|
||||
selector: { id: req.params.id },
|
||||
update: req.validatedBody,
|
||||
@@ -46,7 +47,19 @@ export const POST = async (
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
res.status(200).json({ customer: result[0] as AdminCustomerResponse["customer"] })
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "customer",
|
||||
variables: {
|
||||
filters: { id: req.params.id },
|
||||
},
|
||||
fields: req.remoteQueryConfig.fields,
|
||||
})
|
||||
|
||||
const [customer] = await remoteQuery(queryObject)
|
||||
|
||||
res.status(200).json({ customer })
|
||||
}
|
||||
|
||||
export const DELETE = async (
|
||||
|
||||
@@ -6,6 +6,7 @@ export const defaultAdminCustomerFields = [
|
||||
"first_name",
|
||||
"last_name",
|
||||
"email",
|
||||
"phone",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
|
||||
@@ -24,11 +24,13 @@ export const AdminCustomersParams = createFindParams({
|
||||
q: z.string().optional(),
|
||||
id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
email: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
groups: z.union([
|
||||
AdminCustomerGroupInCustomerParams,
|
||||
z.string(),
|
||||
z.array(z.string()),
|
||||
]).optional(),
|
||||
groups: z
|
||||
.union([
|
||||
AdminCustomerGroupInCustomerParams,
|
||||
z.string(),
|
||||
z.array(z.string()),
|
||||
])
|
||||
.optional(),
|
||||
company_name: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
first_name: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
last_name: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
|
||||
@@ -6,6 +6,7 @@ import { PaginatedResponse } from "../../../common"
|
||||
export interface CustomerGroupResponse {
|
||||
id: string
|
||||
name: string | null
|
||||
customers: CustomerResponse[]
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user