FileMaster
Search
Toggle Dark Mode
Home
/
.
/
wp-content
/
plugins
/
surerank
/
src
/
functions
Edit File: router.js
import { createRoute as createRouteTanstack, createRouter, createHashHistory, RouterProvider, ErrorComponent, Navigate, createRootRouteWithContext, } from '@tanstack/react-router'; // Layout component. import SidebarLayout from '@AdminComponents/layout/sidebar-layout'; import { SureRankLogo } from '@/global/components/icons'; import currentUserCan from './role-capabilities'; /** * @typedef {Object} RouteConfig * @property {string} path - The path of the route * @property {import('react').ComponentType} component - The component to render for this route * @property {Function} [loader] - Optional data loader function for the route * @property {Function} [action] - Optional action function for the route * @property {import('react').ComponentType} [errorComponent] - Optional error boundary component * @property {RouteConfig[]} [children] - Optional child routes * @property {Object} [meta] - Optional metadata for the route * @property {import('react').ComponentType} [layout] - Optional custom layout component for this route * @property {Object} [layoutProps] - Optional props to pass to the layout component */ /** * @typedef {Object} NavLink * @property {string} path - The path of the link * @property {string} label - The label text to display * @property {import('react').ComponentType} [icon] - Optional icon component * @property {NavLink[]} [children] - Optional child navigation links */ /** * @typedef {Object} LayoutConfig * @property {import('react').ComponentType} component - The layout component to use * @property {Object} [props] - Props to pass to the layout component * @property {NavLink[]} [props.navLinks] - Navigation links for the layout * @property {boolean} [props.navbarOnly] - Whether to show only the navbar */ /** * Loading spinner component for pending states * @return {JSX.Element} Loading spinner component */ const LoadingSpinner = () => ( <div className="flex items-center justify-center p-4"> <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-brand-800" /> </div> ); /** * Creates nested routes recursively * @param {RouteConfig} routeConfig - The route configuration * @param {Object} parentRoute - The parent route object * @param {string} completePath - The complete path of the route * @param {Object} defaultLayout - The default layout configuration * @return {Object} The created route */ const createNestedRoutes = ( routeConfig, parentRoute, completePath = '', defaultLayout ) => { const RouteComponent = routeConfig.layout ? () => { const Layout = routeConfig.layout; const Component = routeConfig.component; const layoutProps = { ...defaultLayout.props, ...routeConfig.layoutProps, }; return ( <Layout { ...layoutProps }> <Component /> </Layout> ); } : routeConfig.component; const hasChildren = !! routeConfig.children?.length; const nestedRoutes = hasChildren ? [ ...routeConfig.children ] : []; if ( ! RouteComponent && hasChildren ) { completePath += routeConfig.path; const hasIndexRoute = nestedRoutes.some( ( child ) => child.path === '/' ); if ( ! hasIndexRoute ) { const firstChild = nestedRoutes[ 0 ]; const pathToFirstChild = `${ completePath }${ firstChild.path }`; nestedRoutes.push( { path: '/', component: () => <Navigate to={ pathToFirstChild } replace />, } ); } } let route; const loader = routeConfig.loader ? () => routeConfig.loader : () => void 0; if ( routeConfig?.component && 'loader' in routeConfig?.component ) { route = createRouteTanstack( { getParentRoute: () => parentRoute, path: routeConfig.path, errorComponent: routeConfig.errorComponent, pendingComponent: () => <PendingComponent />, loader, } ).lazy( routeConfig?.component?.loader ); } else { route = createRouteTanstack( { getParentRoute: () => parentRoute, path: routeConfig.path, component: RouteComponent, errorComponent: routeConfig.errorComponent, loader, action: routeConfig.action, meta: routeConfig.meta, } ); } if ( hasChildren ) { const childRoutes = nestedRoutes.map( ( childConfig ) => createNestedRoutes( childConfig, route, completePath, defaultLayout ) ); route.addChildren( childRoutes ); } return route; }; /** * Creates router configuration * @param {Object} routeTree - The route tree * @return {Object} Router configuration */ const createRouterConfig = ( routeTree ) => ( { routeTree, history: createHashHistory(), defaultPendingComponent: LoadingSpinner, defaultPendingMs: 1000, defaultErrorComponent: ErrorComponent, } ); /** * Creates an admin router with enhanced features. * * @param {Object} options - The options for creating the admin router * @param {RouteConfig[]} options.routes - The routes configuration * @param {NavLink[]} [options.navLinks] - Optional navigation links * @param {import('react').ComponentType} [options.notFoundComponent] - Optional 404 page component * @param {import('react').ComponentType} [options.defaultErrorComponent] - Optional default error boundary component * @param {LayoutConfig} [options.defaultLayout] - Optional default layout configuration * @return {Function|null} The router component or null if no routes */ const createAdminRouter = ( { routes = [], navLinks = [], notFoundComponent, defaultErrorComponent, defaultLayout = { component: SidebarLayout, props: {}, }, } ) => { if ( ! routes?.length ) { return null; } // Create Root route with error boundary const rootRoute = createRootRouteWithContext()( { component: () => { const Layout = defaultLayout.component; return ( <Layout routes={ routes } navLinks={ navLinks } { ...defaultLayout.props } /> ); }, errorComponent: defaultErrorComponent, notFoundComponent, loader: () => void 0, } ); // Create routes with nested structure const createdRoutes = routes.map( ( route ) => createNestedRoutes( route, rootRoute, '', defaultLayout ) ); // Add routes to the root route const routeTree = rootRoute.addChildren( createdRoutes ); // Create router with configuration const router = createRouter( createRouterConfig( routeTree ) ); // Router State provider component return () => <RouterProvider router={ router } />; }; /** * Creates a route configuration object * @param {string} path - Route path * @param {Component} component - Route component * @param {Array} children - Optional child routes * @param {Object} options - Optional options * @return {Object|undefined} Route configuration object */ export const createRoute = ( path, component, children = null, options = {} ) => { // Handle - if capability is not allowed, return undefined if ( options?.capability && ! currentUserCan( options?.capability ) ) { return; } // Handle - if third param is array, treat as children example : Advanced Settings if ( Array.isArray( children ) ) { return { path, component, children, ...options, }; } // Handle new options format return { path, component, children, ...options, }; }; /** * Creates a child route configuration object * @param {string} path - Route path * @param {Component} component - Route component * @param {Array} children - Optional nested child routes * @param {Object} options - Optional options * @return {Object|undefined} Child route configuration object */ export const createChildRoute = ( path, component, children = null, options = {} ) => { // Handle - if capability is not allowed, return undefined if ( options?.capability && ! currentUserCan( options?.capability ) ) { return; } // Handle - if third param is array, treat as children if ( Array.isArray( children ) ) { return { path, component, children, ...options, }; } // Handle new options format return { path, component, children, ...options, }; }; /** * Recursively filters out falsy values from routes array and children * @param {RouteConfig[]} routes - Array of route configurations * @return {RouteConfig[]} Filtered routes array without falsy values */ export const filterFalsyRoutes = ( routes ) => { if ( ! Array.isArray( routes ) ) { return []; } return routes .filter( ( route ) => !! route ) // Filter out falsy values .map( ( route ) => { // If route has children, recursively filter them if ( route.children?.length ) { return { ...route, children: filterFalsyRoutes( route.children ), }; } return route; } ); }; /** * Builds a Set of all available route paths from the routes array * @param {RouteConfig[]} routes - Array of route configurations * @return {Set<string>} Set of all route paths */ export const buildRoutePathMap = ( routes ) => { const paths = new Set(); const extractPaths = ( routeArray, basePath = '' ) => { routeArray.forEach( ( route ) => { if ( ! route ) { return; } const fullPath = basePath + route.path; paths.add( fullPath ); // Recursively extract child route paths if ( route.children?.length ) { extractPaths( route.children, fullPath ); } } ); }; extractPaths( routes ); return paths; }; /** * Filters navigation links to only include links that have corresponding routes * @param {Array} navLinks - Array of navigation link sections * @param {Set<string>} routePaths - Set of available route paths * @return {Array} Filtered navigation links */ export const filterNavLinksByRoutes = ( navLinks, routePaths ) => { const filteredSections = []; navLinks.forEach( ( section ) => { if ( ! section.links ) { return; } const filteredLinks = []; section.links.forEach( ( link ) => { // Check if the link's path exists in available routes if ( routePaths.has( link.path ) ) { const filteredLink = { ...link }; // Recursively filter submenu items if ( link.submenu?.length ) { const filteredSubmenu = link.submenu.filter( ( submenuItem ) => routePaths.has( submenuItem.path ) ); // Only include the link if it has submenu items after filtering if ( filteredSubmenu.length > 0 ) { filteredLink.submenu = filteredSubmenu; filteredLinks.push( filteredLink ); } } else { filteredLinks.push( filteredLink ); } } } ); // Only include the section if it has links after filtering if ( filteredLinks.length > 0 ) { filteredSections.push( { ...section, links: filteredLinks, } ); } } ); return filteredSections; }; export default createAdminRouter; const PendingComponent = () => { return ( <div className="w-full h-full flex items-center justify-center p-4"> <div className="h-96 flex-grow flex items-center justify-center w-full"> <SureRankLogo className="w-10 h-10 animate-ping" /> </div> </div> ); };
Save
Back