fix(route_links): add a way to not url encode params
+ 73
- 11
@@ -1,5 +1,5 @@
 {
-  "_generatedAtUnix": 1666821250431,
+  "_generatedAtUnix": 1668091423088,
   "_hashAlgorithm": "sha1",
   "_version": 2,
   "islands": {

...
@@ -22,7 +22,7 @@
       "pathSourceMap": "./public/.islands/PullRequestSourceSelect.bundle.js.map"
     },
     "RepositoriesList": {
-      "hash": "4cf17af8826b391263e248be55ce4116eb7414d6",
+      "hash": "5ab9b0029f1d67d317afbfbd0ee72797f382853c",
       "pathSource": "./app/islands/RepositoriesList.tsx",
       "pathBundle": "./public/.islands/RepositoriesList.bundle.js",
       "pathSourceMap": "./public/.islands/RepositoriesList.bundle.js.map"

app/components/PageHeader.tsx
@@ -21,9 +21,14 @@ export const PageHeader: VFC<PageHeaderProps & WithThemeSchemeProp> = ({
       return (
         <>
           <a
-            href={buildRouteLink(AppRoute.USER_DETAILS, {
-              username: commonProps.currentUserUsername || "ghost",
-            })}
+            aria-label={"View your profile and repositories"}
+            href={buildRouteLink(
+              AppRoute.USER_DETAILS,
+              {
+                username: commonProps.currentUserUsername || "ghost",
+              },
+              { encodeURIComponent: false }
+            )}
           >
             {commonProps.currentUserUsername || "ghost"}
           </a>

...
@@ -80,6 +85,9 @@ export const PageHeader: VFC<PageHeaderProps & WithThemeSchemeProp> = ({
           </a>
         )}
         <a
+          aria-label={`Switch to ${
+            themeScheme === "light" ? "dark" : "light"
+          } theme`}
           data-smooth-scroll={"disabled"}
           href={buildRouteLink(AppRoute.THEME_SET_SCHEME_ACTION, {
             themeScheme: invertThemeScheme,

app/islands/RepositoriesList.tsx
@@ -3,7 +3,7 @@ import type { ReactIsland } from "@ethicdevs/react-monolith";
 // 3rd-party
 import React from "react";
 // generated via script[generate:prisma]
-import { Organization, Repository } from "@prisma/client";
+import type { Organization, Repository } from "@prisma/client";
 // app
 import type { WithThemeSchemeProp } from "../types";
 import { AppRoute } from "../routes.defs";

app/utils/shared/buildRouteLink.test.ts
@@ -58,6 +58,44 @@ describe("buildRouteLink", () => {
       `"/my-super-org/my-super-repo/main/tree/app%2Fservices%2Fauth%2FisExistingEmailAddress.ts"`
     );
   });
+  it(`should return path from route with url encoded params values when it gets called without an options object`, () => {
+    // Given
+    const routeName: AppRoute = AppRoute.USER_DETAILS;
+    // When
+    const link = buildRouteLink(routeName, {
+      username: "test-user",
+    });
+    // Then
+    expect(link).toMatchInlineSnapshot(`"/%40test-user"`);
+  });
+  it(`should return path from route with url encoded params values when it gets called with options.encodeURIComponent is set to true`, () => {
+    // Given
+    const routeName: AppRoute = AppRoute.USER_DETAILS;
+    // When
+    const link = buildRouteLink(
+      routeName,
+      {
+        username: "test-user",
+      },
+      { encodeURIComponent: true }
+    );
+    // Then
+    expect(link).toMatchInlineSnapshot(`"/%40test-user"`);
+  });
+  it(`should return path from route WITHOUT url encoded params values when it gets called with options.encodeURIComponent is set to false`, () => {
+    // Given
+    const routeName: AppRoute = AppRoute.USER_DETAILS;
+    // When
+    const link = buildRouteLink(
+      routeName,
+      {
+        username: "test-user",
+      },
+      { encodeURIComponent: false }
+    );
+    // Then
+    expect(link).toMatchInlineSnapshot(`"/@test-user"`);
+  });
 });
 
 describe("buildPathLink", () => {});

app/utils/shared/buildRouteLink.ts
@@ -1,20 +1,27 @@
 import { AppRoute, AppRoutePaths, AppRoutesParams } from "../../routes.defs";
 
+interface BuildLinkOptions {
+  // @default true
+  encodeURIComponent?: boolean;
+}
+
 export default function buildRouteLink<P extends AppRoute>(
   route: P,
   routeParams: "params" extends keyof AppRoutesParams[P]
     ? AppRoutesParams[P]["params"]
-    : {} | null
+    : {} | null,
+  options?: BuildLinkOptions
 ): typeof AppRoutePaths[P] {
   const path = AppRoutePaths[route];
-  return buildPathLink(path, routeParams);
+  return buildPathLink(path, routeParams, options);
 }
 
 export function buildPathLink<P extends AppRoute>(
   path: string,
   routeParams: "params" extends keyof AppRoutesParams[P]
     ? AppRoutesParams[P]["params"]
-    : {} | null
+    : {} | null,
+  options?: BuildLinkOptions
 ): string {
   if (
     routeParams == null ||

...
@@ -52,6 +59,15 @@ export function buildPathLink<P extends AppRoute>(
     }
   });
 
-  const link = linkBuilder.map((param) => encodeURIComponent(param)).join("/");
-  return link;
+  const shouldEncodeURIComponents = !!(
+    options == null ||
+    options.encodeURIComponent == null ||
+    options.encodeURIComponent !== false
+  );
+
+  return linkBuilder
+    .map((param) =>
+      shouldEncodeURIComponents ? encodeURIComponent(param) : param
+    )
+    .join("/");
 }