diff --git a/fe/index.html b/fe/index.html index fec9267..6d0fefe 100644 --- a/fe/index.html +++ b/fe/index.html @@ -42,8 +42,8 @@ color: #c089ff; background: transparent; opacity: 0; - animation: preload-fade 1500ms ease forwards; - animation-delay: 750ms; + animation: preload-fade 2500ms ease forwards; + animation-delay: 1000ms; } .preload__spinner { @@ -84,5 +84,8 @@ + diff --git a/fe/src/App.tsx b/fe/src/App.tsx index 955e6aa..01b4ef5 100644 --- a/fe/src/App.tsx +++ b/fe/src/App.tsx @@ -1,13 +1,6 @@ import { Theme } from "@radix-ui/themes"; -import { type FC, useEffect, useState } from "react"; -import { - createBrowserRouter, - Link, - Outlet, - RouterProvider, -} from "react-router-dom"; -import Welcome from "./components/Box/sub-pages/Welcome"; -import { MainLayout } from "./components/layout/MainLayout"; +import { lazy, Suspense, useEffect, useState } from "react"; +import { FullPageLoader } from "./components/ui"; function useSystemTheme() { const [theme, setTheme] = useState<"light" | "dark">("light"); @@ -28,95 +21,15 @@ function useSystemTheme() { return theme; } -const Root: FC = () => { - return ( - <> - {window.icod2Dev.topNavTools.get() === true && ( - - )} - - - - - ); -}; - -const router = createBrowserRouter([ - { - path: "/", - Component: Root, - children: [ - { - path: "/crypto-poc", - async lazy() { - const mod = await import("./components/CryptoPlayground"); - return { Component: mod.default }; - }, - }, - { - path: "/components-demo", - async lazy() { - const mod = await import("./components/ComponentsDemo"); - return { Component: mod.default }; - }, - }, - { - path: "/decode-poc", - async lazy() { - const mod = await import("./components/DecodePlayground"); - return { Component: mod.default }; - }, - }, - { - path: "/unlock-box/:roomToken?", - async lazy() { - const mod = await import( - "./components/Box/sub-pages/RestoreBoxes/LockedBox" - ); - return { Component: mod.default }; - }, - }, - { - path: "/lock-box/:roomToken?", - async lazy() { - const mod = await import("./components/Box/sub-pages/CreationBoxes"); - return { Component: mod.RootLockBox }; - }, - }, - { - path: "/", - index: true, - Component: Welcome, - }, - { - path: "*", - Component: Welcome, - }, - ], - }, -]); +const LazyRootRoutes = lazy(() => + import("./components/RootRoutes").then((mod) => ({ + default: mod.RootRoutes, + })), +); function App() { const theme = useSystemTheme(); + console.log("App"); return ( - + }> + + ); } diff --git a/fe/src/components/RootRoutes.tsx b/fe/src/components/RootRoutes.tsx new file mode 100644 index 0000000..7472f64 --- /dev/null +++ b/fe/src/components/RootRoutes.tsx @@ -0,0 +1,118 @@ +import { type FC, lazy, Suspense } from "react"; +import { + createBrowserRouter, + Link, + Outlet, + RouterProvider, +} from "react-router-dom"; +import Welcome from "./Box/sub-pages/Welcome"; +import { MainLayout } from "./layout"; +import { FullPageLoader, Loader } from "./ui"; + +const Root: FC = () => { + console.log("Root component rendered"); + return ( + <> + {window.icod2Dev.topNavTools.get() === true && ( + + )} + + }> + + + + + ); +}; + +const CreationBoxes = lazy(() => + import("./Box/sub-pages/CreationBoxes").then((mod) => ({ + default: mod.RootLockBox, + })), +); + +const LockedBoxes = lazy(() => + import("./Box/sub-pages/RestoreBoxes/LockedBox").then((mod) => ({ + default: mod.default, + })), +); + +const router = createBrowserRouter([ + { + path: "/", + Component: Root, + HydrateFallback: () => , + children: [ + { + path: "/crypto-poc", + async lazy() { + const mod = await import("./CryptoPlayground"); + return { Component: mod.default }; + }, + }, + { + path: "/components-demo", + async lazy() { + const mod = await import("./ComponentsDemo"); + return { Component: mod.default }; + }, + }, + { + path: "/decode-poc", + async lazy() { + const mod = await import("./DecodePlayground"); + return { Component: mod.default }; + }, + }, + { + path: "/unlock-box/:roomToken?", + element: ( + }> + + + ), + }, + { + path: "/lock-box/:roomToken?", + element: ( + }> + + + ), + }, + { + path: "/", + index: true, + Component: Welcome, + }, + { + path: "*", + Component: Welcome, + }, + ], + }, +]); + +export const RootRoutes = () => { + console.log("RootRoutes component rendered"); + return ; +}; diff --git a/fe/src/components/ui/FullPageLoader.tsx b/fe/src/components/ui/FullPageLoader.tsx new file mode 100644 index 0000000..af67b59 --- /dev/null +++ b/fe/src/components/ui/FullPageLoader.tsx @@ -0,0 +1,45 @@ +import { type FC, useEffect, useRef } from "react"; +import { Loader } from "./Loader"; + +let timeoutHandler: NodeJS.Timeout | undefined; + +export const FullPageLoader: FC<{ message?: string }> = ({ + message = "Loading Stashcrate...", +}) => { + const delayRef = useRef(globalDelayFromNow()); + + useEffect(() => { + clearTimeout(timeoutHandler); + + return () => { + timeoutHandler = setTimeout(() => { + window?.document + ?.querySelector("#root") + ?.setAttribute("data-loading-start-timestamp", ""); + console.log("Timeout cleared"); + }, 5000); + }; + }, []); + + return ; +}; + +export default FullPageLoader; + +function globalDelayFromNow() { + const startTimestamp = window?.document + ?.querySelector("#root") + ?.getAttribute("data-loading-start-timestamp"); + + console.log(startTimestamp, "XXX"); + + if (!startTimestamp) { + return 1500; + } + + const nowTimestamp = Date.now(); + + const diffInMs = nowTimestamp - Number(startTimestamp ?? 0); + + return 1500 - diffInMs; +} diff --git a/fe/src/components/ui/Loader.tsx b/fe/src/components/ui/Loader.tsx new file mode 100644 index 0000000..388fbc6 --- /dev/null +++ b/fe/src/components/ui/Loader.tsx @@ -0,0 +1,115 @@ +import { type FC, useRef } from "react"; + +export interface LoaderProps { + message?: string; + fullPage?: boolean; + fadeDelay?: number; +} + +const animationBaseDuration = 2500; + +export const Loader: FC = ({ + message = "Loading...", + fullPage = false, + fadeDelay = 1000, +}) => { + const fadeDelayRef = useRef(fadeDelay); + + const containerStyles = fullPage + ? "fixed inset-0 flex flex-col items-center justify-center bg-white dark:bg-[#222222] z-50" + : "flex flex-col items-center justify-center w-full min-h-[200px]"; + + const animationDuration = Math.max( + animationBaseDuration, + Math.min(0, animationBaseDuration - fadeDelayRef.current), + ); + + const opacityStart = ( + 1 - + lerpRange( + Math.max(-animationBaseDuration, Math.min(0, fadeDelayRef.current)), + [-animationBaseDuration, 0], + [0, 1], + ) + ).toFixed(1); + + console.log( + JSON.stringify({ + fadeDelay: fadeDelayRef.current, + opacityStart, + }), + ); + + return ( +
+ +
+
+

{message}

+
+
+ ); +}; + +export default Loader; + +export function lerpRange( + value: number, + from: [number, number], + to: [number, number], +): number { + const [a, b] = from; + const [x, y] = to; + + if (a === b) { + throw new Error("Source range cannot be zero-length."); + } + + // Normalize value into 0..1 + const t = (value - a) / (b - a); + + // Map into target range + return x + t * (y - x); +} diff --git a/fe/src/components/ui/index.ts b/fe/src/components/ui/index.ts index a3958e5..4ea5630 100644 --- a/fe/src/components/ui/index.ts +++ b/fe/src/components/ui/index.ts @@ -1,6 +1,8 @@ export * from "./Alert"; export * from "./Button"; export * from "./ErrorBoundry"; +export * from "./FullPageLoader"; +export * from "./Loader"; export * from "./NavigateAwayAlert"; export * from "./Retry"; export * from "./ToggleButton";