import NextRouter, { useRouter, withRouter } from "next/router"; import { compile, pathToRegexp } from "path-to-regexp"; import queryString from "query-string"; import { useState, useEffect } from "react"; const routes = process.env.ROUTES || {}; export type Route = { path: string; page: string; public?: boolean; }; export type RouteParams = { [key: string]: string | string[]; }; export const getRouteProps = (routeName: string, params: RouteParams = {}) => { const route = routes[routeName]; if (!route) { // eslint-disable-next-line no-console console.warn( `Route "${route}" does not exist. Did you remember to add it to routes.js?` ); return { href: "" }; } const keys = []; pathToRegexp(route.path, keys); const pathParams = keys.reduce((obj, { name }) => { if (params[name] !== undefined) obj[name] = params[name]; return obj; }, {}); const queryParams = queryString.stringify( Object.keys(params) .filter((key) => !pathParams.hasOwnProperty(key)) .reduce((obj, key) => { obj[key] = params[key]; return obj; }, {}) ); return { href: "/" + route.page.replace(/^\//, "") + "?" + queryString.stringify(params), as: compile(route.path)(pathParams) + (queryParams ? "?" + queryParams : ""), }; }; export const getCurrentRoute = ( context?: { req: { url: string } } | { asPath: string } ) => { let path: string; if (!context) return null; if ("req" in context) { [path] = context.req.url.split("?"); } if ("asPath" in context) { [path] = context.asPath.split("?"); } const key = Object.keys(routes).find((routeKey) => { const regexp = pathToRegexp(routes[routeKey].path); return regexp.test(path); }); if (!key || !routes[key]) return {}; return { ...routes[key], key }; }; export const back = () => { return NextRouter.back(); }; interface PushOptions extends SetParamsOptions {} export const push = ( route: string, params?: RouteParams, options: PushOptions = {} ) => { const { href, as: asPath } = getRouteProps(route, params); return NextRouter.push(href, asPath, options).then((result) => { if (options?.scrollToTop !== false) { window.scrollTo(0, 0); } return result; }); }; export const replace = ( route: string, params?: RouteParams, options: {} = {} ) => { const { href, as: asPath } = getRouteProps(route, params); return NextRouter.replace(href, asPath, options); }; export const redirect = (route: string, ctx?: any) => { const params = ctx?.query || {}; if (ctx?.res && typeof window === "undefined") { const { as } = getRouteProps(route, params); ctx.res.writeHead(302, { Location: as }); ctx.res.end(); } else { return push(route, params); } }; export const reset = push; export type SetParamsOptions = { scrollToTop?: boolean; shallow?: boolean; }; export const setParams = (route, params, options: SetParamsOptions = {}) => { const current = getCurrentRoute(route); push(current.key, { ...route.query, ...params }, options); }; export const setTopLevelNavigator = (navigatorRef) => { // noop }; export const useScreenLoad = (effect, deps) => { const router = useRouter(); const [didRunEffect, setDidRunEffect] = useState(false); useEffect(() => { if (didRunEffect) return; const didRun = effect(); setDidRunEffect(!!didRun); }, [...deps, didRunEffect]); useEffect(() => { setDidRunEffect(false); }, [router.asPath]); }; export default { back, getCurrentRoute, push, redirect, replace, reset, setParams, }; export { withRouter, useRouter };