Merge pull request #3838 from lklacar/fix/service-card-behavior

fix: Fixed service card behavior #3837
This commit is contained in:
Mauricio Siu
2026-03-01 15:38:51 -06:00
committed by GitHub
@@ -23,7 +23,7 @@ import type {
InferGetServerSidePropsType, InferGetServerSidePropsType,
} from "next"; } from "next";
import Head from "next/head"; import Head from "next/head";
import { useRouter } from "next/router"; import Link from "next/link";
import { type ReactElement, useEffect, useMemo, useState } from "react"; import { type ReactElement, useEffect, useMemo, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import superjson from "superjson"; import superjson from "superjson";
@@ -358,7 +358,6 @@ const EnvironmentPage = (
environmentId, environmentId,
}); });
const { data: allProjects } = api.project.all.useQuery(); const { data: allProjects } = api.project.all.useQuery();
const router = useRouter();
const [isMoveDialogOpen, setIsMoveDialogOpen] = useState(false); const [isMoveDialogOpen, setIsMoveDialogOpen] = useState(false);
const [selectedTargetProject, setSelectedTargetProject] = const [selectedTargetProject, setSelectedTargetProject] =
@@ -412,6 +411,7 @@ const EnvironmentPage = (
}; };
const handleServiceSelect = (serviceId: string, event: React.MouseEvent) => { const handleServiceSelect = (serviceId: string, event: React.MouseEvent) => {
event.preventDefault();
event.stopPropagation(); event.stopPropagation();
setSelectedServices((prev) => setSelectedServices((prev) =>
prev.includes(serviceId) prev.includes(serviceId)
@@ -1463,101 +1463,99 @@ const EnvironmentPage = (
<div className="flex w-full flex-col gap-4"> <div className="flex w-full flex-col gap-4">
<div className="gap-5 pb-10 grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3"> <div className="gap-5 pb-10 grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
{filteredServices?.map((service) => ( {filteredServices?.map((service) => (
<Card <Link
key={service.id} key={service.id}
onClick={() => { href={`/dashboard/project/${projectId}/environment/${environmentId}/services/${service.type}/${service.id}`}
router.push( className="block"
`/dashboard/project/${projectId}/environment/${environmentId}/services/${service.type}/${service.id}`,
);
}}
className="flex flex-col group relative cursor-pointer bg-transparent transition-colors hover:bg-border"
> >
{service.serverId && ( <Card className="flex flex-col group relative cursor-pointer bg-transparent transition-colors hover:bg-border">
<div className="absolute -left-1 -top-2"> {service.serverId && (
<ServerIcon className="size-4 text-muted-foreground" /> <div className="absolute -left-1 -top-2">
</div> <ServerIcon className="size-4 text-muted-foreground" />
)}
<div className="absolute -right-1 -top-2">
<StatusTooltip status={service.status} />
</div>
<div
className={cn(
"absolute -left-3 -bottom-3 size-9 translate-y-1 rounded-full p-0 transition-all duration-200 z-10 bg-background border",
selectedServices.includes(service.id)
? "opacity-100 translate-y-0"
: "opacity-0 group-hover:translate-y-0 group-hover:opacity-100",
)}
onClick={(e) =>
handleServiceSelect(service.id, e)
}
>
<div className="h-full w-full flex items-center justify-center">
<Checkbox
checked={selectedServices.includes(
service.id,
)}
className="data-[state=checked]:bg-primary"
/>
</div>
</div>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<div className="flex flex-row items-center gap-2 justify-between w-full">
<div className="flex flex-col gap-2">
<span className="text-base flex items-center gap-2 font-medium leading-none flex-wrap">
{service.name}
</span>
{service.description && (
<span className="text-sm font-medium text-muted-foreground">
{service.description}
</span>
)}
</div>
<span className="text-sm font-medium text-muted-foreground self-start">
{service.type === "postgres" && (
<PostgresqlIcon className="h-7 w-7" />
)}
{service.type === "redis" && (
<RedisIcon className="h-7 w-7" />
)}
{service.type === "mariadb" && (
<MariadbIcon className="h-7 w-7" />
)}
{service.type === "mongo" && (
<MongodbIcon className="h-7 w-7" />
)}
{service.type === "mysql" && (
<MysqlIcon className="h-7 w-7" />
)}
{service.type === "application" && (
<GlobeIcon className="h-6 w-6" />
)}
{service.type === "compose" && (
<CircuitBoard className="h-6 w-6" />
)}
</span>
</div> </div>
</CardTitle> )}
</CardHeader> <div className="absolute -right-1 -top-2">
<CardFooter className="mt-auto"> <StatusTooltip status={service.status} />
<div className="space-y-1 text-sm w-full"> </div>
{service.serverName && (
<div className="flex items-center gap-1.5 text-xs text-muted-foreground mb-1"> <div
<ServerIcon className="size-3" /> className={cn(
<span className="truncate"> "absolute -left-3 -bottom-3 size-9 translate-y-1 rounded-full p-0 transition-all duration-200 z-10 bg-background border",
{service.serverName} selectedServices.includes(service.id)
? "opacity-100 translate-y-0"
: "opacity-0 group-hover:translate-y-0 group-hover:opacity-100",
)}
onClick={(e) =>
handleServiceSelect(service.id, e)
}
>
<div className="h-full w-full flex items-center justify-center">
<Checkbox
checked={selectedServices.includes(
service.id,
)}
className="data-[state=checked]:bg-primary"
/>
</div>
</div>
<CardHeader>
<CardTitle className="flex items-center justify-between">
<div className="flex flex-row items-center gap-2 justify-between w-full">
<div className="flex flex-col gap-2">
<span className="text-base flex items-center gap-2 font-medium leading-none flex-wrap">
{service.name}
</span>
{service.description && (
<span className="text-sm font-medium text-muted-foreground">
{service.description}
</span>
)}
</div>
<span className="text-sm font-medium text-muted-foreground self-start">
{service.type === "postgres" && (
<PostgresqlIcon className="h-7 w-7" />
)}
{service.type === "redis" && (
<RedisIcon className="h-7 w-7" />
)}
{service.type === "mariadb" && (
<MariadbIcon className="h-7 w-7" />
)}
{service.type === "mongo" && (
<MongodbIcon className="h-7 w-7" />
)}
{service.type === "mysql" && (
<MysqlIcon className="h-7 w-7" />
)}
{service.type === "application" && (
<GlobeIcon className="h-6 w-6" />
)}
{service.type === "compose" && (
<CircuitBoard className="h-6 w-6" />
)}
</span> </span>
</div> </div>
)} </CardTitle>
<DateTooltip date={service.createdAt}> </CardHeader>
Created <CardFooter className="mt-auto">
</DateTooltip> <div className="space-y-1 text-sm w-full">
</div> {service.serverName && (
</CardFooter> <div className="flex items-center gap-1.5 text-xs text-muted-foreground mb-1">
</Card> <ServerIcon className="size-3" />
<span className="truncate">
{service.serverName}
</span>
</div>
)}
<DateTooltip date={service.createdAt}>
Created
</DateTooltip>
</div>
</CardFooter>
</Card>
</Link>
))} ))}
</div> </div>
</div> </div>