Kaynağa Gözat

fix(dashboard): Derive breadcrumbs from path/basepath to match sidebar (#3784)

Mohamed Al Ali 4 ay önce
ebeveyn
işleme
562cc540f0
28 değiştirilmiş dosya ile 137 ekleme ve 71 silme
  1. 1 1
      packages/dashboard/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx
  2. 1 0
      packages/dashboard/src/app/routes/_authenticated/_assets/assets.tsx
  3. 1 1
      packages/dashboard/src/app/routes/_authenticated/_assets/assets_.$id.tsx
  4. 1 1
      packages/dashboard/src/app/routes/_authenticated/_channels/channels_.$id.tsx
  5. 1 1
      packages/dashboard/src/app/routes/_authenticated/_collections/collections_.$id.tsx
  6. 1 1
      packages/dashboard/src/app/routes/_authenticated/_countries/countries_.$id.tsx
  7. 1 1
      packages/dashboard/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx
  8. 1 1
      packages/dashboard/src/app/routes/_authenticated/_customers/customers_.$id.tsx
  9. 1 1
      packages/dashboard/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx
  10. 1 1
      packages/dashboard/src/app/routes/_authenticated/_facets/facets_.$id.tsx
  11. 2 2
      packages/dashboard/src/app/routes/_authenticated/_global-settings/global-settings.tsx
  12. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/orders_.$id.tsx
  13. 5 1
      packages/dashboard/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx
  14. 1 1
      packages/dashboard/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx
  15. 1 1
      packages/dashboard/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx
  16. 2 2
      packages/dashboard/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx
  17. 1 1
      packages/dashboard/src/app/routes/_authenticated/_products/products_.$id.tsx
  18. 2 2
      packages/dashboard/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx
  19. 1 1
      packages/dashboard/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx
  20. 1 1
      packages/dashboard/src/app/routes/_authenticated/_roles/roles_.$id.tsx
  21. 1 1
      packages/dashboard/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx
  22. 1 1
      packages/dashboard/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx
  23. 1 1
      packages/dashboard/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx
  24. 1 0
      packages/dashboard/src/app/routes/_authenticated/_system/healthchecks.tsx
  25. 1 1
      packages/dashboard/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx
  26. 1 1
      packages/dashboard/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx
  27. 1 1
      packages/dashboard/src/app/routes/_authenticated/_zones/zones_.$id.tsx
  28. 103 43
      packages/dashboard/src/lib/components/layout/generated-breadcrumbs.tsx

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx

@@ -36,7 +36,7 @@ export const Route = createFileRoute('/_authenticated/_administrators/administra
         breadcrumb: (isNew, entity) => {
             const name = `${entity?.firstName} ${entity?.lastName}`;
             return [
-                { path: '/administrators', label: 'Administrators' },
+                { path: '/administrators', label: <Trans>Administrators</Trans> },
                 isNew ? <Trans>New administrator</Trans> : name,
             ];
         },

+ 1 - 0
packages/dashboard/src/app/routes/_authenticated/_assets/assets.tsx

@@ -6,6 +6,7 @@ import { DeleteAssetsBulkAction } from './components/asset-bulk-actions.js';
 
 export const Route = createFileRoute('/_authenticated/_assets/assets')({
     component: RouteComponent,
+    loader: () => ({ breadcrumb: () => <Trans>Assets</Trans> }),
 });
 
 function RouteComponent() {

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_assets/assets_.$id.tsx

@@ -35,7 +35,7 @@ export const Route = createFileRoute('/_authenticated/_assets/assets_/$id')({
         queryDocument: assetDetailDocument,
         breadcrumb(isNew, entity) {
             return [
-                { path: '/assets', label: 'Assets' },
+                { path: '/assets', label: <Trans>Assets</Trans> },
                 isNew ? <Trans>New asset</Trans> : (entity?.name ?? ''),
             ];
         },

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_channels/channels_.$id.tsx

@@ -37,7 +37,7 @@ export const Route = createFileRoute('/_authenticated/_channels/channels_/$id')(
         queryDocument: channelDetailDocument,
         breadcrumb(isNew, entity) {
             return [
-                { path: '/channels', label: 'Channels' },
+                { path: '/channels', label: <Trans>Channels</Trans> },
                 isNew ? <Trans>New channel</Trans> : <ChannelCodeLabel code={entity?.code ?? ''} />,
             ];
         },

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_collections/collections_.$id.tsx

@@ -41,7 +41,7 @@ export const Route = createFileRoute('/_authenticated/_collections/collections_/
         pageId,
         queryDocument: collectionDetailDocument,
         breadcrumb: (isNew, entity) => [
-            { path: '/collections', label: 'Collections' },
+            { path: '/collections', label: <Trans>Collections</Trans> },
             isNew ? <Trans>New collection</Trans> : entity?.name,
         ],
     }),

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_countries/countries_.$id.tsx

@@ -31,7 +31,7 @@ export const Route = createFileRoute('/_authenticated/_countries/countries_/$id'
         pageId,
         queryDocument: countryDetailDocument,
         breadcrumb: (isNew, entity) => [
-            { path: '/countries', label: 'Countries' },
+            { path: '/countries', label: <Trans>Countries</Trans> },
             isNew ? <Trans>New country</Trans> : entity?.name,
         ],
     }),

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_customer-groups/customer-groups_.$id.tsx

@@ -34,7 +34,7 @@ export const Route = createFileRoute('/_authenticated/_customer-groups/customer-
         pageId,
         queryDocument: customerGroupDetailDocument,
         breadcrumb: (isNew, entity) => [
-            { path: '/customer-groups', label: 'Customer groups' },
+            { path: '/customer-groups', label: <Trans>Customer Groups</Trans> },
             isNew ? <Trans>New customer group</Trans> : entity?.name,
         ],
     }),

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_customers/customers_.$id.tsx

@@ -55,7 +55,7 @@ export const Route = createFileRoute('/_authenticated/_customers/customers_/$id'
         pageId,
         queryDocument: customerDetailDocument,
         breadcrumb: (isNew, entity) => [
-            { path: '/customers', label: 'Customers' },
+            { path: '/customers', label: <Trans>Customers</Trans> },
             isNew ? <Trans>New customer</Trans> : `${entity?.firstName} ${entity?.lastName}`,
         ],
     }),

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_facets/facets_.$facetId.values_.$id.tsx

@@ -36,7 +36,7 @@ export const Route = createFileRoute('/_authenticated/_facets/facets_/$facetId/v
         queryDocument: facetValueDetailDocument,
         breadcrumb(isNew, entity) {
             const facetName = entity?.facet.name ?? 'Facet Value';
-            const breadcrumb: PageBreadcrumb[] = [{ path: '/facets', label: 'Facets' }];
+            const breadcrumb: PageBreadcrumb[] = [{ path: '/facets', label: <Trans>Facets</Trans> }];
             if (isNew) {
                 breadcrumb.push(<Trans>New facet value</Trans>);
             } else if (entity) {

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_facets/facets_.$id.tsx

@@ -32,7 +32,7 @@ export const Route = createFileRoute('/_authenticated/_facets/facets_/$id')({
         pageId,
         queryDocument: facetDetailDocument,
         breadcrumb(isNew, entity) {
-            return [{ path: '/facets', label: 'Facets' }, isNew ? <Trans>New facet</Trans> : entity?.name];
+            return [{ path: '/facets', label: <Trans>Facets</Trans> }, isNew ? <Trans>New facet</Trans> : entity?.name];
         },
     }),
     errorComponent: ({ error }) => <ErrorPage message={error.message} />,

+ 2 - 2
packages/dashboard/src/app/routes/_authenticated/_global-settings/global-settings.tsx

@@ -38,7 +38,7 @@ export const Route = createFileRoute('/_authenticated/_global-settings/global-se
             {},
         );
         return {
-            breadcrumb: [{ path: '/global-settings', label: <Trans>Global settings</Trans> }],
+            breadcrumb: [{ path: '/global-settings', label: <Trans>Global Settings</Trans> }],
         };
     },
     errorComponent: ({ error }) => <ErrorPage message={error.message} />,
@@ -88,7 +88,7 @@ function GlobalSettingsPage() {
     return (
         <Page pageId={pageId} form={form} submitHandler={submitHandler} entity={entity}>
             <PageTitle>
-                <Trans>Global settings</Trans>
+                <Trans>Global Settings</Trans>
             </PageTitle>
             <PageActionBar>
                 <PageActionBarRight>

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/orders_.$id.tsx

@@ -71,7 +71,7 @@ export const Route = createFileRoute('/_authenticated/_orders/orders_/$id')({
         }
 
         return {
-            breadcrumb: [{ path: '/orders', label: 'Orders' }, result.order.code],
+            breadcrumb: [{ path: '/orders', label: <Trans>Orders</Trans> }, result.order.code],
         };
     },
     errorComponent: ({ error }) => <ErrorPage message={error.message} />,

+ 5 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/orders_.$id_.modify.tsx

@@ -62,7 +62,11 @@ export const Route = createFileRoute('/_authenticated/_orders/orders_/$id_/modif
         }
 
         return {
-            breadcrumb: [{ path: '/orders', label: 'Orders' }, result.order.code, { label: 'Modify' }],
+            breadcrumb: [
+                { path: '/orders', label: <Trans>Orders</Trans> },
+                result.order.code,
+                { label: <Trans>Modify</Trans> },
+            ],
         };
     },
     errorComponent: ({ error }) => <ErrorPage message={error.message} />,

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_orders/orders_.draft.$id.tsx

@@ -68,7 +68,7 @@ export const Route = createFileRoute('/_authenticated/_orders/orders_/draft/$id'
         }
 
         return {
-            breadcrumb: [{ path: '/orders', label: 'Orders' }, result.order.code],
+            breadcrumb: [{ path: '/orders', label: <Trans>Orders</Trans> }, result.order.code],
         };
     },
     errorComponent: ({ error }) => <ErrorPage message={error.message} />,

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx

@@ -39,7 +39,7 @@ export const Route = createFileRoute('/_authenticated/_payment-methods/payment-m
         queryDocument: paymentMethodDetailDocument,
         breadcrumb(_isNew, entity) {
             return [
-                { path: '/payment-methods', label: 'Payment methods' },
+                { path: '/payment-methods', label: <Trans>Payment Methods</Trans> },
                 _isNew ? <Trans>New payment method</Trans> : entity?.name,
             ];
         },

+ 2 - 2
packages/dashboard/src/app/routes/_authenticated/_product-variants/product-variants_.$id.tsx

@@ -46,12 +46,12 @@ export const Route = createFileRoute('/_authenticated/_product-variants/product-
         breadcrumb(_isNew, entity, location) {
             if ((location.search as any).from === 'product') {
                 return [
-                    { path: '/product', label: 'Products' },
+                    { path: '/product', label: <Trans>Products</Trans> },
                     { path: `/products/${entity?.product.id}`, label: entity?.product.name ?? '' },
                     entity?.name,
                 ];
             }
-            return [{ path: '/product-variants', label: 'Product Variants' }, entity?.name];
+            return [{ path: '/product-variants', label: <Trans>Product Variants</Trans> }, entity?.name];
         },
     }),
     errorComponent: ({ error }) => <ErrorPage message={error.message} />,

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_products/products_.$id.tsx

@@ -40,7 +40,7 @@ export const Route = createFileRoute('/_authenticated/_products/products_/$id')(
         queryDocument: productDetailDocument,
         breadcrumb(isNew, entity) {
             return [
-                { path: '/products', label: 'Products' },
+                { path: '/products', label: <Trans>Products</Trans> },
                 isNew ? <Trans>New product</Trans> : entity?.name,
             ];
         },

+ 2 - 2
packages/dashboard/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx

@@ -51,9 +51,9 @@ export const Route = createFileRoute('/_authenticated/_products/products_/$id_/v
         });
         return {
             breadcrumb: [
-                { path: '/products', label: 'Products' },
+                { path: '/products', label: <Trans>Products</Trans> },
                 { path: `/products/${params.id}`, label: result.product?.name },
-                <Trans>Manage variants</Trans>,
+                <Trans>Manage Variants</Trans>,
             ],
         };
     },

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx

@@ -40,7 +40,7 @@ export const Route = createFileRoute('/_authenticated/_promotions/promotions_/$i
         queryDocument: promotionDetailDocument,
         breadcrumb(isNew, entity) {
             return [
-                { path: '/promotions', label: 'Promotions' },
+                { path: '/promotions', label: <Trans>Promotions</Trans> },
                 isNew ? <Trans>New promotion</Trans> : entity?.name,
             ];
         },

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_roles/roles_.$id.tsx

@@ -31,7 +31,7 @@ export const Route = createFileRoute('/_authenticated/_roles/roles_/$id')({
         queryDocument: roleDetailDocument,
         breadcrumb(isNew, entity) {
             return [
-                { path: '/roles', label: 'Roles' },
+                { path: '/roles', label: <Trans>Roles</Trans> },
                 isNew ? <Trans>New role</Trans> : entity?.description,
             ];
         },

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_sellers/sellers_.$id.tsx

@@ -28,7 +28,7 @@ export const Route = createFileRoute('/_authenticated/_sellers/sellers_/$id')({
         pageId,
         queryDocument: sellerDetailDocument,
         breadcrumb: (isNew, entity) => [
-            { path: '/sellers', label: 'Sellers' },
+            { path: '/sellers', label: <Trans>Sellers</Trans> },
             isNew ? <Trans>New seller</Trans> : entity?.name,
         ],
     }),

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx

@@ -39,7 +39,7 @@ export const Route = createFileRoute('/_authenticated/_shipping-methods/shipping
         queryDocument: shippingMethodDetailDocument,
         breadcrumb(isNew, entity) {
             return [
-                { path: '/shipping-methods', label: 'Shipping methods' },
+                { path: '/shipping-methods', label: <Trans>Shipping Methods</Trans> },
                 isNew ? <Trans>New shipping method</Trans> : entity?.name,
             ];
         },

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_stock-locations/stock-locations_.$id.tsx

@@ -35,7 +35,7 @@ export const Route = createFileRoute('/_authenticated/_stock-locations/stock-loc
         queryDocument: stockLocationDetailQuery,
         breadcrumb(isNew, entity) {
             return [
-                { path: '/stock-locations', label: 'Stock locations' },
+                { path: '/stock-locations', label: <Trans>Stock Locations</Trans> },
                 isNew ? <Trans>New stock location</Trans> : entity?.name,
             ];
         },

+ 1 - 0
packages/dashboard/src/app/routes/_authenticated/_system/healthchecks.tsx

@@ -10,6 +10,7 @@ import { uiConfig } from 'virtual:vendure-ui-config';
 
 export const Route = createFileRoute('/_authenticated/_system/healthchecks')({
     component: HealthchecksPage,
+    loader: () => ({ breadcrumb: () => <Trans>Healthchecks</Trans> }),
 });
 
 interface HealthcheckItem {

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_tax-categories/tax-categories_.$id.tsx

@@ -35,7 +35,7 @@ export const Route = createFileRoute('/_authenticated/_tax-categories/tax-catego
         queryDocument: taxCategoryDetailDocument,
         breadcrumb(isNew, entity) {
             return [
-                { path: '/tax-categories', label: 'Tax categories' },
+                { path: '/tax-categories', label: <Trans>Tax Categories</Trans> },
                 isNew ? <Trans>New tax category</Trans> : entity?.name,
             ];
         },

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_tax-rates/tax-rates_.$id.tsx

@@ -34,7 +34,7 @@ export const Route = createFileRoute('/_authenticated/_tax-rates/tax-rates_/$id'
         queryDocument: taxRateDetailDocument,
         breadcrumb(isNew, entity) {
             return [
-                { path: '/tax-rates', label: 'Tax rates' },
+                { path: '/tax-rates', label: <Trans>Tax Rates</Trans> },
                 isNew ? <Trans>New tax rate</Trans> : entity?.name,
             ];
         },

+ 1 - 1
packages/dashboard/src/app/routes/_authenticated/_zones/zones_.$id.tsx

@@ -30,7 +30,7 @@ export const Route = createFileRoute('/_authenticated/_zones/zones_/$id')({
         pageId,
         queryDocument: zoneDetailDocument,
         breadcrumb(isNew, entity) {
-            return [{ path: '/zones', label: 'Zones' }, isNew ? <Trans>New zone</Trans> : entity?.name];
+            return [{ path: '/zones', label: <Trans>Zones</Trans> }, isNew ? <Trans>New zone</Trans> : entity?.name];
         },
     }),
     errorComponent: ({ error }) => <ErrorPage message={error.message} />,

+ 103 - 43
packages/dashboard/src/lib/components/layout/generated-breadcrumbs.tsx

@@ -5,9 +5,11 @@ import {
     BreadcrumbList,
     BreadcrumbSeparator,
 } from '@/vdb/components/ui/breadcrumb.js';
-import { Link, useRouterState } from '@tanstack/react-router';
+import { Link, useRouter, useRouterState } from '@tanstack/react-router';
 import * as React from 'react';
 import { Fragment } from 'react';
+import { getNavMenuConfig } from '@/vdb/framework/nav-menu/nav-menu-extensions.js';
+import type { NavMenuItem, NavMenuSection } from '@/vdb/framework/nav-menu/nav-menu-extensions.js';
 
 export interface BreadcrumbPair {
     label: string | React.ReactElement;
@@ -20,54 +22,112 @@ export type PageBreadcrumb = BreadcrumbPair | BreadcrumbShorthand;
 
 export function GeneratedBreadcrumbs() {
     const matches = useRouterState({ select: s => s.matches });
-    const breadcrumbs: BreadcrumbPair[] = matches
-        .filter(match => match.loaderData?.breadcrumb)
-        .map(({ pathname, loaderData }) => {
-            if (typeof loaderData.breadcrumb === 'string') {
-                return {
-                    label: loaderData.breadcrumb,
-                    path: pathname,
-                };
-            }
-            if (Array.isArray(loaderData.breadcrumb)) {
-                return loaderData.breadcrumb.map((breadcrumb: PageBreadcrumb) => {
-                    if (typeof breadcrumb === 'string') {
-                        return {
-                            label: breadcrumb,
-                            path: pathname,
-                        };
-                    } else if (React.isValidElement(breadcrumb)) {
-                        return {
-                            label: breadcrumb,
-                            path: pathname,
-                        };
-                    } else {
-                        return {
-                            label: breadcrumb.label,
-                            path: breadcrumb.path,
-                        };
-                    }
-                });
-            }
-            if (typeof loaderData.breadcrumb === 'function') {
-                return {
-                    label: loaderData.breadcrumb(),
-                    path: pathname,
-                };
+    const currentPath = useRouterState({ select: s => s.location.pathname });
+    const router = useRouter();
+    const navMenuConfig = getNavMenuConfig();
+    const basePath = router.basepath || '';
+
+    const normalizeBreadcrumb = (breadcrumb: any, pathname: string): BreadcrumbPair[] => {
+        if (typeof breadcrumb === 'string') {
+            return [{ label: breadcrumb, path: pathname }];
+        }
+        if (React.isValidElement(breadcrumb)) {
+            return [{ label: breadcrumb, path: pathname }];
+        }
+        if (typeof breadcrumb === 'function') {
+            return [{ label: breadcrumb(), path: pathname }];
+        }
+        if (Array.isArray(breadcrumb)) {
+            return breadcrumb.map((crumb: PageBreadcrumb) => {
+                if (typeof crumb === 'string' || React.isValidElement(crumb)) {
+                    return { label: crumb, path: pathname };
+                }
+                return { label: crumb.label, path: crumb.path };
+            });
+        }
+        return [];
+    };
+
+    const rawCrumbs: BreadcrumbPair[] = React.useMemo(() => {
+        return matches
+            .filter(match => match.loaderData?.breadcrumb)
+            .flatMap(({ pathname, loaderData }) => 
+                normalizeBreadcrumb(loaderData.breadcrumb, pathname)
+            );
+    }, [matches]);
+
+    const isBaseRoute = (p: string) => p === basePath || p === `${basePath}/`;
+    const pageCrumbs: BreadcrumbPair[] = rawCrumbs.filter(c => !isBaseRoute(c.path));
+
+    const normalizePath = (path: string): string => {
+        const normalizedPath = basePath && path.startsWith(basePath) ? path.slice(basePath.length) : path;
+        return normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`;
+    };
+
+    const pathMatches = (cleanPath: string, rawUrl?: string): boolean => {
+        if (!rawUrl) return false;
+        const strip = (p: string) => (p !== '/' && p.endsWith('/') ? p.slice(0, -1) : p);
+        const p = strip(cleanPath);
+        const u = strip(rawUrl);
+        return p === u || p.startsWith(`${u}/`);
+    };
+
+    const checkSectionItems = (
+        section: NavMenuSection | NavMenuItem,
+        cleanPath: string,
+    ): BreadcrumbPair | undefined => {
+        if (!('items' in section) || !Array.isArray(section.items)) {
+            return undefined;
+        }
+
+        for (const item of section.items) {
+            if (!item?.url) continue;
+            if (pathMatches(cleanPath, item.url)) {
+                return { label: section.title, path: item.url };
             }
-            if (React.isValidElement(loaderData.breadcrumb)) {
-                return {
-                    label: loaderData.breadcrumb,
-                    path: pathname,
-                };
+        }
+        return undefined;
+    };
+
+    const checkDirectSection = (
+        section: NavMenuSection | NavMenuItem,
+        cleanPath: string,
+    ): BreadcrumbPair | undefined => {
+        if ('url' in section && section.url && pathMatches(cleanPath, section.url)) {
+            return { label: section.title, path: section.url };
+        }
+        return undefined;
+    };
+
+    const findSectionCrumb = (path: string): BreadcrumbPair | undefined => {
+        const cleanPath = normalizePath(path);
+        const sections: Array<NavMenuSection | NavMenuItem> = navMenuConfig?.sections ?? [];
+        if (sections.length === 0) return undefined;
+
+        for (const section of sections) {
+            const result = checkSectionItems(section, cleanPath) || checkDirectSection(section, cleanPath);
+            if (result) {
+                return result;
             }
-        })
-        .flat();
+        }
+        return undefined;
+    };
+
+    const sectionCrumb = React.useMemo(
+        () => findSectionCrumb(currentPath),
+        [currentPath, basePath, navMenuConfig],
+    );
+    const breadcrumbs: BreadcrumbPair[] = React.useMemo(() => {
+        const arr = sectionCrumb ? [sectionCrumb, ...pageCrumbs] : pageCrumbs;
+        return arr.filter((c, i, self) =>
+            self.findIndex(x => x.path === c.path && x.label === c.label) === i,
+        );
+    }, [sectionCrumb, pageCrumbs]);
     return (
         <Breadcrumb>
             <BreadcrumbList>
                 {breadcrumbs.map(({ label, path }, index, arr) => (
-                    <Fragment key={index}>
+                    <Fragment key={`${path}-${index}`}>
                         <BreadcrumbItem className="hidden md:block">
                             <BreadcrumbLink asChild>
                                 <Link to={path}>{label}</Link>