From 585e473367bebd98506b2a249b07792345b826fb Mon Sep 17 00:00:00 2001
From: arndom <aminonimisi2@gmail.com>
Date: Tue, 21 Jan 2025 15:36:13 +0100
Subject: [PATCH 1/2] fix(core): add dynamic ids as part of breadcrumbs

- display end id when on action(show/edit) page
- display nested id in route
- add test case for these
- add changeset
---
 .changeset/shaggy-pants-argue.md              |  7 +++
 .../core/src/hooks/breadcrumb/index.spec.tsx  | 58 +++++++++++++++++++
 packages/core/src/hooks/breadcrumb/index.ts   | 50 ++++++++++++++++
 3 files changed, 115 insertions(+)
 create mode 100644 .changeset/shaggy-pants-argue.md

diff --git a/.changeset/shaggy-pants-argue.md b/.changeset/shaggy-pants-argue.md
new file mode 100644
index 000000000000..ce3c6ad6172d
--- /dev/null
+++ b/.changeset/shaggy-pants-argue.md
@@ -0,0 +1,7 @@
+---
+"@refinedev/core": patch
+---
+
+Update `useBreadcrumb` to include dynamic ids in return.
+
+[Resolves #6584](https://github.com/refinedev/refine/issues/6584)
diff --git a/packages/core/src/hooks/breadcrumb/index.spec.tsx b/packages/core/src/hooks/breadcrumb/index.spec.tsx
index 0425186dd0b5..5bfd2ac8b683 100644
--- a/packages/core/src/hooks/breadcrumb/index.spec.tsx
+++ b/packages/core/src/hooks/breadcrumb/index.spec.tsx
@@ -181,4 +181,62 @@ describe("useBreadcrumb Hook", () => {
       { label: "buttons.show" },
     ]);
   });
+
+  it("if resource has nested resource with dynamic id -> route/:id/nested-route, the nested should be in breadcrumbs", async () => {
+    const { result } = renderHook(() => useBreadcrumb(), {
+      wrapper: renderWrapper({
+        resources: [
+          {
+            name: "blog_posts",
+            list: "/blog-posts",
+            show: "/blog-posts/show/:id",
+            create: "/blog-posts/create",
+            edit: "/blog-posts/edit/:id",
+            meta: {
+              canDelete: true,
+            },
+          },
+
+          {
+            name: "comments",
+            list: "blog-posts/show/:id/comments",
+            show: "blog-posts/show/:id/comments/:id",
+            meta: {
+              parent: "blog-posts",
+            },
+          },
+        ],
+        routerProvider: mockRouterProvider({
+          action: "list",
+          id: "2",
+          pathname: "/blog-posts/show/2/comments",
+          resource: {
+            name: "comments",
+            list: "blog-posts/show/:id/comments",
+            show: "blog-posts/show/:id/comments/:id",
+            meta: {
+              parent: "blog-posts",
+            },
+          },
+        }),
+      }),
+    });
+
+    expect(result.current.breadcrumbs).toEqual([
+      {
+        label: "Blog posts",
+        href: undefined,
+        icon: undefined,
+      },
+      {
+        label: "2",
+        href: "blog-posts/show/2",
+      },
+      {
+        label: "Comments",
+        href: "/blog-posts/show/2/comments",
+        icon: undefined,
+      },
+    ]);
+  });
 });
diff --git a/packages/core/src/hooks/breadcrumb/index.ts b/packages/core/src/hooks/breadcrumb/index.ts
index 762e7b9a1630..d2ef95b7ef5d 100644
--- a/packages/core/src/hooks/breadcrumb/index.ts
+++ b/packages/core/src/hooks/breadcrumb/index.ts
@@ -80,6 +80,48 @@ export const useBreadcrumb = ({
           : composeRoute(hrefRaw, parentResource?.meta, parsed, metaFromProps)
         : undefined;
 
+      // innner route params: /:id in main-route/:id/sub-route
+      if (hrefRaw) {
+        const regex = /[^/]+\/:id\//g;
+        const hrefRegexMatch = hrefRaw.match(regex);
+
+        if (hrefRegexMatch) {
+          const matches = hrefRegexMatch.map((match) => {
+            const end = hrefRaw.indexOf(match) + match.length;
+            return hrefRaw.slice(0, end);
+          });
+
+          if (matches && parsed.pathname) {
+            const lastMatch = matches.at(-1);
+
+            if (lastMatch) {
+              const pathnameSegments = parsed.pathname
+                .split("/")
+                .filter((segment) => segment);
+              const lastMatchSegments = lastMatch
+                .split("/")
+                .filter((segment) => segment);
+
+              const urlBits: string[] = [];
+              lastMatchSegments.forEach((segment, index) => {
+                let x = segment;
+
+                if (segment !== pathnameSegments[index]) {
+                  x = pathnameSegments[index];
+                }
+
+                urlBits.push(x);
+              });
+
+              const label = urlBits.at(-1);
+              const href = [...urlBits].join("/");
+
+              breadcrumbs.push({ label: String(label), href });
+            }
+          }
+        }
+      }
+
       breadcrumbs.push({
         label:
           pickNotDeprecated(
@@ -121,6 +163,14 @@ export const useBreadcrumb = ({
         label: translate(key, textTransformers.humanize(action)),
       });
     }
+
+    // add action path param to breadcrum
+    if (parsed.id) {
+      breadcrumbs.push({
+        label: String(parsed.id),
+        href: parsed.pathname,
+      });
+    }
   }
 
   return {

From e7a23153df0fce2b6ed60a1b0c7264238acbe1f2 Mon Sep 17 00:00:00 2001
From: arndom <aminonimisi2@gmail.com>
Date: Thu, 23 Jan 2025 16:07:28 +0100
Subject: [PATCH 2/2] fix(core): update regex to match any `:id` formats in
 breadcrumb

- to match ids like: `:orgId`, `:projectId`, not just `:id`
---
 packages/core/src/hooks/breadcrumb/index.ts | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/packages/core/src/hooks/breadcrumb/index.ts b/packages/core/src/hooks/breadcrumb/index.ts
index d2ef95b7ef5d..58aaf6fb45ba 100644
--- a/packages/core/src/hooks/breadcrumb/index.ts
+++ b/packages/core/src/hooks/breadcrumb/index.ts
@@ -82,7 +82,11 @@ export const useBreadcrumb = ({
 
       // innner route params: /:id in main-route/:id/sub-route
       if (hrefRaw) {
-        const regex = /[^/]+\/:id\//g;
+        // [^/]+  --> any number of letters not `/`
+        //      \/: ---> start with /:
+        //         [^/]+  --> any number of letters not `/`
+        //              \/ -- end with /
+        const regex = /[^/]+\/:[^/]+\//g;
         const hrefRegexMatch = hrefRaw.match(regex);
 
         if (hrefRegexMatch) {