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")} + + + + + + ) + }} + /> {