Added basic responsive layout

This commit is contained in:
Arunavo Ray
2025-07-07 17:34:54 +05:30
parent 6155e39360
commit 24bd0aefe6
10 changed files with 715 additions and 538 deletions

View File

@@ -7,13 +7,15 @@ import { toast } from "sonner";
import { Skeleton } from "@/components/ui/skeleton";
import { useLiveRefresh } from "@/hooks/useLiveRefresh";
import { useConfigStatus } from "@/hooks/useConfigStatus";
import { Menu } from "lucide-react";
interface HeaderProps {
currentPage?: "dashboard" | "repositories" | "organizations" | "configuration" | "activity-log";
onNavigate?: (page: string) => void;
onMenuClick: () => void;
}
export function Header({ currentPage, onNavigate }: HeaderProps) {
export function Header({ currentPage, onNavigate, onMenuClick }: HeaderProps) {
const { user, logout, isLoading } = useAuth();
const { isLiveEnabled, toggleLive } = useLiveRefresh();
const { isFullyConfigured, isLoading: configLoading } = useConfigStatus();
@@ -54,35 +56,48 @@ export function Header({ currentPage, onNavigate }: HeaderProps) {
return (
<header className="border-b bg-background">
<div className="flex h-[4.5rem] items-center justify-between px-6">
<button
onClick={() => {
if (currentPage !== 'dashboard') {
window.history.pushState({}, '', '/');
onNavigate?.('dashboard');
}
}}
className="flex items-center gap-2 py-1 hover:opacity-80 transition-opacity"
>
<img
src="/logo-light.svg"
alt="Gitea Mirror Logo"
className="h-6 w-6 dark:hidden"
/>
<img
src="/logo-dark.svg"
alt="Gitea Mirror Logo"
className="h-6 w-6 hidden dark:block"
/>
<span className="text-xl font-bold">Gitea Mirror</span>
</button>
<div className="flex h-[4.5rem] items-center justify-between px-4 sm:px-6">
<div className="flex items-center gap-2">
{/* Hamburger Menu Button - Mobile Only */}
<Button
variant="ghost"
size="sm"
className="lg:hidden p-2"
onClick={onMenuClick}
>
<Menu className="h-5 w-5" />
<span className="sr-only">Toggle menu</span>
</Button>
<button
onClick={() => {
if (currentPage !== 'dashboard') {
window.history.pushState({}, '', '/');
onNavigate?.('dashboard');
}
}}
className="flex items-center gap-2 py-1 hover:opacity-80 transition-opacity"
>
<img
src="/logo-light.svg"
alt="Gitea Mirror Logo"
className="h-6 w-6 dark:hidden"
/>
<img
src="/logo-dark.svg"
alt="Gitea Mirror Logo"
className="h-6 w-6 hidden dark:block"
/>
<span className="text-xl font-bold hidden sm:inline">Gitea Mirror</span>
</button>
</div>
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 sm:gap-4">
{showLiveButton && (
<Button
variant="outline"
size="lg"
className="flex items-center gap-2"
size="sm"
className="flex items-center gap-2 sm:px-4"
onClick={toggleLive}
title={getTooltip()}
>
@@ -95,7 +110,7 @@ export function Header({ currentPage, onNavigate }: HeaderProps) {
? 'bg-orange-400'
: 'bg-gray-500'
}`} />
<span>LIVE</span>
<span className="hidden sm:inline">LIVE</span>
</Button>
)}
@@ -111,12 +126,12 @@ export function Header({ currentPage, onNavigate }: HeaderProps) {
{user.username.charAt(0).toUpperCase()}
</AvatarFallback>
</Avatar>
<Button variant="outline" size="lg" onClick={handleLogout}>
<Button variant="outline" size="sm" onClick={handleLogout} className="hidden sm:inline-flex">
Logout
</Button>
</>
) : (
<Button variant="outline" size="lg" asChild>
<Button variant="outline" size="sm" asChild>
<a href="/login">Login</a>
</Button>
)}

View File

@@ -44,6 +44,7 @@ function AppWithProviders({ page: initialPage }: AppProps) {
const { isLoading: configLoading } = useConfigStatus();
const [currentPage, setCurrentPage] = useState<AppProps['page']>(initialPage);
const [navigationKey, setNavigationKey] = useState(0);
const [sidebarOpen, setSidebarOpen] = useState(false);
useRepoSync({
userId: user?.id,
@@ -99,10 +100,18 @@ function AppWithProviders({ page: initialPage }: AppProps) {
return (
<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)]">
<Header
currentPage={currentPage}
onNavigate={handleNavigation}
onMenuClick={() => setSidebarOpen(!sidebarOpen)}
/>
<div className="flex flex-1 relative">
<Sidebar
onNavigate={handleNavigation}
isOpen={sidebarOpen}
onClose={() => setSidebarOpen(false)}
/>
<section className="flex-1 p-4 sm:p-6 overflow-y-auto h-[calc(100dvh-4.55rem)] w-full lg:w-[calc(100%-16rem)]">
{currentPage === "dashboard" && <Dashboard />}
{currentPage === "repositories" && <Repository />}
{currentPage === "organizations" && <Organization />}

View File

@@ -7,9 +7,11 @@ import { VersionInfo } from "./VersionInfo";
interface SidebarProps {
className?: string;
onNavigate?: (page: string) => void;
isOpen: boolean;
onClose: () => void;
}
export function Sidebar({ className, onNavigate }: SidebarProps) {
export function Sidebar({ className, onNavigate, isOpen, onClose }: SidebarProps) {
const [currentPath, setCurrentPath] = useState<string>("");
useEffect(() => {
@@ -50,53 +52,75 @@ export function Sidebar({ className, onNavigate }: SidebarProps) {
const pageName = pageMap[href] || 'dashboard';
onNavigate?.(pageName);
// Close sidebar on mobile after navigation
if (window.innerWidth < 1024) {
onClose();
}
};
return (
<aside className={cn("w-64 border-r bg-background", className)}>
<div className="flex flex-col h-full pt-4">
<nav className="flex flex-col gap-y-1 pl-2 pr-3">
{links.map((link, index) => {
const isActive = currentPath === link.href;
const Icon = link.icon;
<>
{/* Mobile Backdrop */}
{isOpen && (
<div
className="fixed inset-0 bg-background/80 backdrop-blur-sm z-40 lg:hidden"
onClick={onClose}
/>
)}
{/* Sidebar */}
<aside
className={cn(
"fixed lg:static inset-y-0 left-0 z-50 w-64 bg-background border-r flex flex-col h-full transition-transform duration-200 ease-in-out lg:translate-x-0",
isOpen ? "translate-x-0" : "-translate-x-full",
className
)}
>
<div className="flex flex-col h-full pt-4">
<nav className="flex flex-col gap-y-1 pl-2 pr-3">
{links.map((link, index) => {
const isActive = currentPath === link.href;
const Icon = link.icon;
return (
<button
key={index}
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 w-full text-left",
isActive
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
)}
return (
<button
key={index}
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 w-full text-left",
isActive
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
)}
>
<Icon className="h-4 w-4" />
{link.label}
</button>
);
})}
</nav>
<div className="mt-auto px-4 py-4">
<div className="rounded-md bg-muted p-3">
<h4 className="text-sm font-medium mb-2">Need Help?</h4>
<p className="text-xs text-muted-foreground mb-2">
Check out the documentation for help with setup and configuration.
</p>
<a
href="/docs"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 text-xs text-primary hover:underline"
>
<Icon className="h-4 w-4" />
{link.label}
</button>
);
})}
</nav>
<div className="mt-auto px-4 py-4">
<div className="rounded-md bg-muted p-3">
<h4 className="text-sm font-medium mb-2">Need Help?</h4>
<p className="text-xs text-muted-foreground mb-2">
Check out the documentation for help with setup and configuration.
</p>
<a
href="/docs"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 text-xs text-primary hover:underline"
>
Documentation
<ExternalLink className="h-3 w-3" />
</a>
Documentation
<ExternalLink className="h-3 w-3" />
</a>
</div>
<VersionInfo />
</div>
<VersionInfo />
</div>
</div>
</aside>
</aside>
</>
);
}