From 7beb0fc3673b7db48b5eed34a07deed9876e456e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 4 Mar 2025 19:42:54 +0000 Subject: [PATCH 1/3] Add policy comments functionality Co-Authored-By: Lewis Carhart --- .../actions/policy/create-policy-comment.ts | 38 ++++++ apps/app/src/actions/schema.ts | 14 +++ .../policies/all/[policyId]/page.tsx | 3 + .../policies/create-policy-comment-form.tsx | 108 ++++++++++++++++++ .../components/policies/policy-comments.tsx | 75 ++++++++++++ .../sheets/policy-comment-sheet.tsx | 74 ++++++++++++ .../migration.sql | 27 +++++ packages/db/prisma/schema.prisma | 19 +++ 8 files changed, 358 insertions(+) create mode 100644 apps/app/src/actions/policy/create-policy-comment.ts create mode 100644 apps/app/src/components/forms/policies/create-policy-comment-form.tsx create mode 100644 apps/app/src/components/policies/policy-comments.tsx create mode 100644 apps/app/src/components/sheets/policy-comment-sheet.tsx create mode 100644 packages/db/prisma/migrations/20250304193025_add_policy_comments/migration.sql diff --git a/apps/app/src/actions/policy/create-policy-comment.ts b/apps/app/src/actions/policy/create-policy-comment.ts new file mode 100644 index 0000000000..d896b50112 --- /dev/null +++ b/apps/app/src/actions/policy/create-policy-comment.ts @@ -0,0 +1,38 @@ +"use server"; + +import { db } from "@bubba/db"; +import { revalidatePath, revalidateTag } from "next/cache"; +import { authActionClient } from "../safe-action"; +import { createPolicyCommentSchema } from "../schema"; + +export const createPolicyCommentAction = authActionClient + .schema(createPolicyCommentSchema) + .metadata({ + name: "create-policy-comment", + track: { + event: "create-policy-comment", + channel: "server", + }, + }) + .action(async ({ parsedInput, ctx }) => { + const { policyId, content } = parsedInput; + const { user } = ctx; + + if (!user.id || !user.organizationId) { + throw new Error("Invalid user input"); + } + + await db.policyComments.create({ + data: { + organizationPolicyId: policyId, + content, + ownerId: user.id, + organizationId: user.organizationId, + }, + }); + + revalidatePath(`/policies/all/${policyId}`); + revalidateTag(`policy_${user.organizationId}`); + + return { success: true }; + }); diff --git a/apps/app/src/actions/schema.ts b/apps/app/src/actions/schema.ts index e12ed67f11..6afefa635b 100644 --- a/apps/app/src/actions/schema.ts +++ b/apps/app/src/actions/schema.ts @@ -309,3 +309,17 @@ export const apiKeySchema = z.object({ .max(64, { message: "Name must be less than 64 characters" }), expiresAt: z.enum(["30days", "90days", "1year", "never"]), }); + +export const createPolicyCommentSchema = z.object({ + policyId: z.string().min(1, { + message: "Policy ID is required", + }), + content: z + .string() + .min(1, { + message: "Comment content is required", + }) + .max(1000, { + message: "Comment content should be at most 1000 characters", + }), +}); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/page.tsx index 86d7e4c5be..dddace78d9 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/policies/all/[policyId]/page.tsx @@ -1,4 +1,5 @@ import { auth } from "@/auth"; +import { PolicyComment } from "@/components/policies/policy-comments"; import { PolicyOverview } from "@/components/policies/policy-overview"; import { getI18n } from "@/locales/server"; import { db } from "@bubba/db"; @@ -33,6 +34,7 @@ export default async function PolicyDetails({ return (
+
); } @@ -58,6 +60,7 @@ const getPolicy = unstable_cache( where: { id: policyId, organizationId }, include: { policy: true, + policyComments: true, }, }); return policy; diff --git a/apps/app/src/components/forms/policies/create-policy-comment-form.tsx b/apps/app/src/components/forms/policies/create-policy-comment-form.tsx new file mode 100644 index 0000000000..06bb2d9d74 --- /dev/null +++ b/apps/app/src/components/forms/policies/create-policy-comment-form.tsx @@ -0,0 +1,108 @@ +"use client"; + +import { createPolicyCommentAction } from "@/actions/policy/create-policy-comment"; +import { createPolicyCommentSchema } from "@/actions/schema"; +import { useI18n } from "@/locales/client"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@bubba/ui/accordion"; +import { Button } from "@bubba/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormMessage, +} from "@bubba/ui/form"; +import { Textarea } from "@bubba/ui/textarea"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { ArrowRightIcon } from "lucide-react"; +import { useAction } from "next-safe-action/hooks"; +import { useParams } from "next/navigation"; +import { useQueryState } from "nuqs"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import type { z } from "zod"; + +export function CreatePolicyCommentForm() { + const t = useI18n(); + const [_, setPolicyCommentSheet] = useQueryState("policy-comment-sheet"); + const params = useParams<{ policyId: string }>(); + + const createPolicyComment = useAction(createPolicyCommentAction, { + onSuccess: () => { + toast.success(t("common.comments.success")); + setPolicyCommentSheet(null); + }, + onError: () => { + toast.error(t("common.comments.error")); + }, + }); + + const form = useForm>({ + resolver: zodResolver(createPolicyCommentSchema), + defaultValues: { + content: "", + policyId: params.policyId, + }, + }); + + const onSubmit = (data: z.infer) => { + createPolicyComment.execute(data); + }; + + return ( +
+ +
+
+ + + {t("common.comments.new")} + +
+ ( + + +