refactor(ui): enhance update server button and sidebar layout

- Improve UpdateServer component with flexible rendering and tooltip support
- Modify sidebar layout to integrate update server button more cleanly
- Add conditional rendering and styling for update availability
- Introduce more consistent button and tooltip interactions
This commit is contained in:
Mauricio Siu 2025-03-08 15:31:08 -06:00
parent 8ba3a42c1e
commit c89f957133
3 changed files with 119 additions and 41 deletions

View File

@ -5,6 +5,12 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { api } from "@/utils/api";
import type { IUpdateData } from "@dokploy/server/index";
import {
@ -24,9 +30,17 @@ import { UpdateWebServer } from "./update-webserver";
interface Props {
updateData?: IUpdateData;
children?: React.ReactNode;
isOpen?: boolean;
onOpenChange?: (open: boolean) => void;
}
export const UpdateServer = ({ updateData }: Props) => {
export const UpdateServer = ({
updateData,
children,
isOpen: isOpenProp,
onOpenChange: onOpenChangeProp,
}: Props) => {
const [hasCheckedUpdate, setHasCheckedUpdate] = useState(!!updateData);
const [isUpdateAvailable, setIsUpdateAvailable] = useState(
!!updateData?.updateAvailable,
@ -35,10 +49,10 @@ export const UpdateServer = ({ updateData }: Props) => {
api.settings.getUpdateData.useMutation();
const { data: dokployVersion } = api.settings.getDokployVersion.useQuery();
const { data: releaseTag } = api.settings.getReleaseTag.useQuery();
const [isOpen, setIsOpen] = useState(false);
const [latestVersion, setLatestVersion] = useState(
updateData?.latestVersion ?? "",
);
const [isOpenInternal, setIsOpenInternal] = useState(false);
const handleCheckUpdates = async () => {
try {
@ -65,28 +79,52 @@ export const UpdateServer = ({ updateData }: Props) => {
}
};
const isOpen = isOpenInternal || isOpenProp;
const onOpenChange = (open: boolean) => {
setIsOpenInternal(open);
onOpenChangeProp?.(open);
};
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<Dialog open={isOpen} onOpenChange={onOpenChange}>
<DialogTrigger asChild>
<Button
variant={updateData ? "outline" : "secondary"}
className="gap-2"
>
{updateData ? (
<>
<span className="flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-2 w-2 rounded-full bg-emerald-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-500" />
</span>
Update available
</>
) : (
<>
<Sparkles className="h-4 w-4" />
Updates
</>
)}
</Button>
{children ? (
children
) : (
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant={updateData ? "outline" : "secondary"}
size="sm"
onClick={() => onOpenChange?.(true)}
>
<Download className="h-4 w-4 flex-shrink-0" />
{updateData ? (
<span className="font-medium truncate group-data-[collapsible=icon]:hidden">
Update Available
</span>
) : (
<span className="font-medium truncate group-data-[collapsible=icon]:hidden">
Check for updates
</span>
)}
{updateData && (
<span className="absolute right-2 flex h-2 w-2 group-data-[collapsible=icon]:hidden">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-500" />
</span>
)}
</Button>
</TooltipTrigger>
{updateData && (
<TooltipContent side="right" sideOffset={10}>
<p>Update Available</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
)}
</DialogTrigger>
<DialogContent className="max-w-lg p-6">
<div className="flex items-center justify-between mb-8">
@ -217,7 +255,7 @@ export const UpdateServer = ({ updateData }: Props) => {
<div className="space-y-4 flex items-center justify-end">
<div className="flex items-center gap-2">
<Button variant="outline" onClick={() => setIsOpen(false)}>
<Button variant="outline" onClick={() => onOpenChange?.(false)}>
Cancel
</Button>
{isUpdateAvailable ? (

View File

@ -37,8 +37,6 @@ import {
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import {
Collapsible,
@ -1017,18 +1015,16 @@ export default function Page({ children }: Props) {
</SidebarMenuButton>
</SidebarMenuItem>
))}
{!isCloud && auth?.role === "owner" && (
<SidebarMenuItem>
<SidebarMenuButton asChild>
<UpdateServerButton />
</SidebarMenuButton>
</SidebarMenuItem>
)}
</SidebarMenu>
</SidebarGroup>
</SidebarContent>
<SidebarFooter>
<SidebarMenu>
<SidebarMenu className="flex flex-col gap-2">
{!isCloud && auth?.role === "owner" && (
<SidebarMenuItem>
<UpdateServerButton />
</SidebarMenuItem>
)}
<SidebarMenuItem>
<UserNav />
</SidebarMenuItem>

View File

@ -3,7 +3,14 @@ import type { IUpdateData } from "@dokploy/server/index";
import { useRouter } from "next/router";
import { useEffect, useRef, useState } from "react";
import UpdateServer from "../dashboard/settings/web-server/update-server";
import { Button } from "../ui/button";
import { Download } from "lucide-react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "../ui/tooltip";
const AUTO_CHECK_UPDATES_INTERVAL_MINUTES = 7;
export const UpdateServerButton = () => {
@ -15,6 +22,7 @@ export const UpdateServerButton = () => {
const { data: isCloud } = api.settings.isCloud.useQuery();
const { mutateAsync: getUpdateData } =
api.settings.getUpdateData.useMutation();
const [isOpen, setIsOpen] = useState(false);
const checkUpdatesIntervalRef = useRef<null | NodeJS.Timeout>(null);
@ -69,11 +77,47 @@ export const UpdateServerButton = () => {
};
}, []);
return (
updateData.updateAvailable && (
<div>
<UpdateServer updateData={updateData} />
</div>
)
);
return updateData.updateAvailable ? (
<div className="border-t pt-4">
<UpdateServer
updateData={updateData}
isOpen={isOpen}
onOpenChange={setIsOpen}
>
<TooltipProvider delayDuration={0}>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant={updateData ? "outline" : "secondary"}
className="w-full"
onClick={() => setIsOpen(true)}
>
<Download className="h-4 w-4 flex-shrink-0" />
{updateData ? (
<span className="font-medium truncate group-data-[collapsible=icon]:hidden">
Update Available
</span>
) : (
<span className="font-medium truncate group-data-[collapsible=icon]:hidden">
Check for updates
</span>
)}
{updateData && (
<span className="absolute right-2 flex h-2 w-2 group-data-[collapsible=icon]:hidden">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" />
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-500" />
</span>
)}
</Button>
</TooltipTrigger>
{updateData && (
<TooltipContent side="right" sideOffset={10}>
<p>Update Available</p>
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
</UpdateServer>
</div>
) : null;
};