UI updates

This commit is contained in:
Arunavo Ray
2025-08-29 19:08:39 +05:30
parent 9ee67ce77d
commit ac1b09f7a1
4 changed files with 176 additions and 44 deletions

View File

@@ -7,7 +7,7 @@ import { toast } from "sonner";
import { Skeleton } from "@/components/ui/skeleton";
import { useLiveRefresh } from "@/hooks/useLiveRefresh";
import { useConfigStatus } from "@/hooks/useConfigStatus";
import { Menu, LogOut } from "lucide-react";
import { Menu, LogOut, PanelRightOpen, PanelRightClose } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
@@ -19,9 +19,12 @@ interface HeaderProps {
currentPage?: "dashboard" | "repositories" | "organizations" | "configuration" | "activity-log";
onNavigate?: (page: string) => void;
onMenuClick: () => void;
onToggleCollapse?: () => void;
isSidebarCollapsed?: boolean;
isSidebarOpen?: boolean;
}
export function Header({ currentPage, onNavigate, onMenuClick }: HeaderProps) {
export function Header({ currentPage, onNavigate, onMenuClick, onToggleCollapse, isSidebarCollapsed, isSidebarOpen }: HeaderProps) {
const { user, logout, isLoading } = useAuth();
const { isLiveEnabled, toggleLive } = useLiveRefresh();
const { isFullyConfigured, isLoading: configLoading } = useConfigStatus();
@@ -63,18 +66,38 @@ export function Header({ currentPage, onNavigate, onMenuClick }: HeaderProps) {
return (
<header className="border-b bg-background">
<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 */}
<div className="flex items-center gap-12">
{/* Sidebar Toggle - Mobile uses slide-in, Medium uses collapse */}
<Button
variant="outline"
size="lg"
className="lg:hidden"
variant="ghost"
size="icon"
className="md:hidden h-10 w-10"
onClick={onMenuClick}
>
<Menu className="h-5 w-5" />
{isSidebarOpen ? (
<PanelRightOpen className="h-5 w-5" />
) : (
<PanelRightClose className="h-5 w-5" />
)}
<span className="sr-only">Toggle menu</span>
</Button>
{/* Sidebar Collapse Toggle - Only on medium screens (768px - 1280px) */}
<Button
variant="ghost"
size="icon"
className="hidden md:flex xl:hidden h-10 w-10"
onClick={onToggleCollapse}
title={isSidebarCollapsed ? "Expand sidebar" : "Collapse sidebar"}
>
{isSidebarCollapsed ? (
<PanelRightClose className="h-5 w-5" />
) : (
<PanelRightOpen className="h-5 w-5" />
)}
<span className="sr-only">Toggle sidebar</span>
</Button>
<button
onClick={() => {
if (currentPage !== 'dashboard') {

View File

@@ -45,6 +45,13 @@ function AppWithProviders({ page: initialPage }: AppProps) {
const [currentPage, setCurrentPage] = useState<AppProps['page']>(initialPage);
const [navigationKey, setNavigationKey] = useState(0);
const [sidebarOpen, setSidebarOpen] = useState(false);
const [sidebarCollapsed, setSidebarCollapsed] = useState(() => {
// Check if we're on medium screens (768px - 1280px)
if (typeof window !== 'undefined') {
return window.innerWidth >= 768 && window.innerWidth < 1280;
}
return false;
});
useRepoSync({
userId: user?.id,
@@ -83,6 +90,23 @@ function AppWithProviders({ page: initialPage }: AppProps) {
return () => window.removeEventListener('popstate', handlePopState);
}, []);
// Handle window resize to auto-collapse sidebar on medium screens
useEffect(() => {
const handleResize = () => {
const width = window.innerWidth;
// Auto-collapse on medium screens (768px - 1280px)
if (width >= 768 && width < 1280) {
setSidebarCollapsed(true);
} else if (width >= 1280) {
// Expand on large screens
setSidebarCollapsed(false);
}
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
// Show loading state only during initial auth/config loading
const isInitialLoading = authLoading || (configLoading && !user);
@@ -113,14 +137,21 @@ function AppWithProviders({ page: initialPage }: AppProps) {
currentPage={currentPage}
onNavigate={handleNavigation}
onMenuClick={() => setSidebarOpen(!sidebarOpen)}
onToggleCollapse={() => setSidebarCollapsed(!sidebarCollapsed)}
isSidebarCollapsed={sidebarCollapsed}
isSidebarOpen={sidebarOpen}
/>
<div className="flex flex-1 relative">
<Sidebar
onNavigate={handleNavigation}
isOpen={sidebarOpen}
isCollapsed={sidebarCollapsed}
onClose={() => setSidebarOpen(false)}
onToggleCollapse={() => setSidebarCollapsed(!sidebarCollapsed)}
/>
<section className="flex-1 p-4 sm:p-6 overflow-y-auto h-[calc(100dvh-4.55rem)] w-full lg:w-[calc(100%-16rem)]">
<section className={`flex-1 p-4 sm:p-6 overflow-y-auto h-[calc(100dvh-4.55rem)] w-full transition-all duration-200 ${
sidebarCollapsed ? 'md:w-[calc(100%-5rem)] xl:w-[calc(100%-16rem)]' : 'md:w-[calc(100%-16rem)]'
}`}>
{currentPage === "dashboard" && <Dashboard />}
{currentPage === "repositories" && <Repository />}
{currentPage === "organizations" && <Organization />}

View File

@@ -3,15 +3,23 @@ import { cn } from "@/lib/utils";
import { ExternalLink } from "lucide-react";
import { links } from "@/data/Sidebar";
import { VersionInfo } from "./VersionInfo";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
interface SidebarProps {
className?: string;
onNavigate?: (page: string) => void;
isOpen: boolean;
isCollapsed?: boolean;
onClose: () => void;
onToggleCollapse?: () => void;
}
export function Sidebar({ className, onNavigate, isOpen, onClose }: SidebarProps) {
export function Sidebar({ className, onNavigate, isOpen, isCollapsed = false, onClose, onToggleCollapse }: SidebarProps) {
const [currentPath, setCurrentPath] = useState<string>("");
useEffect(() => {
@@ -53,7 +61,7 @@ export function Sidebar({ className, onNavigate, isOpen, onClose }: SidebarProps
onNavigate?.(pageName);
// Close sidebar on mobile after navigation
if (window.innerWidth < 1024) {
if (window.innerWidth < 768) {
onClose();
}
};
@@ -63,7 +71,7 @@ export function Sidebar({ className, onNavigate, isOpen, onClose }: SidebarProps
{/* Mobile Backdrop */}
{isOpen && (
<div
className="fixed inset-0 backdrop-blur-sm z-40 lg:hidden"
className="fixed inset-0 backdrop-blur-sm z-40 md:hidden"
onClick={onClose}
/>
)}
@@ -71,56 +79,128 @@ export function Sidebar({ className, onNavigate, isOpen, onClose }: SidebarProps
{/* 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 lg:h-[calc(100vh-4.5rem)] transition-transform duration-200 ease-in-out lg:translate-x-0",
"fixed md:static inset-y-0 left-0 z-50 bg-background border-r flex flex-col h-full md:h-[calc(100vh-4.5rem)] transition-all duration-200 ease-in-out md:translate-x-0",
isOpen ? "translate-x-0" : "-translate-x-full",
isCollapsed ? "md:w-20 xl:w-64" : "w-64",
className
)}
>
<div className="flex flex-col h-full">
<nav className="flex flex-col gap-y-1 lg:gap-y-1 pl-2 pr-3 pt-4 flex-shrink-0">
<nav className={cn(
"flex flex-col pt-4 flex-shrink-0",
isCollapsed
? "md:gap-y-2 md:items-center md:px-2 xl:gap-y-1 xl:items-stretch xl:pl-2 xl:pr-3 gap-y-1 pl-2 pr-3"
: "gap-y-1 pl-2 pr-3"
)}>
{links.map((link, index) => {
const isActive = currentPath === link.href;
const Icon = link.icon;
return (
const button = (
<button
key={index}
onClick={(e) => handleNavigation(link.href, e)}
className={cn(
"flex items-center gap-3 rounded-md px-3 py-3 lg:py-2 text-sm lg:text-sm font-medium transition-colors w-full text-left",
"flex items-center rounded-md text-sm font-medium transition-colors w-full",
isCollapsed
? "md:h-12 md:w-12 md:justify-center md:p-0 xl:h-auto xl:w-full xl:justify-start xl:px-3 xl:py-2 h-auto px-3 py-3"
: "px-3 py-3 md:py-2",
isActive
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
)}
>
<Icon className="h-5 w-5 lg:h-4 lg:w-4" />
<Icon className={cn(
"flex-shrink-0",
isCollapsed
? "md:h-5 md:w-5 md:mr-0 xl:h-4 xl:w-4 xl:mr-3 h-5 w-5 mr-3"
: "h-5 w-5 md:h-4 md:w-4 mr-3"
)} />
<span className={cn(
"transition-all duration-200",
isCollapsed ? "md:hidden xl:inline" : "inline"
)}>
{link.label}
</span>
</button>
);
// Wrap in tooltip when collapsed on medium screens
if (isCollapsed) {
return (
<TooltipProvider key={index}>
<Tooltip delayDuration={0}>
<TooltipTrigger asChild>
{button}
</TooltipTrigger>
<TooltipContent side="right" className="hidden md:block xl:hidden">
{link.label}
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
return button;
})}
</nav>
<div className="flex-1 min-h-0" />
<div className="px-4 py-4 flex-shrink-0">
<div className="rounded-md bg-muted p-3 lg:p-3">
<div className={cn(
"py-4 flex-shrink-0",
isCollapsed ? "md:px-2 xl:px-4 px-4" : "px-4"
)}>
<div className={cn(
"rounded-md bg-muted transition-all duration-200",
isCollapsed ? "md:p-0 xl:p-3 p-3" : "p-3"
)}>
<div className={cn(
isCollapsed ? "md:hidden xl:block" : "block"
)}>
<h4 className="text-sm font-medium mb-2">Need Help?</h4>
<p className="text-xs text-muted-foreground mb-3 lg:mb-2">
<p className="text-xs text-muted-foreground mb-3 md:mb-2">
Check out the documentation for help with setup and configuration.
</p>
<a
href="/docs"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 text-xs lg:text-xs text-primary hover:underline py-2 lg:py-0"
className="inline-flex items-center gap-1.5 text-xs md:text-xs text-primary hover:underline py-2 md:py-0"
>
Documentation
<ExternalLink className="h-3.5 w-3.5 lg:h-3 lg:w-3" />
<ExternalLink className="h-3.5 w-3.5 md:h-3 md:w-3" />
</a>
</div>
{/* Icon-only help button for collapsed state on medium screens */}
<TooltipProvider>
<Tooltip delayDuration={0}>
<TooltipTrigger asChild>
<a
href="/docs"
target="_blank"
rel="noopener noreferrer"
className={cn(
"flex items-center justify-center rounded-md hover:bg-accent transition-colors",
isCollapsed ? "md:h-12 md:w-12 xl:hidden hidden" : "hidden"
)}
>
<ExternalLink className="h-5 w-5" />
</a>
</TooltipTrigger>
<TooltipContent side="right">
Documentation
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className={cn(
isCollapsed ? "md:hidden xl:block" : "block"
)}>
<VersionInfo />
</div>
</div>
</div>
</aside>
</>
);

View File

@@ -5,7 +5,7 @@ import { FlipHorizontal, GitFork, RefreshCw, RotateCcw, Star, Lock, Ban, Check,
import { SiGithub, SiGitea } from "react-icons/si";
import type { Repository } from "@/lib/db/schema";
import { Button } from "@/components/ui/button";
import { formatDate, getStatusColor } from "@/lib/utils";
import { formatDate, formatLastSyncTime, getStatusColor } from "@/lib/utils";
import type { FilterParams } from "@/types/filter";
import { Skeleton } from "@/components/ui/skeleton";
import { useGiteaConfig } from "@/hooks/useGiteaConfig";
@@ -242,7 +242,7 @@ export default function RepositoryTable({
{repo.status}
</Badge>
<span className="text-xs text-muted-foreground">
{repo.lastMirrored ? formatDate(repo.lastMirrored) : "Never mirrored"}
{formatLastSyncTime(repo.lastMirrored)}
</span>
</div>
</div>
@@ -410,7 +410,7 @@ export default function RepositoryTable({
<div className="h-full p-3 flex items-center justify-center flex-[0.3]">
<Skeleton className="h-4 w-4" />
</div>
<div className="h-full p-3 text-sm font-medium flex-[2.5]">
<div className="h-full py-3 text-sm font-medium flex-[2.3]">
Repository
</div>
<div className="h-full p-3 text-sm font-medium flex-[1]">Owner</div>
@@ -437,7 +437,7 @@ export default function RepositoryTable({
<div className="h-full p-3 flex items-center justify-center flex-[0.3]">
<Skeleton className="h-4 w-4" />
</div>
<div className="h-full p-3 flex-[2.5]">
<div className="h-full p-3 flex-[2.3]">
<Skeleton className="h-5 w-48" />
<Skeleton className="h-3 w-24 mt-1" />
</div>
@@ -530,7 +530,7 @@ export default function RepositoryTable({
aria-label="Select all repositories"
/>
</div>
<div className="h-full p-3 text-sm font-medium flex-[2.5]">
<div className="h-full py-3 text-sm font-medium flex-[2.3]">
Repository
</div>
<div className="h-full p-3 text-sm font-medium flex-[1]">Owner</div>
@@ -588,7 +588,7 @@ export default function RepositoryTable({
</div>
{/* Repository */}
<div className="h-full py-3 flex items-center gap-2 flex-[2.5]">
<div className="h-full py-3 flex items-center gap-2 flex-[2.3]">
<div className="flex-1">
<div className="font-medium flex items-center gap-1">
{repo.name}
@@ -629,9 +629,7 @@ export default function RepositoryTable({
{/* Last Mirrored */}
<div className="h-full p-3 flex items-center flex-[1]">
<p className="text-sm">
{repo.lastMirrored
? formatDate(new Date(repo.lastMirrored))
: "Never"}
{formatLastSyncTime(repo.lastMirrored)}
</p>
</div>