diff --git a/package.json b/package.json
index 1078c84..acca6fe 100644
--- a/package.json
+++ b/package.json
@@ -3,12 +3,16 @@
"version": "0.1.0",
"dependencies": {
"@types/qs": "^6.9.7",
+ "@types/react": "^17.0.15",
+ "@types/react-router-dom": "^5.1.8",
"@types/sprintf-js": "^1.1.2",
"axios": "^0.21.1",
"loglevel": "^1.7.1",
"modern-css-reset": "^1.4.0",
"qs": "^6.10.1",
"ramda": "^0.27.1",
+ "react": "^17.0.2",
+ "react-router-dom": "^5.2.0",
"sass-rem": "^3.0.0",
"sprintf-js": "^1.1.2"
}
diff --git a/routing/Route.ts b/routing/Route.ts
new file mode 100644
index 0000000..9a1bdee
--- /dev/null
+++ b/routing/Route.ts
@@ -0,0 +1,15 @@
+import React from 'react'
+
+interface Route {
+ path: string
+ name?: string
+ redirect?: string
+ exact?: boolean
+ navbar?: boolean
+ isIndex?: boolean
+ icon?: React.ComponentType
+ childRoutes?: Route[]
+ component?: React.ComponentType
+}
+
+export default Route
diff --git a/routing/Router.tsx b/routing/Router.tsx
new file mode 100644
index 0000000..ca85189
--- /dev/null
+++ b/routing/Router.tsx
@@ -0,0 +1,94 @@
+import React from 'react'
+import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'
+import { prepareRoutes } from './config'
+import RouteDefinition from './Route'
+
+const getComponentRoute = (contextPath: string, component: React.ComponentType) => (
+
+)
+
+const renderRouteConfig = (
+ Container: React.ComponentType,
+ routes: RouteDefinition[],
+ contextPath: string,
+): JSX.Element => {
+ // Resolve route config object in React Router v3.
+ const children: React.ReactNode[] = []
+
+ const renderRoute = (item: RouteDefinition, routeContextPath: string) => {
+ let newContextPath: string
+
+ if (/(^\/)|(^\*)/.test(item.path)) {
+ newContextPath = item.path
+ } else {
+ newContextPath = `${routeContextPath}/${item.path}`
+ }
+
+ newContextPath = newContextPath.replace(/\/+/g, '/')
+
+ if (item.redirect) {
+ const route = (
+ }
+ path={newContextPath}
+ />
+ )
+
+ children.push(route)
+ } else if (item.component && item.childRoutes) {
+ const routeConfig = renderRouteConfig(item.component, item.childRoutes, newContextPath)
+
+ children.push(routeConfig)
+ } else if (item.component) {
+ const route = getComponentRoute(newContextPath, item.component)
+
+ children.push(route)
+ } else if (item.childRoutes) {
+ item.childRoutes.forEach(r => renderRoute(r, newContextPath))
+ }
+ }
+
+ routes.forEach(item => renderRoute(item, contextPath))
+
+ // Use Switch as the default container by default
+ if (!Container) {
+ return (
+
+ {children as JSX.TChildren[]}
+
+ )
+ }
+
+ return (
+
+
+
+ {children as JSX.TChildren[]}
+
+
+
+ )
+}
+
+interface Props {
+ routeConfig: RouteDefinition[]
+ component: React.ComponentType
+ baseUrlPath: string
+}
+
+const Router: React.FC = (props: Props) => {
+ const { routeConfig, component, baseUrlPath } = props
+
+ const preparedRoutes = prepareRoutes(routeConfig)
+
+ return renderRouteConfig(component, preparedRoutes, baseUrlPath)
+}
+
+export default Router
diff --git a/routing/config.ts b/routing/config.ts
new file mode 100644
index 0000000..434be7c
--- /dev/null
+++ b/routing/config.ts
@@ -0,0 +1,33 @@
+import * as R from 'ramda'
+import Route from './Route'
+
+// Handle isIndex property of route config:
+// Duplicate it and put it as the first route rule.
+const handleIndexRoute = (route: Route) => {
+ if (!route.childRoutes || !route.childRoutes.length) {
+ return
+ }
+
+ const indexRoute = route.childRoutes.find(R.prop('isIndex'))
+
+ if (indexRoute) {
+ const first = {
+ ...indexRoute,
+ path: route.path,
+ exact: true,
+ }
+
+ route.childRoutes.unshift(first)
+ }
+
+ route.childRoutes.forEach(handleIndexRoute)
+}
+
+export const prepareRoutes: (route: Route[]) => Route[] = R.pipe(
+ R.filter((r: Route): boolean => Boolean(
+ r.redirect
+ || r.component
+ || (r.childRoutes && r.childRoutes.length > 0),
+ )),
+ R.forEach(handleIndexRoute),
+)