From 175e85bf3ff5dbc7c86c5b0e3d0f429654163be1 Mon Sep 17 00:00:00 2001 From: Saurabhsing21 Date: Thu, 16 Jan 2025 13:57:36 +0530 Subject: [PATCH] feat: Implement fancy loading page with skeleton (#533) - Added a simple and lightweight skeleton loading page for all navigation. - Ensured no JavaScript is loaded via + + + diff --git a/src/App.tsx b/src/App.tsx index f26eaa47..50b86ded 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,96 +1,93 @@ -import { ComponentType, Suspense, lazy } from "react" -import { Toaster } from "@/components/ui/toaster" -import { Route, Switch } from "wouter" -import "./components/CmdKMenu" -import { ContextProviders } from "./ContextProviders" -import React from "react" -import { Skeleton } from "./components/ui/skeleton" -import SkeletonLoadingPage from "./components/SkeletonLoader" -import UniversalSkeleton from "./components/SkeletonLoader" -import FullSkeletonLoader from "./components/SkeletonLoader" -import FullPageSkeletonLoader from "./components/SkeletonLoader" +import { ComponentType, Suspense, lazy, useEffect } from "react"; // Added `useEffect` for handling skeleton cleanup +import { Toaster } from "@/components/ui/toaster"; +import { Route, Switch } from "wouter"; +import "./components/CmdKMenu"; +import { ContextProviders } from "./ContextProviders"; +import React from "react"; +// Lazy loading helper const lazyImport = (importFn: () => Promise) => lazy>(async () => { try { - const module = await importFn() - + const module = await importFn(); if (module.default) { - return { default: module.default } + return { default: module.default }; } - - const pageExportNames = ["Page", "Component", "View"] + const pageExportNames = ["Page", "Component", "View"]; for (const suffix of pageExportNames) { - const keys = Object.keys(module).filter((key) => key.endsWith(suffix)) + const keys = Object.keys(module).filter((key) => key.endsWith(suffix)); if (keys.length > 0) { - return { default: module[keys[0]] } + return { default: module[keys[0]] }; } } - const componentExport = Object.values(module).find( - (exp) => typeof exp === "function" && exp.prototype?.isReactComponent, - ) + (exp) => typeof exp === "function" && exp.prototype?.isReactComponent + ); if (componentExport) { - return { default: componentExport } + return { default: componentExport }; } - throw new Error( - `No valid React component found in module. Available exports: ${Object.keys(module).join(", ")}`, - ) + `No valid React component found in module. Available exports: ${Object.keys(module).join(", ")}` + ); } catch (error) { - console.error("Failed to load component:", error) - throw error + console.error("Failed to load component:", error); + throw error; } - }) + }); -const AiPage = lazyImport(() => import("@/pages/ai")) -const AuthenticatePage = lazyImport(() => import("@/pages/authorize")) -const DashboardPage = lazyImport(() => import("@/pages/dashboard")) -const EditorPage = lazyImport(async () => { - const [editorModule] = await Promise.all([ - import("@/pages/editor"), - import("@/lib/utils/load-prettier").then((m) => m.loadPrettier()), - ]) - return editorModule -}) -const LandingPage = lazyImport(() => import("@/pages/landing")) -const MyOrdersPage = lazyImport(() => import("@/pages/my-orders")) -const NewestPage = lazyImport(() => import("@/pages/newest")) -const PreviewPage = lazyImport(() => import("@/pages/preview")) -const QuickstartPage = lazyImport(() => import("@/pages/quickstart")) -const SearchPage = lazyImport(() => import("@/pages/search")) -const SettingsPage = lazyImport(() => import("@/pages/settings")) -const UserProfilePage = lazyImport(() => import("@/pages/user-profile")) -const ViewOrderPage = lazyImport(() => import("@/pages/view-order")) -const ViewSnippetPage = lazyImport(() => import("@/pages/view-snippet")) -const DevLoginPage = lazyImport(() => import("@/pages/dev-login")) +// Lazy-loaded pages +const LandingPage = lazyImport(() => import("@/pages/landing")); +const EditorPage = lazyImport(() => import("@/pages/editor")); +const QuickstartPage = lazyImport(() => import("@/pages/quickstart")); +const DashboardPage = lazyImport(() => import("@/pages/dashboard")); +const AiPage = lazyImport(() => import("@/pages/ai")); +const NewestPage = lazyImport(() => import("@/pages/newest")); +const SettingsPage = lazyImport(() => import("@/pages/settings")); +const SearchPage = lazyImport(() => import("@/pages/search")); +const AuthenticatePage = lazyImport(() => import("@/pages/authorize")); +const MyOrdersPage = lazyImport(() => import("@/pages/my-orders")); +const ViewOrderPage = lazyImport(() => import("@/pages/view-order")); +const PreviewPage = lazyImport(() => import("@/pages/preview")); +const DevLoginPage = lazyImport(() => import("@/pages/dev-login")); +const UserProfilePage = lazyImport(() => import("@/pages/user-profile")); +const ViewSnippetPage = lazyImport(() => import("@/pages/view-snippet")); class ErrorBoundary extends React.Component< { children: React.ReactNode }, { hasError: boolean } > { constructor(props: { children: React.ReactNode }) { - super(props) - this.state = { hasError: false } + super(props); + this.state = { hasError: false }; } static getDerivedStateFromError() { - return { hasError: true } + return { hasError: true }; } render() { if (this.state.hasError) { - return
Something went wrong loading the page.
+ return
Something went wrong loading the page.
; } - return this.props.children + return this.props.children; } } function App() { + // Added useEffect to handle cleanup of the skeleton loader + useEffect(() => { + // Hide the skeleton from index.html when React mounts + const skeletonLoader = document.getElementById("skeleton-loader"); + if (skeletonLoader) { + skeletonLoader.style.display = "none"; // Hides the skeleton after the React app is ready + } + }, []); + return ( - }> + {/* Modified Suspense fallback to use the skeleton loader from index.html */} + }> @@ -112,7 +109,7 @@ function App() { - ) + ); } -export default App +export default App; diff --git a/src/components/SkeletonLoader.tsx b/src/components/SkeletonLoader.tsx deleted file mode 100644 index 883f990e..00000000 --- a/src/components/SkeletonLoader.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import React from "react"; -import styled, { keyframes } from "styled-components"; - -const FullPageSkeletonLoader: React.FC = () => { - return ( - - {/* Navigation Skeleton */} - - - - {[...Array(4)].map((_, index) => ( - - ))} - - - - - - - - {/* Mid Content Skeleton */} - - - {/* Initial lines with different lengths */} - {[...Array(3)].map((_, index) => ( - - ))} - - {/* Two horizontal boxes */} - - - - - - {/* Additional lines with varying lengths */} - {[...Array(5)].map((_, index) => ( - - ))} - - - {[...Array(5)].map((_, index) => ( - - ))} - - - - ); -}; - -// Shimmer animation for skeleton -const shimmer = keyframes` - 0% { - background-position: -150%; - } - 100% { - background-position: 150%; - } -`; - -// Styled Skeleton Components -const SkeletonBase = styled.div` - background: linear-gradient( - 90deg, - #f0f0f0 25%, - #e0e0e0 50%, - #f0f0f0 75% - ); - background-size: 200% 100%; - animation: ${shimmer} 1.5s infinite ease-in-out; - border-radius: 8px; -`; - -const PageWrapper = styled.div` - display: flex; - flex-direction: column; - gap: 16px; - height: 100vh; /* Full screen height */ - padding: 0; - margin: 0; -`; - -const NavSkeleton = styled.div` - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 20px; - background: #ffffff; - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); - border-radius: 8px; -`; - -const LogoPlaceholder = styled(SkeletonBase)` - width: 100px; - height: 40px; - border-radius: 4px; -`; - -const NavItems = styled.div` - display: flex; - gap: 16px; -`; - -const NavItem = styled(SkeletonBase)` - width: 80px; - height: 20px; - border-radius: 4px; -`; - -const ActionButtons = styled.div` - display: flex; - gap: 12px; -`; - -const ActionPlaceholder = styled(SkeletonBase)<{ width: string }>` - width: ${(props) => props.width}; - height: 40px; - border-radius: 8px; -`; - -const ContentSkeleton = styled.div` - flex: 1; /* Fills remaining space dynamically */ - display: flex; - flex-direction: row; - gap: 24px; /* Space between content and sidebar */ - padding: 20px; - background: #ffffff; - border-radius: 8px; - box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); - overflow-y: auto; /* Allows scrolling if content overflows */ -`; - -const MainContent = styled.div` - flex: 3; /* Takes up most of the width */ - display: flex; - flex-direction: column; - gap: 24px; /* Space between lines */ -`; - -const BoxContainer = styled.div` - display: flex; - gap: 16px; /* Space between boxes */ -`; - -const Box = styled(SkeletonBase)` - flex: 1; /* Equal width for both boxes */ - height: 120px; - border-radius: 8px; -`; - -const Sidebar = styled.div` - flex: 1; /* Smaller width for the sidebar */ - display: flex; - flex-direction: column; - gap: 16px; /* Space between blocks */ -`; - -const HorizontalLine = styled(SkeletonBase)` - width: 100%; - height: 16px; - border-radius: 8px; -`; - -const SidebarBlock = styled(SkeletonBase)` - width: 100%; - height: 80px; - border-radius: 8px; -`; - -export default FullPageSkeletonLoader;