diff --git a/packages/admin-next/dashboard/src/i18n/translations/en.json b/packages/admin-next/dashboard/src/i18n/translations/en.json index be2b8d041f..9ee6da9fed 100644 --- a/packages/admin-next/dashboard/src/i18n/translations/en.json +++ b/packages/admin-next/dashboard/src/i18n/translations/en.json @@ -200,8 +200,6 @@ "attributes": "Attributes", "editProduct": "Edit Product", "editAttributes": "Edit Attributes", - "organization": "Organize", - "editOrganization": "Edit Organization", "editOptions": "Edit Options", "editPrices": "Edit prices", "media": { @@ -338,6 +336,15 @@ "successToast": "Option {{title}} was successfully created." } }, + "organization": { + "header": "Organize", + "edit": { + "header": "Edit Organization", + "toasts": { + "success": "Successfully updated the organization of {{title}}." + } + } + }, "toasts": { "delete": { "success": { diff --git a/packages/admin-next/dashboard/src/lib/client/client.ts b/packages/admin-next/dashboard/src/lib/client/client.ts index 0de119006b..0362707631 100644 --- a/packages/admin-next/dashboard/src/lib/client/client.ts +++ b/packages/admin-next/dashboard/src/lib/client/client.ts @@ -39,7 +39,7 @@ export const client = { salesChannels: salesChannels, shippingOptions: shippingOptions, shippingProfiles: shippingProfiles, - tags: tags, + productTags: tags, users: users, orders: orders, taxes: taxes, diff --git a/packages/admin-next/dashboard/src/lib/client/tags.ts b/packages/admin-next/dashboard/src/lib/client/tags.ts index 70e92c55f6..58ddc5b61c 100644 --- a/packages/admin-next/dashboard/src/lib/client/tags.ts +++ b/packages/admin-next/dashboard/src/lib/client/tags.ts @@ -1,11 +1,11 @@ import { getRequest } from "./common" async function listProductTags(query?: Record) { - return getRequest(`/admin/tags`, query) + return getRequest(`/admin/product-tags`, query) } async function retrieveProductTag(id: string, query?: Record) { - return getRequest(`/admin/tags/${id}`, query) + return getRequest(`/admin/product-tags/${id}`, query) } export const tags = { diff --git a/packages/admin-next/dashboard/src/routes/products/common/components/category-combobox/category-combobox.tsx b/packages/admin-next/dashboard/src/routes/products/common/components/category-combobox/category-combobox.tsx index 2eca3930b0..252d12bf01 100644 --- a/packages/admin-next/dashboard/src/routes/products/common/components/category-combobox/category-combobox.tsx +++ b/packages/admin-next/dashboard/src/routes/products/common/components/category-combobox/category-combobox.tsx @@ -156,7 +156,7 @@ export const CategoryCombobox = forwardRef< } return ( - +
+ data.product_tags.map((tag) => ({ + label: tag.value, + value: tag.id, + })), + }) + const { fields, remove, replace } = useFieldArray({ control: form.control, name: "sales_channels", @@ -53,7 +63,7 @@ export const ProductCreateOrganizationSection = ({ return (
- {t("products.organization")} + {t("products.organization.header")}
-
+
+ ) }} @@ -121,14 +133,16 @@ export const ProductCreateOrganizationSection = ({ options={collections.options} searchValue={collections.searchValue} onSearchValueChange={collections.onSearchValueChange} + fetchNextPage={collections.fetchNextPage} /> + ) }} />
-
+
+ + + ) + }} + /> + { + return ( + + + {t("products.fields.tags.label")} + + + + + ) }} diff --git a/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-organization-section/product-organization-section.tsx b/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-organization-section/product-organization-section.tsx index 0ed59bd62b..d0065c794f 100644 --- a/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-organization-section/product-organization-section.tsx +++ b/packages/admin-next/dashboard/src/routes/products/product-detail/components/product-organization-section/product-organization-section.tsx @@ -18,7 +18,7 @@ export const ProductOrganizationSection = ({ return (
- {t("products.organization")} + {t("products.organization.header")} ({ queryKey: productsQueryKeys.detail(id), - queryFn: async () => client.products.retrieve(id), + queryFn: async () => + client.products.retrieve(id, { fields: PRODUCT_DETAIL_FIELDS }), }) export const productLoader = async ({ params }: LoaderFunctionArgs) => { diff --git a/packages/admin-next/dashboard/src/routes/products/product-detail/product-detail.tsx b/packages/admin-next/dashboard/src/routes/products/product-detail/product-detail.tsx index 21a70c0d5d..ba8805e0e3 100644 --- a/packages/admin-next/dashboard/src/routes/products/product-detail/product-detail.tsx +++ b/packages/admin-next/dashboard/src/routes/products/product-detail/product-detail.tsx @@ -9,6 +9,7 @@ import { ProductOptionSection } from "./components/product-option-section" import { ProductOrganizationSection } from "./components/product-organization-section" import { ProductSalesChannelSection } from "./components/product-sales-channel-section" import { ProductVariantSection } from "./components/product-variant-section" +import { PRODUCT_DETAIL_FIELDS } from "./constants" import { productLoader } from "./loader" import after from "virtual:medusa/widgets/product/details/after" @@ -16,16 +17,19 @@ import before from "virtual:medusa/widgets/product/details/before" import sideAfter from "virtual:medusa/widgets/product/details/side/after" import sideBefore from "virtual:medusa/widgets/product/details/side/before" -// TODO: Use product domain translations only export const ProductDetail = () => { const initialData = useLoaderData() as Awaited< ReturnType > const { id } = useParams() - const { product, isLoading, isError, error } = useProduct(id!, undefined, { - initialData: initialData, - }) + const { product, isLoading, isError, error } = useProduct( + id!, + { fields: PRODUCT_DETAIL_FIELDS }, + { + initialData: initialData, + } + ) if (isLoading || !product) { return
Loading...
diff --git a/packages/admin-next/dashboard/src/routes/products/product-organization/components/product-organization-form/product-organization-form.tsx b/packages/admin-next/dashboard/src/routes/products/product-organization/components/product-organization-form/product-organization-form.tsx index a4d42b4de0..f6af72bdc6 100644 --- a/packages/admin-next/dashboard/src/routes/products/product-organization/components/product-organization-form/product-organization-form.tsx +++ b/packages/admin-next/dashboard/src/routes/products/product-organization/components/product-organization-form/product-organization-form.tsx @@ -1,6 +1,6 @@ import { zodResolver } from "@hookform/resolvers/zod" import { Product } from "@medusajs/medusa" -import { Button, Select } from "@medusajs/ui" +import { Button, toast } from "@medusajs/ui" import { useForm } from "react-hook-form" import { useTranslation } from "react-i18next" import * as zod from "zod" @@ -10,11 +10,10 @@ import { RouteDrawer, useRouteModal, } from "../../../../../components/route-modal" -import { useCategories } from "../../../../../hooks/api/categories" -import { useCollections } from "../../../../../hooks/api/collections" -import { useProductTypes } from "../../../../../hooks/api/product-types" import { useUpdateProduct } from "../../../../../hooks/api/products" -import { useTags } from "../../../../../hooks/api/tags" +import { useComboboxData } from "../../../../../hooks/use-combobox-data" +import { client, sdk } from "../../../../../lib/client" +import { CategoryCombobox } from "../../../common/components/category-combobox" type ProductOrganizationFormProps = { product: Product @@ -23,8 +22,8 @@ type ProductOrganizationFormProps = { const ProductOrganizationSchema = zod.object({ type_id: zod.string().optional(), collection_id: zod.string().optional(), - category_ids: zod.array(zod.string()).optional(), - tags: zod.array(zod.string()).optional(), + category_ids: zod.array(zod.string()), + tag_ids: zod.array(zod.string()), }) export const ProductOrganizationForm = ({ @@ -32,38 +31,76 @@ export const ProductOrganizationForm = ({ }: ProductOrganizationFormProps) => { const { t } = useTranslation() const { handleSuccess } = useRouteModal() - const { product_types, isLoading: isLoadingTypes } = useProductTypes() - const { product_tags, isLoading: isLoadingTags } = useTags() - const { collections, isLoading: isLoadingCollections } = useCollections() - const { product_categories, isLoading: isLoadingCategories } = useCategories() + + const collections = useComboboxData({ + queryKey: ["product_collections"], + queryFn: sdk.admin.collection.list, + getOptions: (data) => + data.collections.map((collection) => ({ + label: collection.title!, + value: collection.id!, + })), + }) + + const types = useComboboxData({ + queryKey: ["product_types"], + queryFn: client.productTypes.list, + getOptions: (data) => + data.product_types.map((type) => ({ + label: type.value, + value: type.id, + })), + }) + + const tags = useComboboxData({ + queryKey: ["product_tags"], + queryFn: client.productTags.list, + getOptions: (data) => + data.product_tags.map((tag) => ({ + label: tag.value, + value: tag.id, + })), + }) const form = useForm>({ defaultValues: { - type_id: product.type_id || undefined, - collection_id: product.collection_id || undefined, - category_ids: product.categories?.map((c) => c.id) || undefined, - tags: product.tags?.map((t) => t.id) || undefined, + type_id: product.type_id || "", + collection_id: product.collection_id || "", + category_ids: product.categories?.map((c) => c.id) || [], + tag_ids: product.tags?.map((t) => t.id) || [], }, resolver: zodResolver(ProductOrganizationSchema), }) - const { mutateAsync, isLoading } = useUpdateProduct(product.id) + const { mutateAsync, isPending } = useUpdateProduct(product.id) const handleSubmit = form.handleSubmit(async (data) => { await mutateAsync( { type_id: data.type_id || undefined, collection_id: data.collection_id || undefined, - category_ids: data.category_ids || undefined, + categories: data.category_ids.map((id) => ({ id })) || undefined, tags: - data.tags?.map((t) => { + data.tag_ids?.map((t) => { t }) || undefined, }, { - onSuccess: () => { + onSuccess: ({ product }) => { + toast.success(t("general.success"), { + description: t("products.organization.edit.toasts.success", { + title: product.title, + }), + }) handleSuccess() }, + onError: (error) => { + toast.error(t("general.error"), { + description: error.message, + dismissable: true, + dismissLabel: t("actions.close"), + }) + }, } ) }) @@ -72,34 +109,26 @@ export const ProductOrganizationForm = ({
-
+
{ + render={({ field }) => { return ( {t("products.fields.type.label")} - + options={types.options} + searchValue={types.searchValue} + onSearchValueChange={types.onSearchValueChange} + fetchNextPage={types.fetchNextPage} + /> + ) }} @@ -107,33 +136,22 @@ export const ProductOrganizationForm = ({ { + render={({ field }) => { return ( {t("products.fields.collection.label")} - + options={collections.options} + searchValue={collections.searchValue} + onSearchValueChange={collections.onSearchValueChange} + fetchNextPage={collections.fetchNextPage} + /> + ) }} @@ -148,22 +166,16 @@ export const ProductOrganizationForm = ({ {t("products.fields.categories.label")} - ({ - label: category.name, - value: category.id, - }))} - {...field} - /> + + ) }} /> { return ( @@ -172,14 +184,15 @@ export const ProductOrganizationForm = ({ ({ - label: tag.value, - value: tag.id, - }))} {...field} + multiple + options={tags.options} + searchValue={tags.searchValue} + onSearchValueChange={tags.onSearchValueChange} + fetchNextPage={tags.fetchNextPage} /> + ) }} @@ -193,7 +206,7 @@ export const ProductOrganizationForm = ({ {t("actions.cancel")} -
diff --git a/packages/admin-next/dashboard/src/routes/products/product-organization/product-organization.tsx b/packages/admin-next/dashboard/src/routes/products/product-organization/product-organization.tsx index 91b71679b6..0852e9c612 100644 --- a/packages/admin-next/dashboard/src/routes/products/product-organization/product-organization.tsx +++ b/packages/admin-next/dashboard/src/routes/products/product-organization/product-organization.tsx @@ -4,13 +4,16 @@ import { useParams } from "react-router-dom" import { RouteDrawer } from "../../../components/route-modal" import { useProduct } from "../../../hooks/api/products" +import { PRODUCT_DETAIL_FIELDS } from "../product-detail/constants" import { ProductOrganizationForm } from "./components/product-organization-form" export const ProductOrganization = () => { const { id } = useParams() const { t } = useTranslation() - const { product, isLoading, isError, error } = useProduct(id!) + const { product, isLoading, isError, error } = useProduct(id!, { + fields: PRODUCT_DETAIL_FIELDS, + }) if (isError) { throw error @@ -19,7 +22,7 @@ export const ProductOrganization = () => { return ( - {t("products.editOrganization")} + {t("products.organization.edit.header")} {!isLoading && product && }