From 6c3ca1c70df8df62b35efa80d5fa6c53eea50225 Mon Sep 17 00:00:00 2001 From: alorig <220087330+alorig@users.noreply.github.com> Date: Sun, 9 Nov 2025 20:31:40 +0500 Subject: [PATCH] page transition --- frontend/src/App.tsx | 713 ++++++++++++++---- .../src/components/common/PageTransition.tsx | 76 ++ frontend/src/layout/AppLayout.tsx | 29 +- 3 files changed, 665 insertions(+), 153 deletions(-) create mode 100644 frontend/src/components/common/PageTransition.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 01426e2c..18e3cdc5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,98 +1,117 @@ +import { Suspense, lazy } from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router"; -import SignIn from "./pages/AuthPages/SignIn"; -import SignUp from "./pages/AuthPages/SignUp"; -import NotFound from "./pages/OtherPage/NotFound"; import AppLayout from "./layout/AppLayout"; import { ScrollToTop } from "./components/common/ScrollToTop"; import ProtectedRoute from "./components/auth/ProtectedRoute"; import GlobalErrorDisplay from "./components/common/GlobalErrorDisplay"; import LoadingStateMonitor from "./components/common/LoadingStateMonitor"; -import Home from "./pages/Dashboard/Home"; +import PageTransition from "./components/common/PageTransition"; -// Planner Module -import PlannerDashboard from "./pages/Planner/Dashboard"; -import Keywords from "./pages/Planner/Keywords"; -import Clusters from "./pages/Planner/Clusters"; -import Ideas from "./pages/Planner/Ideas"; +// Modern page loader component +const PageLoader = () => ( +
+
+
+
+
+
+
+
+
+); -// Writer Module -import WriterDashboard from "./pages/Writer/Dashboard"; -import Tasks from "./pages/Writer/Tasks"; -import Content from "./pages/Writer/Content"; -import Drafts from "./pages/Writer/Drafts"; -import Images from "./pages/Writer/Images"; -import Published from "./pages/Writer/Published"; +// Auth pages - loaded immediately (needed for login) +import SignIn from "./pages/AuthPages/SignIn"; +import SignUp from "./pages/AuthPages/SignUp"; +import NotFound from "./pages/OtherPage/NotFound"; -// Thinker Module -import ThinkerDashboard from "./pages/Thinker/Dashboard"; -import Prompts from "./pages/Thinker/Prompts"; -import AuthorProfiles from "./pages/Thinker/AuthorProfiles"; -import ThinkerProfile from "./pages/Thinker/Profile"; -import Strategies from "./pages/Thinker/Strategies"; -import ImageTesting from "./pages/Thinker/ImageTesting"; +// Lazy load all other pages - only loads when navigated to +const Home = lazy(() => import("./pages/Dashboard/Home")); -// Billing Module -import Credits from "./pages/Billing/Credits"; -import Transactions from "./pages/Billing/Transactions"; -import Usage from "./pages/Billing/Usage"; +// Planner Module - Lazy loaded +const PlannerDashboard = lazy(() => import("./pages/Planner/Dashboard")); +const Keywords = lazy(() => import("./pages/Planner/Keywords")); +const Clusters = lazy(() => import("./pages/Planner/Clusters")); +const Ideas = lazy(() => import("./pages/Planner/Ideas")); +const KeywordOpportunities = lazy(() => import("./pages/Planner/KeywordOpportunities")); -// Reference Data -import SeedKeywords from "./pages/Reference/SeedKeywords"; -import KeywordOpportunities from "./pages/Planner/KeywordOpportunities"; -import ReferenceIndustries from "./pages/Reference/Industries"; +// Writer Module - Lazy loaded +const WriterDashboard = lazy(() => import("./pages/Writer/Dashboard")); +const Tasks = lazy(() => import("./pages/Writer/Tasks")); +const Content = lazy(() => import("./pages/Writer/Content")); +const Drafts = lazy(() => import("./pages/Writer/Drafts")); +const Images = lazy(() => import("./pages/Writer/Images")); +const Published = lazy(() => import("./pages/Writer/Published")); -// Other Pages -import Analytics from "./pages/Analytics"; -import Schedules from "./pages/Schedules"; +// Thinker Module - Lazy loaded +const ThinkerDashboard = lazy(() => import("./pages/Thinker/Dashboard")); +const Prompts = lazy(() => import("./pages/Thinker/Prompts")); +const AuthorProfiles = lazy(() => import("./pages/Thinker/AuthorProfiles")); +const ThinkerProfile = lazy(() => import("./pages/Thinker/Profile")); +const Strategies = lazy(() => import("./pages/Thinker/Strategies")); +const ImageTesting = lazy(() => import("./pages/Thinker/ImageTesting")); -// Settings -import GeneralSettings from "./pages/Settings/General"; -import Users from "./pages/Settings/Users"; -import Subscriptions from "./pages/Settings/Subscriptions"; -import SystemSettings from "./pages/Settings/System"; -import AccountSettings from "./pages/Settings/Account"; -import ModuleSettings from "./pages/Settings/Modules"; -import AISettings from "./pages/Settings/AI"; -import Plans from "./pages/Settings/Plans"; -import Industries from "./pages/Settings/Industries"; -import Status from "./pages/Settings/Status"; -import Integration from "./pages/Settings/Integration"; -import Sites from "./pages/Settings/Sites"; -import ImportExport from "./pages/Settings/ImportExport"; +// Billing Module - Lazy loaded +const Credits = lazy(() => import("./pages/Billing/Credits")); +const Transactions = lazy(() => import("./pages/Billing/Transactions")); +const Usage = lazy(() => import("./pages/Billing/Usage")); -// Help -import Help from "./pages/Help/Help"; -import Docs from "./pages/Help/Docs"; -import SystemTesting from "./pages/Help/SystemTesting"; -import FunctionTesting from "./pages/Help/FunctionTesting"; +// Reference Data - Lazy loaded +const SeedKeywords = lazy(() => import("./pages/Reference/SeedKeywords")); +const ReferenceIndustries = lazy(() => import("./pages/Reference/Industries")); -// Components -import Components from "./pages/Components"; +// Other Pages - Lazy loaded +const Analytics = lazy(() => import("./pages/Analytics")); +const Schedules = lazy(() => import("./pages/Schedules")); -// UI Elements -import Alerts from "./pages/Settings/UiElements/Alerts"; -import Avatars from "./pages/Settings/UiElements/Avatars"; -import Badges from "./pages/Settings/UiElements/Badges"; -import Breadcrumb from "./pages/Settings/UiElements/Breadcrumb"; -import Buttons from "./pages/Settings/UiElements/Buttons"; -import ButtonsGroup from "./pages/Settings/UiElements/ButtonsGroup"; -import Cards from "./pages/Settings/UiElements/Cards"; -import Carousel from "./pages/Settings/UiElements/Carousel"; -import Dropdowns from "./pages/Settings/UiElements/Dropdowns"; -import ImagesUI from "./pages/Settings/UiElements/Images"; -import Links from "./pages/Settings/UiElements/Links"; -import List from "./pages/Settings/UiElements/List"; -import Modals from "./pages/Settings/UiElements/Modals"; -import Notifications from "./pages/Settings/UiElements/Notifications"; -import Pagination from "./pages/Settings/UiElements/Pagination"; -import Popovers from "./pages/Settings/UiElements/Popovers"; -import PricingTable from "./pages/Settings/UiElements/PricingTable"; -import Progressbar from "./pages/Settings/UiElements/Progressbar"; -import Ribbons from "./pages/Settings/UiElements/Ribbons"; -import Spinners from "./pages/Settings/UiElements/Spinners"; -import Tabs from "./pages/Settings/UiElements/Tabs"; -import Tooltips from "./pages/Settings/UiElements/Tooltips"; -import Videos from "./pages/Settings/UiElements/Videos"; +// Settings - Lazy loaded +const GeneralSettings = lazy(() => import("./pages/Settings/General")); +const Users = lazy(() => import("./pages/Settings/Users")); +const Subscriptions = lazy(() => import("./pages/Settings/Subscriptions")); +const SystemSettings = lazy(() => import("./pages/Settings/System")); +const AccountSettings = lazy(() => import("./pages/Settings/Account")); +const ModuleSettings = lazy(() => import("./pages/Settings/Modules")); +const AISettings = lazy(() => import("./pages/Settings/AI")); +const Plans = lazy(() => import("./pages/Settings/Plans")); +const Industries = lazy(() => import("./pages/Settings/Industries")); +const Status = lazy(() => import("./pages/Settings/Status")); +const Integration = lazy(() => import("./pages/Settings/Integration")); +const Sites = lazy(() => import("./pages/Settings/Sites")); +const ImportExport = lazy(() => import("./pages/Settings/ImportExport")); + +// Help - Lazy loaded +const Help = lazy(() => import("./pages/Help/Help")); +const Docs = lazy(() => import("./pages/Help/Docs")); +const SystemTesting = lazy(() => import("./pages/Help/SystemTesting")); +const FunctionTesting = lazy(() => import("./pages/Help/FunctionTesting")); + +// Components - Lazy loaded +const Components = lazy(() => import("./pages/Components")); + +// UI Elements - Lazy loaded (rarely used) +const Alerts = lazy(() => import("./pages/Settings/UiElements/Alerts")); +const Avatars = lazy(() => import("./pages/Settings/UiElements/Avatars")); +const Badges = lazy(() => import("./pages/Settings/UiElements/Badges")); +const Breadcrumb = lazy(() => import("./pages/Settings/UiElements/Breadcrumb")); +const Buttons = lazy(() => import("./pages/Settings/UiElements/Buttons")); +const ButtonsGroup = lazy(() => import("./pages/Settings/UiElements/ButtonsGroup")); +const Cards = lazy(() => import("./pages/Settings/UiElements/Cards")); +const Carousel = lazy(() => import("./pages/Settings/UiElements/Carousel")); +const Dropdowns = lazy(() => import("./pages/Settings/UiElements/Dropdowns")); +const ImagesUI = lazy(() => import("./pages/Settings/UiElements/Images")); +const Links = lazy(() => import("./pages/Settings/UiElements/Links")); +const List = lazy(() => import("./pages/Settings/UiElements/List")); +const Modals = lazy(() => import("./pages/Settings/UiElements/Modals")); +const Notifications = lazy(() => import("./pages/Settings/UiElements/Notifications")); +const Pagination = lazy(() => import("./pages/Settings/UiElements/Pagination")); +const Popovers = lazy(() => import("./pages/Settings/UiElements/Popovers")); +const PricingTable = lazy(() => import("./pages/Settings/UiElements/PricingTable")); +const Progressbar = lazy(() => import("./pages/Settings/UiElements/Progressbar")); +const Ribbons = lazy(() => import("./pages/Settings/UiElements/Ribbons")); +const Spinners = lazy(() => import("./pages/Settings/UiElements/Spinners")); +const Tabs = lazy(() => import("./pages/Settings/UiElements/Tabs")); +const Tooltips = lazy(() => import("./pages/Settings/UiElements/Tooltips")); +const Videos = lazy(() => import("./pages/Settings/UiElements/Videos")); export default function App() { return ( @@ -115,95 +134,497 @@ export default function App() { } > {/* Dashboard */} - } /> + + }> + + + + } /> {/* Planner Module */} - } /> - } /> - } /> - } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> {/* Writer Module */} - } /> - } /> - } /> - } /> - } /> - } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> {/* Thinker Module */} - } /> - } /> - } /> - } /> - } /> - } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> {/* Billing Module */} - } /> - } /> - } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> {/* Reference Data */} - } /> - } /> - } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> {/* Other Pages */} - } /> - } /> + + }> + + + + } /> + + }> + + + + } /> {/* Settings */} - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> {/* Help */} - } /> - } /> - } /> - } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> {/* UI Elements */} - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> + + }> + + + + } /> {/* Components (Showcase Page) */} - } /> + + }> + + + + } /> {/* Redirect old notification route */} - } /> + + }> + + + + } /> {/* Fallback Route */} diff --git a/frontend/src/components/common/PageTransition.tsx b/frontend/src/components/common/PageTransition.tsx new file mode 100644 index 00000000..76f06336 --- /dev/null +++ b/frontend/src/components/common/PageTransition.tsx @@ -0,0 +1,76 @@ +import { ReactNode, useEffect, useState } from 'react'; +import { useLocation } from 'react-router'; + +interface PageTransitionProps { + children: ReactNode; +} + +/** + * Smooth page transition wrapper with modern loading indicator + * Provides seamless transitions between pages without feeling like a page load + * Uses subtle fade effects and minimal loading indicator + */ +export default function PageTransition({ children }: PageTransitionProps) { + const location = useLocation(); + const [isTransitioning, setIsTransitioning] = useState(false); + const [displayChildren, setDisplayChildren] = useState(children); + const [currentPath, setCurrentPath] = useState(location.pathname); + + useEffect(() => { + // Only show transition if pathname actually changed + if (location.pathname === currentPath) { + setDisplayChildren(children); + return; + } + + // Start transition with minimal delay + setIsTransitioning(true); + setCurrentPath(location.pathname); + + // Quick fade-out, then swap content + const fadeOutTimer = setTimeout(() => { + setDisplayChildren(children); + }, 100); + + // Complete transition quickly for smooth feel + const fadeInTimer = setTimeout(() => { + setIsTransitioning(false); + }, 200); + + return () => { + clearTimeout(fadeOutTimer); + clearTimeout(fadeInTimer); + }; + }, [location.pathname, children, currentPath]); + + return ( +
+ {/* Subtle fade overlay - very light */} +
+ + {/* Minimal loading indicator - only shows briefly */} + {isTransitioning && ( +
+
+
+
+
+
+ )} + + {/* Page content with smooth fade */} +
+ {displayChildren} +
+
+ ); +} + diff --git a/frontend/src/layout/AppLayout.tsx b/frontend/src/layout/AppLayout.tsx index 1004ccf9..855556af 100644 --- a/frontend/src/layout/AppLayout.tsx +++ b/frontend/src/layout/AppLayout.tsx @@ -99,27 +99,42 @@ const LayoutContent: React.FC = () => { } }, [activeSite?.id, activeSite?.is_active]); // Depend on both ID and is_active - // Refresh user data on mount and periodically to get latest account/plan changes + // Refresh user data on mount and when app version changes (after code updates) // This ensures changes are reflected immediately without requiring re-login useEffect(() => { if (!isAuthenticated) return; - const refreshUserData = async () => { + const APP_VERSION = import.meta.env.VITE_APP_VERSION || '2.0.2'; + const VERSION_STORAGE_KEY = 'igny8-app-version'; + + const refreshUserData = async (force = false) => { const now = Date.now(); - // Throttle: only refresh if last refresh was more than 30 seconds ago - if (now - lastUserRefresh.current < 30000) return; + // Throttle: only refresh if last refresh was more than 30 seconds ago (unless forced) + if (!force && now - lastUserRefresh.current < 30000) return; try { lastUserRefresh.current = now; await refreshUser(); + + // Store current version after successful refresh + if (force) { + localStorage.setItem(VERSION_STORAGE_KEY, APP_VERSION); + } } catch (error) { // Silently fail - user might still be authenticated console.debug('User data refresh failed (non-critical):', error); } }; - // Refresh on mount - refreshUserData(); + // Check if app version changed (indicates code update) + const storedVersion = localStorage.getItem(VERSION_STORAGE_KEY); + if (storedVersion !== APP_VERSION) { + // Force refresh on version change + refreshUserData(true); + } else { + // Normal refresh on mount + refreshUserData(); + } // Refresh when window becomes visible (user switches back to tab) const handleVisibilityChange = () => { @@ -134,7 +149,7 @@ const LayoutContent: React.FC = () => { }; // Periodic refresh every 2 minutes - const intervalId = setInterval(refreshUserData, 120000); + const intervalId = setInterval(() => refreshUserData(), 120000); document.addEventListener('visibilitychange', handleVisibilityChange); window.addEventListener('focus', handleFocus);