From 7e66dd0dd03f856add42056008aaf00e170cdf3a Mon Sep 17 00:00:00 2001
From: Kasper Fabricius Kristensen
<45367945+kasperkristensen@users.noreply.github.com>
Date: Wed, 17 Apr 2024 10:32:21 +0200
Subject: [PATCH] fix(dashboard,medusa): Fixes to Customer and Customer Groups
domains (#7081)
**What**
- Cleanup of domains
- Adds toasts
- Adds delete customer hook
- Fixes validation of create and update customer endpoints.
---
.changeset/young-geckos-battle.md | 5 +
.../public/locales/en-US/translation.json | 73 ++++++++----
.../common/text-cell/text-cell.tsx | 10 +-
.../customer/account-cell/account-cell.tsx | 4 +-
.../first-seen-cell/first-seen-cell.tsx | 9 +-
.../dashboard/src/hooks/api/customers.tsx | 27 ++++-
.../use-customer-group-table-columns.tsx | 33 +++---
.../columns/use-customer-table-columns.tsx | 4 +-
.../use-customer-group-table-filters.tsx | 2 +-
.../dashboard/src/lib/client/customers.ts | 8 +-
.../dashboard/src/types/api-payloads.ts | 2 +-
.../add-customers-form/add-customers-form.tsx | 80 ++++---------
.../create-customer-group-form.tsx | 24 +++-
.../customer-group-customer-section.tsx | 39 ++++---
.../customer-group-general-section.tsx | 89 ++++++++++-----
.../customer-group-detail.tsx | 18 ++-
.../customer-group-detail/loader.ts | 5 +-
.../edit-customer-group-form.tsx | 18 ++-
.../customer-group-edit.tsx | 4 +-
.../customer-group-list-table.tsx | 107 +++++++++++++++++-
.../use-customer-group-table-columns.tsx | 90 ---------------
.../use-customer-group-table-query.tsx | 32 ------
.../create-customer-form.tsx | 70 ++++++++----
.../customer-general-section.tsx | 82 ++++++++++++--
.../customer-group-section.tsx | 77 +++++++++----
.../customer-detail/customer-detail.tsx | 16 +--
.../edit-customer-form/edit-customer-form.tsx | 67 ++++++++---
.../customer-list-table.tsx | 21 ++--
.../src/api-v2/admin/customers/validators.ts | 10 +-
29 files changed, 636 insertions(+), 390 deletions(-)
create mode 100644 .changeset/young-geckos-battle.md
rename packages/admin-next/dashboard/src/{v2-routes/customer-groups/customer-group-list/components/customer-group-list-table => hooks/table/filters}/use-customer-group-table-filters.tsx (86%)
delete mode 100644 packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-list/components/customer-group-list-table/use-customer-group-table-columns.tsx
delete mode 100644 packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-list/components/customer-group-list-table/use-customer-group-table-query.tsx
diff --git a/.changeset/young-geckos-battle.md b/.changeset/young-geckos-battle.md
new file mode 100644
index 0000000000..6c8032e3f1
--- /dev/null
+++ b/.changeset/young-geckos-battle.md
@@ -0,0 +1,5 @@
+---
+"@medusajs/medusa": patch
+---
+
+fix(medusa): Fix validation of V2 POST /customers and POST /customers/:id
diff --git a/packages/admin-next/dashboard/public/locales/en-US/translation.json b/packages/admin-next/dashboard/public/locales/en-US/translation.json
index d4a5555ab3..a1c9eab913 100644
--- a/packages/admin-next/dashboard/public/locales/en-US/translation.json
+++ b/packages/admin-next/dashboard/public/locales/en-US/translation.json
@@ -43,7 +43,9 @@
"unsavedChangesTitle": "Are you sure you want to leave this page?",
"unsavedChangesDescription": "You have unsaved changes that will be lost if you leave this page.",
"includesTaxTooltip": "Enter the total amount including tax. The net amount excluding tax will be automatically calculated and saved.",
- "timeline": "Timeline"
+ "timeline": "Timeline",
+ "success": "Success",
+ "error": "Error"
},
"validation": {
"mustBeInt": "The value must be a whole number.",
@@ -353,29 +355,56 @@
},
"customers": {
"domain": "Customers",
- "editCustomer": "Edit Customer",
- "createCustomer": "Create Customer",
- "createCustomerHint": "Create a new customer to manage their details.",
- "passwordHint": "Create a password for the customer to use when logging in to the storefront. Make sure that you communicate the password to the customer.",
- "changePassword": "Change password",
- "changePasswordPromptTitle": "Change password",
- "changePasswordPromptDescription": "You are about to change the password for {{email}}. Make sure that you have communicated the new password to the customer before proceeding.",
- "guest": "Guest",
- "registered": "Registered",
- "firstSeen": "First seen",
- "viewOrder": "View order",
- "groups": "Groups"
+ "create": {
+ "header": "Create Customer",
+ "hint": "Create a new customer to manage their details.",
+ "successToast": "Customer {{email}} was successfully created."
+ },
+ "edit": {
+ "header": "Edit Customer",
+ "emailDisabledTooltip": "The email address cannot be changed for registered customers.",
+ "successToast": "Customer {{email}} was successfully updated."
+ },
+ "delete": {
+ "title": "Delete Customer",
+ "description": "You are about to delete the customer {{email}}. This action cannot be undone.",
+ "successToast": "Customer {{email}} was successfully deleted."
+ },
+ "fields": {
+ "guest": "Guest",
+ "registered": "Registered",
+ "groups": "Groups"
+ }
},
"customerGroups": {
"domain": "Customer Groups",
- "createGroup": "Create group",
- "createCustomerGroup": "Create Customer Group",
- "createCustomerGroupHint": "Create a new customer group to segment your customers.",
- "customerAlreadyAdded": "The customer has already been added to the group.",
- "editCustomerGroup": "Edit Customer Group",
- "removeCustomersWarning_one": "You are about to remove {{count}} customer from the customer group. This action cannot be undone.",
- "removeCustomersWarning_other": "You are about to remove {{count}} customers from the customer group. This action cannot be undone.",
- "deleteCustomerGroupWarning": "You are about to delete the customer group {{name}}. This action cannot be undone."
+ "create": {
+ "header": "Create Customer Group",
+ "hint": "Create a new customer group to segment your customers.",
+ "successToast": "Customer group {{name}} was successfully created."
+ },
+ "edit": {
+ "header": "Edit Customer Group",
+ "successToast": "Customer group {{name}} was successfully updated."
+ },
+ "delete": {
+ "title": "Delete Customer Group",
+ "description": "You are about to delete the customer group {{name}}. This action cannot be undone.",
+ "successToast": "Customer group {{name}} was successfully deleted."
+ },
+ "customers": {
+ "alreadyAddedTooltip": "The customer has already been added to the group.",
+ "add": {
+ "successToast_one": "Customer was successfully added to the group.",
+ "successToast_other": "Customers were successfully added to the group."
+ },
+ "remove": {
+ "title_one": "Remove customer",
+ "title_other": "Remove customers",
+ "description_one": "You are about to remove {{count}} customer from the customer group. This action cannot be undone.",
+ "description_other": "You are about to remove {{count}} customers from the customer group. This action cannot be undone."
+ }
+ }
},
"orders": {
"domain": "Orders",
@@ -575,7 +604,7 @@
"addZone": "Add shipping zone",
"enablePickup": "Enable pickup",
"enableDelivery": "Enable delivery",
- "noRecords" : {
+ "noRecords": {
"action": "Add Location",
"title": "No inventory locations",
"message": "Please create an invnetory location first."
diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/common/text-cell/text-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/common/text-cell/text-cell.tsx
index dba1cf5d68..a8694cce14 100644
--- a/packages/admin-next/dashboard/src/components/table/table-cells/common/text-cell/text-cell.tsx
+++ b/packages/admin-next/dashboard/src/components/table/table-cells/common/text-cell/text-cell.tsx
@@ -1,3 +1,5 @@
+import { PlaceholderCell } from "../placeholder-cell"
+
type CellProps = {
text?: string | number
}
@@ -7,6 +9,10 @@ type HeaderProps = {
}
export const TextCell = ({ text }: CellProps) => {
+ if (!text) {
+ return
+ }
+
return (
{text}
@@ -16,8 +22,8 @@ export const TextCell = ({ text }: CellProps) => {
export const TextHeader = ({ text }: HeaderProps) => {
return (
-
-
{text}
+
+ {text}
)
}
diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/customer/account-cell/account-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/customer/account-cell/account-cell.tsx
index 4dbc983865..e69372ac66 100644
--- a/packages/admin-next/dashboard/src/components/table/table-cells/customer/account-cell/account-cell.tsx
+++ b/packages/admin-next/dashboard/src/components/table/table-cells/customer/account-cell/account-cell.tsx
@@ -9,7 +9,9 @@ export const AccountCell = ({ hasAccount }: AccountCellProps) => {
const { t } = useTranslation()
const color = hasAccount ? "green" : ("orange" as const)
- const text = hasAccount ? t("customers.registered") : t("customers.guest")
+ const text = hasAccount
+ ? t("customers.fields.registered")
+ : t("customers.fields.guest")
return
{text}
}
diff --git a/packages/admin-next/dashboard/src/components/table/table-cells/customer/first-seen-cell/first-seen-cell.tsx b/packages/admin-next/dashboard/src/components/table/table-cells/customer/first-seen-cell/first-seen-cell.tsx
index 6b508d5e7a..5800540715 100644
--- a/packages/admin-next/dashboard/src/components/table/table-cells/customer/first-seen-cell/first-seen-cell.tsx
+++ b/packages/admin-next/dashboard/src/components/table/table-cells/customer/first-seen-cell/first-seen-cell.tsx
@@ -1,11 +1,16 @@
import { useTranslation } from "react-i18next"
import { DateCell } from "../../common/date-cell"
+import { PlaceholderCell } from "../../common/placeholder-cell"
type FirstSeenCellProps = {
- createdAt: Date
+ createdAt?: Date | string | null
}
export const FirstSeenCell = ({ createdAt }: FirstSeenCellProps) => {
+ if (!createdAt) {
+ return
+ }
+
return
}
@@ -14,7 +19,7 @@ export const FirstSeenHeader = () => {
return (
- {t("customers.firstSeen")}
+ {t("fields.createdAt")}
)
}
diff --git a/packages/admin-next/dashboard/src/hooks/api/customers.tsx b/packages/admin-next/dashboard/src/hooks/api/customers.tsx
index de7d66e4d7..ec9e5f4443 100644
--- a/packages/admin-next/dashboard/src/hooks/api/customers.tsx
+++ b/packages/admin-next/dashboard/src/hooks/api/customers.tsx
@@ -1,3 +1,8 @@
+import {
+ AdminCustomerListResponse,
+ AdminCustomerResponse,
+ DeleteResponse,
+} from "@medusajs/types"
import {
QueryKey,
UseMutationOptions,
@@ -9,10 +14,6 @@ import { client } from "../../lib/client"
import { queryClient } from "../../lib/medusa"
import { queryKeysFactory } from "../../lib/query-key-factory"
import { CreateCustomerReq, UpdateCustomerReq } from "../../types/api-payloads"
-import {
- AdminCustomerResponse,
- AdminCustomerListResponse,
-} from "@medusajs/types"
const CUSTOMERS_QUERY_KEY = "customers" as const
export const customersQueryKeys = queryKeysFactory(CUSTOMERS_QUERY_KEY)
@@ -88,3 +89,21 @@ export const useUpdateCustomer = (
...options,
})
}
+
+export const useDeleteCustomer = (
+ id: string,
+ options?: UseMutationOptions
+) => {
+ return useMutation({
+ mutationFn: () => client.customers.delete(id),
+ onSuccess: (data, variables, context) => {
+ queryClient.invalidateQueries({ queryKey: customersQueryKeys.lists() })
+ queryClient.invalidateQueries({
+ queryKey: customersQueryKeys.detail(id),
+ })
+
+ options?.onSuccess?.(data, variables, context)
+ },
+ ...options,
+ })
+}
diff --git a/packages/admin-next/dashboard/src/hooks/table/columns/use-customer-group-table-columns.tsx b/packages/admin-next/dashboard/src/hooks/table/columns/use-customer-group-table-columns.tsx
index b7f0f1cc07..f663524eef 100644
--- a/packages/admin-next/dashboard/src/hooks/table/columns/use-customer-group-table-columns.tsx
+++ b/packages/admin-next/dashboard/src/hooks/table/columns/use-customer-group-table-columns.tsx
@@ -1,35 +1,34 @@
-import { Text } from "@medusajs/ui"
import { createColumnHelper } from "@tanstack/react-table"
import { useMemo } from "react"
import { AdminCustomerGroupResponse } from "@medusajs/types"
+import { useTranslation } from "react-i18next"
import {
- CreatedAtCell,
- CreatedAtHeader,
-} from "../../../components/table/table-cells/common/created-at-cell"
-import { NameHeader } from "../../../components/table/table-cells/common/name-cell"
+ TextCell,
+ TextHeader,
+} from "../../../components/table/table-cells/common/text-cell"
const columnHelper =
createColumnHelper()
export const useCustomerGroupTableColumns = () => {
+ const { t } = useTranslation()
+
return useMemo(
() => [
- columnHelper.display({
- id: "name",
- header: () => ,
- cell: ({
- row: {
- original: { name },
- },
- }) => {name},
+ columnHelper.accessor("name", {
+ header: () => ,
+ cell: ({ getValue }) => ,
}),
+ columnHelper.accessor("customers", {
+ header: () => ,
+ cell: ({ getValue }) => {
+ const count = getValue()?.length ?? 0
- columnHelper.accessor("created_at", {
- header: () => ,
- cell: ({ getValue }) => ,
+ return
+ },
}),
],
- []
+ [t]
)
}
diff --git a/packages/admin-next/dashboard/src/hooks/table/columns/use-customer-table-columns.tsx b/packages/admin-next/dashboard/src/hooks/table/columns/use-customer-table-columns.tsx
index d8bdb47826..277d38bef6 100644
--- a/packages/admin-next/dashboard/src/hooks/table/columns/use-customer-table-columns.tsx
+++ b/packages/admin-next/dashboard/src/hooks/table/columns/use-customer-table-columns.tsx
@@ -1,7 +1,7 @@
-import { Customer } from "@medusajs/medusa"
import { createColumnHelper } from "@tanstack/react-table"
import { useMemo } from "react"
+import { AdminCustomerResponse } from "@medusajs/types"
import {
EmailCell,
EmailHeader,
@@ -19,7 +19,7 @@ import {
FirstSeenHeader,
} from "../../../components/table/table-cells/customer/first-seen-cell"
-const columnHelper = createColumnHelper()
+const columnHelper = createColumnHelper()
export const useCustomerTableColumns = () => {
return useMemo(
diff --git a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-list/components/customer-group-list-table/use-customer-group-table-filters.tsx b/packages/admin-next/dashboard/src/hooks/table/filters/use-customer-group-table-filters.tsx
similarity index 86%
rename from packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-list/components/customer-group-list-table/use-customer-group-table-filters.tsx
rename to packages/admin-next/dashboard/src/hooks/table/filters/use-customer-group-table-filters.tsx
index 8830b37a92..67fbb742c4 100644
--- a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-list/components/customer-group-list-table/use-customer-group-table-filters.tsx
+++ b/packages/admin-next/dashboard/src/hooks/table/filters/use-customer-group-table-filters.tsx
@@ -1,5 +1,5 @@
import { useTranslation } from "react-i18next"
-import { Filter } from "../../../../../components/table/data-table"
+import { Filter } from "../../../components/table/data-table"
export const useCustomerGroupTableFilters = () => {
const { t } = useTranslation()
diff --git a/packages/admin-next/dashboard/src/lib/client/customers.ts b/packages/admin-next/dashboard/src/lib/client/customers.ts
index 555ad1b9dc..d55b7ab05e 100644
--- a/packages/admin-next/dashboard/src/lib/client/customers.ts
+++ b/packages/admin-next/dashboard/src/lib/client/customers.ts
@@ -1,9 +1,10 @@
import {
AdminCustomerListResponse,
AdminCustomerResponse,
+ DeleteResponse,
} from "@medusajs/types"
import { CreateCustomerReq, UpdateCustomerReq } from "../../types/api-payloads"
-import { getRequest, postRequest } from "./common"
+import { deleteRequest, getRequest, postRequest } from "./common"
async function retrieveCustomer(id: string, query?: Record) {
return getRequest(`/admin/customers/${id}`, query)
@@ -21,9 +22,14 @@ async function updateCustomer(id: string, payload: UpdateCustomerReq) {
return postRequest(`/admin/customers/${id}`, payload)
}
+async function deleteCustomer(id: string) {
+ return deleteRequest(`/admin/customers/${id}`)
+}
+
export const customers = {
retrieve: retrieveCustomer,
list: listCustomers,
create: createCustomer,
update: updateCustomer,
+ delete: deleteCustomer,
}
diff --git a/packages/admin-next/dashboard/src/types/api-payloads.ts b/packages/admin-next/dashboard/src/types/api-payloads.ts
index 0792736f2a..9b06cc0c60 100644
--- a/packages/admin-next/dashboard/src/types/api-payloads.ts
+++ b/packages/admin-next/dashboard/src/types/api-payloads.ts
@@ -49,7 +49,7 @@ export type UpdateApiKeyReq = UpdateApiKeyDTO
// Customers
export type CreateCustomerReq = CreateCustomerDTO
-export type UpdateCustomerReq = UpdateCustomerDTO
+export type UpdateCustomerReq = Omit
// Sales Channels
export type CreateSalesChannelReq = CreateSalesChannelDTO
diff --git a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-add-customers/components/add-customers-form/add-customers-form.tsx b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-add-customers/components/add-customers-form/add-customers-form.tsx
index cb2234d4e5..1bc2cda529 100644
--- a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-add-customers/components/add-customers-form/add-customers-form.tsx
+++ b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-add-customers/components/add-customers-form/add-customers-form.tsx
@@ -1,44 +1,27 @@
import { zodResolver } from "@hookform/resolvers/zod"
-import { Customer } from "@medusajs/medusa"
-import { Button, Checkbox, Hint, Table, Tooltip, clx } from "@medusajs/ui"
+import { Button, Checkbox, Hint, Tooltip, toast } from "@medusajs/ui"
import {
OnChangeFn,
- PaginationState,
RowSelectionState,
createColumnHelper,
- flexRender,
- getCoreRowModel,
- useReactTable,
} from "@tanstack/react-table"
-import {
- adminCustomerKeys,
- useAdminAddCustomersToCustomerGroup,
- useAdminCustomers,
-} from "medusa-react"
import { useEffect, useMemo, useState } from "react"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import * as zod from "zod"
-import {
- NoRecords,
- NoResults,
-} from "../../../../../components/common/empty-table-content"
-import { Query } from "../../../../../components/filtering/query"
-import { LocalizedTablePagination } from "../../../../../components/localization/localized-table-pagination"
+import { AdminCustomerResponse } from "@medusajs/types"
import {
RouteFocusModal,
useRouteModal,
} from "../../../../../components/route-modal"
-import { useQueryParams } from "../../../../../hooks/use-query-params"
-import { queryClient } from "../../../../../lib/medusa"
-import { useCustomers } from "../../../../../hooks/api/customers"
+import { DataTable } from "../../../../../components/table/data-table"
import { useAddCustomersToGroup } from "../../../../../hooks/api/customer-groups"
-import { useDataTable } from "../../../../../hooks/use-data-table"
+import { useCustomers } from "../../../../../hooks/api/customers"
+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 { DataTable } from "../../../../../components/table/data-table"
-import { AdminCustomerResponse } from "@medusajs/types"
+import { useDataTable } from "../../../../../hooks/use-data-table"
type AddCustomersFormProps = {
customerGroupId: string
@@ -65,19 +48,6 @@ export const AddCustomersForm = ({
const { setValue } = form
- const [{ pageIndex, pageSize }, setPagination] = useState({
- pageIndex: 0,
- pageSize: PAGE_SIZE,
- })
-
- const pagination = useMemo(
- () => ({
- pageIndex,
- pageSize,
- }),
- [pageIndex, pageSize]
- )
-
const [rowSelection, setRowSelection] = useState({})
useEffect(() => {
@@ -130,8 +100,7 @@ export const AddCustomersForm = ({
},
})
- const { mutateAsync, isLoading: isMutating } =
- useAddCustomersToGroup(customerGroupId)
+ const { mutateAsync, isPending } = useAddCustomersToGroup(customerGroupId)
const handleSubmit = form.handleSubmit(async (data) => {
await mutateAsync(
@@ -140,6 +109,13 @@ export const AddCustomersForm = ({
},
{
onSuccess: () => {
+ toast.success(t("general.success"), {
+ description: t("customerGroups.customers.add.successToast", {
+ count: data.customer_ids.length,
+ }),
+ dismissLabel: t("actions.close"),
+ })
+
handleSuccess(`/customer-groups/${customerGroupId}`)
},
}
@@ -172,13 +148,13 @@ export const AddCustomersForm = ({
type="submit"
variant="primary"
size="small"
- isLoading={isMutating}
+ isLoading={isPending}
>
- {t("general.add")}
+ {t("actions.save")}
-
+
@@ -207,6 +184,7 @@ const columnHelper = createColumnHelper()
const useColumns = () => {
const { t } = useTranslation()
+ const base = useCustomerTableColumns()
const columns = useMemo(
() => [
@@ -244,7 +222,7 @@ const useColumns = () => {
if (isPreSelected) {
return (
{Component}
@@ -255,23 +233,9 @@ const useColumns = () => {
return Component
},
}),
- columnHelper.accessor("email", {
- header: t("fields.email"),
- cell: ({ getValue }) => getValue(),
- }),
- columnHelper.display({
- id: "name",
- header: t("fields.name"),
- cell: ({ row }) => {
- const name = [row.original.first_name, row.original.last_name]
- .filter(Boolean)
- .join(" ")
-
- return name || "-"
- },
- }),
+ ...base,
],
- [t]
+ [t, base]
)
return columns
diff --git a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-create/components/create-customer-group-form/create-customer-group-form.tsx b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-create/components/create-customer-group-form/create-customer-group-form.tsx
index 8e0587269b..b6858d1aeb 100644
--- a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-create/components/create-customer-group-form/create-customer-group-form.tsx
+++ b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-create/components/create-customer-group-form/create-customer-group-form.tsx
@@ -1,6 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod"
-import { Button, Heading, Input, Text } from "@medusajs/ui"
-import { useAdminCreateCustomerGroup } from "medusa-react"
+import { Button, Heading, Input, Text, toast } from "@medusajs/ui"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import * as zod from "zod"
@@ -27,7 +26,7 @@ export const CreateCustomerGroupForm = () => {
resolver: zodResolver(CreateCustomerGroupSchema),
})
- const { mutateAsync, isLoading } = useCreateCustomerGroup()
+ const { mutateAsync, isPending } = useCreateCustomerGroup()
const handleSubmit = form.handleSubmit(async (data) => {
await mutateAsync(
@@ -36,8 +35,21 @@ export const CreateCustomerGroupForm = () => {
},
{
onSuccess: ({ customer_group }) => {
+ toast.success(t("general.success"), {
+ description: t("customerGroups.create.successToast", {
+ name: customer_group.name,
+ }),
+ dismissLabel: t("actions.close"),
+ })
+
handleSuccess(`/customer-groups/${customer_group.id}`)
},
+ onError: (error) => {
+ toast.error(t("general.error"), {
+ description: error.message,
+ dismissLabel: t("actions.close"),
+ })
+ },
}
)
})
@@ -56,7 +68,7 @@ export const CreateCustomerGroupForm = () => {
type="submit"
variant="primary"
size="small"
- isLoading={isLoading}
+ isLoading={isPending}
>
{t("actions.create")}
@@ -65,9 +77,9 @@ export const CreateCustomerGroupForm = () => {
- {t("customerGroups.createCustomerGroup")}
+ {t("customerGroups.create.header")}
- {t("customerGroups.createCustomerGroupHint")}
+ {t("customerGroups.create.hint")}
diff --git a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-detail/components/customer-group-customer-section/customer-group-customer-section.tsx b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-detail/components/customer-group-customer-section/customer-group-customer-section.tsx
index 4ebf1aa742..0ecb8420d6 100644
--- a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-detail/components/customer-group-customer-section/customer-group-customer-section.tsx
+++ b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-detail/components/customer-group-customer-section/customer-group-customer-section.tsx
@@ -1,27 +1,22 @@
-import { Button, Checkbox, Container, Heading, usePrompt } from "@medusajs/ui"
-import { Customer } from "@medusajs/medusa"
import { PencilSquare, Trash } from "@medusajs/icons"
import {
- adminCustomerGroupKeys,
- useAdminCustomPost,
- useAdminCustomerGroupCustomers,
-} from "medusa-react"
+ AdminCustomerGroupResponse,
+ AdminCustomerResponse,
+} from "@medusajs/types"
+import { Button, Checkbox, Container, Heading, usePrompt } from "@medusajs/ui"
+import { RowSelectionState, createColumnHelper } from "@tanstack/react-table"
+import { useMemo, useState } 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 { useRemoveCustomersFromGroup } from "../../../../../hooks/api/customer-groups"
+import { useCustomers } from "../../../../../hooks/api/customers"
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: AdminCustomerGroupResponse["customer_group"]
@@ -72,8 +67,10 @@ export const CustomerGroupCustomerSection = ({
const keys = Object.keys(rowSelection)
const res = await prompt({
- title: t("general.areYouSure"),
- description: t("customerGroups.removeCustomersWarning", {
+ title: t("customerGroups.customers.remove.title", {
+ count: keys.length,
+ }),
+ description: t("customerGroups.customers.remove.description", {
count: keys.length,
}),
confirmText: t("actions.continue"),
@@ -151,8 +148,10 @@ const CustomerActions = ({
const handleRemove = async () => {
const res = await prompt({
- title: t("general.areYouSure"),
- description: t("customerGroups.removeCustomersWarning", {
+ title: t("customerGroups.customers.remove.title", {
+ count: 1,
+ }),
+ description: t("customerGroups.customers.remove.description", {
count: 1,
}),
confirmText: t("actions.continue"),
diff --git a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-detail/components/customer-group-general-section/customer-group-general-section.tsx b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-detail/components/customer-group-general-section/customer-group-general-section.tsx
index 7b1cbd8fe4..b0294acb6f 100644
--- a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-detail/components/customer-group-general-section/customer-group-general-section.tsx
+++ b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-detail/components/customer-group-general-section/customer-group-general-section.tsx
@@ -1,9 +1,9 @@
import { PencilSquare, Trash } from "@medusajs/icons"
-import { Container, Heading } from "@medusajs/ui"
+import { AdminCustomerGroupResponse } from "@medusajs/types"
+import { Container, Heading, Text, toast, usePrompt } from "@medusajs/ui"
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 = {
@@ -14,43 +14,80 @@ export const CustomerGroupGeneralSection = ({
group,
}: CustomerGroupGeneralSectionProps) => {
const { t } = useTranslation()
+ const prompt = usePrompt()
const navigate = useNavigate()
const { mutateAsync } = useDeleteCustomerGroup(group.id)
const handleDelete = async () => {
+ const res = await prompt({
+ title: t("customerGroups.delete.title"),
+ description: t("customerGroups.delete.description", {
+ name: group.name,
+ }),
+ confirmText: t("actions.delete"),
+ cancelText: t("actions.cancel"),
+ })
+
+ if (!res) {
+ return
+ }
+
await mutateAsync(undefined, {
onSuccess: () => {
+ toast.success(t("general.success"), {
+ description: t("customerGroups.delete.successToast", {
+ name: group.name,
+ }),
+ dismissLabel: t("actions.close"),
+ })
+
navigate("/customer-groups", { replace: true })
},
+ onError: (error) => {
+ toast.error(t("general.error"), {
+ description: error.message,
+ dismissLabel: t("actions.close"),
+ })
+ },
})
}
return (
-
- {group.name}
- ,
- label: t("actions.edit"),
- to: `/customer-groups/${group.id}/edit`,
- },
- ],
- },
- {
- actions: [
- {
- icon: ,
- label: t("actions.delete"),
- onClick: handleDelete,
- },
- ],
- },
- ]}
- />
+
+
+
{group.name}
+
,
+ label: t("actions.edit"),
+ to: `/customer-groups/${group.id}/edit`,
+ },
+ ],
+ },
+ {
+ actions: [
+ {
+ icon:
,
+ label: t("actions.delete"),
+ onClick: handleDelete,
+ },
+ ],
+ },
+ ]}
+ />
+
+
+
+ {t("customers.domain")}
+
+
+ {group.customers?.length || "-"}
+
+
)
}
diff --git a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-detail/customer-group-detail.tsx b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-detail/customer-group-detail.tsx
index eebcbe7c54..1dc9437984 100644
--- a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-detail/customer-group-detail.tsx
+++ b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-detail/customer-group-detail.tsx
@@ -1,9 +1,9 @@
-import { Outlet, json, useLoaderData, useParams } from "react-router-dom"
+import { Outlet, useLoaderData, useParams } from "react-router-dom"
import { JsonViewSection } from "../../../components/common/json-view-section"
+import { useCustomerGroup } from "../../../hooks/api/customer-groups"
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<
@@ -13,20 +13,18 @@ export const CustomerGroupDetail = () => {
const { id } = useParams()
const { customer_group, isLoading, isError, error } = useCustomerGroup(
id!,
- undefined,
+ {
+ fields: "+customers.id",
+ },
{ initialData }
)
- if (isLoading) {
+ if (isLoading || !customer_group) {
return Loading...
}
- if (isError || !customer_group) {
- if (error) {
- throw error
- }
-
- throw json("An unknown error occurred", 500)
+ if (isError) {
+ throw error
}
return (
diff --git a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-detail/loader.ts b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-detail/loader.ts
index a2beb15d28..cbe15af277 100644
--- a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-detail/loader.ts
+++ b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-detail/loader.ts
@@ -7,7 +7,10 @@ import { medusa, queryClient } from "../../../lib/medusa"
const customerGroupDetailQuery = (id: string) => ({
queryKey: adminProductKeys.detail(id),
- queryFn: async () => medusa.admin.customerGroups.retrieve(id),
+ queryFn: async () =>
+ medusa.admin.customerGroups.retrieve(id, {
+ fields: "+customers.id",
+ }),
})
export const customerGroupLoader = async ({ params }: LoaderFunctionArgs) => {
diff --git a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-edit/components/edit-customer-group-form/edit-customer-group-form.tsx b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-edit/components/edit-customer-group-form/edit-customer-group-form.tsx
index 4e75daccbe..cdaa4e17f4 100644
--- a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-edit/components/edit-customer-group-form/edit-customer-group-form.tsx
+++ b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-edit/components/edit-customer-group-form/edit-customer-group-form.tsx
@@ -1,6 +1,6 @@
import { zodResolver } from "@hookform/resolvers/zod"
-import { Button, Input } from "@medusajs/ui"
-import { useAdminUpdateCustomerGroup } from "medusa-react"
+import { AdminCustomerGroupResponse } from "@medusajs/types"
+import { Button, Input, toast } from "@medusajs/ui"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import * as z from "zod"
@@ -9,7 +9,6 @@ import {
RouteDrawer,
useRouteModal,
} from "../../../../../components/route-modal"
-import { AdminCustomerGroupResponse } from "@medusajs/types"
import { useUpdateCustomerGroup } from "../../../../../hooks/api/customer-groups"
type EditCustomerGroupFormProps = {
@@ -33,11 +32,18 @@ export const EditCustomerGroupForm = ({
resolver: zodResolver(EditCustomerGroupSchema),
})
- const { mutateAsync, isLoading } = useUpdateCustomerGroup(group.id)
+ const { mutateAsync, isPending } = useUpdateCustomerGroup(group.id)
const handleSubmit = form.handleSubmit(async (data) => {
await mutateAsync(data, {
- onSuccess: () => {
+ onSuccess: ({ customer_group }) => {
+ toast.success(t("general.success"), {
+ description: t("customerGroups.edit.successToast", {
+ name: customer_group.name,
+ }),
+ dismissLabel: t("actions.close"),
+ })
+
handleSuccess()
},
})
@@ -73,7 +79,7 @@ export const EditCustomerGroupForm = ({
{t("actions.cancel")}
-
diff --git a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-edit/customer-group-edit.tsx b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-edit/customer-group-edit.tsx
index 33d2114e26..72c90d3943 100644
--- a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-edit/customer-group-edit.tsx
+++ b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-edit/customer-group-edit.tsx
@@ -2,8 +2,8 @@ import { Heading } from "@medusajs/ui"
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"
+import { EditCustomerGroupForm } from "./components/edit-customer-group-form"
export const CustomerGroupEdit = () => {
const { id } = useParams()
@@ -18,7 +18,7 @@ export const CustomerGroupEdit = () => {
return (
- {t("customerGroups.editCustomerGroup")}
+ {t("customerGroups.edit.header")}
{!isLoading && customer_group && (
diff --git a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-list/components/customer-group-list-table/customer-group-list-table.tsx b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-list/components/customer-group-list-table/customer-group-list-table.tsx
index f508209e87..8aa7f03865 100644
--- a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-list/components/customer-group-list-table/customer-group-list-table.tsx
+++ b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-list/components/customer-group-list-table/customer-group-list-table.tsx
@@ -1,12 +1,21 @@
-import { Button, Container, Heading } from "@medusajs/ui"
+import { PencilSquare, Trash } from "@medusajs/icons"
+import { AdminCustomerGroupResponse } from "@medusajs/types"
+import { Button, Container, Heading, toast, usePrompt } from "@medusajs/ui"
+import { createColumnHelper } from "@tanstack/react-table"
+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 {
+ useCustomerGroups,
+ useDeleteCustomerGroup,
+} from "../../../../../hooks/api/customer-groups"
+import { useCustomerGroupTableColumns } from "../../../../../hooks/table/columns/use-customer-group-table-columns"
+import { useCustomerGroupTableFilters } from "../../../../../hooks/table/filters/use-customer-group-table-filters"
+import { useCustomerGroupTableQuery } from "../../../../../hooks/table/query/use-customer-group-table-query"
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
@@ -23,7 +32,7 @@ export const CustomerGroupListTable = () => {
})
const filters = useCustomerGroupTableFilters()
- const columns = useCustomerGroupTableColumns()
+ const columns = useColumns()
const { table } = useDataTable({
data: customer_groups ?? [],
@@ -64,3 +73,89 @@ export const CustomerGroupListTable = () => {
)
}
+
+const CustomerGroupRowActions = ({
+ group,
+}: {
+ group: AdminCustomerGroupResponse["customer_group"]
+}) => {
+ const { t } = useTranslation()
+ const prompt = usePrompt()
+
+ const { mutateAsync } = useDeleteCustomerGroup(group.id)
+
+ const handleDelete = async () => {
+ const res = await prompt({
+ title: t("customerGroups.delete.title"),
+ description: t("customerGroups.delete.description", {
+ name: group.name,
+ }),
+ confirmText: t("actions.delete"),
+ cancelText: t("actions.cancel"),
+ })
+
+ if (!res) {
+ return
+ }
+
+ await mutateAsync(undefined, {
+ onSuccess: () => {
+ toast.success(t("general.success"), {
+ description: t("customerGroups.delete.successToast", {
+ name: group.name,
+ }),
+ dismissLabel: t("actions.close"),
+ })
+ },
+ onError: (error) => {
+ toast.error(t("general.error"), {
+ description: error.message,
+ dismissLabel: t("actions.close"),
+ })
+ },
+ })
+ }
+
+ return (
+ ,
+ },
+ ],
+ },
+ {
+ actions: [
+ {
+ label: t("actions.delete"),
+ onClick: handleDelete,
+ icon: ,
+ },
+ ],
+ },
+ ]}
+ />
+ )
+}
+
+const columnHelper =
+ createColumnHelper()
+
+const useColumns = () => {
+ const columns = useCustomerGroupTableColumns()
+
+ return useMemo(
+ () => [
+ ...columns,
+ columnHelper.display({
+ id: "actions",
+ cell: ({ row }) => ,
+ }),
+ ],
+ [columns]
+ )
+}
diff --git a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-list/components/customer-group-list-table/use-customer-group-table-columns.tsx b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-list/components/customer-group-list-table/use-customer-group-table-columns.tsx
deleted file mode 100644
index 49c747a348..0000000000
--- a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-list/components/customer-group-list-table/use-customer-group-table-columns.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { PencilSquare, Trash } from "@medusajs/icons"
-import { usePrompt } from "@medusajs/ui"
-import { createColumnHelper } from "@tanstack/react-table"
-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()
-
-export const useCustomerGroupTableColumns = () => {
- const { t } = useTranslation()
-
- return useMemo(
- () => [
- columnHelper.accessor("name", {
- header: t("fields.name"),
- cell: ({ getValue }) => getValue(),
- }),
- columnHelper.accessor("customers", {
- header: t("customers.domain"),
- cell: ({ getValue }) => {
- const count = getValue()?.length ?? 0
-
- return count
- },
- }),
- columnHelper.display({
- id: "actions",
- cell: ({ row }) => ,
- }),
- ],
- [t]
- )
-}
-
-const CustomerGroupActions = ({
- group,
-}: {
- group: AdminCustomerGroupResponse["customer_group"]
-}) => {
- const { t } = useTranslation()
- const prompt = usePrompt()
-
- const { mutateAsync } = useDeleteCustomerGroup(group.id)
-
- const handleDelete = async () => {
- const res = await prompt({
- title: t("general.areYouSure"),
- description: t("customerGroups.deleteCustomerGroupWarning", {
- name: group.name,
- }),
- confirmText: t("actions.delete"),
- cancelText: t("actions.cancel"),
- })
-
- if (!res) {
- return
- }
-
- await mutateAsync()
- }
-
- return (
- ,
- },
- ],
- },
- {
- actions: [
- {
- label: t("actions.delete"),
- onClick: handleDelete,
- icon: ,
- },
- ],
- },
- ]}
- />
- )
-}
diff --git a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-list/components/customer-group-list-table/use-customer-group-table-query.tsx b/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-list/components/customer-group-list-table/use-customer-group-table-query.tsx
deleted file mode 100644
index 2a714182d6..0000000000
--- a/packages/admin-next/dashboard/src/v2-routes/customer-groups/customer-group-list/components/customer-group-list-table/use-customer-group-table-query.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { useQueryParams } from "../../../../../hooks/use-query-params"
-
-type UseCustomerGroupTableQueryProps = {
- prefix?: string
- pageSize?: number
-}
-
-export const useCustomerGroupTableQuery = ({
- prefix,
- pageSize = 20,
-}: UseCustomerGroupTableQueryProps) => {
- const queryObject = useQueryParams(
- ["offset", "q", "order", "created_at", "updated_at"],
- prefix
- )
-
- const { offset, created_at, updated_at, q, order } = queryObject
-
- const searchParams = {
- limit: pageSize,
- offset: offset ? Number(offset) : 0,
- order,
- created_at: created_at ? JSON.parse(created_at) : undefined,
- updated_at: updated_at ? JSON.parse(updated_at) : undefined,
- q,
- }
-
- return {
- searchParams,
- raw: queryObject,
- }
-}
diff --git a/packages/admin-next/dashboard/src/v2-routes/customers/customer-create/components/create-customer-form/create-customer-form.tsx b/packages/admin-next/dashboard/src/v2-routes/customers/customer-create/components/create-customer-form/create-customer-form.tsx
index 171e44294a..820f621971 100644
--- a/packages/admin-next/dashboard/src/v2-routes/customers/customer-create/components/create-customer-form/create-customer-form.tsx
+++ b/packages/admin-next/dashboard/src/v2-routes/customers/customer-create/components/create-customer-form/create-customer-form.tsx
@@ -1,33 +1,37 @@
+import { zodResolver } from "@hookform/resolvers/zod"
+import { Button, Heading, Input, Text, toast } from "@medusajs/ui"
+import { useForm } from "react-hook-form"
+import { useTranslation } from "react-i18next"
import * as zod from "zod"
-import { Button, Heading, Input, Text } from "@medusajs/ui"
+
+import { Form } from "../../../../../components/common/form"
import {
RouteFocusModal,
useRouteModal,
} from "../../../../../components/route-modal"
-import { Form } from "../../../../../components/common/form"
-import { useForm } from "react-hook-form"
-import { useTranslation } from "react-i18next"
-import { zodResolver } from "@hookform/resolvers/zod"
import { useCreateCustomer } from "../../../../../hooks/api/customers"
const CreateCustomerSchema = zod.object({
email: zod.string().email(),
- first_name: zod.string().min(1),
- last_name: zod.string().min(1),
- phone: zod.string().min(1).optional(),
+ first_name: zod.string().optional(),
+ last_name: zod.string().optional(),
+ company_name: zod.string().optional(),
+ phone: zod.string().optional(),
})
export const CreateCustomerForm = () => {
const { t } = useTranslation()
const { handleSuccess } = useRouteModal()
- const { mutateAsync, isLoading } = useCreateCustomer()
+ const { mutateAsync, isPending } = useCreateCustomer()
const form = useForm>({
defaultValues: {
email: "",
first_name: "",
last_name: "",
+ phone: "",
+ company_name: "",
},
resolver: zodResolver(CreateCustomerSchema),
})
@@ -35,15 +39,28 @@ export const CreateCustomerForm = () => {
const handleSubmit = form.handleSubmit(async (data) => {
await mutateAsync(
{
- email: data.email,
- first_name: data.first_name,
- last_name: data.last_name,
- phone: data.phone,
+ email: data.email || undefined,
+ first_name: data.first_name || undefined,
+ last_name: data.last_name || undefined,
+ company_name: data.company_name || undefined,
+ phone: data.phone || undefined,
},
{
onSuccess: ({ customer }) => {
+ toast.success(t("general.success"), {
+ description: t("customers.create.successToast", {
+ email: customer.email,
+ }),
+ dismissLabel: t("actions.close"),
+ })
handleSuccess(`/customers/${customer.id}`)
},
+ onError: (error) => {
+ toast.error(t("general.error"), {
+ description: error.message,
+ dismissLabel: t("actions.close"),
+ })
+ },
}
)
})
@@ -62,7 +79,7 @@ export const CreateCustomerForm = () => {
size="small"
variant="primary"
type="submit"
- isLoading={isLoading}
+ isLoading={isPending}
>
{t("actions.create")}
@@ -71,19 +88,19 @@ export const CreateCustomerForm = () => {
- {t("customers.createCustomer")}
+ {t("customers.create.header")}
- {t("customers.createCustomerHint")}
+ {t("customers.create.hint")}
-
+
{
return (
- {t("fields.firstName")}
+ {t("fields.firstName")}
@@ -98,7 +115,7 @@ export const CreateCustomerForm = () => {
render={({ field }) => {
return (
- {t("fields.lastName")}
+ {t("fields.lastName")}
@@ -122,6 +139,21 @@ export const CreateCustomerForm = () => {
)
}}
/>
+ {
+ return (
+
+ {t("fields.company")}
+
+
+
+
+
+ )
+ }}
+ />
{
const { t } = useTranslation()
+ const prompt = usePrompt()
+ const navigate = useNavigate()
+
+ const { mutateAsync } = useDeleteCustomer(customer.id)
const name = [customer.first_name, customer.last_name]
.filter(Boolean)
@@ -19,8 +32,44 @@ export const CustomerGeneralSection = ({
const statusColor = customer.has_account ? "green" : "orange"
const statusText = customer.has_account
- ? t("customers.registered")
- : t("customers.guest")
+ ? t("customers.fields.registered")
+ : t("customers.fields.guest")
+
+ const handleDelete = async () => {
+ const res = await prompt({
+ title: t("customers.delete.title"),
+ description: t("customers.delete.description", {
+ email: customer.email,
+ }),
+ verificationInstruction: t("general.typeToConfirm"),
+ verificationText: customer.email,
+ confirmText: t("actions.delete"),
+ cancelText: t("actions.cancel"),
+ })
+
+ if (!res) {
+ return
+ }
+
+ await mutateAsync(undefined, {
+ onSuccess: () => {
+ toast.success(t("general.success"), {
+ description: t("customers.delete.successToast", {
+ email: customer.email,
+ }),
+ dismissLabel: t("actions.close"),
+ })
+
+ navigate("/customers", { replace: true })
+ },
+ onError: (error) => {
+ toast.error(t("general.error"), {
+ description: error.message,
+ dismissLabel: t("actions.close"),
+ })
+ },
+ })
+ }
return (
@@ -39,6 +88,15 @@ export const CustomerGeneralSection = ({
},
],
},
+ {
+ actions: [
+ {
+ label: t("actions.delete"),
+ icon: ,
+ onClick: handleDelete,
+ },
+ ],
+ },
]}
/>
@@ -48,7 +106,15 @@ export const CustomerGeneralSection = ({
{t("fields.name")}
- {name ?? "-"}
+ {name || "-"}
+
+
+
+
+ {t("fields.company")}
+
+
+ {customer.company_name || "-"}
@@ -56,7 +122,7 @@ export const CustomerGeneralSection = ({
{t("fields.phone")}
- {customer.phone ?? "-"}
+ {customer.phone || "-"}
diff --git a/packages/admin-next/dashboard/src/v2-routes/customers/customer-detail/components/customer-group-section/customer-group-section.tsx b/packages/admin-next/dashboard/src/v2-routes/customers/customer-detail/components/customer-group-section/customer-group-section.tsx
index 2aa6c3a349..c8e30d7ba4 100644
--- a/packages/admin-next/dashboard/src/v2-routes/customers/customer-detail/components/customer-group-section/customer-group-section.tsx
+++ b/packages/admin-next/dashboard/src/v2-routes/customers/customer-detail/components/customer-group-section/customer-group-section.tsx
@@ -1,17 +1,22 @@
-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 { Container, Heading } from "@medusajs/ui"
+import { createColumnHelper } from "@tanstack/react-table"
import { t } from "i18next"
+import { useMemo } from "react"
+
+import { PencilSquare } from "@medusajs/icons"
+import { keepPreviousData } from "@tanstack/react-query"
+import { useTranslation } from "react-i18next"
+import { ActionMenu } from "../../../../../components/common/action-menu"
+import { DataTable } from "../../../../../components/table/data-table"
+import { useCustomerGroups } from "../../../../../hooks/api/customer-groups"
+import { useCustomerGroupTableColumns } from "../../../../../hooks/table/columns/use-customer-group-table-columns"
+import { useCustomerGroupTableFilters } from "../../../../../hooks/table/filters/use-customer-group-table-filters"
+import { useCustomerGroupTableQuery } from "../../../../../hooks/table/query/use-customer-group-table-query"
+import { useDataTable } from "../../../../../hooks/use-data-table"
type CustomerGroupSectionProps = {
customer: AdminCustomerResponse["customer"]
@@ -22,10 +27,20 @@ const PAGE_SIZE = 10
export const CustomerGroupSection = ({
customer,
}: CustomerGroupSectionProps) => {
+ const { raw, searchParams } = useCustomerGroupTableQuery({
+ pageSize: PAGE_SIZE,
+ })
const { customer_groups, count, isLoading, isError, error } =
- useCustomerGroups({
- customers: { id: customer.id },
- })
+ useCustomerGroups(
+ {
+ ...searchParams,
+ fields: "+customers.id",
+ customers: { id: customer.id },
+ },
+ {
+ placeholderData: keepPreviousData,
+ }
+ )
const filters = useCustomerGroupTableFilters()
const columns = useColumns()
@@ -64,27 +79,51 @@ export const CustomerGroupSection = ({
search
pagination
orderBy={["name", "created_at", "updated_at"]}
+ queryObject={raw}
/>
)
}
+// TODO: Add remove association when /customer-groups/:id/batch has been created.
+const CustomerGroupRowActions = ({
+ group,
+}: {
+ group: AdminCustomerGroupResponse["customer_group"]
+}) => {
+ const { t } = useTranslation()
+
+ return (
+
,
+ to: `/customer-groups/${group.id}/edit`,
+ },
+ ],
+ },
+ ]}
+ />
+ )
+}
+
const columnHelper =
createColumnHelper
()
const useColumns = () => {
- const { t } = useTranslation()
+ const columns = useCustomerGroupTableColumns()
return useMemo(
() => [
+ ...columns,
columnHelper.display({
- id: "select",
- }),
- columnHelper.accessor("name", {
- header: t("fields.name"),
- cell: ({ getValue }) => getValue(),
+ id: "actions",
+ cell: ({ row }) => ,
}),
],
- [t]
+ [columns]
)
}
diff --git a/packages/admin-next/dashboard/src/v2-routes/customers/customer-detail/customer-detail.tsx b/packages/admin-next/dashboard/src/v2-routes/customers/customer-detail/customer-detail.tsx
index 3e1c6b8d3c..6a616b251d 100644
--- a/packages/admin-next/dashboard/src/v2-routes/customers/customer-detail/customer-detail.tsx
+++ b/packages/admin-next/dashboard/src/v2-routes/customers/customer-detail/customer-detail.tsx
@@ -1,9 +1,9 @@
-import { Outlet, json, useLoaderData, useParams } from "react-router-dom"
-import { CustomerGeneralSection } from "./components/customer-general-section"
+import { Outlet, useLoaderData, useParams } from "react-router-dom"
import { JsonViewSection } from "../../../components/common/json-view-section"
-import { customerLoader } from "./loader"
import { useCustomer } from "../../../hooks/api/customers"
+import { CustomerGeneralSection } from "./components/customer-general-section"
import { CustomerGroupSection } from "./components/customer-group-section"
+import { customerLoader } from "./loader"
export const CustomerDetail = () => {
const { id } = useParams()
@@ -15,16 +15,12 @@ export const CustomerDetail = () => {
initialData,
})
- if (isLoading) {
+ if (isLoading || !customer) {
return Loading...
}
- if (isError || !customer) {
- if (error) {
- throw error
- }
-
- throw json("An unknown error occurred", 500)
+ if (isError) {
+ throw error
}
return (
diff --git a/packages/admin-next/dashboard/src/v2-routes/customers/customer-edit/components/edit-customer-form/edit-customer-form.tsx b/packages/admin-next/dashboard/src/v2-routes/customers/customer-edit/components/edit-customer-form/edit-customer-form.tsx
index 3507eb8b9c..c0fe4e4d28 100644
--- a/packages/admin-next/dashboard/src/v2-routes/customers/customer-edit/components/edit-customer-form/edit-customer-form.tsx
+++ b/packages/admin-next/dashboard/src/v2-routes/customers/customer-edit/components/edit-customer-form/edit-customer-form.tsx
@@ -1,14 +1,15 @@
+import { zodResolver } from "@hookform/resolvers/zod"
+import { AdminCustomerResponse } from "@medusajs/types"
+import { Button, Input, toast } from "@medusajs/ui"
+import { useForm } from "react-hook-form"
+import { useTranslation } from "react-i18next"
import * as zod from "zod"
-import { Button, Input } from "@medusajs/ui"
+import { ConditionalTooltip } from "../../../../../components/common/conditional-tooltip"
+import { Form } from "../../../../../components/common/form"
import {
RouteDrawer,
useRouteModal,
} from "../../../../../components/route-modal"
-import { Form } from "../../../../../components/common/form"
-import { useForm } from "react-hook-form"
-import { useTranslation } from "react-i18next"
-import { zodResolver } from "@hookform/resolvers/zod"
-import { AdminCustomerResponse } from "@medusajs/types"
import { useUpdateCustomer } from "../../../../../hooks/api/customers"
type EditCustomerFormProps = {
@@ -17,8 +18,9 @@ type EditCustomerFormProps = {
const EditCustomerSchema = zod.object({
email: zod.string().email(),
- first_name: zod.string().min(1).optional(),
- last_name: zod.string().min(1).optional(),
+ first_name: zod.string().optional(),
+ last_name: zod.string().optional(),
+ company_name: zod.string().optional(),
phone: zod.string().optional(),
})
@@ -31,25 +33,40 @@ export const EditCustomerForm = ({ customer }: EditCustomerFormProps) => {
email: customer.email || "",
first_name: customer.first_name || "",
last_name: customer.last_name || "",
+ company_name: customer.company_name || "",
phone: customer.phone || "",
},
resolver: zodResolver(EditCustomerSchema),
})
- const { mutateAsync, isLoading } = useUpdateCustomer(customer.id)
+ const { mutateAsync, isPending } = useUpdateCustomer(customer.id)
const handleSubmit = form.handleSubmit(async (data) => {
await mutateAsync(
{
email: customer.has_account ? undefined : data.email,
- first_name: data.first_name,
- last_name: data.last_name,
- phone: data.phone,
+ first_name: data.first_name || null,
+ last_name: data.last_name || null,
+ phone: data.phone || null,
+ company_name: data.company_name || null,
},
{
- onSuccess: () => {
+ onSuccess: ({ customer }) => {
+ toast.success(t("general.success"), {
+ description: t("customers.edit.successToast", {
+ email: customer.email,
+ }),
+ dismissLabel: t("actions.close"),
+ })
+
handleSuccess()
},
+ onError: (error) => {
+ toast.error(t("general.error"), {
+ description: error.message,
+ dismissLabel: t("actions.close"),
+ })
+ },
}
)
})
@@ -67,7 +84,12 @@ export const EditCustomerForm = ({ customer }: EditCustomerFormProps) => {
{t("fields.email")}
-
+
+
+
@@ -104,6 +126,21 @@ export const EditCustomerForm = ({ customer }: EditCustomerFormProps) => {
)
}}
/>
+ {
+ return (
+
+ {t("fields.company")}
+
+
+
+
+
+ )
+ }}
+ />
{
{
const { t } = useTranslation()
const { searchParams, raw } = useCustomerTableQuery({ pageSize: PAGE_SIZE })
- const { customers, count, isLoading, isError, error } = useCustomers({
- ...searchParams,
- })
+ const { customers, count, isLoading, isError, error } = useCustomers(
+ {
+ ...searchParams,
+ },
+ {
+ placeholderData: keepPreviousData,
+ }
+ )
const filters = useCustomerTableFilters()
const columns = useColumns()
@@ -110,5 +117,5 @@ const useColumns = () => {
}),
],
[columns]
- ) as ColumnDef[]
+ )
}
diff --git a/packages/medusa/src/api-v2/admin/customers/validators.ts b/packages/medusa/src/api-v2/admin/customers/validators.ts
index 871a3efd7a..a540bad30c 100644
--- a/packages/medusa/src/api-v2/admin/customers/validators.ts
+++ b/packages/medusa/src/api-v2/admin/customers/validators.ts
@@ -44,14 +44,20 @@ export const AdminCustomersParams = createFindParams({
)
export const AdminCreateCustomer = z.object({
+ email: z.string().email().optional(),
company_name: z.string().optional(),
first_name: z.string().optional(),
last_name: z.string().optional(),
- email: z.string().optional(),
phone: z.string().optional(),
})
-export const AdminUpdateCustomer = AdminCreateCustomer
+export const AdminUpdateCustomer = z.object({
+ email: z.string().email().nullable().optional(),
+ company_name: z.string().nullable().optional(),
+ first_name: z.string().nullable().optional(),
+ last_name: z.string().nullable().optional(),
+ phone: z.string().nullable().optional(),
+})
export const AdminCreateCustomerAddress = z.object({
address_name: z.string().optional(),