-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve client app router configuration (#231)
### Summary & Motivation Improve developer experience - setting up filesystem based routing. Configuring React Router adding things like error handling, code splitting, dynamic path segments etc. should be as easy as creating a file. Inspired by NextJS and the ease of use convention based routing. ``` /app layout.tsx - shared layout for child routes page.tsx - entry point for this route segment error.tsx - page to show when errors occur not-found.tsx - page to show if resource not found loading.tsx - code split and load this route dynamically /[id] page.tsx - show details using the param "id" ``` *(NextJS has more functionality to work on server and client - [docs](https://nextjs.org/docs/app/building-your-application/routing))* ### Checklist - [x] I have added a Label to the pull-request - [x] I have added tests, and done manual regression tests - [x] I have updated the documentation, if necessary
- Loading branch information
Showing
26 changed files
with
415 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -380,6 +380,7 @@ FodyWeavers.xsd | |
|
||
# Generated files | ||
**/*.generated.d.ts | ||
**/*.generated.ts | ||
|
||
# Frontend builds | ||
dist/ |
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { useEffect } from "react"; | ||
|
||
type ErrorProps = { | ||
error: Error; | ||
reset: () => void; | ||
}; | ||
|
||
export default function Error({ error, reset }: ErrorProps) { | ||
useEffect(() => { | ||
// Log the error to an error reporting service | ||
console.error(error); | ||
}, [error]); | ||
|
||
return ( | ||
<div> | ||
<h2>Something went wrong!</h2> | ||
<button | ||
onClick={ | ||
// Attempt to recover by trying to re-render the segment | ||
() => reset() | ||
} | ||
> | ||
Try again | ||
</button> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { useNavigate } from "react-router-dom"; | ||
import AcmeLogo from "@/ui/acme-logo.svg"; | ||
import { Button } from "react-aria-components"; | ||
|
||
type LayoutProps = { | ||
children: React.ReactNode; | ||
params: Record<string, string>; | ||
}; | ||
|
||
export default function Root({ children, params }: LayoutProps) { | ||
const navigate = useNavigate(); | ||
|
||
function handleCreateTenant() { | ||
navigate("/tenant/create"); | ||
} | ||
|
||
return ( | ||
<div className="flex flex-row h-full w-full"> | ||
<div className="flex gap-2 flex-col h-full w-80 border-r border-border bg-gray-100 px-6"> | ||
<h1 className="flex gap-1 items-center order-1 border-t border-border px-4 py-8"> | ||
<AcmeLogo className="w-6 h-6" /> ACME Company | ||
</h1> | ||
<div className="justify-start flex flex-row border-b border-border py-4"> | ||
<Button className="bg-blue-600 text-white py-2 px-4 rounded-full" onPress={handleCreateTenant}> | ||
Create Tenant | ||
</Button> | ||
</div> | ||
<nav className="grow"> | ||
<ul> | ||
<li className="p-4 hover:bg-gray-200 rounded-xl cursor-pointer"> | ||
<a href={`/`}>Account Management</a> | ||
</li> | ||
<li className="p-4 hover:bg-gray-200 rounded-xl cursor-pointer"> | ||
<a href={`/user-management`}>User Management</a> | ||
</li> | ||
</ul> | ||
</nav> | ||
</div> | ||
<div className="flex flex-col w-full h-full bg-background">{children}</div> | ||
</div> | ||
); | ||
} |
11 changes: 11 additions & 0 deletions
11
application/account-management/WebApp/src/app/not-found.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Link } from "react-router-dom"; | ||
|
||
export default function NotFound() { | ||
return ( | ||
<div> | ||
<h2>Not Found</h2> | ||
<p>Could not find requested resource</p> | ||
<Link to="/">Return Home</Link> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export default function Page() { | ||
return ( | ||
<div> | ||
<h2>Root</h2> | ||
<p>This is the main page</p> | ||
</div> | ||
); | ||
} |
11 changes: 11 additions & 0 deletions
11
application/account-management/WebApp/src/app/tenant/[id]/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
type PageProps = { | ||
params: Record<string, string>; | ||
}; | ||
|
||
export default function Page({ params }: PageProps) { | ||
return ( | ||
<div> | ||
<h1>Show tenant id: "{params.id}"</h1> | ||
</div> | ||
); | ||
} |
4 changes: 2 additions & 2 deletions
4
...anagement/WebApp/src/ui/tenant/actions.ts → ...t/WebApp/src/app/tenant/create/actions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
11 changes: 11 additions & 0 deletions
11
application/account-management/WebApp/src/app/tenant/create/success/loading.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
type PageProps = { | ||
params: Record<string, string>; | ||
}; | ||
|
||
export default function LoadingPage({ params }: PageProps) { | ||
return ( | ||
<div className="items-center flex flex-col justify-center h-full"> | ||
<div className="p-8 bg-gray-800 text-white rounded-xl shadow-md text-center gap-4 flex flex-col animate-ping"></div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
4 changes: 4 additions & 0 deletions
4
application/account-management/WebApp/src/lib/router/router.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { routes } from "./router.generated"; | ||
import { createBrowserRouter } from "react-router-dom"; | ||
|
||
export const router = createBrowserRouter(routes); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
File renamed without changes.
File renamed without changes.
File renamed without changes.
98 changes: 98 additions & 0 deletions
98
application/account-management/WebApp/src/shared/router/generate.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
#!/usr/bin/env bun | ||
// @ts- | ||
|
||
import fs from "fs"; | ||
import path from "path"; | ||
import prettier from "prettier"; | ||
import { RoutePage, RouteType, getRouteDetails } from "./routeDetails"; | ||
import { | ||
autoGeneratedBannerCode, | ||
createBrowserRouterCode, | ||
errorPageCode, | ||
importCode, | ||
layoutPageCode, | ||
lazyLoadingPageCode, | ||
normalPageCode, | ||
} from "./pageCode"; | ||
|
||
const sourceRoot = fs.existsSync(path.join(process.cwd(), "src")) ? path.join(process.cwd(), "src") : process.cwd(); | ||
const outputPath = path.join(sourceRoot, "lib", "router", "router.generated.ts"); | ||
const appPath = path.join(sourceRoot, "app"); | ||
const importPrefix = "@"; | ||
|
||
if (!fs.existsSync(appPath)) { | ||
throw new Error(`Could not find app directory at "${appPath}".`); | ||
} | ||
|
||
type TemplateObject = Record<string, string>; | ||
|
||
function generateReactRouterCode(routeItem: RouteType): string { | ||
if (routeItem.type === "page") { | ||
if (routeItem.loadingPage) { | ||
const landingRoutePage: RoutePage = { ...routeItem, page: routeItem.loadingPage }; | ||
return `{ | ||
index: true, | ||
element: ${lazyLoadingPageCode(routeItem, landingRoutePage)}, | ||
}`; | ||
} | ||
return `{ | ||
index: true, | ||
element: ${normalPageCode(routeItem)}, | ||
}`; | ||
} | ||
if (routeItem.type === "not-found") { | ||
return `{ | ||
path: "*", | ||
element: ${normalPageCode(routeItem)}, | ||
}`; | ||
} | ||
|
||
if (routeItem.type === "entry") { | ||
const result: TemplateObject = {}; | ||
|
||
if (routeItem.layout != null) { | ||
result.element = layoutPageCode(routeItem.layout); | ||
} | ||
|
||
if (routeItem.error != null) { | ||
result.errorElement = errorPageCode(routeItem.error); | ||
} | ||
|
||
if (routeItem.children.length > 0) { | ||
result.children = `[${routeItem.children.map((c) => generateReactRouterCode(c)).join(",\n")}]`; | ||
} | ||
|
||
return routeItem.aliases | ||
.map((alias) => | ||
serializeTemplateObject({ | ||
path: `"${alias}"`, | ||
...result, | ||
}) | ||
) | ||
.join(",\n"); | ||
} | ||
|
||
throw new Error(`Unhandled route type "${routeItem.type}".`); | ||
} | ||
|
||
const route = getRouteDetails([""], { appPath, importPrefix }); | ||
const code = `${autoGeneratedBannerCode()} | ||
${importCode()} | ||
${createBrowserRouterCode(generateReactRouterCode(route))} | ||
`; | ||
|
||
try { | ||
const routerFileContents = await prettier.format(code, { | ||
parser: "typescript", | ||
}); | ||
fs.writeFileSync(outputPath, routerFileContents, "utf-8"); | ||
console.log("Routes generated successfully!"); | ||
} catch (error) { | ||
console.error(error); | ||
console.info(code); | ||
process.exit(1); | ||
} | ||
|
||
function serializeTemplateObject(object: TemplateObject) { | ||
return Object.entries(object).reduce((result, [key, value]) => `${result}${key}: ${value},\n`, "{\n") + "}"; | ||
} |
Oops, something went wrong.