From 1aa45beba46057f6b4c451885f67b338780aa330 Mon Sep 17 00:00:00 2001 From: Amirhossein Khalili Date: Sat, 20 Jun 2026 19:55:11 +0330 Subject: [PATCH] fix(app): show loader during protected boot --- src/App.tsx | 45 +++++++++++++++++++++++++++++++++----- src/components/Navbar.tsx | 36 +++++------------------------- src/components/Sidebar.tsx | 42 +++++++++-------------------------- src/context/AppContext.tsx | 27 +++++++++++++++++++++-- 4 files changed, 80 insertions(+), 70 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index aa34b4d..78d0d9e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,13 +1,14 @@ import { createBrowserRouter, RouterProvider, Navigate, Outlet, useLocation } from "react-router-dom" import { useState } from "react" +import { Command } from "lucide-react" import { ThemeProvider } from "./components/ThemeProvider" import { LanguageProvider } from "./components/LanguageProvider" import { Toaster } from "./components/ui/toaster" import { Navbar } from "./components/Navbar" import { Sidebar } from './components/Sidebar'; -import { AppProvider } from "./context/AppContext" +import { AppProvider, useAppContext } from "./context/AppContext" import { NotificationsProvider } from "./context/NotificationsContext" -import { WorkspaceProvider } from "./context/WorkspaceContext" +import { useWorkspace, WorkspaceProvider } from "./context/WorkspaceContext" import Auth from "./pages/Auth" import GoogleAuthCallback from "./pages/GoogleAuthCallback" import Profile from "./pages/Profile" @@ -39,6 +40,7 @@ import { SignupPasswordPage } from "./pages/auth/SignupPasswordPage" import { ForgotPasswordMobilePage } from "./pages/auth/ForgotPasswordMobilePage" import { ForgotPasswordOtpPage } from "./pages/auth/ForgotPasswordOtpPage" import { ForgotPasswordPasswordPage } from "./pages/auth/ForgotPasswordPasswordPage" +import { useTranslation } from "./hooks/useTranslation" const MainLayout = () => { const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false) @@ -94,12 +96,45 @@ const AuthLayout = () => ( ) +const ProtectedBootLoader = () => { + const { t } = useTranslation() + + return ( +
+
+
+
+ +
+
+
+
+

{t.title || "Qlockify"}

+

{t.loading || "Loading..."}

+
+
+ ) +} + +const ProtectedAppGate = () => { + const { isLoadingUser } = useAppContext() + const { isLoading } = useWorkspace() + + if (isLoadingUser || isLoading) { + return + } + + return ( + + + + ) +} + const ProtectedAppLayout = () => ( - - - + ) diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 6e30383..737d084 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -5,12 +5,13 @@ import { Button } from "./ui/button" import { SettingsMenu } from "./SettingsMenu" import { FlaskConical, LogOut, User, Moon, Sun, Globe, Command, Menu, RefreshCcw } from "lucide-react" import { useTheme } from "./ThemeProvider" -import { logoutUser, getUserProfile } from "../api/users" +import { logoutUser } from "../api/users" import { WorkspaceSelector } from "./WorkspaceSelector" import { toast } from "sonner" import { NotificationBell } from "./notifications/NotificationBell" -import { clearSessionTokens, getAccessToken, getRefreshToken, setDemoSessionMeta, setSessionTokens } from "../lib/session" +import { clearSessionTokens, getRefreshToken, setDemoSessionMeta, setSessionTokens } from "../lib/session" import { startDemo } from "../api/demo" +import { useAppContext } from "../context/AppContext" type NavbarProps = { onOpenSidebar?: () => void @@ -19,12 +20,12 @@ type NavbarProps = { export function Navbar({ onOpenSidebar }: NavbarProps) { const { t, lang, setLanguage } = useTranslation() const { theme, setTheme } = useTheme() + const { user, isLoadingUser, setUser } = useAppContext() const navigate = useNavigate() const [showLogoutModal, setShowLogoutModal] = useState(false) const [isDropdownOpen, setIsDropdownOpen] = useState(false) const [isResettingDemo, setIsResettingDemo] = useState(false) - const [user, setUser] = useState(null) const dropdownRef = useRef(null) @@ -40,33 +41,6 @@ export function Navbar({ onOpenSidebar }: NavbarProps) { }).format(new Date(user.demo_expires_at)) : null - useEffect(() => { - const handleProfileUpdated = ((e: CustomEvent) => { - if (e.detail) { - setUser((prev: any) => (prev ? { ...prev, ...e.detail } : e.detail)) - } - }) as EventListener - - window.addEventListener("profile_updated", handleProfileUpdated) - return () => window.removeEventListener("profile_updated", handleProfileUpdated) - }, []) - - useEffect(() => { - const fetchUser = async () => { - const token = getAccessToken() - if (!token) return - - try { - const userData = await getUserProfile() - setUser(userData) - } catch (error) { - console.error("Failed to fetch user profile:", error) - } - } - - void fetchUser() - }, []) - useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { @@ -279,7 +253,7 @@ export function Navbar({ onOpenSidebar }: NavbarProps) { )}
- ) : ( + ) : isLoadingUser ? null : ( <> diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index f8ef1d9..84b87b6 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react" +import { useState } from "react" import { NavLink, useNavigate } from "react-router-dom" import { Users, @@ -28,9 +28,10 @@ import { canWorkspace, WORKSPACE_LOGS_VIEW } from "../lib/permissions" import { WorkspaceSelector } from "./WorkspaceSelector" import { SettingsMenu } from "./SettingsMenu" import { Button } from "./ui/button" -import { getUserProfile, logoutUser } from "../api/users" -import { clearSessionTokens, getAccessToken, getRefreshToken, setDemoSessionMeta, setSessionTokens } from "../lib/session" +import { logoutUser } from "../api/users" +import { clearSessionTokens, getRefreshToken, setDemoSessionMeta, setSessionTokens } from "../lib/session" import { startDemo } from "../api/demo" +import { useAppContext } from "../context/AppContext" type SidebarProps = { mobileOpen?: boolean @@ -41,7 +42,7 @@ export const Sidebar = ({ mobileOpen = false, onMobileClose }: SidebarProps) => const [isCollapsed, setIsCollapsed] = useState(false) const [showLogoutModal, setShowLogoutModal] = useState(false) const [isResettingDemo, setIsResettingDemo] = useState(false) - const [user, setUser] = useState(null) + const { user, isLoadingUser, setUser } = useAppContext() const navigate = useNavigate() const { t, lang, setLanguage } = useTranslation() @@ -67,33 +68,6 @@ export const Sidebar = ({ mobileOpen = false, onMobileClose }: SidebarProps) => ? PanelLeftOpen : PanelLeftClose - useEffect(() => { - const handleProfileUpdated = ((e: CustomEvent) => { - if (e.detail) { - setUser((prev: any) => (prev ? { ...prev, ...e.detail } : e.detail)) - } - }) as EventListener - - window.addEventListener("profile_updated", handleProfileUpdated) - return () => window.removeEventListener("profile_updated", handleProfileUpdated) - }, []) - - useEffect(() => { - const fetchUser = async () => { - const token = getAccessToken() - if (!token) return - - try { - const userData = await getUserProfile() - setUser(userData) - } catch (error) { - console.error("Failed to fetch user profile:", error) - } - } - - void fetchUser() - }, []) - const handleLogout = async () => { try { const refreshToken = getRefreshToken() @@ -245,7 +219,7 @@ export const Sidebar = ({ mobileOpen = false, onMobileClose }: SidebarProps) => } const renderMobileFooterSection = () => { - if (!user) { + if (!user && !isLoadingUser) { return (