revine 1.2.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +4 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -1
- package/dist/components/MiddlewareGuard.d.ts +10 -0
- package/dist/components/MiddlewareGuard.d.ts.map +1 -0
- package/dist/components/MiddlewareGuard.js +28 -0
- package/dist/hooks/useRouter.d.ts +12 -0
- package/dist/hooks/useRouter.d.ts.map +1 -0
- package/dist/hooks/useRouter.js +22 -0
- package/dist/runtime/bundler/revinePlugin.d.ts.map +1 -1
- package/dist/runtime/bundler/revinePlugin.js +51 -4
- package/dist/runtime/middleware.d.ts +24 -0
- package/dist/runtime/middleware.d.ts.map +1 -0
- package/dist/runtime/middleware.js +7 -0
- package/package.json +1 -1
- package/src/client.ts +4 -1
- package/src/components/MiddlewareGuard.tsx +39 -0
- package/src/hooks/useRouter.ts +25 -0
- package/src/runtime/bundler/revinePlugin.ts +51 -4
- package/src/runtime/middleware.ts +29 -0
- package/dist/runtime/bundler/errorBoundary.d.ts +0 -2
- package/dist/runtime/bundler/errorBoundary.d.ts.map +0 -1
- package/dist/runtime/bundler/errorBoundary.js +0 -122
package/dist/client.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
export { Outlet, RouterProvider, useLocation, useNavigate, useParams, useSearchParams
|
|
1
|
+
export { Outlet, RouterProvider, useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
|
|
2
2
|
export { Image } from "./components/Image.js";
|
|
3
3
|
export type { ImageProps } from "./components/Image.js";
|
|
4
4
|
export { Link } from "./components/Link.js";
|
|
5
5
|
export type { LinkProps } from "./components/Link.js";
|
|
6
6
|
export { NavLink } from "./components/NavLink.js";
|
|
7
7
|
export type { NavLinkProps } from "./components/NavLink.js";
|
|
8
|
+
export { useRouter } from "./hooks/useRouter.js";
|
|
8
9
|
export { defineConfig } from "./runtime/defineConfig.js";
|
|
9
10
|
export { env, envAll } from "./runtime/env.js";
|
|
11
|
+
export { middlewareResponse } from "./runtime/middleware.js";
|
|
12
|
+
export type { MiddlewareConfig, MiddlewareFn, MiddlewareRequest, MiddlewareResponse } from "./runtime/middleware.js";
|
|
10
13
|
export type { LayoutProps } from "./runtime/types.js";
|
|
11
14
|
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,cAAc,EACd,WAAW,EACX,WAAW,EACX,SAAS,EACT,eAAe,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,cAAc,EACd,WAAW,EACX,WAAW,EACX,SAAS,EACT,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAC9C,YAAY,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAC5C,YAAY,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AACrH,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
export { Outlet, RouterProvider, useLocation, useNavigate, useParams, useSearchParams
|
|
1
|
+
export { Outlet, RouterProvider, useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
|
|
2
2
|
export { Image } from "./components/Image.js";
|
|
3
3
|
export { Link } from "./components/Link.js";
|
|
4
4
|
export { NavLink } from "./components/NavLink.js";
|
|
5
|
+
export { useRouter } from "./hooks/useRouter.js";
|
|
5
6
|
export { defineConfig } from "./runtime/defineConfig.js";
|
|
6
7
|
export { env, envAll } from "./runtime/env.js";
|
|
8
|
+
export { middlewareResponse } from "./runtime/middleware.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { MiddlewareFn } from "../runtime/middleware.js";
|
|
3
|
+
type Props = {
|
|
4
|
+
middleware: MiddlewareFn;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
loadingFallback?: React.ReactNode;
|
|
7
|
+
};
|
|
8
|
+
export declare function MiddlewareGuard({ middleware, children, loadingFallback }: Props): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=MiddlewareGuard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MiddlewareGuard.d.ts","sourceRoot":"","sources":["../../src/components/MiddlewareGuard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D,KAAK,KAAK,GAAG;IACT,UAAU,EAAE,YAAY,CAAC;IACzB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,eAAe,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CACrC,CAAC;AAEF,wBAAgB,eAAe,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAE,KAAK,2CA4B/E"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { useLocation, useNavigate } from "react-router-dom";
|
|
4
|
+
export function MiddlewareGuard({ middleware, children, loadingFallback }) {
|
|
5
|
+
const location = useLocation();
|
|
6
|
+
const navigate = useNavigate();
|
|
7
|
+
const [status, setStatus] = useState("pending");
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
setStatus("pending");
|
|
10
|
+
const request = {
|
|
11
|
+
pathname: location.pathname,
|
|
12
|
+
searchParams: new URLSearchParams(location.search),
|
|
13
|
+
};
|
|
14
|
+
Promise.resolve(middleware(request)).then((response) => {
|
|
15
|
+
if (response.type === "redirect") {
|
|
16
|
+
setStatus("redirecting");
|
|
17
|
+
navigate(response.destination, { replace: true });
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
setStatus("allowed");
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}, [location.pathname]);
|
|
24
|
+
if (status === "pending" || status === "redirecting") {
|
|
25
|
+
return _jsx(_Fragment, { children: loadingFallback ?? null });
|
|
26
|
+
}
|
|
27
|
+
return _jsx(_Fragment, { children: children });
|
|
28
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare function useRouter(): {
|
|
2
|
+
pathname: string;
|
|
3
|
+
params: Readonly<import("react-router").Params<string>>;
|
|
4
|
+
searchParams: URLSearchParams;
|
|
5
|
+
setSearchParams: import("react-router-dom").SetURLSearchParams;
|
|
6
|
+
push: (path: string) => void;
|
|
7
|
+
replace: (path: string) => void;
|
|
8
|
+
back: () => void;
|
|
9
|
+
forward: () => void;
|
|
10
|
+
prefetch: (_path: string) => void;
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=useRouter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useRouter.d.ts","sourceRoot":"","sources":["../../src/hooks/useRouter.ts"],"names":[],"mappings":"AAEA,wBAAgB,SAAS;;;;;iBAcR,MAAM;oBACH,MAAM;;;sBAGJ,MAAM;EAI3B"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useNavigate, useLocation, useParams, useSearchParams } from "react-router-dom";
|
|
2
|
+
export function useRouter() {
|
|
3
|
+
const navigate = useNavigate();
|
|
4
|
+
const location = useLocation();
|
|
5
|
+
const params = useParams();
|
|
6
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
7
|
+
return {
|
|
8
|
+
// Current route info
|
|
9
|
+
pathname: location.pathname,
|
|
10
|
+
params,
|
|
11
|
+
searchParams,
|
|
12
|
+
setSearchParams,
|
|
13
|
+
// Navigation methods
|
|
14
|
+
push: (path) => navigate(path),
|
|
15
|
+
replace: (path) => navigate(path, { replace: true }),
|
|
16
|
+
back: () => navigate(-1),
|
|
17
|
+
forward: () => navigate(1),
|
|
18
|
+
prefetch: (_path) => {
|
|
19
|
+
// Reserved for future prefetch support
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"revinePlugin.d.ts","sourceRoot":"","sources":["../../../src/runtime/bundler/revinePlugin.ts"],"names":[],"mappings":"AA2VA,wBAAgB,YAAY,IAAI,GAAG,
|
|
1
|
+
{"version":3,"file":"revinePlugin.d.ts","sourceRoot":"","sources":["../../../src/runtime/bundler/revinePlugin.ts"],"names":[],"mappings":"AA2VA,wBAAgB,YAAY,IAAI,GAAG,CA4LlC"}
|
|
@@ -365,12 +365,51 @@ export function revinePlugin() {
|
|
|
365
365
|
load(id) {
|
|
366
366
|
if (id === VIRTUAL_ROUTING_ID) {
|
|
367
367
|
return `
|
|
368
|
-
import { createBrowserRouter, useRouteError } from "react-router-dom";
|
|
369
|
-
import { lazy, Suspense, createElement } from "react";
|
|
368
|
+
import { createBrowserRouter, useRouteError, Outlet } from "react-router-dom";
|
|
369
|
+
import { lazy, Suspense, createElement, useState, useEffect, useRef } from "react";
|
|
370
370
|
import React from "react";
|
|
371
371
|
|
|
372
|
+
// ── Middleware support ──────────────────────────────────────────────
|
|
373
|
+
const middlewareModules = import.meta.glob("/src/middleware.{ts,tsx}", { eager: true });
|
|
374
|
+
const middlewareMod = Object.values(middlewareModules)[0];
|
|
375
|
+
const userMiddleware = middlewareMod?.default ?? null;
|
|
376
|
+
|
|
372
377
|
${errorBoundaryComponent}
|
|
373
378
|
|
|
379
|
+
// ── MiddlewareGuard ─────────────────────────────────────────────────
|
|
380
|
+
function MiddlewareGuard({ children }) {
|
|
381
|
+
const [status, setStatus] = React.useState("pending");
|
|
382
|
+
const lastPathnameRef = React.useRef(null);
|
|
383
|
+
|
|
384
|
+
React.useEffect(() => {
|
|
385
|
+
if (!userMiddleware) { setStatus("allowed"); return; }
|
|
386
|
+
|
|
387
|
+
const run = async (pathname, search) => {
|
|
388
|
+
if (pathname === lastPathnameRef.current) return;
|
|
389
|
+
lastPathnameRef.current = pathname;
|
|
390
|
+
|
|
391
|
+
setStatus("pending");
|
|
392
|
+
const req = { pathname, searchParams: new URLSearchParams(search) };
|
|
393
|
+
const res = await Promise.resolve(userMiddleware(req));
|
|
394
|
+
if (res.type === "redirect") {
|
|
395
|
+
router.navigate(res.destination, { replace: true });
|
|
396
|
+
setStatus("redirecting");
|
|
397
|
+
} else {
|
|
398
|
+
setStatus("allowed");
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
run(window.location.pathname, window.location.search);
|
|
403
|
+
|
|
404
|
+
return router.subscribe((state) => {
|
|
405
|
+
run(state.location.pathname, state.location.search);
|
|
406
|
+
});
|
|
407
|
+
}, []);
|
|
408
|
+
|
|
409
|
+
if (status === "pending" || status === "redirecting") return null;
|
|
410
|
+
return React.createElement(React.Fragment, null, children);
|
|
411
|
+
}
|
|
412
|
+
|
|
374
413
|
const notFoundModules = import.meta.glob("/src/NotFound.tsx", { eager: true });
|
|
375
414
|
const NotFoundComponent = Object.values(notFoundModules)[0]?.default;
|
|
376
415
|
|
|
@@ -430,7 +469,7 @@ const pageEntries = Object.entries(pages).filter(([filePath]) => {
|
|
|
430
469
|
return !segments.some((s) => s.startsWith("_"));
|
|
431
470
|
});
|
|
432
471
|
|
|
433
|
-
const
|
|
472
|
+
const innerRoutes = pageEntries.map(([filePath, component]) => {
|
|
434
473
|
const routePath = toRoutePath(filePath);
|
|
435
474
|
const Component = lazy(component);
|
|
436
475
|
const layouts = getLayoutsForPath(filePath);
|
|
@@ -453,7 +492,7 @@ const routes = pageEntries.map(([filePath, component]) => {
|
|
|
453
492
|
};
|
|
454
493
|
});
|
|
455
494
|
|
|
456
|
-
|
|
495
|
+
innerRoutes.push({
|
|
457
496
|
path: "*",
|
|
458
497
|
element: NotFoundComponent
|
|
459
498
|
? createElement(NotFoundComponent)
|
|
@@ -461,6 +500,14 @@ routes.push({
|
|
|
461
500
|
errorElement: createElement(RevineErrorDialog),
|
|
462
501
|
});
|
|
463
502
|
|
|
503
|
+
const routes = [
|
|
504
|
+
{
|
|
505
|
+
element: createElement(MiddlewareGuard, null, createElement(Outlet)),
|
|
506
|
+
children: innerRoutes,
|
|
507
|
+
errorElement: createElement(RevineErrorDialog),
|
|
508
|
+
},
|
|
509
|
+
];
|
|
510
|
+
|
|
464
511
|
export const router = createBrowserRouter(routes, {
|
|
465
512
|
future: {
|
|
466
513
|
v7_startTransition: true,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type MiddlewareRequest = {
|
|
2
|
+
pathname: string;
|
|
3
|
+
searchParams: URLSearchParams;
|
|
4
|
+
};
|
|
5
|
+
export type MiddlewareResponse = {
|
|
6
|
+
type: "next";
|
|
7
|
+
} | {
|
|
8
|
+
type: "redirect";
|
|
9
|
+
destination: string;
|
|
10
|
+
};
|
|
11
|
+
export declare const middlewareResponse: {
|
|
12
|
+
next: () => MiddlewareResponse;
|
|
13
|
+
redirect: (destination: string) => MiddlewareResponse;
|
|
14
|
+
};
|
|
15
|
+
export type MiddlewareFn = (request: MiddlewareRequest) => MiddlewareResponse | Promise<MiddlewareResponse>;
|
|
16
|
+
export type MiddlewareConfig = {
|
|
17
|
+
publicPaths?: string[];
|
|
18
|
+
authPaths?: string[];
|
|
19
|
+
redirects?: {
|
|
20
|
+
whenAuthenticated?: string;
|
|
21
|
+
whenUnauthenticated?: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/runtime/middleware.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,eAAe,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9C,eAAO,MAAM,kBAAkB;gBACnB,kBAAkB;4BACJ,MAAM,KAAG,kBAAkB;CAIpD,CAAC;AAEF,MAAM,MAAM,YAAY,GAAG,CACzB,OAAO,EAAE,iBAAiB,KACvB,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAEtD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE;QACV,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAC;KAC9B,CAAC;CACH,CAAC"}
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -4,7 +4,7 @@ export {
|
|
|
4
4
|
useLocation,
|
|
5
5
|
useNavigate,
|
|
6
6
|
useParams,
|
|
7
|
-
useSearchParams
|
|
7
|
+
useSearchParams
|
|
8
8
|
} from "react-router-dom";
|
|
9
9
|
export { Image } from "./components/Image.js";
|
|
10
10
|
export type { ImageProps } from "./components/Image.js";
|
|
@@ -12,6 +12,9 @@ export { Link } from "./components/Link.js";
|
|
|
12
12
|
export type { LinkProps } from "./components/Link.js";
|
|
13
13
|
export { NavLink } from "./components/NavLink.js";
|
|
14
14
|
export type { NavLinkProps } from "./components/NavLink.js";
|
|
15
|
+
export { useRouter } from "./hooks/useRouter.js";
|
|
15
16
|
export { defineConfig } from "./runtime/defineConfig.js";
|
|
16
17
|
export { env, envAll } from "./runtime/env.js";
|
|
18
|
+
export { middlewareResponse } from "./runtime/middleware.js";
|
|
19
|
+
export type { MiddlewareConfig, MiddlewareFn, MiddlewareRequest, MiddlewareResponse } from "./runtime/middleware.js";
|
|
17
20
|
export type { LayoutProps } from "./runtime/types.js";
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import { useLocation, useNavigate } from "react-router-dom";
|
|
3
|
+
import type { MiddlewareFn } from "../runtime/middleware.js";
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
middleware: MiddlewareFn;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
loadingFallback?: React.ReactNode;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function MiddlewareGuard({ middleware, children, loadingFallback }: Props) {
|
|
12
|
+
const location = useLocation();
|
|
13
|
+
const navigate = useNavigate();
|
|
14
|
+
const [status, setStatus] = useState<"pending" | "allowed" | "redirecting">("pending");
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
setStatus("pending");
|
|
18
|
+
|
|
19
|
+
const request = {
|
|
20
|
+
pathname: location.pathname,
|
|
21
|
+
searchParams: new URLSearchParams(location.search),
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
Promise.resolve(middleware(request)).then((response) => {
|
|
25
|
+
if (response.type === "redirect") {
|
|
26
|
+
setStatus("redirecting");
|
|
27
|
+
navigate(response.destination, { replace: true });
|
|
28
|
+
} else {
|
|
29
|
+
setStatus("allowed");
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}, [location.pathname]);
|
|
33
|
+
|
|
34
|
+
if (status === "pending" || status === "redirecting") {
|
|
35
|
+
return <>{loadingFallback ?? null}</>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return <>{children}</>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useNavigate, useLocation, useParams, useSearchParams } from "react-router-dom";
|
|
2
|
+
|
|
3
|
+
export function useRouter() {
|
|
4
|
+
const navigate = useNavigate();
|
|
5
|
+
const location = useLocation();
|
|
6
|
+
const params = useParams();
|
|
7
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
// Current route info
|
|
11
|
+
pathname: location.pathname,
|
|
12
|
+
params,
|
|
13
|
+
searchParams,
|
|
14
|
+
setSearchParams,
|
|
15
|
+
|
|
16
|
+
// Navigation methods
|
|
17
|
+
push: (path: string) => navigate(path),
|
|
18
|
+
replace: (path: string) => navigate(path, { replace: true }),
|
|
19
|
+
back: () => navigate(-1),
|
|
20
|
+
forward: () => navigate(1),
|
|
21
|
+
prefetch: (_path: string) => {
|
|
22
|
+
// Reserved for future prefetch support
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -380,12 +380,51 @@ export function revinePlugin(): any {
|
|
|
380
380
|
load(id: string) {
|
|
381
381
|
if (id === VIRTUAL_ROUTING_ID) {
|
|
382
382
|
return `
|
|
383
|
-
import { createBrowserRouter, useRouteError } from "react-router-dom";
|
|
384
|
-
import { lazy, Suspense, createElement } from "react";
|
|
383
|
+
import { createBrowserRouter, useRouteError, Outlet } from "react-router-dom";
|
|
384
|
+
import { lazy, Suspense, createElement, useState, useEffect, useRef } from "react";
|
|
385
385
|
import React from "react";
|
|
386
386
|
|
|
387
|
+
// ── Middleware support ──────────────────────────────────────────────
|
|
388
|
+
const middlewareModules = import.meta.glob("/src/middleware.{ts,tsx}", { eager: true });
|
|
389
|
+
const middlewareMod = Object.values(middlewareModules)[0];
|
|
390
|
+
const userMiddleware = middlewareMod?.default ?? null;
|
|
391
|
+
|
|
387
392
|
${errorBoundaryComponent}
|
|
388
393
|
|
|
394
|
+
// ── MiddlewareGuard ─────────────────────────────────────────────────
|
|
395
|
+
function MiddlewareGuard({ children }) {
|
|
396
|
+
const [status, setStatus] = React.useState("pending");
|
|
397
|
+
const lastPathnameRef = React.useRef(null);
|
|
398
|
+
|
|
399
|
+
React.useEffect(() => {
|
|
400
|
+
if (!userMiddleware) { setStatus("allowed"); return; }
|
|
401
|
+
|
|
402
|
+
const run = async (pathname, search) => {
|
|
403
|
+
if (pathname === lastPathnameRef.current) return;
|
|
404
|
+
lastPathnameRef.current = pathname;
|
|
405
|
+
|
|
406
|
+
setStatus("pending");
|
|
407
|
+
const req = { pathname, searchParams: new URLSearchParams(search) };
|
|
408
|
+
const res = await Promise.resolve(userMiddleware(req));
|
|
409
|
+
if (res.type === "redirect") {
|
|
410
|
+
router.navigate(res.destination, { replace: true });
|
|
411
|
+
setStatus("redirecting");
|
|
412
|
+
} else {
|
|
413
|
+
setStatus("allowed");
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
run(window.location.pathname, window.location.search);
|
|
418
|
+
|
|
419
|
+
return router.subscribe((state) => {
|
|
420
|
+
run(state.location.pathname, state.location.search);
|
|
421
|
+
});
|
|
422
|
+
}, []);
|
|
423
|
+
|
|
424
|
+
if (status === "pending" || status === "redirecting") return null;
|
|
425
|
+
return React.createElement(React.Fragment, null, children);
|
|
426
|
+
}
|
|
427
|
+
|
|
389
428
|
const notFoundModules = import.meta.glob("/src/NotFound.tsx", { eager: true });
|
|
390
429
|
const NotFoundComponent = Object.values(notFoundModules)[0]?.default;
|
|
391
430
|
|
|
@@ -445,7 +484,7 @@ const pageEntries = Object.entries(pages).filter(([filePath]) => {
|
|
|
445
484
|
return !segments.some((s) => s.startsWith("_"));
|
|
446
485
|
});
|
|
447
486
|
|
|
448
|
-
const
|
|
487
|
+
const innerRoutes = pageEntries.map(([filePath, component]) => {
|
|
449
488
|
const routePath = toRoutePath(filePath);
|
|
450
489
|
const Component = lazy(component);
|
|
451
490
|
const layouts = getLayoutsForPath(filePath);
|
|
@@ -468,7 +507,7 @@ const routes = pageEntries.map(([filePath, component]) => {
|
|
|
468
507
|
};
|
|
469
508
|
});
|
|
470
509
|
|
|
471
|
-
|
|
510
|
+
innerRoutes.push({
|
|
472
511
|
path: "*",
|
|
473
512
|
element: NotFoundComponent
|
|
474
513
|
? createElement(NotFoundComponent)
|
|
@@ -476,6 +515,14 @@ routes.push({
|
|
|
476
515
|
errorElement: createElement(RevineErrorDialog),
|
|
477
516
|
});
|
|
478
517
|
|
|
518
|
+
const routes = [
|
|
519
|
+
{
|
|
520
|
+
element: createElement(MiddlewareGuard, null, createElement(Outlet)),
|
|
521
|
+
children: innerRoutes,
|
|
522
|
+
errorElement: createElement(RevineErrorDialog),
|
|
523
|
+
},
|
|
524
|
+
];
|
|
525
|
+
|
|
479
526
|
export const router = createBrowserRouter(routes, {
|
|
480
527
|
future: {
|
|
481
528
|
v7_startTransition: true,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type MiddlewareRequest = {
|
|
2
|
+
pathname: string;
|
|
3
|
+
searchParams: URLSearchParams;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export type MiddlewareResponse =
|
|
7
|
+
| { type: "next" }
|
|
8
|
+
| { type: "redirect"; destination: string };
|
|
9
|
+
|
|
10
|
+
export const middlewareResponse = {
|
|
11
|
+
next: (): MiddlewareResponse => ({ type: "next" }),
|
|
12
|
+
redirect: (destination: string): MiddlewareResponse => ({
|
|
13
|
+
type: "redirect",
|
|
14
|
+
destination,
|
|
15
|
+
}),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type MiddlewareFn = (
|
|
19
|
+
request: MiddlewareRequest,
|
|
20
|
+
) => MiddlewareResponse | Promise<MiddlewareResponse>;
|
|
21
|
+
|
|
22
|
+
export type MiddlewareConfig = {
|
|
23
|
+
publicPaths?: string[]; // paths accessible WITHOUT login
|
|
24
|
+
authPaths?: string[]; // paths only for GUESTS (login, register)
|
|
25
|
+
redirects?: {
|
|
26
|
+
whenAuthenticated?: string; // redirect auth pages to (default: "/")
|
|
27
|
+
whenUnauthenticated?: string; // redirect private pages to (default: "/login")
|
|
28
|
+
};
|
|
29
|
+
};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
export declare const errorBoundaryComponent = "\nfunction RevineErrorDialog() {\n const error = useRouteError();\n const [expanded, setExpanded] = React.useState(false);\n\n const message = error?.message || String(error) || \"An unexpected error occurred.\";\n const stack = error?.stack || \"\";\n // Pull only the first meaningful line from the stack (skip the error message repeat)\n const stackLines = stack\n .split(\"\\n\")\n .filter((l) => l.trim().startsWith(\"at \"))\n .slice(0, 8);\n\n return React.createElement(\n \"div\",\n { style: overlayStyle },\n React.createElement(\n \"div\",\n { style: dialogStyle },\n // Header\n React.createElement(\n \"div\",\n { style: headerStyle },\n React.createElement(\"span\", { style: iconStyle }, \"\u2715\"),\n React.createElement(\"span\", { style: titleStyle }, \"Application Error\")\n ),\n // Message\n React.createElement(\"p\", { style: messageStyle }, message),\n // Stack toggle\n stackLines.length > 0 &&\n React.createElement(\n \"div\",\n null,\n React.createElement(\n \"button\",\n { onClick: () => setExpanded((v) => !v), style: toggleBtnStyle },\n expanded ? \"\u25B2 Hide stack trace\" : \"\u25BC Show stack trace\"\n ),\n expanded &&\n React.createElement(\n \"pre\",\n { style: stackStyle },\n stackLines.join(\"\\n\")\n )\n ),\n // Actions\n React.createElement(\n \"div\",\n { style: actionsStyle },\n React.createElement(\n \"button\",\n { onClick: () => window.location.reload(), style: primaryBtnStyle },\n \"Reload page\"\n ),\n React.createElement(\n \"button\",\n { onClick: () => (window.location.href = \"/\"), style: secondaryBtnStyle },\n \"Go to home\"\n )\n )\n )\n );\n}\n\nconst overlayStyle = {\n position: \"fixed\", inset: 0, background: \"rgba(0,0,0,0.65)\",\n backdropFilter: \"blur(4px)\", display: \"flex\",\n alignItems: \"center\", justifyContent: \"center\",\n zIndex: 9999, fontFamily: \"ui-monospace, 'Cascadia Code', monospace\",\n};\nconst dialogStyle = {\n background: \"#1a1a1a\", border: \"1px solid #ff4d4f55\",\n borderRadius: \"10px\", padding: \"28px 32px\",\n maxWidth: \"560px\", width: \"90%\", boxShadow: \"0 24px 64px rgba(0,0,0,0.6)\",\n color: \"#e5e5e5\",\n};\nconst headerStyle = {\n display: \"flex\", alignItems: \"center\", gap: \"10px\",\n marginBottom: \"14px\",\n};\nconst iconStyle = {\n display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\",\n width: \"26px\", height: \"26px\", borderRadius: \"50%\",\n background: \"#ff4d4f22\", color: \"#ff4d4f\", fontSize: \"13px\", fontWeight: 700,\n};\nconst titleStyle = {\n fontFamily: \"system-ui, sans-serif\",\n fontSize: \"16px\", fontWeight: 600, color: \"#fff\",\n};\nconst messageStyle = {\n fontFamily: \"ui-monospace, monospace\",\n fontSize: \"13px\", color: \"#ff7875\",\n background: \"#ff4d4f0f\", border: \"1px solid #ff4d4f22\",\n borderRadius: \"6px\", padding: \"10px 14px\",\n marginBottom: \"16px\", wordBreak: \"break-word\", lineHeight: 1.6,\n};\nconst toggleBtnStyle = {\n background: \"none\", border: \"none\", cursor: \"pointer\",\n color: \"#888\", fontSize: \"12px\", padding: \"0 0 10px 0\",\n fontFamily: \"system-ui, sans-serif\",\n};\nconst stackStyle = {\n background: \"#111\", borderRadius: \"6px\", padding: \"12px 14px\",\n fontSize: \"11px\", color: \"#aaa\", overflowX: \"auto\",\n lineHeight: 1.7, marginBottom: \"16px\",\n border: \"1px solid #2a2a2a\",\n};\nconst actionsStyle = {\n display: \"flex\", gap: \"10px\", marginTop: \"6px\",\n};\nconst primaryBtnStyle = {\n flex: 1, padding: \"9px 0\", borderRadius: \"6px\", border: \"none\",\n background: \"#ff4d4f\", color: \"#fff\", fontWeight: 600,\n fontSize: \"13px\", cursor: \"pointer\", fontFamily: \"system-ui, sans-serif\",\n};\nconst secondaryBtnStyle = {\n flex: 1, padding: \"9px 0\", borderRadius: \"6px\",\n border: \"1px solid #333\", background: \"transparent\",\n color: \"#aaa\", fontSize: \"13px\", cursor: \"pointer\",\n fontFamily: \"system-ui, sans-serif\",\n};\n";
|
|
2
|
-
//# sourceMappingURL=errorBoundary.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errorBoundary.d.ts","sourceRoot":"","sources":["../../../src/runtime/bundler/errorBoundary.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,suIAyHlC,CAAC"}
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
export const errorBoundaryComponent = `
|
|
2
|
-
function RevineErrorDialog() {
|
|
3
|
-
const error = useRouteError();
|
|
4
|
-
const [expanded, setExpanded] = React.useState(false);
|
|
5
|
-
|
|
6
|
-
const message = error?.message || String(error) || "An unexpected error occurred.";
|
|
7
|
-
const stack = error?.stack || "";
|
|
8
|
-
// Pull only the first meaningful line from the stack (skip the error message repeat)
|
|
9
|
-
const stackLines = stack
|
|
10
|
-
.split("\\n")
|
|
11
|
-
.filter((l) => l.trim().startsWith("at "))
|
|
12
|
-
.slice(0, 8);
|
|
13
|
-
|
|
14
|
-
return React.createElement(
|
|
15
|
-
"div",
|
|
16
|
-
{ style: overlayStyle },
|
|
17
|
-
React.createElement(
|
|
18
|
-
"div",
|
|
19
|
-
{ style: dialogStyle },
|
|
20
|
-
// Header
|
|
21
|
-
React.createElement(
|
|
22
|
-
"div",
|
|
23
|
-
{ style: headerStyle },
|
|
24
|
-
React.createElement("span", { style: iconStyle }, "✕"),
|
|
25
|
-
React.createElement("span", { style: titleStyle }, "Application Error")
|
|
26
|
-
),
|
|
27
|
-
// Message
|
|
28
|
-
React.createElement("p", { style: messageStyle }, message),
|
|
29
|
-
// Stack toggle
|
|
30
|
-
stackLines.length > 0 &&
|
|
31
|
-
React.createElement(
|
|
32
|
-
"div",
|
|
33
|
-
null,
|
|
34
|
-
React.createElement(
|
|
35
|
-
"button",
|
|
36
|
-
{ onClick: () => setExpanded((v) => !v), style: toggleBtnStyle },
|
|
37
|
-
expanded ? "▲ Hide stack trace" : "▼ Show stack trace"
|
|
38
|
-
),
|
|
39
|
-
expanded &&
|
|
40
|
-
React.createElement(
|
|
41
|
-
"pre",
|
|
42
|
-
{ style: stackStyle },
|
|
43
|
-
stackLines.join("\\n")
|
|
44
|
-
)
|
|
45
|
-
),
|
|
46
|
-
// Actions
|
|
47
|
-
React.createElement(
|
|
48
|
-
"div",
|
|
49
|
-
{ style: actionsStyle },
|
|
50
|
-
React.createElement(
|
|
51
|
-
"button",
|
|
52
|
-
{ onClick: () => window.location.reload(), style: primaryBtnStyle },
|
|
53
|
-
"Reload page"
|
|
54
|
-
),
|
|
55
|
-
React.createElement(
|
|
56
|
-
"button",
|
|
57
|
-
{ onClick: () => (window.location.href = "/"), style: secondaryBtnStyle },
|
|
58
|
-
"Go to home"
|
|
59
|
-
)
|
|
60
|
-
)
|
|
61
|
-
)
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const overlayStyle = {
|
|
66
|
-
position: "fixed", inset: 0, background: "rgba(0,0,0,0.65)",
|
|
67
|
-
backdropFilter: "blur(4px)", display: "flex",
|
|
68
|
-
alignItems: "center", justifyContent: "center",
|
|
69
|
-
zIndex: 9999, fontFamily: "ui-monospace, 'Cascadia Code', monospace",
|
|
70
|
-
};
|
|
71
|
-
const dialogStyle = {
|
|
72
|
-
background: "#1a1a1a", border: "1px solid #ff4d4f55",
|
|
73
|
-
borderRadius: "10px", padding: "28px 32px",
|
|
74
|
-
maxWidth: "560px", width: "90%", boxShadow: "0 24px 64px rgba(0,0,0,0.6)",
|
|
75
|
-
color: "#e5e5e5",
|
|
76
|
-
};
|
|
77
|
-
const headerStyle = {
|
|
78
|
-
display: "flex", alignItems: "center", gap: "10px",
|
|
79
|
-
marginBottom: "14px",
|
|
80
|
-
};
|
|
81
|
-
const iconStyle = {
|
|
82
|
-
display: "inline-flex", alignItems: "center", justifyContent: "center",
|
|
83
|
-
width: "26px", height: "26px", borderRadius: "50%",
|
|
84
|
-
background: "#ff4d4f22", color: "#ff4d4f", fontSize: "13px", fontWeight: 700,
|
|
85
|
-
};
|
|
86
|
-
const titleStyle = {
|
|
87
|
-
fontFamily: "system-ui, sans-serif",
|
|
88
|
-
fontSize: "16px", fontWeight: 600, color: "#fff",
|
|
89
|
-
};
|
|
90
|
-
const messageStyle = {
|
|
91
|
-
fontFamily: "ui-monospace, monospace",
|
|
92
|
-
fontSize: "13px", color: "#ff7875",
|
|
93
|
-
background: "#ff4d4f0f", border: "1px solid #ff4d4f22",
|
|
94
|
-
borderRadius: "6px", padding: "10px 14px",
|
|
95
|
-
marginBottom: "16px", wordBreak: "break-word", lineHeight: 1.6,
|
|
96
|
-
};
|
|
97
|
-
const toggleBtnStyle = {
|
|
98
|
-
background: "none", border: "none", cursor: "pointer",
|
|
99
|
-
color: "#888", fontSize: "12px", padding: "0 0 10px 0",
|
|
100
|
-
fontFamily: "system-ui, sans-serif",
|
|
101
|
-
};
|
|
102
|
-
const stackStyle = {
|
|
103
|
-
background: "#111", borderRadius: "6px", padding: "12px 14px",
|
|
104
|
-
fontSize: "11px", color: "#aaa", overflowX: "auto",
|
|
105
|
-
lineHeight: 1.7, marginBottom: "16px",
|
|
106
|
-
border: "1px solid #2a2a2a",
|
|
107
|
-
};
|
|
108
|
-
const actionsStyle = {
|
|
109
|
-
display: "flex", gap: "10px", marginTop: "6px",
|
|
110
|
-
};
|
|
111
|
-
const primaryBtnStyle = {
|
|
112
|
-
flex: 1, padding: "9px 0", borderRadius: "6px", border: "none",
|
|
113
|
-
background: "#ff4d4f", color: "#fff", fontWeight: 600,
|
|
114
|
-
fontSize: "13px", cursor: "pointer", fontFamily: "system-ui, sans-serif",
|
|
115
|
-
};
|
|
116
|
-
const secondaryBtnStyle = {
|
|
117
|
-
flex: 1, padding: "9px 0", borderRadius: "6px",
|
|
118
|
-
border: "1px solid #333", background: "transparent",
|
|
119
|
-
color: "#aaa", fontSize: "13px", cursor: "pointer",
|
|
120
|
-
fontFamily: "system-ui, sans-serif",
|
|
121
|
-
};
|
|
122
|
-
`;
|