fix(dashboard): Add Breadcrumb components (#10079)

**What**
- Adds Breadcrumb component to all routes that needs breadcrumbs.
- The Breadcrumb components use a combination of loader data and useQuery to ensure that the displayed value is kept up to date if the underlying data is changed via a mutation.
- Also fixes a couple of places where the breadcrumb was not setup correctly.

Resolves CMRC-688
This commit is contained in:
Kasper Fabricius Kristensen
2024-11-15 14:13:03 +01:00
committed by GitHub
parent 8ed3d87c23
commit 493d242c12
86 changed files with 1123 additions and 344 deletions
@@ -0,0 +1,24 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useApiKey } from "../../../hooks/api"
type ApiKeyManagementDetailBreadcrumbProps =
UIMatch<HttpTypes.AdminApiKeyResponse>
export const ApiKeyManagementDetailBreadcrumb = (
props: ApiKeyManagementDetailBreadcrumbProps
) => {
const { id } = props.params || {}
const { api_key } = useApiKey(id!, {
initialData: props.data,
enabled: Boolean(id),
})
if (!api_key) {
return null
}
return <span>{api_key.title}</span>
}
@@ -1,2 +1,3 @@
export { ApiKeyManagementDetail as Component } from "./api-key-management-detail"
export { ApiKeyManagementDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { apiKeyLoader as loader } from "./loader"
@@ -1,6 +1,5 @@
import { LoaderFunctionArgs } from "react-router-dom"
import { HttpTypes } from "@medusajs/types"
import { apiKeysQueryKeys } from "../../../hooks/api/api-keys"
import { sdk } from "../../../lib/client"
import { queryClient } from "../../../lib/query-client"
@@ -14,8 +13,5 @@ export const apiKeyLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = apiKeyDetailQuery(id!)
return (
queryClient.getQueryData<HttpTypes.AdminApiKeyResponse>(query.queryKey) ??
(await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,29 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useCampaign } from "../../../hooks/api"
import { CAMPAIGN_DETAIL_FIELDS } from "./constants"
type CampaignDetailBreadcrumbProps = UIMatch<HttpTypes.AdminCampaignResponse>
export const CampaignDetailBreadcrumb = (
props: CampaignDetailBreadcrumbProps
) => {
const { id } = props.params || {}
const { campaign } = useCampaign(
id!,
{
fields: CAMPAIGN_DETAIL_FIELDS,
},
{
initialData: props.data,
enabled: Boolean(id),
}
)
if (!campaign) {
return null
}
return <span>{campaign.name}</span>
}
@@ -11,6 +11,7 @@ import { TwoColumnPageSkeleton } from "../../../components/common/skeleton"
import { TwoColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions"
import { CampaignConfigurationSection } from "./components/campaign-configuration-section"
import { CAMPAIGN_DETAIL_FIELDS } from "./constants"
export const CampaignDetail = () => {
const initialData = useLoaderData() as Awaited<
@@ -20,7 +21,7 @@ export const CampaignDetail = () => {
const { id } = useParams()
const { campaign, isLoading, isError, error } = useCampaign(
id!,
{ fields: "+promotions.id" },
{ fields: CAMPAIGN_DETAIL_FIELDS },
{ initialData }
)
@@ -0,0 +1 @@
export const CAMPAIGN_DETAIL_FIELDS = "+promotions.id"
@@ -1,2 +1,3 @@
export { CampaignDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { CampaignDetail as Component } from "./campaign-detail"
export { campaignLoader as loader } from "./loader"
@@ -1,15 +1,15 @@
import { AdminCampaignResponse } from "@medusajs/types"
import { LoaderFunctionArgs } from "react-router-dom"
import { campaignsQueryKeys } from "../../../hooks/api/campaigns"
import { sdk } from "../../../lib/client"
import { queryClient } from "../../../lib/query-client"
import { CAMPAIGN_DETAIL_FIELDS } from "./constants"
const campaignDetailQuery = (id: string) => ({
queryKey: campaignsQueryKeys.detail(id),
queryFn: async () =>
sdk.admin.campaign.retrieve(id, {
fields: "+promotions.id",
fields: CAMPAIGN_DETAIL_FIELDS,
}),
})
@@ -17,8 +17,5 @@ export const campaignLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = campaignDetailQuery(id!)
return (
queryClient.getQueryData<AdminCampaignResponse>(query.queryKey) ??
(await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,29 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useProductCategory } from "../../../hooks/api"
type CategoryDetailBreadcrumbProps =
UIMatch<HttpTypes.AdminProductCategoryResponse>
export const CategoryDetailBreadcrumb = (
props: CategoryDetailBreadcrumbProps
) => {
const { id } = props.params || {}
const { product_category } = useProductCategory(
id!,
{
fields: "name",
},
{
initialData: props.data,
enabled: Boolean(id),
}
)
if (!product_category) {
return null
}
return <span>{product_category.name}</span>
}
@@ -1,2 +1,3 @@
export { CategoryDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { CategoryDetail as Component } from "./category-detail"
export { categoryLoader as loader } from "./loader"
@@ -1,4 +1,3 @@
import { AdminProductCategoryResponse } from "@medusajs/types"
import { LoaderFunctionArgs } from "react-router-dom"
import { categoriesQueryKeys } from "../../../hooks/api/categories"
@@ -14,8 +13,5 @@ export const categoryLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = categoryDetailQuery(id!)
return (
queryClient.getQueryData<AdminProductCategoryResponse>(query.queryKey) ??
(await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,23 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useCollection } from "../../../hooks/api"
type CollectionDetailBreadcrumbProps =
UIMatch<HttpTypes.AdminCollectionResponse>
export const CollectionDetailBreadcrumb = (
props: CollectionDetailBreadcrumbProps
) => {
const { id } = props.params || {}
const { collection } = useCollection(id!, {
initialData: props.data,
enabled: Boolean(id),
})
if (!collection) {
return null
}
return <span>{collection.title}</span>
}
@@ -1,2 +1,3 @@
export { CollectionDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { CollectionDetail as Component } from "./collection-detail"
export { collectionLoader as loader } from "./loader"
@@ -1,4 +1,3 @@
import { HttpTypes } from "@medusajs/types"
import { LoaderFunctionArgs } from "react-router-dom"
import { collectionsQueryKeys } from "../../../hooks/api/collections"
@@ -14,9 +13,5 @@ export const collectionLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = collectionDetailQuery(id!)
return (
queryClient.getQueryData<{ collection: HttpTypes.AdminCollection }>(
query.queryKey
) ?? (await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,31 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useCustomerGroup } from "../../../hooks/api"
import { CUSTOMER_GROUP_DETAIL_FIELDS } from "./constants"
type CustomerGroupDetailBreadcrumbProps =
UIMatch<HttpTypes.AdminCustomerGroupResponse>
export const CustomerGroupDetailBreadcrumb = (
props: CustomerGroupDetailBreadcrumbProps
) => {
const { id } = props.params || {}
const { customer_group } = useCustomerGroup(
id!,
{
fields: CUSTOMER_GROUP_DETAIL_FIELDS,
},
{
initialData: props.data,
enabled: Boolean(id),
}
)
if (!customer_group) {
return null
}
return <span>{customer_group.name}</span>
}
@@ -0,0 +1 @@
export const CUSTOMER_GROUP_DETAIL_FIELDS = "+customers.id"
@@ -8,6 +8,7 @@ import { customerGroupLoader } from "./loader"
import { SingleColumnPageSkeleton } from "../../../components/common/skeleton"
import { useDashboardExtension } from "../../../extensions"
import { CUSTOMER_GROUP_DETAIL_FIELDS } from "./constants"
export const CustomerGroupDetail = () => {
const initialData = useLoaderData() as Awaited<
@@ -18,7 +19,7 @@ export const CustomerGroupDetail = () => {
const { customer_group, isLoading, isError, error } = useCustomerGroup(
id!,
{
fields: "+customers.id",
fields: CUSTOMER_GROUP_DETAIL_FIELDS,
},
{ initialData }
)
@@ -1,2 +1,3 @@
export { CustomerGroupDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { CustomerGroupDetail as Component } from "./customer-group-detail"
export { customerGroupLoader as loader } from "./loader"
@@ -1,14 +1,14 @@
import { HttpTypes } from "@medusajs/types"
import { LoaderFunctionArgs } from "react-router-dom"
import { productsQueryKeys } from "../../../hooks/api/products"
import { sdk } from "../../../lib/client"
import { queryClient } from "../../../lib/query-client"
import { CUSTOMER_GROUP_DETAIL_FIELDS } from "./constants"
const customerGroupDetailQuery = (id: string) => ({
queryKey: productsQueryKeys.detail(id),
queryFn: async () =>
sdk.admin.customerGroup.retrieve(id, {
fields: "+customers.id",
fields: CUSTOMER_GROUP_DETAIL_FIELDS,
}),
})
@@ -16,9 +16,5 @@ export const customerGroupLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = customerGroupDetailQuery(id!)
return (
queryClient.getQueryData<{
customer_group: HttpTypes.AdminCustomerGroup
}>(query.queryKey) ?? (await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,29 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useCustomer } from "../../../hooks/api"
type CustomerDetailBreadcrumbProps = UIMatch<HttpTypes.AdminCustomerResponse>
export const CustomerDetailBreadcrumb = (
props: CustomerDetailBreadcrumbProps
) => {
const { id } = props.params || {}
const { customer } = useCustomer(id!, undefined, {
initialData: props.data,
enabled: Boolean(id),
})
if (!customer) {
return null
}
const name = [customer.first_name, customer.last_name]
.filter(Boolean)
.join(" ")
const display = name || customer.email
return <span>{display}</span>
}
@@ -1,2 +1,3 @@
export { CustomerDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { CustomerDetail as Component } from "./customer-detail"
export { customerLoader as loader } from "./loader"
@@ -2,7 +2,6 @@ import { LoaderFunctionArgs } from "react-router-dom"
import { productsQueryKeys } from "../../../hooks/api/products"
import { sdk } from "../../../lib/client"
import { queryClient } from "../../../lib/query-client"
import { HttpTypes } from "@medusajs/types"
const customerDetailQuery = (id: string) => ({
queryKey: productsQueryKeys.detail(id),
@@ -13,9 +12,5 @@ export const customerLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = customerDetailQuery(id!)
return (
queryClient.getQueryData<{ customer: HttpTypes.AdminCustomer }>(
query.queryKey
) ?? (await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,31 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useInventoryItem } from "../../../hooks/api"
import { INVENTORY_DETAIL_FIELDS } from "./constants"
type InventoryDetailBreadcrumbProps =
UIMatch<HttpTypes.AdminInventoryItemResponse>
export const InventoryDetailBreadcrumb = (
props: InventoryDetailBreadcrumbProps
) => {
const { id } = props.params || {}
const { inventory_item } = useInventoryItem(
id!,
{
fields: INVENTORY_DETAIL_FIELDS,
},
{
initialData: props.data,
enabled: Boolean(id),
}
)
if (!inventory_item) {
return null
}
return <span>{inventory_item.title ?? inventory_item.sku ?? id}</span>
}
@@ -0,0 +1,2 @@
export const INVENTORY_DETAIL_FIELDS =
"*variants,*variants.product,*variants.options"
@@ -1,2 +1,3 @@
export { InventoryDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { InventoryDetail as Component } from "./inventory-detail"
export { inventoryItemLoader as loader } from "./loader"
@@ -11,6 +11,7 @@ import { InventoryItemVariantsSection } from "./components/inventory-item-varian
import { inventoryItemLoader } from "./loader"
import { useDashboardExtension } from "../../../extensions"
import { INVENTORY_DETAIL_FIELDS } from "./constants"
export const InventoryDetail = () => {
const { id } = useParams()
@@ -27,7 +28,7 @@ export const InventoryDetail = () => {
} = useInventoryItem(
id!,
{
fields: "*variants,*variants.product,*variants.options",
fields: INVENTORY_DETAIL_FIELDS,
},
{
initialData,
@@ -1,15 +1,15 @@
import { LoaderFunctionArgs } from "react-router-dom"
import { HttpTypes } from "@medusajs/types"
import { inventoryItemsQueryKeys } from "../../../hooks/api/inventory"
import { sdk } from "../../../lib/client"
import { queryClient } from "../../../lib/query-client"
import { INVENTORY_DETAIL_FIELDS } from "./constants"
const inventoryDetailQuery = (id: string) => ({
queryKey: inventoryItemsQueryKeys.detail(id),
queryFn: async () =>
sdk.admin.inventoryItem.retrieve(id, {
fields: "*variants,*variants.product,*variants.options",
fields: INVENTORY_DETAIL_FIELDS,
}),
})
@@ -17,9 +17,5 @@ export const inventoryItemLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = inventoryDetailQuery(id!)
return (
queryClient.getQueryData<HttpTypes.AdminInventoryItemResponse>(
query.queryKey
) ?? (await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,31 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useStockLocation } from "../../../hooks/api/stock-locations"
import { LOCATION_DETAILS_FIELD } from "./constants"
type LocationDetailBreadcrumbProps =
UIMatch<HttpTypes.AdminStockLocationResponse>
export const LocationDetailBreadcrumb = (
props: LocationDetailBreadcrumbProps
) => {
const { location_id } = props.params || {}
const { stock_location } = useStockLocation(
location_id!,
{
fields: LOCATION_DETAILS_FIELD,
},
{
initialData: props.data,
enabled: Boolean(location_id),
}
)
if (!stock_location) {
return null
}
return <span>{stock_location.name}</span>
}
@@ -1,2 +1,2 @@
export const detailsFields =
export const LOCATION_DETAILS_FIELD =
"name,*sales_channels,*address,fulfillment_sets.type,fulfillment_sets.name,*fulfillment_sets.service_zones.geo_zones,*fulfillment_sets.service_zones,*fulfillment_sets.service_zones.shipping_options,*fulfillment_sets.service_zones.shipping_options.rules,*fulfillment_sets.service_zones.shipping_options.shipping_profile,*fulfillment_providers"
@@ -1,2 +1,3 @@
export { LocationDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { locationLoader as loader } from "./loader"
export { LocationDetail as Component } from "./location-detail"
@@ -1,38 +1,23 @@
import { HttpTypes } from "@medusajs/types"
import { LoaderFunctionArgs, redirect } from "react-router-dom"
import { LoaderFunctionArgs } from "react-router-dom"
import { FetchError } from "@medusajs/js-sdk"
import { stockLocationsQueryKeys } from "../../../hooks/api/stock-locations"
import { sdk } from "../../../lib/client"
import { queryClient } from "../../../lib/query-client"
import { detailsFields } from "./const"
import { LOCATION_DETAILS_FIELD } from "./constants"
const locationQuery = (id: string) => ({
queryKey: stockLocationsQueryKeys.detail(id, {
fields: detailsFields,
fields: LOCATION_DETAILS_FIELD,
}),
queryFn: async () => {
return await sdk.admin.stockLocation
.retrieve(id, {
fields: detailsFields,
})
.catch((error: FetchError) => {
if (error.status === 401) {
throw redirect("/login")
}
throw error
})
},
queryFn: async () =>
sdk.admin.stockLocation.retrieve(id, {
fields: LOCATION_DETAILS_FIELD,
}),
})
export const locationLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.location_id
const query = locationQuery(id!)
return (
queryClient.getQueryData<{ stock_location: HttpTypes.AdminStockLocation }>(
query.queryKey
) ?? (await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -9,7 +9,7 @@ import { TwoColumnPageSkeleton } from "../../../components/common/skeleton"
import { TwoColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions"
import LocationsFulfillmentProvidersSection from "./components/location-fulfillment-providers-section/location-fulfillment-providers-section"
import { detailsFields } from "./const"
import { LOCATION_DETAILS_FIELD } from "./constants"
export const LocationDetail = () => {
const initialData = useLoaderData() as Awaited<
@@ -22,7 +22,11 @@ export const LocationDetail = () => {
isPending: isLoading,
isError,
error,
} = useStockLocation(location_id!, { fields: detailsFields }, { initialData })
} = useStockLocation(
location_id!,
{ fields: LOCATION_DETAILS_FIELD },
{ initialData }
)
const { getWidgets } = useDashboardExtension()
@@ -0,0 +1,27 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useOrder } from "../../../hooks/api"
import { DEFAULT_FIELDS } from "./constants"
type OrderDetailBreadcrumbProps = UIMatch<HttpTypes.AdminOrderResponse>
export const OrderDetailBreadcrumb = (props: OrderDetailBreadcrumbProps) => {
const { id } = props.params || {}
const { order } = useOrder(
id!,
{
fields: DEFAULT_FIELDS,
},
{
initialData: props.data,
enabled: Boolean(id),
}
)
if (!order) {
return null
}
return <span>#{order.display_id}</span>
}
@@ -1,2 +1,3 @@
export { OrderDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { orderLoader as loader } from "./loader"
export { OrderDetail as Component } from "./order-detail"
@@ -1,6 +1,5 @@
import { LoaderFunctionArgs } from "react-router-dom"
import { HttpTypes } from "@medusajs/types"
import { ordersQueryKeys } from "../../../hooks/api/orders"
import { sdk } from "../../../lib/client"
import { queryClient } from "../../../lib/query-client"
@@ -18,8 +17,5 @@ export const orderLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = orderDetailQuery(id!)
return (
queryClient.getQueryData<HttpTypes.AdminOrderResponse>(query.queryKey) ??
(await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,23 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { usePriceList } from "../../../hooks/api"
type PriceListDetailBreadcrumbProps = UIMatch<HttpTypes.AdminPriceListResponse>
export const PriceListDetailBreadcrumb = (
props: PriceListDetailBreadcrumbProps
) => {
const { id } = props.params || {}
const { price_list } = usePriceList(id!, undefined, {
initialData: props.data,
enabled: Boolean(id),
})
if (!price_list) {
return null
}
return <span>{price_list.title}</span>
}
@@ -1,2 +1,3 @@
export { PriceListDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { pricingLoader as loader } from "./loader"
export { PriceListDetails as Component } from "./price-list-detail"
@@ -1,4 +1,3 @@
import { HttpTypes } from "@medusajs/types"
import { LoaderFunctionArgs } from "react-router-dom"
import { priceListsQueryKeys } from "../../../hooks/api/price-lists"
import { sdk } from "../../../lib/client"
@@ -13,9 +12,5 @@ export const pricingLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = pricingDetailQuery(id!)
return (
queryClient.getQueryData<HttpTypes.AdminPriceListResponse>(
query.queryKey
) ?? (await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,23 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useProductTag } from "../../../hooks/api"
type ProductTagDetailBreadcrumbProps =
UIMatch<HttpTypes.AdminProductTagResponse>
export const ProductTagDetailBreadcrumb = (
props: ProductTagDetailBreadcrumbProps
) => {
const { id } = props.params || {}
const { product_tag } = useProductTag(id!, undefined, {
initialData: props.data,
enabled: Boolean(id),
})
if (!product_tag) {
return null
}
return <span>{product_tag.value}</span>
}
@@ -1,2 +1,3 @@
export { ProductTagDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { productTagLoader as loader } from "./loader"
export { ProductTagDetail as Component } from "./product-tag-detail"
@@ -13,8 +13,5 @@ export const productTagLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = productTagDetailQuery(id!)
return (
queryClient.getQueryData<any>(query.queryKey) ??
(await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,24 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useProductType } from "../../../hooks/api"
type ProductTypeDetailBreadcrumbProps =
UIMatch<HttpTypes.AdminProductTypeResponse>
export const ProductTypeDetailBreadcrumb = (
props: ProductTypeDetailBreadcrumbProps
) => {
const { id } = props.params || {}
const { product_type } = useProductType(id!, undefined, {
initialData: props.data,
enabled: Boolean(id),
})
if (!product_type) {
return null
}
return <span>{product_type.value}</span>
}
@@ -1,2 +1,3 @@
export { ProductTypeDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { productTypeLoader as loader } from "./loader"
export { ProductTypeDetail as Component } from "./product-type-detail"
@@ -1,6 +1,5 @@
import { LoaderFunctionArgs } from "react-router-dom"
import { HttpTypes } from "@medusajs/types"
import { productTypesQueryKeys } from "../../../hooks/api/product-types"
import { sdk } from "../../../lib/client"
import { queryClient } from "../../../lib/query-client"
@@ -14,9 +13,5 @@ export const productTypeLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = productTypeDetailQuery(id!)
return (
queryClient.getQueryData<HttpTypes.AdminProductTypeResponse>(
query.queryKey
) ?? (await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,31 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useProductVariant } from "../../../hooks/api"
import { VARIANT_DETAIL_FIELDS } from "./constants"
type ProductVariantDetailBreadcrumbProps =
UIMatch<HttpTypes.AdminProductVariantResponse>
export const ProductVariantDetailBreadcrumb = (
props: ProductVariantDetailBreadcrumbProps
) => {
const { id, variant_id } = props.params || {}
const { variant } = useProductVariant(
id!,
variant_id!,
{
fields: VARIANT_DETAIL_FIELDS,
},
{
initialData: props.data,
enabled: Boolean(id) && Boolean(variant_id),
}
)
if (!variant) {
return null
}
return <span>{variant.title}</span>
}
@@ -1,2 +1,3 @@
export { ProductVariantDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { variantLoader as loader } from "./loader"
export { ProductVariantDetail as Component } from "./product-variant-detail"
@@ -21,8 +21,5 @@ export const variantLoader = async ({ params }: LoaderFunctionArgs) => {
const query = variantDetailQuery(productId!, variantId!)
return (
queryClient.getQueryData<any>(query.queryKey) ??
(await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,29 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useProduct } from "../../../hooks/api"
import { PRODUCT_DETAIL_FIELDS } from "./constants"
type ProductDetailBreadcrumbProps = UIMatch<HttpTypes.AdminProductResponse>
export const ProductDetailBreadcrumb = (
props: ProductDetailBreadcrumbProps
) => {
const { id } = props.params || {}
const { product } = useProduct(
id!,
{
fields: PRODUCT_DETAIL_FIELDS,
},
{
initialData: props.data,
enabled: Boolean(id),
}
)
if (!product) {
return null
}
return <span>{product.title}</span>
}
@@ -1,2 +1,3 @@
export { ProductDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { productLoader as loader } from "./loader"
export { ProductDetail as Component } from "./product-detail"
@@ -6,7 +6,7 @@ import { queryClient } from "../../../lib/query-client"
import { PRODUCT_DETAIL_FIELDS } from "./constants"
const productDetailQuery = (id: string) => ({
queryKey: productsQueryKeys.detail(id),
queryKey: productsQueryKeys.detail(id, { fields: PRODUCT_DETAIL_FIELDS }),
queryFn: async () =>
sdk.admin.product.retrieve(id, { fields: PRODUCT_DETAIL_FIELDS }),
})
@@ -15,8 +15,10 @@ export const productLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = productDetailQuery(id!)
return (
queryClient.getQueryData<any>(query.queryKey) ??
(await queryClient.fetchQuery(query))
)
const response = await queryClient.ensureQueryData({
...query,
staleTime: 90000,
})
return response
}
@@ -0,0 +1,22 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { usePromotion } from "../../../hooks/api"
type PromotionDetailBreadcrumbProps = UIMatch<HttpTypes.AdminPromotionResponse>
export const PromotionDetailBreadcrumb = (
props: PromotionDetailBreadcrumbProps
) => {
const { id } = props.params || {}
const { promotion } = usePromotion(id!, {
initialData: props.data,
enabled: Boolean(id),
})
if (!promotion) {
return null
}
return <span>{promotion.code}</span>
}
@@ -1,2 +1,3 @@
export { PromotionDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { promotionLoader as loader } from "./loader.ts"
export { PromotionDetail as Component } from "./promotion-detail.tsx"
@@ -1,4 +1,3 @@
import { HttpTypes } from "@medusajs/types"
import { LoaderFunctionArgs } from "react-router-dom"
import { promotionsQueryKeys } from "../../../hooks/api/promotions"
import { sdk } from "../../../lib/client"
@@ -13,9 +12,5 @@ export const promotionLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = promotionDetailQuery(id!)
return (
queryClient.getQueryData<HttpTypes.AdminPromotionResponse>(
query.queryKey
) ?? (await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,27 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useRegion } from "../../../hooks/api/regions"
import { REGION_DETAIL_FIELDS } from "./constants"
type RegionDetailBreadcrumbProps = UIMatch<HttpTypes.AdminRegionResponse>
export const RegionDetailBreadcrumb = (props: RegionDetailBreadcrumbProps) => {
const { id } = props.params || {}
const { region } = useRegion(
id!,
{
fields: REGION_DETAIL_FIELDS,
},
{
initialData: props.data,
enabled: Boolean(id),
}
)
if (!region) {
return null
}
return <span>{region.name}</span>
}
@@ -0,0 +1,2 @@
export const REGION_DETAIL_FIELDS =
"*payment_providers,*countries,+automatic_taxes"
@@ -1,2 +1,3 @@
export { RegionDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { regionLoader as loader } from "./loader"
export { RegionDetail as Component } from "./region-detail"
@@ -3,12 +3,13 @@ import { LoaderFunctionArgs } from "react-router-dom"
import { regionsQueryKeys } from "../../../hooks/api/regions"
import { sdk } from "../../../lib/client"
import { queryClient } from "../../../lib/query-client"
import { REGION_DETAIL_FIELDS } from "./constants"
const regionQuery = (id: string) => ({
queryKey: regionsQueryKeys.detail(id),
queryFn: async () =>
sdk.admin.region.retrieve(id, {
fields: "*payment_providers,*countries,+automatic_taxes",
fields: REGION_DETAIL_FIELDS,
}),
})
@@ -9,6 +9,7 @@ import { SingleColumnPageSkeleton } from "../../../components/common/skeleton"
import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions"
import { usePricePreferences } from "../../../hooks/api/price-preferences"
import { REGION_DETAIL_FIELDS } from "./constants"
export const RegionDetail = () => {
const initialData = useLoaderData() as Awaited<
@@ -23,7 +24,7 @@ export const RegionDetail = () => {
error: regionError,
} = useRegion(
id!,
{ fields: "*payment_providers,*countries,+automatic_taxes" },
{ fields: REGION_DETAIL_FIELDS },
{
initialData,
}
@@ -0,0 +1,29 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useReservationItem } from "../../../hooks/api"
type ReservationDetailBreadcrumbProps =
UIMatch<HttpTypes.AdminReservationResponse>
export const ReservationDetailBreadcrumb = (
props: ReservationDetailBreadcrumbProps
) => {
const { id } = props.params || {}
const { reservation } = useReservationItem(id!, undefined, {
initialData: props.data,
enabled: Boolean(id),
})
if (!reservation) {
return null
}
const display =
reservation?.inventory_item?.title ??
reservation?.inventory_item?.sku ??
reservation.id
return <span>{display}</span>
}
@@ -1,2 +1,3 @@
export { ReservationDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { reservationItemLoader as loader } from "./loader"
export { ReservationDetail as Component } from "./reservation-detail"
@@ -1,4 +1,3 @@
import { HttpTypes } from "@medusajs/types"
import { LoaderFunctionArgs } from "react-router-dom"
import { reservationItemsQueryKeys } from "../../../hooks/api/reservations"
import { sdk } from "../../../lib/client"
@@ -13,9 +12,5 @@ export const reservationItemLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = reservationDetailQuery(id!)
return (
queryClient.getQueryData<HttpTypes.AdminReservationResponse>(
query.queryKey
) ?? (await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,23 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useSalesChannel } from "../../../hooks/api/sales-channels"
type SalesChannelDetailBreadcrumbProps =
UIMatch<HttpTypes.AdminSalesChannelResponse>
export const SalesChannelDetailBreadcrumb = (
props: SalesChannelDetailBreadcrumbProps
) => {
const { id } = props.params || {}
const { sales_channel } = useSalesChannel(id!, {
initialData: props.data,
enabled: Boolean(id),
})
if (!sales_channel) {
return null
}
return <span>{sales_channel.name}</span>
}
@@ -1,2 +1,3 @@
export { SalesChannelDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { salesChannelLoader as loader } from "./loader"
export { SalesChannelDetail as Component } from "./sales-channel-detail"
@@ -1,6 +1,5 @@
import { LoaderFunctionArgs } from "react-router-dom"
import { AdminSalesChannelResponse } from "@medusajs/types"
import { productsQueryKeys } from "../../../hooks/api/products"
import { sdk } from "../../../lib/client"
import { queryClient } from "../../../lib/query-client"
@@ -14,8 +13,5 @@ export const salesChannelLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = salesChannelDetailQuery(id!)
return (
queryClient.getQueryData<AdminSalesChannelResponse>(query.queryKey) ??
(await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,27 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useShippingProfile } from "../../../hooks/api/shipping-profiles"
type ShippingProfileDetailBreadcrumbProps =
UIMatch<HttpTypes.AdminShippingProfileResponse>
export const ShippingProfileDetailBreadcrumb = (
props: ShippingProfileDetailBreadcrumbProps
) => {
const { shipping_profile_id } = props.params || {}
const { shipping_profile } = useShippingProfile(
shipping_profile_id!,
undefined,
{
initialData: props.data,
enabled: Boolean(shipping_profile_id),
}
)
if (!shipping_profile) {
return null
}
return <span>{shipping_profile.name}</span>
}
@@ -1,2 +1,3 @@
export { ShippingProfileDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { shippingProfileLoader as loader } from "./loader"
export { ShippingProfileDetail as Component } from "./shipping-profile-detail"
@@ -1,9 +1,8 @@
import { HttpTypes } from "@medusajs/types"
import { LoaderFunctionArgs } from "react-router-dom"
import { shippingProfileQueryKeys } from "../../../hooks/api/shipping-profiles"
import { sdk } from "../../../lib/client"
import { queryClient } from "../../../lib/query-client"
import { shippingProfileQueryKeys } from "../../../hooks/api/shipping-profiles"
const shippingProfileQuery = (id: string) => ({
queryKey: shippingProfileQueryKeys.detail(id),
@@ -11,12 +10,8 @@ const shippingProfileQuery = (id: string) => ({
})
export const shippingProfileLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const id = params.shipping_profile_id
const query = shippingProfileQuery(id!)
return (
queryClient.getQueryData<{
shipping_profile: HttpTypes.AdminShippingProfile
}>(query.queryKey) ?? (await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -1,4 +1,4 @@
import { useParams } from "react-router-dom"
import { useLoaderData, useParams } from "react-router-dom"
import { SingleColumnPageSkeleton } from "../../../components/common/skeleton"
import { useShippingProfile } from "../../../hooks/api/shipping-profiles"
@@ -6,12 +6,19 @@ import { ShippingProfileGeneralSection } from "./components/shipping-profile-gen
import { SingleColumnPage } from "../../../components/layout/pages"
import { useDashboardExtension } from "../../../extensions"
import { shippingProfileLoader } from "./loader"
export const ShippingProfileDetail = () => {
const { id } = useParams()
const { shipping_profile_id } = useParams()
const initialData = useLoaderData() as Awaited<
ReturnType<typeof shippingProfileLoader>
>
const { shipping_profile, isLoading, isError, error } = useShippingProfile(
id!
shipping_profile_id!,
undefined,
{ initialData }
)
const { getWidgets } = useDashboardExtension()
@@ -0,0 +1,29 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useTaxRegion } from "../../../hooks/api"
import { getCountryByIso2 } from "../../../lib/data/countries"
type TaxRegionDetailBreadcrumbProps = UIMatch<HttpTypes.AdminTaxRegionResponse>
export const TaxRegionDetailBreadcrumb = (
props: TaxRegionDetailBreadcrumbProps
) => {
const { id } = props.params || {}
const { tax_region } = useTaxRegion(id!, undefined, {
initialData: props.data,
enabled: Boolean(id),
})
if (!tax_region) {
return null
}
return (
<span>
{getCountryByIso2(tax_region.country_code)?.display_name ||
tax_region.country_code?.toUpperCase()}
</span>
)
}
@@ -1,4 +1,5 @@
export * from "./tax-region-detail"
export { TaxRegionDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { taxRegionLoader as loader } from "./loader"
export { TaxRegionDetail as Component } from "./tax-region-detail"
@@ -1,4 +1,3 @@
import { AdminTaxRegionResponse } from "@medusajs/types"
import { LoaderFunctionArgs } from "react-router-dom"
import { taxRegionsQueryKeys } from "../../../hooks/api/tax-regions"
import { sdk } from "../../../lib/client"
@@ -13,8 +12,5 @@ export const taxRegionLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = taxRegionDetailQuery(id!)
return (
queryClient.getQueryData<AdminTaxRegionResponse>(query.queryKey) ??
(await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,32 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useTaxRegion } from "../../../hooks/api"
import {
getProvinceByIso2,
isProvinceInCountry,
} from "../../../lib/data/country-states"
type TaxRegionDetailBreadcrumbProps = UIMatch<HttpTypes.AdminTaxRegionResponse>
export const TaxRegionDetailBreadcrumb = (
props: TaxRegionDetailBreadcrumbProps
) => {
const { province_id } = props.params || {}
const { tax_region } = useTaxRegion(province_id!, undefined, {
initialData: props.data,
enabled: Boolean(province_id),
})
if (!tax_region) {
return null
}
const countryCode = tax_region.country_code?.toUpperCase()
const provinceCode = tax_region.province_code?.toUpperCase()
const isValid = isProvinceInCountry(countryCode, provinceCode)
return <span>{isValid ? getProvinceByIso2(provinceCode) : provinceCode}</span>
}
@@ -1,4 +1,5 @@
export * from "./tax-region-detail"
export { TaxRegionDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { taxRegionLoader as loader } from "./loader"
export { TaxRegionDetail as Component } from "./tax-region-detail"
@@ -1,4 +1,3 @@
import { AdminTaxRegionResponse } from "@medusajs/types"
import { LoaderFunctionArgs } from "react-router-dom"
import { taxRegionsQueryKeys } from "../../../hooks/api/tax-regions"
import { sdk } from "../../../lib/client"
@@ -13,8 +12,5 @@ export const taxRegionLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.province_id
const query = taxRegionDetailQuery(id!)
return (
queryClient.getQueryData<AdminTaxRegionResponse>(query.queryKey) ??
(await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,24 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useUser } from "../../../hooks/api/users"
type UserDetailBreadcrumbProps = UIMatch<HttpTypes.AdminUserResponse>
export const UserDetailBreadcrumb = (props: UserDetailBreadcrumbProps) => {
const { id } = props.params || {}
const { user } = useUser(id!, undefined, {
initialData: props.data,
enabled: Boolean(id),
})
if (!user) {
return null
}
const name = [user.first_name, user.last_name].filter(Boolean).join(" ")
const display = name || user.email
return <span>{display}</span>
}
@@ -1,2 +1,3 @@
export { UserDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { userLoader as loader } from "./loader"
export { UserDetail as Component } from "./user-detail"
@@ -1,6 +1,5 @@
import { LoaderFunctionArgs } from "react-router-dom"
import { HttpTypes } from "@medusajs/types"
import { productsQueryKeys } from "../../../hooks/api/products"
import { sdk } from "../../../lib/client"
import { queryClient } from "../../../lib/query-client"
@@ -14,8 +13,5 @@ export const userLoader = async ({ params }: LoaderFunctionArgs) => {
const id = params.id
const query = userDetailQuery(id!)
return (
queryClient.getQueryData<HttpTypes.AdminUserResponse>(query.queryKey) ??
(await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}
@@ -0,0 +1,26 @@
import { HttpTypes } from "@medusajs/types"
import { UIMatch } from "react-router-dom"
import { useWorkflowExecution } from "../../../hooks/api"
type WorkflowExecutionDetailBreadcrumbProps =
UIMatch<HttpTypes.AdminWorkflowExecutionResponse>
export const WorkflowExecutionDetailBreadcrumb = (
props: WorkflowExecutionDetailBreadcrumbProps
) => {
const { id } = props.params || {}
const { workflow_execution } = useWorkflowExecution(id!, {
initialData: props.data,
enabled: Boolean(id),
})
if (!workflow_execution) {
return null
}
const cleanId = workflow_execution.id.replace("wf_exec_", "")
return <span>{cleanId}</span>
}
@@ -1 +1,3 @@
export { WorkflowExecutionDetailBreadcrumb as Breadcrumb } from "./breadcrumb"
export { workflowExecutionLoader as loader } from "./loader"
export { ExecutionDetail as Component } from "./workflow-detail"
@@ -1,6 +1,5 @@
import { LoaderFunctionArgs } from "react-router-dom"
import { HttpTypes } from "@medusajs/types"
import { workflowExecutionsQueryKeys } from "../../../hooks/api/workflow-executions"
import { sdk } from "../../../lib/client"
import { queryClient } from "../../../lib/query-client"
@@ -10,13 +9,11 @@ const executionDetailQuery = (id: string) => ({
queryFn: async () => sdk.admin.workflowExecution.retrieve(id),
})
export const executionLoader = async ({ params }: LoaderFunctionArgs) => {
export const workflowExecutionLoader = async ({
params,
}: LoaderFunctionArgs) => {
const id = params.id
const query = executionDetailQuery(id!)
return (
queryClient.getQueryData<HttpTypes.AdminWorkflowExecutionResponse>(
query.queryKey
) ?? (await queryClient.fetchQuery(query))
)
return queryClient.ensureQueryData(query)
}