feat: Translations UI (#14217)
* Add Translations route and guard it with feature flag. Empty TranslationsList main component to test route. * Translation list component * Add translations namespace to js-sdk * Translations hook * Avoid incorrectly throwing when updating and locale not included * Translations bulk editor component v1 * Add batch method to translations namespace in js-sdk * Protect translations edit route with feature flag * Handle reference_id search param * Replace entity_type entity_id for reference reference_id * Manage translations from product detail page * Dynamically resolve base hook for retrieving translations * Fix navigation from outside settings/translations * Navigation to bulk editor from product list * Add Translations to various product module types * Type useVariants hook * Handle product module entities translations in bulk editor * Fix categories issue in datagrid due to column clash * Translations bulk navigation from remaining entities detail pages * Add remaining bulk editor navigation for list components. Fix invalidation query for variants * Expandable text cell v1 * Popover approach * Add *supported_locales.locale to default fields in stores list endpoint * Make popover more aligned to excell approach * Correctly tie the focused cell anchor to popover * Rework translations main component UI * Fix link def export * Swap axis for translations datagrid * Add original column to translations data grid * Remove is_default store locale from backend * Remove ldefault locale from ui * Type * Add changeset * Comments * Remove unused import * Add translations to admin product categories endpoint allowed fields * Default locale removal * Lazy loading with infinite scroll data grid * Infinite list hook and implementation for products and variants * Translation bulk editor lazy loaded datagrid * Prevent scroll when forcing focus, to avoid scrollTop reset on infinite loading * Confgiure placeholder data * Cleanup logs and refactor * Infinite query hooks for translatable entities * Batch requests for translation batch endpoint * Clean up * Update icon * Add query param validator in settings endpoint * Settings endpoint param type * JS sdk methods for translation settings and statistics * Retrieve translatable fields and entities dynamically. Remove hardcoded information from tranlations list * Resolve translation aggregate completion dynamically * Format label * Resolve bulk editor header label dynamically * Include type and collection in translations config * Avoid showing product option and option values in translatable entities list * Translations * Make translations bulk editor content columns wider * Disable hiding Original column in translations bulk editor * Adjust translations completion styles * Fix translations config screen * Locale selector switcher with conditional locale column rendering * Batch one locale at a time * Hooks save actions to footer buttons * Reset dirty state on save * Dynamic row heights for translations bulk editor. Replace expandable cell for text cell, with additional isMultiLine config * Make columns take as much responsive width as possible and divide equally * more padding to avoid unnecessary horizontal scrollbar * Update statistics graphs * Translations * Statistics graphs translations * Translation, text sizes and weight in stat graphs * Conditionally show/hide column visibility dropdown in datagrid * Allow to pass component to place in DataGrid header and use it in translations bulk editor * Center text regardless of multiLine config * Apply full height to datagrid cell regardles of multiSelect config * Colors and fonts * Handle key down for text area in text cell * MultilineCell with special keydown handling * Rework form schema to match new single locale edit flow * Update created translations to include id, to avoid duplication issue on subsequent calls * Handle space key for text cells * Finish hooking up multiline cell with key and mouse events * Disable remaining buttons when batch is ongoing * Style updates * Update style * Refactor to make form updates and sync/comparison with server data more comprehensive and robust * Update styles * Bars and labels alignment * Add languages tooltip * Styles and translation * Navigation update * Disable edit translations button when no reference count * Invert colors --------- Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com> Co-authored-by: Adrien de Peretti <adrien.deperetti@gmail.com>
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
InfiniteData,
|
||||
QueryKey,
|
||||
UseInfiniteQueryOptions,
|
||||
UseMutationOptions,
|
||||
UseQueryOptions,
|
||||
useMutation,
|
||||
@@ -11,6 +13,7 @@ import { sdk } from "../../lib/client"
|
||||
import { queryClient } from "../../lib/query-client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import { productsQueryKeys } from "./products"
|
||||
import { useInfiniteList } from "../use-infinite-list"
|
||||
|
||||
const CATEGORIES_QUERY_KEY = "categories" as const
|
||||
export const categoriesQueryKeys = queryKeysFactory(CATEGORIES_QUERY_KEY)
|
||||
@@ -58,6 +61,35 @@ export const useProductCategories = (
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const useInfiniteCategories = (
|
||||
query?: Omit<HttpTypes.AdminProductCategoryListParams, "offset" | "limit"> & {
|
||||
limit?: number
|
||||
},
|
||||
options?: Omit<
|
||||
UseInfiniteQueryOptions<
|
||||
HttpTypes.AdminProductCategoryListResponse,
|
||||
FetchError,
|
||||
InfiniteData<HttpTypes.AdminProductCategoryListResponse, number>,
|
||||
HttpTypes.AdminProductCategoryListResponse,
|
||||
QueryKey,
|
||||
number
|
||||
>,
|
||||
"queryFn" | "queryKey" | "initialPageParam" | "getNextPageParam"
|
||||
>
|
||||
) => {
|
||||
return useInfiniteList<
|
||||
HttpTypes.AdminProductCategoryListResponse,
|
||||
HttpTypes.AdminProductCategoryListParams,
|
||||
FetchError,
|
||||
QueryKey
|
||||
>({
|
||||
queryKey: (params) => categoriesQueryKeys.list(params),
|
||||
queryFn: (params) => sdk.admin.productCategory.list(params),
|
||||
query,
|
||||
options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useCreateProductCategory = (
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminProductCategoryResponse,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { FindParams, HttpTypes, PaginatedResponse } from "@medusajs/types"
|
||||
import {
|
||||
InfiniteData,
|
||||
QueryKey,
|
||||
UseInfiniteQueryOptions,
|
||||
UseMutationOptions,
|
||||
UseQueryOptions,
|
||||
useMutation,
|
||||
@@ -11,6 +13,7 @@ import { sdk } from "../../lib/client"
|
||||
import { queryClient } from "../../lib/query-client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import { productsQueryKeys } from "./products"
|
||||
import { useInfiniteList } from "../use-infinite-list"
|
||||
|
||||
const COLLECTION_QUERY_KEY = "collections" as const
|
||||
export const collectionsQueryKeys = queryKeysFactory(COLLECTION_QUERY_KEY)
|
||||
@@ -57,6 +60,34 @@ export const useCollections = (
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const useInfiniteCollections = (
|
||||
query?: Omit<HttpTypes.AdminCollectionListParams, "offset" | "limit"> & {
|
||||
limit?: number
|
||||
},
|
||||
options?: Omit<
|
||||
UseInfiniteQueryOptions<
|
||||
HttpTypes.AdminCollectionListResponse,
|
||||
FetchError,
|
||||
InfiniteData<HttpTypes.AdminCollectionListResponse, number>,
|
||||
HttpTypes.AdminCollectionListResponse,
|
||||
QueryKey,
|
||||
number
|
||||
>,
|
||||
"queryFn" | "queryKey" | "initialPageParam" | "getNextPageParam"
|
||||
>
|
||||
) => {
|
||||
return useInfiniteList<
|
||||
HttpTypes.AdminCollectionListResponse,
|
||||
HttpTypes.AdminCollectionListParams,
|
||||
FetchError,
|
||||
QueryKey
|
||||
>({
|
||||
queryKey: (params) => collectionsQueryKeys.list(params),
|
||||
queryFn: (params) => sdk.admin.productCollection.list(params),
|
||||
query,
|
||||
options,
|
||||
})
|
||||
}
|
||||
export const useUpdateCollection = (
|
||||
id: string,
|
||||
options?: UseMutationOptions<
|
||||
|
||||
@@ -34,6 +34,7 @@ export * from "./store"
|
||||
export * from "./tags"
|
||||
export * from "./tax-rates"
|
||||
export * from "./tax-regions"
|
||||
export * from "./translations"
|
||||
export * from "./users"
|
||||
export * from "./views"
|
||||
export * from "./workflow-executions"
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
InfiniteData,
|
||||
QueryKey,
|
||||
UseInfiniteQueryOptions,
|
||||
UseMutationOptions,
|
||||
UseQueryOptions,
|
||||
useMutation,
|
||||
@@ -10,6 +12,7 @@ import {
|
||||
import { sdk } from "../../lib/client"
|
||||
import { queryClient } from "../../lib/query-client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import { useInfiniteList } from "../use-infinite-list"
|
||||
|
||||
const PRODUCT_TYPES_QUERY_KEY = "product_types" as const
|
||||
export const productTypesQueryKeys = queryKeysFactory(PRODUCT_TYPES_QUERY_KEY)
|
||||
@@ -57,6 +60,35 @@ export const useProductTypes = (
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const useInfiniteProductTypes = (
|
||||
query?: Omit<HttpTypes.AdminProductTypeListParams, "offset" | "limit"> & {
|
||||
limit?: number
|
||||
},
|
||||
options?: Omit<
|
||||
UseInfiniteQueryOptions<
|
||||
HttpTypes.AdminProductTypeListResponse,
|
||||
FetchError,
|
||||
InfiniteData<HttpTypes.AdminProductTypeListResponse, number>,
|
||||
HttpTypes.AdminProductTypeListResponse,
|
||||
QueryKey,
|
||||
number
|
||||
>,
|
||||
"queryFn" | "queryKey" | "initialPageParam" | "getNextPageParam"
|
||||
>
|
||||
) => {
|
||||
return useInfiniteList<
|
||||
HttpTypes.AdminProductTypeListResponse,
|
||||
HttpTypes.AdminProductTypeListParams,
|
||||
FetchError,
|
||||
QueryKey
|
||||
>({
|
||||
queryKey: (params) => productTypesQueryKeys.list(params),
|
||||
queryFn: (params) => sdk.admin.productType.list(params),
|
||||
query,
|
||||
options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useCreateProductType = (
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminProductTypeResponse,
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { QueryKey, useQuery, UseQueryOptions } from "@tanstack/react-query"
|
||||
import {
|
||||
QueryKey,
|
||||
UseInfiniteQueryOptions,
|
||||
useQuery,
|
||||
UseQueryOptions,
|
||||
} from "@tanstack/react-query"
|
||||
import { InfiniteData } from "@tanstack/query-core"
|
||||
import { sdk } from "../../lib/client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import { useInfiniteList } from "../use-infinite-list"
|
||||
|
||||
const PRODUCT_VARIANT_QUERY_KEY = "product_variant" as const
|
||||
export const productVariantQueryKeys = queryKeysFactory(
|
||||
@@ -9,9 +17,14 @@ export const productVariantQueryKeys = queryKeysFactory(
|
||||
)
|
||||
|
||||
export const useVariants = (
|
||||
query?: Record<string, any>,
|
||||
query?: HttpTypes.AdminProductVariantParams,
|
||||
options?: Omit<
|
||||
UseQueryOptions<any, FetchError, any, QueryKey>,
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminProductVariantListResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminProductVariantListResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryFn" | "queryKey"
|
||||
>
|
||||
) => {
|
||||
@@ -23,3 +36,34 @@ export const useVariants = (
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const useInfiniteVariants = (
|
||||
query?: Omit<HttpTypes.AdminProductVariantParams, "offset" | "limit"> & {
|
||||
limit?: number
|
||||
},
|
||||
options?: Omit<
|
||||
UseInfiniteQueryOptions<
|
||||
HttpTypes.AdminProductVariantListResponse,
|
||||
FetchError,
|
||||
InfiniteData<HttpTypes.AdminProductVariantListResponse, number>,
|
||||
HttpTypes.AdminProductVariantListResponse,
|
||||
QueryKey,
|
||||
number
|
||||
>,
|
||||
"queryFn" | "queryKey" | "initialPageParam" | "getNextPageParam"
|
||||
>
|
||||
) => {
|
||||
return useInfiniteList<
|
||||
HttpTypes.AdminProductVariantListResponse,
|
||||
HttpTypes.AdminProductVariantParams,
|
||||
FetchError,
|
||||
QueryKey
|
||||
>({
|
||||
queryKey: (params) => productVariantQueryKeys.list(params),
|
||||
queryFn: async (params) => {
|
||||
return await sdk.admin.productVariant.list(params)
|
||||
},
|
||||
query,
|
||||
options,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,15 +2,18 @@ import { FetchError } from "@medusajs/js-sdk"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
QueryKey,
|
||||
UseInfiniteQueryOptions,
|
||||
useMutation,
|
||||
UseMutationOptions,
|
||||
useQuery,
|
||||
UseQueryOptions,
|
||||
} from "@tanstack/react-query"
|
||||
import { InfiniteData } from "@tanstack/query-core"
|
||||
import { sdk } from "../../lib/client"
|
||||
import { queryClient } from "../../lib/query-client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import { inventoryItemsQueryKeys } from "./inventory.tsx"
|
||||
import { useInfiniteList } from "../use-infinite-list.tsx"
|
||||
|
||||
const PRODUCTS_QUERY_KEY = "products" as const
|
||||
export const productsQueryKeys = queryKeysFactory(PRODUCTS_QUERY_KEY)
|
||||
@@ -310,6 +313,32 @@ export const useProducts = (
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const useInfiniteProducts = (
|
||||
query?: HttpTypes.AdminProductListParams,
|
||||
options?: Omit<
|
||||
UseInfiniteQueryOptions<
|
||||
HttpTypes.AdminProductListResponse,
|
||||
FetchError,
|
||||
InfiniteData<HttpTypes.AdminProductListResponse, number>,
|
||||
HttpTypes.AdminProductListResponse,
|
||||
QueryKey,
|
||||
number
|
||||
>,
|
||||
"queryFn" | "queryKey" | "initialPageParam" | "getNextPageParam"
|
||||
>
|
||||
) => {
|
||||
return useInfiniteList<
|
||||
HttpTypes.AdminProductListResponse,
|
||||
HttpTypes.AdminProductListParams,
|
||||
FetchError,
|
||||
QueryKey
|
||||
>({
|
||||
queryKey: (params) => productsQueryKeys.list(params),
|
||||
queryFn: (params) => sdk.admin.product.list(params),
|
||||
query,
|
||||
options,
|
||||
})
|
||||
}
|
||||
export const useCreateProduct = (
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminProductResponse,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
InfiniteData,
|
||||
QueryKey,
|
||||
UseInfiniteQueryOptions,
|
||||
UseMutationOptions,
|
||||
UseQueryOptions,
|
||||
useMutation,
|
||||
@@ -10,6 +12,7 @@ import {
|
||||
import { sdk } from "../../lib/client"
|
||||
import { queryClient } from "../../lib/query-client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import { useInfiniteList } from "../use-infinite-list"
|
||||
|
||||
const TAGS_QUERY_KEY = "tags" as const
|
||||
export const productTagsQueryKeys = queryKeysFactory(TAGS_QUERY_KEY)
|
||||
@@ -57,6 +60,35 @@ export const useProductTags = (
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const useInfiniteProductTags = (
|
||||
query?: Omit<HttpTypes.AdminProductTagListParams, "offset" | "limit"> & {
|
||||
limit?: number
|
||||
},
|
||||
options?: Omit<
|
||||
UseInfiniteQueryOptions<
|
||||
HttpTypes.AdminProductTagListResponse,
|
||||
FetchError,
|
||||
InfiniteData<HttpTypes.AdminProductTagListResponse, number>,
|
||||
HttpTypes.AdminProductTagListResponse,
|
||||
QueryKey,
|
||||
number
|
||||
>,
|
||||
"queryFn" | "queryKey" | "initialPageParam" | "getNextPageParam"
|
||||
>
|
||||
) => {
|
||||
return useInfiniteList<
|
||||
HttpTypes.AdminProductTagListResponse,
|
||||
HttpTypes.AdminProductTagListParams,
|
||||
FetchError,
|
||||
QueryKey
|
||||
>({
|
||||
queryKey: (params) => productTagsQueryKeys.list(params),
|
||||
queryFn: (params) => sdk.admin.productTag.list(params),
|
||||
query,
|
||||
options,
|
||||
})
|
||||
}
|
||||
|
||||
export const useCreateProductTag = (
|
||||
query?: HttpTypes.AdminProductTagParams,
|
||||
options?: UseMutationOptions<
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { HttpTypes } from "@medusajs/types"
|
||||
import {
|
||||
QueryKey,
|
||||
UseInfiniteQueryOptions,
|
||||
UseInfiniteQueryResult,
|
||||
useMutation,
|
||||
UseMutationOptions,
|
||||
useQuery,
|
||||
UseQueryOptions,
|
||||
} from "@tanstack/react-query"
|
||||
import { sdk } from "../../lib/client"
|
||||
import { queryKeysFactory } from "../../lib/query-key-factory"
|
||||
import { queryClient } from "../../lib/query-client"
|
||||
import { productsQueryKeys, useInfiniteProducts } from "./products"
|
||||
import {
|
||||
productVariantQueryKeys,
|
||||
useInfiniteVariants,
|
||||
} from "./product-variants"
|
||||
import { categoriesQueryKeys, useInfiniteCategories } from "./categories"
|
||||
import { collectionsQueryKeys, useInfiniteCollections } from "./collections"
|
||||
import { productTagsQueryKeys, useInfiniteProductTags } from "./tags"
|
||||
import { productTypesQueryKeys, useInfiniteProductTypes } from "./product-types"
|
||||
|
||||
const TRANSLATIONS_QUERY_KEY = "translations" as const
|
||||
export const translationsQueryKeys = queryKeysFactory(TRANSLATIONS_QUERY_KEY)
|
||||
|
||||
const TRANSLATION_SETTINGS_QUERY_KEY = "translation_settings" as const
|
||||
export const translationSettingsQueryKeys = queryKeysFactory(
|
||||
TRANSLATION_SETTINGS_QUERY_KEY
|
||||
)
|
||||
|
||||
const TRANSLATION_STATISTICS_QUERY_KEY = "translation_statistics" as const
|
||||
export const translationStatisticsQueryKeys = queryKeysFactory(
|
||||
TRANSLATION_STATISTICS_QUERY_KEY
|
||||
)
|
||||
|
||||
export const useReferenceTranslations = (
|
||||
reference: string,
|
||||
translatableFields: string[],
|
||||
referenceId?: string | string[],
|
||||
options?: Omit<
|
||||
UseInfiniteQueryOptions<any, FetchError, any, any, QueryKey, number>,
|
||||
"queryFn" | "queryKey" | "initialPageParam" | "getNextPageParam"
|
||||
>
|
||||
) => {
|
||||
const referenceHookMap = new Map<
|
||||
string,
|
||||
() => Omit<UseInfiniteQueryResult<any, FetchError>, "data"> & {
|
||||
data: {
|
||||
translations: HttpTypes.AdminTranslation[]
|
||||
references: (Record<string, any> & { id: string })[]
|
||||
count: number
|
||||
}
|
||||
}
|
||||
>([
|
||||
[
|
||||
"product",
|
||||
() => {
|
||||
const fields = translatableFields.concat(["translations.*"]).join(",")
|
||||
|
||||
const { data, ...rest } = useInfiniteProducts(
|
||||
{ fields, id: referenceId ?? [] },
|
||||
options
|
||||
)
|
||||
const products = data?.pages.flatMap((page) => page.products) ?? []
|
||||
|
||||
return {
|
||||
...rest,
|
||||
data: {
|
||||
translations:
|
||||
products?.flatMap((product) => product.translations ?? []) ?? [],
|
||||
references: products ?? [],
|
||||
count: data?.pages[0]?.count ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
[
|
||||
"product_variant",
|
||||
() => {
|
||||
const fields = translatableFields.concat(["translations.*"]).join(",")
|
||||
|
||||
const { data, ...rest } = useInfiniteVariants(
|
||||
{ id: referenceId ?? [], fields },
|
||||
options
|
||||
)
|
||||
const variants = data?.pages.flatMap((page) => page.variants) ?? []
|
||||
|
||||
return {
|
||||
...rest,
|
||||
data: {
|
||||
translations:
|
||||
variants?.flatMap((variant) => variant.translations ?? []) ?? [],
|
||||
references: variants ?? [],
|
||||
translatableFields,
|
||||
count: data?.pages[0]?.count ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
[
|
||||
"product_category",
|
||||
() => {
|
||||
const fields = translatableFields.concat(["translations.*"]).join(",")
|
||||
|
||||
const { data, ...rest } = useInfiniteCategories(
|
||||
{ id: referenceId ?? [], fields },
|
||||
options
|
||||
)
|
||||
const categories =
|
||||
data?.pages.flatMap((page) => page.product_categories) ?? []
|
||||
|
||||
return {
|
||||
...rest,
|
||||
data: {
|
||||
translations:
|
||||
categories?.flatMap((category) => category.translations ?? []) ??
|
||||
[],
|
||||
references: categories ?? [],
|
||||
translatableFields,
|
||||
count: data?.pages[0]?.count ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
[
|
||||
"product_collection",
|
||||
() => {
|
||||
const fields = translatableFields.concat(["translations.*"]).join(",")
|
||||
|
||||
const { data, ...rest } = useInfiniteCollections(
|
||||
{ id: referenceId ?? [], fields },
|
||||
options
|
||||
)
|
||||
const collections =
|
||||
data?.pages.flatMap((page) => page.collections) ?? []
|
||||
|
||||
return {
|
||||
...rest,
|
||||
data: {
|
||||
translations:
|
||||
collections?.flatMap(
|
||||
(collection) => collection.translations ?? []
|
||||
) ?? [],
|
||||
references: collections ?? [],
|
||||
translatableFields,
|
||||
count: data?.pages[0]?.count ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
[
|
||||
"product_type",
|
||||
() => {
|
||||
const fields = translatableFields.concat(["translations.*"]).join(",")
|
||||
|
||||
const { data, ...rest } = useInfiniteProductTypes(
|
||||
{ id: referenceId ?? [], fields },
|
||||
options
|
||||
)
|
||||
const product_types =
|
||||
data?.pages.flatMap((page) => page.product_types) ?? []
|
||||
|
||||
return {
|
||||
...rest,
|
||||
data: {
|
||||
translations:
|
||||
product_types?.flatMap((type) => type.translations ?? []) ?? [],
|
||||
references: product_types ?? [],
|
||||
count: data?.pages[0]?.count ?? 0,
|
||||
translatableFields,
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
[
|
||||
"product_tag",
|
||||
() => {
|
||||
const fields = translatableFields.concat(["translations.*"]).join(",")
|
||||
|
||||
const { data, ...rest } = useInfiniteProductTags(
|
||||
{ id: referenceId ?? [], fields },
|
||||
options
|
||||
)
|
||||
const product_tags =
|
||||
data?.pages.flatMap((page) => page.product_tags) ?? []
|
||||
|
||||
return {
|
||||
...rest,
|
||||
data: {
|
||||
translations:
|
||||
product_tags?.flatMap((tag) => tag.translations ?? []) ?? [],
|
||||
references: product_tags ?? [],
|
||||
translatableFields,
|
||||
count: data?.pages[0]?.count ?? 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
],
|
||||
// TODO: product option and option values
|
||||
])
|
||||
const referenceHook = referenceHookMap.get(reference)
|
||||
if (!referenceHook) {
|
||||
throw new Error(`No hook found for reference type: ${reference}`)
|
||||
}
|
||||
const { data, ...rest } = referenceHook()
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const useTranslations = (
|
||||
query?: HttpTypes.AdminTranslationsListParams,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminTranslationsListResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminTranslationsListResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryFn" | "queryKey"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryKey: translationsQueryKeys.list(query),
|
||||
queryFn: () => sdk.admin.translation.list(query),
|
||||
...options,
|
||||
})
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
const referenceInvalidationKeysMap = new Map<string, QueryKey>([
|
||||
["product", productsQueryKeys.lists()],
|
||||
["product_variant", productVariantQueryKeys.lists()],
|
||||
["product_category", categoriesQueryKeys.lists()],
|
||||
["product_collection", collectionsQueryKeys.lists()],
|
||||
["product_type", productTypesQueryKeys.lists()],
|
||||
["product_tag", productTagsQueryKeys.lists()],
|
||||
])
|
||||
|
||||
export const useBatchTranslations = (
|
||||
reference: string,
|
||||
options?: UseMutationOptions<
|
||||
HttpTypes.AdminTranslationsBatchResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminBatchTranslations
|
||||
>
|
||||
) => {
|
||||
const mutation = useMutation({
|
||||
mutationFn: (payload: HttpTypes.AdminBatchTranslations) =>
|
||||
sdk.admin.translation.batch(payload),
|
||||
...options,
|
||||
})
|
||||
|
||||
/**
|
||||
* Useful to call the invalidation separately from the batch request and await the refetch finishes.
|
||||
*/
|
||||
const invalidateQueries = async () => {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: referenceInvalidationKeysMap.get(reference),
|
||||
}),
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: translationStatisticsQueryKeys.lists(),
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
return {
|
||||
...mutation,
|
||||
invalidateQueries,
|
||||
}
|
||||
}
|
||||
|
||||
export const useTranslationSettings = (
|
||||
query?: HttpTypes.AdminTranslationSettingsParams,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminTranslationSettingsResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminTranslationSettingsResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryFn" | "queryKey"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryKey: translationSettingsQueryKeys.list(query),
|
||||
queryFn: () => sdk.admin.translation.settings(query),
|
||||
...options,
|
||||
})
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
|
||||
export const useTranslationStatistics = (
|
||||
query?: HttpTypes.AdminTranslationStatisticsParams,
|
||||
options?: Omit<
|
||||
UseQueryOptions<
|
||||
HttpTypes.AdminTranslationStatisticsResponse,
|
||||
FetchError,
|
||||
HttpTypes.AdminTranslationStatisticsResponse,
|
||||
QueryKey
|
||||
>,
|
||||
"queryFn" | "queryKey"
|
||||
>
|
||||
) => {
|
||||
const { data, ...rest } = useQuery({
|
||||
queryKey: translationStatisticsQueryKeys.list(query),
|
||||
queryFn: () => sdk.admin.translation.statistics(query),
|
||||
...options,
|
||||
})
|
||||
|
||||
return { ...data, ...rest }
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { FetchError } from "@medusajs/js-sdk"
|
||||
import { PaginatedResponse } from "@medusajs/types"
|
||||
import {
|
||||
QueryKey,
|
||||
UseInfiniteQueryOptions,
|
||||
InfiniteData,
|
||||
useInfiniteQuery,
|
||||
} from "@tanstack/react-query"
|
||||
|
||||
/**
|
||||
* Generic hook for infinite queries with pagination support.
|
||||
*
|
||||
* @template TResponse - The response type that must include count, offset, and limit
|
||||
* @template TParams - The query parameters type (offset and limit will be handled internally)
|
||||
* @template TError - The error type (defaults to FetchError)
|
||||
* @template TQueryKey - The query key type (defaults to QueryKey)
|
||||
*
|
||||
* @param config - Configuration object
|
||||
* @param config.queryKey - Function or array that generates the query key
|
||||
* @param config.queryFn - Function that fetches data with offset and limit
|
||||
* @param config.query - Query parameters (offset is ignored, limit is optional)
|
||||
* @param config.options - Additional options for useInfiniteQuery
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const { data, fetchNextPage, hasNextPage } = useInfiniteList({
|
||||
* queryKey: (params) => productVariantQueryKeys.list(params),
|
||||
* queryFn: async (params) => sdk.admin.productVariant.list(params),
|
||||
* query: { status: "published" },
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export const useInfiniteList = <
|
||||
TResponse extends PaginatedResponse<unknown>,
|
||||
TParams extends { offset?: number; limit?: number } = {
|
||||
offset?: number
|
||||
limit?: number
|
||||
},
|
||||
TError = FetchError,
|
||||
TQueryKey extends QueryKey = QueryKey
|
||||
>({
|
||||
queryKey,
|
||||
queryFn,
|
||||
query,
|
||||
options,
|
||||
}: {
|
||||
queryKey: ((params: Omit<TParams, "limit">) => TQueryKey) | TQueryKey
|
||||
queryFn: (params: TParams) => Promise<TResponse>
|
||||
query?: TParams
|
||||
options?: Omit<
|
||||
UseInfiniteQueryOptions<
|
||||
TResponse,
|
||||
TError,
|
||||
InfiniteData<TResponse, number>,
|
||||
TResponse,
|
||||
TQueryKey,
|
||||
number
|
||||
>,
|
||||
"queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"
|
||||
>
|
||||
}) => {
|
||||
const { limit = 50, offset: _, ..._query } = query ?? {}
|
||||
const resolvedQueryKey =
|
||||
typeof queryKey === "function"
|
||||
? queryKey(_query as Omit<TParams, "limit">)
|
||||
: queryKey
|
||||
const infiniteQueryKey =
|
||||
resolvedQueryKey[resolvedQueryKey.length - 1] === "__infinite"
|
||||
? resolvedQueryKey
|
||||
: ([...resolvedQueryKey, "__infinite"] as unknown as TQueryKey)
|
||||
|
||||
return useInfiniteQuery<
|
||||
TResponse,
|
||||
TError,
|
||||
InfiniteData<TResponse, number>,
|
||||
TQueryKey,
|
||||
number
|
||||
>({
|
||||
// Infinite queries must not share the exact same queryKey as non-infinite queries,
|
||||
// since the cached data shape differs (InfiniteData<T> vs T).
|
||||
queryKey: infiniteQueryKey,
|
||||
queryFn: ({ pageParam = 0 }) => {
|
||||
return queryFn({ ..._query, limit, offset: pageParam } as TParams)
|
||||
},
|
||||
initialPageParam: 0,
|
||||
getNextPageParam: (lastPage) => {
|
||||
const moreItemsExist = lastPage.count > lastPage.offset + lastPage.limit
|
||||
return moreItemsExist ? lastPage.offset + lastPage.limit : undefined
|
||||
},
|
||||
...options,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user