feat: implement navigation context and enhance component loading states across the application

This commit is contained in:
Arunavo Ray
2025-05-24 12:51:57 +05:30
parent a3ac31795c
commit 70b3e412ad
9 changed files with 229 additions and 48 deletions

View File

@@ -10,9 +10,10 @@ import { useConfigStatus } from "@/hooks/useConfigStatus";
interface HeaderProps {
currentPage?: "dashboard" | "repositories" | "organizations" | "configuration" | "activity-log";
onNavigate?: (page: string) => void;
}
export function Header({ currentPage }: HeaderProps) {
export function Header({ currentPage, onNavigate }: HeaderProps) {
const { user, logout, isLoading } = useAuth();
const { isLiveEnabled, toggleLive } = useLiveRefresh();
const { isFullyConfigured, isLoading: configLoading } = useConfigStatus();
@@ -49,10 +50,18 @@ export function Header({ currentPage }: HeaderProps) {
return (
<header className="border-b bg-background">
<div className="flex h-[4.5rem] items-center justify-between px-6">
<a href="/" className="flex items-center gap-2 py-1">
<button
onClick={() => {
if (currentPage !== 'dashboard') {
window.history.pushState({}, '', '/');
onNavigate?.('dashboard');
}
}}
className="flex items-center gap-2 py-1 hover:opacity-80 transition-opacity"
>
<SiGitea className="h-6 w-6" />
<span className="text-xl font-bold">Gitea Mirror</span>
</a>
</button>
<div className="flex items-center gap-4">
{showLiveButton && (

View File

@@ -1,3 +1,4 @@
import { useState, useEffect, createContext, useContext } from "react";
import { Header } from "./Header";
import { Sidebar } from "./Sidebar";
import { Dashboard } from "@/components/dashboard/Dashboard";
@@ -9,6 +10,12 @@ import { Organization } from "../organizations/Organization";
import { Toaster } from "@/components/ui/sonner";
import { useAuth } from "@/hooks/useAuth";
import { useRepoSync } from "@/hooks/useSyncRepo";
import { useConfigStatus } from "@/hooks/useConfigStatus";
// Navigation context to signal when navigation happens
const NavigationContext = createContext<{ navigationKey: number }>({ navigationKey: 0 });
export const useNavigation = () => useContext(NavigationContext);
interface AppProps {
page:
@@ -32,8 +39,12 @@ export default function App({ page }: AppProps) {
);
}
function AppWithProviders({ page }: AppProps) {
const { user } = useAuth();
function AppWithProviders({ page: initialPage }: AppProps) {
const { user, isLoading: authLoading } = useAuth();
const { isLoading: configLoading } = useConfigStatus();
const [currentPage, setCurrentPage] = useState<AppProps['page']>(initialPage);
const [navigationKey, setNavigationKey] = useState(0);
useRepoSync({
userId: user?.id,
enabled: user?.syncEnabled,
@@ -42,20 +53,63 @@ function AppWithProviders({ page }: AppProps) {
nextSync: user?.nextSync,
});
// Handle navigation from sidebar
const handleNavigation = (pageName: string) => {
setCurrentPage(pageName as AppProps['page']);
// Increment navigation key to force components to refresh their loading state
setNavigationKey(prev => prev + 1);
};
// Handle browser back/forward navigation
useEffect(() => {
const handlePopState = () => {
const path = window.location.pathname;
const pageMap: Record<string, AppProps['page']> = {
'/': 'dashboard',
'/repositories': 'repositories',
'/organizations': 'organizations',
'/config': 'configuration',
'/activity': 'activity-log'
};
const pageName = pageMap[path] || 'dashboard';
setCurrentPage(pageName);
};
window.addEventListener('popstate', handlePopState);
return () => window.removeEventListener('popstate', handlePopState);
}, []);
// Show loading state only during initial auth/config loading
const isInitialLoading = authLoading || (configLoading && !user);
if (isInitialLoading) {
return (
<main className="flex min-h-screen flex-col items-center justify-center">
<div className="flex flex-col items-center gap-4">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
<p className="text-sm text-muted-foreground">Loading...</p>
</div>
</main>
);
}
return (
<main className="flex min-h-screen flex-col">
<Header currentPage={page} />
<div className="flex flex-1">
<Sidebar />
<section className="flex-1 p-6 overflow-y-auto h-[calc(100dvh-4.55rem)]">
{page === "dashboard" && <Dashboard />}
{page === "repositories" && <Repository />}
{page === "organizations" && <Organization />}
{page === "configuration" && <ConfigTabs />}
{page === "activity-log" && <ActivityLog />}
</section>
</div>
<Toaster />
</main>
<NavigationContext.Provider value={{ navigationKey }}>
<main className="flex min-h-screen flex-col">
<Header currentPage={currentPage} onNavigate={handleNavigation} />
<div className="flex flex-1">
<Sidebar onNavigate={handleNavigation} />
<section className="flex-1 p-6 overflow-y-auto h-[calc(100dvh-4.55rem)]">
{currentPage === "dashboard" && <Dashboard />}
{currentPage === "repositories" && <Repository />}
{currentPage === "organizations" && <Organization />}
{currentPage === "configuration" && <ConfigTabs />}
{currentPage === "activity-log" && <ActivityLog />}
</section>
</div>
<Toaster />
</main>
</NavigationContext.Provider>
);
}

View File

@@ -6,9 +6,10 @@ import { VersionInfo } from "./VersionInfo";
interface SidebarProps {
className?: string;
onNavigate?: (page: string) => void;
}
export function Sidebar({ className }: SidebarProps) {
export function Sidebar({ className, onNavigate }: SidebarProps) {
const [currentPath, setCurrentPath] = useState<string>("");
useEffect(() => {
@@ -18,6 +19,39 @@ export function Sidebar({ className }: SidebarProps) {
console.log("Hydrated path:", path); // Should log now
}, []);
// Listen for URL changes (browser back/forward)
useEffect(() => {
const handlePopState = () => {
setCurrentPath(window.location.pathname);
};
window.addEventListener('popstate', handlePopState);
return () => window.removeEventListener('popstate', handlePopState);
}, []);
const handleNavigation = (href: string, event: React.MouseEvent) => {
event.preventDefault();
// Don't navigate if already on the same page
if (currentPath === href) return;
// Update URL without page reload
window.history.pushState({}, '', href);
setCurrentPath(href);
// Map href to page name for the parent component
const pageMap: Record<string, string> = {
'/': 'dashboard',
'/repositories': 'repositories',
'/organizations': 'organizations',
'/config': 'configuration',
'/activity': 'activity-log'
};
const pageName = pageMap[href] || 'dashboard';
onNavigate?.(pageName);
};
return (
<aside className={cn("w-64 border-r bg-background", className)}>
<div className="flex flex-col h-full pt-4">
@@ -27,11 +61,11 @@ export function Sidebar({ className }: SidebarProps) {
const Icon = link.icon;
return (
<a
<button
key={index}
href={link.href}
onClick={(e) => handleNavigation(link.href, e)}
className={cn(
"flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors",
"flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors w-full text-left",
isActive
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
@@ -39,7 +73,7 @@ export function Sidebar({ className }: SidebarProps) {
>
<Icon className="h-4 w-4" />
{link.label}
</a>
</button>
);
})}
</nav>