Skip to content

Commit 7b1f97e

Browse files
authored
Merge pull request #717 from trycompai/main
[comp] Production Deploy
2 parents 4e0b239 + fb18981 commit 7b1f97e

File tree

7 files changed

+546
-39
lines changed

7 files changed

+546
-39
lines changed

apps/app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "0.1.0",
44
"private": true,
55
"scripts": {
6-
"dev": "bun run apply-migrations && bunx concurrently --kill-others --names \"next,trigger\" --prefix-colors \"yellow,blue\" \"next dev --turbo -p 3000\" \"bun run trigger:dev\"",
6+
"dev": "bun install && bun run apply-migrations && bunx concurrently --kill-others --names \"next,trigger\" --prefix-colors \"yellow,blue\" \"next dev --turbo -p 3000\" \"bun run trigger:dev\"",
77
"trigger:dev": "npx trigger.dev@latest dev",
88
"build": "next build --turbopack",
99
"start": "next start",
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"use server";
2+
3+
import { db } from "@comp/db";
4+
import { revalidatePath, revalidateTag } from "next/cache";
5+
import { z } from "zod";
6+
import { authActionClient } from "@/actions/safe-action";
7+
8+
const deleteTaskSchema = z.object({
9+
id: z.string(),
10+
entityId: z.string(),
11+
});
12+
13+
export const deleteTaskAction = authActionClient
14+
.schema(deleteTaskSchema)
15+
.metadata({
16+
name: "delete-task",
17+
track: {
18+
event: "delete-task",
19+
description: "Delete Task",
20+
channel: "server",
21+
},
22+
})
23+
.action(async ({ parsedInput, ctx }) => {
24+
const { id } = parsedInput;
25+
const { activeOrganizationId } = ctx.session;
26+
27+
if (!activeOrganizationId) {
28+
return {
29+
success: false,
30+
error: "Not authorized",
31+
};
32+
}
33+
34+
try {
35+
const task = await db.task.findUnique({
36+
where: {
37+
id,
38+
organizationId: activeOrganizationId,
39+
},
40+
});
41+
42+
if (!task) {
43+
return {
44+
success: false,
45+
error: "Task not found",
46+
};
47+
}
48+
49+
// Delete the task
50+
await db.task.delete({
51+
where: { id },
52+
});
53+
54+
// Revalidate paths to update UI
55+
revalidatePath(`/${activeOrganizationId}/tasks`);
56+
revalidatePath(`/${activeOrganizationId}/tasks/all`);
57+
revalidateTag("tasks");
58+
59+
return {
60+
success: true,
61+
};
62+
} catch (error) {
63+
console.error(error);
64+
return {
65+
success: false,
66+
error: "Failed to delete task",
67+
};
68+
}
69+
});

apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/[taskId]/components/SingleTask.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
"use client";
22

33
import type { Attachment, Member, Task, User } from "@comp/db/types";
4-
import { useMemo } from "react";
4+
import { useMemo, useState } from "react";
5+
import { CommentWithAuthor } from "../../../components/comments/Comments";
6+
import { updateTask } from "../../actions/updateTask";
7+
import { TaskDeleteDialog } from "./TaskDeleteDialog";
58
import { TaskMainContent } from "./TaskMainContent";
69
import { TaskPropertiesSidebar } from "./TaskPropertiesSidebar";
7-
import { updateTask } from "../../actions/updateTask";
8-
import { CommentWithAuthor } from "../../../components/comments/Comments";
910

1011
interface SingleTaskProps {
1112
task: Task & { fileUrls?: string[] };
@@ -20,6 +21,8 @@ export function SingleTask({
2021
comments,
2122
attachments,
2223
}: SingleTaskProps) {
24+
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
25+
2326
const assignedMember = useMemo(() => {
2427
if (!task.assigneeId || !members) return null;
2528
return members.find((m) => m.id === task.assigneeId);
@@ -64,6 +67,14 @@ export function SingleTask({
6467
members={members}
6568
assignedMember={assignedMember}
6669
handleUpdateTask={handleUpdateTask}
70+
onDeleteClick={() => setDeleteDialogOpen(true)}
71+
/>
72+
73+
{/* Delete Dialog */}
74+
<TaskDeleteDialog
75+
isOpen={deleteDialogOpen}
76+
onClose={() => setDeleteDialogOpen(false)}
77+
task={task}
6778
/>
6879
</div>
6980
);
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"use client";
2+
3+
import { deleteTaskAction } from "../actions/delete-task";
4+
import { Task } from "@comp/db/types";
5+
import { Button } from "@comp/ui/button";
6+
import {
7+
Dialog,
8+
DialogContent,
9+
DialogDescription,
10+
DialogFooter,
11+
DialogHeader,
12+
DialogTitle,
13+
} from "@comp/ui/dialog";
14+
import { Form } from "@comp/ui/form";
15+
import { zodResolver } from "@hookform/resolvers/zod";
16+
import { Trash2 } from "lucide-react";
17+
import { useAction } from "next-safe-action/hooks";
18+
import { useRouter } from "next/navigation";
19+
import { useState } from "react";
20+
import { useForm } from "react-hook-form";
21+
import { toast } from "sonner";
22+
import { z } from "zod";
23+
24+
const formSchema = z.object({
25+
comment: z.string().optional(),
26+
});
27+
28+
type FormValues = z.infer<typeof formSchema>;
29+
30+
interface TaskDeleteDialogProps {
31+
isOpen: boolean;
32+
onClose: () => void;
33+
task: Task;
34+
}
35+
36+
export function TaskDeleteDialog({
37+
isOpen,
38+
onClose,
39+
task,
40+
}: TaskDeleteDialogProps) {
41+
const router = useRouter();
42+
const [isSubmitting, setIsSubmitting] = useState(false);
43+
44+
const form = useForm<FormValues>({
45+
resolver: zodResolver(formSchema),
46+
defaultValues: {
47+
comment: "",
48+
},
49+
});
50+
51+
const deleteTask = useAction(deleteTaskAction, {
52+
onSuccess: () => {
53+
toast.info("Task deleted! Redirecting to tasks list...");
54+
onClose();
55+
router.push(`/${task.organizationId}/tasks`);
56+
},
57+
onError: () => {
58+
toast.error("Failed to delete task.");
59+
setIsSubmitting(false);
60+
},
61+
});
62+
63+
const handleSubmit = async (values: FormValues) => {
64+
setIsSubmitting(true);
65+
deleteTask.execute({
66+
id: task.id,
67+
entityId: task.id,
68+
});
69+
};
70+
71+
return (
72+
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
73+
<DialogContent className="sm:max-w-[425px]">
74+
<DialogHeader>
75+
<DialogTitle>Delete Task</DialogTitle>
76+
<DialogDescription>
77+
Are you sure you want to delete this task? This action
78+
cannot be undone.
79+
</DialogDescription>
80+
</DialogHeader>
81+
<Form {...form}>
82+
<form
83+
onSubmit={form.handleSubmit(handleSubmit)}
84+
className="space-y-4"
85+
>
86+
<DialogFooter className="gap-2">
87+
<Button
88+
type="button"
89+
variant="outline"
90+
onClick={onClose}
91+
disabled={isSubmitting}
92+
>
93+
Cancel
94+
</Button>
95+
<Button
96+
type="submit"
97+
variant="destructive"
98+
disabled={isSubmitting}
99+
className="gap-2"
100+
>
101+
{isSubmitting ? (
102+
<span className="flex items-center gap-2">
103+
<span className="h-3 w-3 animate-spin rounded-full border-2 border-current border-t-transparent" />
104+
Deleting...
105+
</span>
106+
) : (
107+
<span className="flex items-center gap-2">
108+
<Trash2 className="h-3 w-3" />
109+
Delete
110+
</span>
111+
)}
112+
</Button>
113+
</DialogFooter>
114+
</form>
115+
</Form>
116+
</DialogContent>
117+
</Dialog>
118+
);
119+
}

apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tasks/[taskId]/components/TaskPropertiesSidebar.tsx

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ import { TaskStatusIndicator } from "../../components/TaskStatusIndicator";
1717
import { Button } from "@comp/ui/button";
1818
import { Badge } from "@comp/ui/badge";
1919
import { Avatar, AvatarFallback, AvatarImage } from "@comp/ui/avatar";
20+
import {
21+
DropdownMenu,
22+
DropdownMenuContent,
23+
DropdownMenuItem,
24+
DropdownMenuTrigger,
25+
} from "@comp/ui/dropdown-menu";
26+
import { MoreVertical, Trash2 } from "lucide-react";
27+
import { useState } from "react";
2028

2129
interface TaskPropertiesSidebarProps {
2230
task: Task;
@@ -27,19 +35,47 @@ interface TaskPropertiesSidebarProps {
2735
Pick<Task, "status" | "assigneeId" | "frequency" | "department">
2836
>,
2937
) => void;
38+
onDeleteClick?: () => void;
3039
}
3140

3241
export function TaskPropertiesSidebar({
3342
task,
3443
members,
3544
assignedMember,
3645
handleUpdateTask,
46+
onDeleteClick,
3747
}: TaskPropertiesSidebarProps) {
48+
const [dropdownOpen, setDropdownOpen] = useState(false);
3849
return (
3950
<aside className="w-full md:w-64 lg:w-72 flex-shrink-0 md:border-l md:pt-8 md:pl-8 hidden lg:flex flex-col">
40-
<h2 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-4 flex-shrink-0">
41-
Properties
42-
</h2>
51+
<div className="flex justify-between items-center mb-4">
52+
<h2 className="text-xs font-semibold text-muted-foreground uppercase tracking-wider flex-shrink-0">
53+
Properties
54+
</h2>
55+
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
56+
<DropdownMenuTrigger asChild>
57+
<Button
58+
size="icon"
59+
variant="ghost"
60+
className="p-2 m-0 size-auto"
61+
>
62+
<MoreVertical className="h-4 w-4" />
63+
</Button>
64+
</DropdownMenuTrigger>
65+
<DropdownMenuContent align="end">
66+
<DropdownMenuItem
67+
onClick={() => {
68+
setDropdownOpen(false);
69+
if (onDeleteClick) onDeleteClick();
70+
}}
71+
className="text-destructive focus:text-destructive"
72+
>
73+
<Trash2 className="h-4 w-4 mr-2" />
74+
Delete
75+
</DropdownMenuItem>
76+
</DropdownMenuContent>
77+
</DropdownMenu>
78+
</div>
4379
<div className="space-y-4 overflow-y-auto">
4480
{/* Status Selector */}
4581
<div className="flex justify-between items-center text-sm group">

0 commit comments

Comments
 (0)