Skip to content

Commit ceb6533

Browse files
committed
add delete option to frontend for envs
1 parent d895d0c commit ceb6533

File tree

2 files changed

+158
-32
lines changed

2 files changed

+158
-32
lines changed
Lines changed: 126 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,32 @@
1-
import { Calendar, Layers } from "lucide-react";
1+
import { useState } from "react";
2+
import { Calendar, Layers, MoreVertical, Trash2 } from "lucide-react";
23
import { Link } from "react-router";
34

5+
import { trpc } from "~/api/trpc";
6+
import {
7+
AlertDialog,
8+
AlertDialogAction,
9+
AlertDialogCancel,
10+
AlertDialogContent,
11+
AlertDialogDescription,
12+
AlertDialogFooter,
13+
AlertDialogHeader,
14+
AlertDialogTitle,
15+
} from "~/components/ui/alert-dialog";
16+
import { Button } from "~/components/ui/button";
417
import {
518
Card,
619
CardContent,
720
CardDescription,
821
CardHeader,
922
CardTitle,
1023
} from "~/components/ui/card";
24+
import {
25+
DropdownMenu,
26+
DropdownMenuContent,
27+
DropdownMenuItem,
28+
DropdownMenuTrigger,
29+
} from "~/components/ui/dropdown-menu";
1130
import { useWorkspace } from "~/components/WorkspaceProvider";
1231

1332
type EnvironmentCardProps = {
@@ -27,8 +46,26 @@ export const EnvironmentCard: React.FC<EnvironmentCardProps> = ({
2746
system,
2847
}) => {
2948
const { workspace } = useWorkspace();
49+
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
3050

3151
const environmentUrl = `/${workspace.slug}/environments/${environment.id}`;
52+
const utils = trpc.useUtils();
53+
54+
const deleteEnvironment = trpc.environment.delete.useMutation({
55+
onSuccess: () => {
56+
void utils.environment.list.invalidate();
57+
},
58+
});
59+
60+
const handleDelete = (e: React.MouseEvent) => {
61+
e.preventDefault();
62+
e.stopPropagation();
63+
void deleteEnvironment.mutate({
64+
workspaceId: workspace.id,
65+
environmentId: environment.id,
66+
});
67+
setShowDeleteDialog(false);
68+
};
3269

3370
const formatDate = (date?: string) => {
3471
if (!date) return null;
@@ -40,38 +77,95 @@ export const EnvironmentCard: React.FC<EnvironmentCardProps> = ({
4077
};
4178

4279
return (
43-
<Link to={environmentUrl} className="block">
44-
<Card className="h-56 transition-all hover:border-primary/50 hover:shadow-md">
45-
<CardHeader>
46-
<CardTitle className="flex items-center justify-between">
47-
<span className="truncate">{environment.name}</span>
48-
</CardTitle>
80+
<>
81+
<Link to={environmentUrl} className="block">
82+
<Card className="h-56 transition-all hover:border-primary/50 hover:shadow-md">
83+
<CardHeader>
84+
<CardTitle className="flex items-center justify-between">
85+
<span className="truncate">{environment.name}</span>
86+
<DropdownMenu>
87+
<DropdownMenuTrigger
88+
asChild
89+
onClick={(e) => e.preventDefault()}
90+
>
91+
<Button
92+
variant="ghost"
93+
size="icon"
94+
className="h-8 w-8 text-muted-foreground hover:text-foreground"
95+
>
96+
<MoreVertical className="h-4 w-4" />
97+
</Button>
98+
</DropdownMenuTrigger>
99+
<DropdownMenuContent
100+
align="end"
101+
onClick={(e) => e.preventDefault()}
102+
>
103+
<DropdownMenuItem
104+
className="text-destructive focus:text-destructive"
105+
onClick={(e) => {
106+
e.preventDefault();
107+
e.stopPropagation();
108+
setShowDeleteDialog(true);
109+
}}
110+
>
111+
<Trash2 className="mr-2 h-4 w-4" />
112+
Delete
113+
</DropdownMenuItem>
114+
</DropdownMenuContent>
115+
</DropdownMenu>
116+
</CardTitle>
117+
118+
<CardDescription className="text-xs">{system.name}</CardDescription>
49119

50-
<CardDescription className="text-xs">{system.name}</CardDescription>
120+
{environment.description && (
121+
<p className="mt-2 text-xs text-muted-foreground">
122+
{environment.description}
123+
</p>
124+
)}
125+
</CardHeader>
126+
<CardContent className="space-y-3">
127+
{environment.resourceSelector?.cel && (
128+
<div className="flex items-start gap-2">
129+
<Layers className="mt-0.5 h-4 w-4 flex-shrink-0 text-muted-foreground" />
130+
<code className="block truncate rounded bg-muted px-2 py-1 text-xs">
131+
{environment.resourceSelector.cel}
132+
</code>
133+
</div>
134+
)}
135+
{environment.createdAt && (
136+
<div className="flex items-center gap-2 text-xs text-muted-foreground">
137+
<Calendar className="h-3 w-3" />
138+
<span>Created {formatDate(environment.createdAt)}</span>
139+
</div>
140+
)}
141+
</CardContent>
142+
</Card>
143+
</Link>
51144

52-
{environment.description && (
53-
<p className="mt-2 text-xs text-muted-foreground">
54-
{environment.description}
55-
</p>
56-
)}
57-
</CardHeader>
58-
<CardContent className="space-y-3">
59-
{environment.resourceSelector?.cel && (
60-
<div className="flex items-start gap-2">
61-
<Layers className="mt-0.5 h-4 w-4 flex-shrink-0 text-muted-foreground" />
62-
<code className="block truncate rounded bg-muted px-2 py-1 text-xs">
63-
{environment.resourceSelector.cel}
64-
</code>
65-
</div>
66-
)}
67-
{environment.createdAt && (
68-
<div className="flex items-center gap-2 text-xs text-muted-foreground">
69-
<Calendar className="h-3 w-3" />
70-
<span>Created {formatDate(environment.createdAt)}</span>
71-
</div>
72-
)}
73-
</CardContent>
74-
</Card>
75-
</Link>
145+
<AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
146+
<AlertDialogContent onClick={(e) => e.stopPropagation()}>
147+
<AlertDialogHeader>
148+
<AlertDialogTitle>Delete Environment</AlertDialogTitle>
149+
<AlertDialogDescription>
150+
Are you sure you want to delete{" "}
151+
<strong>{environment.name}</strong>? This action cannot be undone
152+
and will permanently remove the environment and all its associated
153+
data.
154+
</AlertDialogDescription>
155+
</AlertDialogHeader>
156+
<AlertDialogFooter>
157+
<AlertDialogCancel onClick={(e) => e.stopPropagation()}>
158+
Cancel
159+
</AlertDialogCancel>
160+
<AlertDialogAction
161+
onClick={handleDelete}
162+
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
163+
>
164+
Delete
165+
</AlertDialogAction>
166+
</AlertDialogFooter>
167+
</AlertDialogContent>
168+
</AlertDialog>
169+
</>
76170
);
77171
};

packages/trpc/src/routes/environments.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,4 +215,36 @@ export const environmentRouter = router({
215215

216216
return environment;
217217
}),
218+
219+
delete: protectedProcedure
220+
.input(
221+
z.object({
222+
workspaceId: z.uuid(),
223+
environmentId: z.string(),
224+
}),
225+
)
226+
.mutation(async ({ input }) => {
227+
const { workspaceId, environmentId } = input;
228+
229+
const env = await getClientFor(workspaceId).GET(
230+
"/v1/workspaces/{workspaceId}/environments/{environmentId}",
231+
{ params: { path: { workspaceId, environmentId } } },
232+
);
233+
234+
if (!env.data) {
235+
throw new TRPCError({
236+
code: "NOT_FOUND",
237+
message: "Environment not found",
238+
});
239+
}
240+
241+
await sendGoEvent({
242+
workspaceId,
243+
eventType: Event.EnvironmentDeleted,
244+
timestamp: Date.now(),
245+
data: env.data,
246+
});
247+
248+
return { success: true };
249+
}),
218250
});

0 commit comments

Comments
 (0)