diff --git a/src/components/app-main/AppMainElement.ts b/src/components/app-main/AppMainElement.ts index 956fb26..0e838ae 100644 --- a/src/components/app-main/AppMainElement.ts +++ b/src/components/app-main/AppMainElement.ts @@ -42,17 +42,28 @@ class AppMainElement extends BaseComponentElement { layout: "menu-layout", middleware: this.isAuth, }, + { + path: "/wallet", + component: "history-page", + layout: "menu-layout", + middleware: this.isAuth, + children: [ + { + path: "/:walletId", + component: "history-page", + layout: "menu-layout", + }, + ], + }, { path: "/register", component: "register-page", layout: "menu-layout", - middleware: this.isAuth, }, { path: "/login", component: "login-page", layout: "menu-layout", - middleware: this.isAuth, }, { path: "/unauthorized", @@ -64,6 +75,11 @@ class AppMainElement extends BaseComponentElement { component: "login-page", layout: "menu-layout", }, + { + path: "/not-found", + component: "not-found", + layout: "menu-layout", + }, { path: "/logout", component: "logout-page", @@ -81,6 +97,7 @@ class AppMainElement extends BaseComponentElement { createModal = (element: string) => { this.closeModal(); + this.appMain.addEventListener("routechanged", this.closeModal); const _appModal = this.createAppModal(); const _modalContent = this.createModalContent(element); @@ -131,6 +148,7 @@ class AppMainElement extends BaseComponentElement { closeModal = () => { if (this.appModal) this.removeChild(this.appModal); + this.appMain.removeEventListener("routechanged", this.closeModal); }; isAuth = (): boolean => { diff --git a/src/core/services/router-service/RouterService.ts b/src/core/services/router-service/RouterService.ts index e8a570e..b8161c3 100644 --- a/src/core/services/router-service/RouterService.ts +++ b/src/core/services/router-service/RouterService.ts @@ -32,14 +32,27 @@ class RouterService { public setRoutes = (routes: Array): void => { if (!Array.isArray(this._routes)) this._routes = []; routes.forEach((route) => { - const { path, component, data, layout, middleware } = route; + const { path, component, data, layout, middleware, children } = + route; + const _pathArr = path?.split?.("/").filter((a) => a); + let newPath = ["", ..._pathArr].join("/"); + if (newPath == "") newPath = "/"; const _routeState: RouteState = new RouteState( - path, + newPath, component, data, layout, middleware ); + if (Array.isArray(children) && children?.length > 0) { + children.forEach((child) => { + const _childState: RouteState = this.createChildState( + child, + route + ); + this._routes?.push(_childState); + }); + } this._routes?.push(_routeState); }); }; @@ -47,9 +60,24 @@ class RouterService { public update = (): void => { if (!this._routes) return; const path = window.location.pathname; + const [hasDynamic, _dynamicIndex] = this.hasDynamicPath(path); const _mainRoot = this.mainRoot; - const route = this.routerState; - if (path == route?.path || route?.path == "/not-found") { + let route: RouteState = this.routerState; + if (route?.middleware) { + if ( + !( + typeof route?.middleware == "function" && route.middleware() + ) || + route.middleware === false + ) { + return this.goTo("/unauthorized"); + } + } + if ( + path == route?.path || + route?.path == "/not-found" || + (hasDynamic && this?._routes?.[_dynamicIndex]?.path == route?.path) + ) { let changed: boolean = false; if (_mainRoot?.childNodes.length > 0) { _mainRoot?.childNodes?.forEach?.((child: BaseLayoutElement) => { @@ -114,26 +142,53 @@ class RouterService { } } } else { - const newRoute = this.findByPath(); + const [isDynamic, _dynamicIndex, dynamicProps] = + this.hasDynamicPath(path); + let newRoute: RouteState; + if (isDynamic && _dynamicIndex !== -1) { + newRoute = this._routes[_dynamicIndex]; + newRoute.data = dynamicProps; + } else { + newRoute = this.findByPath(); + } this.historyStack.push(newRoute); this.update(); } this.appMain.dispatchEvent(this.domEvents.routechanged); }; - public goTo = (path: string): void => { + public goTo = (path: string, data?: any): void => { if (!Array.isArray(this.historyStack)) this.historyStack = []; const currentPath = window.location.pathname; if (path == currentPath) return; const _index = this._routes.findIndex((route) => route.path === path); - if (_index >= 0) { - const newRoute = this._routes[_index]; - this.historyStack.push(newRoute); - const url = new URL(window.location.toString()); - url.pathname = path; - window.history.pushState({}, "", url.toString()); - this.update(); + const _indexOfEmpty = this._routes.findIndex( + (route) => route.path === "/not-found" + ); + const [isDynamic, _dynamicIndex, dynamicProps] = + this.hasDynamicPath(path); + if (isDynamic) { + const [isCurrentDynamic, currIndex] = + this.hasDynamicPath(currentPath); + if (isCurrentDynamic && currIndex === _dynamicIndex) return; } + let newRoute: RouteState; + if (isDynamic && _dynamicIndex !== -1) { + newRoute = this._routes[_dynamicIndex]; + newRoute.data = dynamicProps; + } else if (_index === -1 && _indexOfEmpty !== -1) { + newRoute = this._routes[_indexOfEmpty]; + } else if (_index === -1 && _indexOfEmpty === -1) { + newRoute = new RouteState("/not-found", "not-found"); + } else { + newRoute = this._routes[_index]; + } + + this.historyStack.push(newRoute); + const url = new URL(window.location.toString()); + url.pathname = path; + window.history.pushState({}, "", url.toString()); + this.update(); }; public goBack = (): void => { @@ -177,6 +232,59 @@ class RouterService { } return false; }; + + private createChildState = (child: any, parent: any): RouteState => { + const { path, middleware, layout, component, data, children } = child; + const _pathArr = path?.split?.("/").filter((a) => a); + const _parentArr = parent?.path?.split?.("/").filter((a) => a); + const newPath = ["", ..._parentArr, ..._pathArr].join("/"); + const _child = new RouteState( + newPath, + component, + data, + layout, + middleware ? middleware : parent?.middleware + ); + + if (Array.isArray(children) && children?.length > 0) { + children.forEach((child2) => { + const _childState: RouteState = this.createChildState( + child2, + _child + ); + this._routes?.push(_childState); + }); + } + return _child; + }; + + private hasDynamicPath = (path: string): [boolean, number, any] => { + const _pathArr = path.split("/").filter((a) => a); + let matchedIndex: number = 0; + let matched: boolean = false; + let dynamicProps: any = {}; + this._routes.forEach((route, _routeId) => { + const _routeArr = route.path.split("/").filter((a) => a); + if (_pathArr.length === _routeArr.length) { + let pathMatches: number = 0; + let hasDynamic: boolean = false; + _pathArr.forEach((pathr, i) => { + if (pathr == _routeArr[i]) { + pathMatches++; + } else if (_routeArr[i].startsWith?.(":")) { + pathMatches++; + hasDynamic = true; + dynamicProps[_routeArr[i].substr(1)] = pathr; + } + }); + if (pathMatches === _pathArr.length && hasDynamic) { + matchedIndex = _routeId; + matched = true; + } + } + }); + return [matched, matchedIndex, dynamicProps]; + }; } class RouteState { @@ -189,4 +297,9 @@ class RouteState { ) {} } +type DynamicProp = { + index: string; + path: string; +}; + export default RouterService;