diff --git a/convex/tickets.ts b/convex/tickets.ts
index dfda7aa..2b30b81 100644
--- a/convex/tickets.ts
+++ b/convex/tickets.ts
@@ -723,6 +723,10 @@ export const updateComment = mutation({
throw new ConvexError("Ticket não encontrado")
}
const ticketDoc = ticket as Doc<"tickets">
+ const actor = (await ctx.db.get(actorId)) as Doc<"users"> | null
+ if (!actor || actor.tenantId !== ticketDoc.tenantId) {
+ throw new ConvexError("Autor do comentário inválido")
+ }
const comment = await ctx.db.get(commentId);
if (!comment || comment.ticketId !== ticketId) {
throw new ConvexError("Comentário não encontrado");
@@ -730,8 +734,15 @@ export const updateComment = mutation({
if (comment.authorId !== actorId) {
throw new ConvexError("Você não tem permissão para editar este comentário");
}
+ const normalizedRole = (actor.role ?? "AGENT").toUpperCase()
if (ticketDoc.requesterId === actorId) {
- await requireCustomer(ctx, actorId, ticketDoc.tenantId)
+ if (normalizedRole === "CUSTOMER") {
+ await requireCustomer(ctx, actorId, ticketDoc.tenantId)
+ } else if (STAFF_ROLES.has(normalizedRole)) {
+ await requireTicketStaff(ctx, actorId, ticketDoc)
+ } else {
+ throw new ConvexError("Autor não possui permissão para editar")
+ }
} else {
await requireTicketStaff(ctx, actorId, ticketDoc)
}
@@ -742,7 +753,6 @@ export const updateComment = mutation({
updatedAt: now,
});
- const actor = (await ctx.db.get(actorId)) as Doc<"users"> | null;
await ctx.db.insert("ticketEvents", {
ticketId,
type: "COMMENT_EDITED",
@@ -772,6 +782,10 @@ export const removeCommentAttachment = mutation({
throw new ConvexError("Ticket não encontrado")
}
const ticketDoc = ticket as Doc<"tickets">
+ const actor = (await ctx.db.get(actorId)) as Doc<"users"> | null
+ if (!actor || actor.tenantId !== ticketDoc.tenantId) {
+ throw new ConvexError("Autor do comentário inválido")
+ }
const comment = await ctx.db.get(commentId);
if (!comment || comment.ticketId !== ticketId) {
throw new ConvexError("Comentário não encontrado");
@@ -780,8 +794,15 @@ export const removeCommentAttachment = mutation({
throw new ConvexError("Você não pode alterar anexos de outro usuário")
}
+ const normalizedRole = (actor.role ?? "AGENT").toUpperCase()
if (ticketDoc.requesterId === actorId) {
- await requireCustomer(ctx, actorId, ticketDoc.tenantId)
+ if (normalizedRole === "CUSTOMER") {
+ await requireCustomer(ctx, actorId, ticketDoc.tenantId)
+ } else if (STAFF_ROLES.has(normalizedRole)) {
+ await requireTicketStaff(ctx, actorId, ticketDoc)
+ } else {
+ throw new ConvexError("Autor não possui permissão para alterar anexos")
+ }
} else {
await requireTicketStaff(ctx, actorId, ticketDoc)
}
@@ -800,7 +821,6 @@ export const removeCommentAttachment = mutation({
updatedAt: now,
});
- const actor = (await ctx.db.get(actorId)) as Doc<"users"> | null;
await ctx.db.insert("ticketEvents", {
ticketId,
type: "ATTACHMENT_REMOVED",
diff --git a/src/app/tickets/[id]/page.tsx b/src/app/tickets/[id]/page.tsx
index 40286c6..1cb2583 100644
--- a/src/app/tickets/[id]/page.tsx
+++ b/src/app/tickets/[id]/page.tsx
@@ -2,10 +2,10 @@ import { AppShell } from "@/components/app-shell"
import { SiteHeader } from "@/components/site-header"
import { TicketDetailView } from "@/components/tickets/ticket-detail-view"
import { TicketDetailStatic } from "@/components/tickets/ticket-detail-static"
-import { NewTicketDialog } from "@/components/tickets/new-ticket-dialog"
+import { NewTicketDialogDeferred } from "@/components/tickets/new-ticket-dialog.client"
import { getTicketById } from "@/lib/mocks/tickets"
import type { TicketWithDetails } from "@/lib/schemas/ticket"
-
+
type TicketDetailPageProps = {
params: Promise<{ id: string }>
}
@@ -22,7 +22,7 @@ export default async function TicketDetailPage({ params }: TicketDetailPageProps
title={`Ticket #${id}`}
lead={"Detalhes do ticket"}
secondaryAction={Compartilhar}
- primaryAction={}
+ primaryAction={}
/>
}
>
diff --git a/src/components/tickets/new-ticket-dialog.client.tsx b/src/components/tickets/new-ticket-dialog.client.tsx
new file mode 100644
index 0000000..df315bb
--- /dev/null
+++ b/src/components/tickets/new-ticket-dialog.client.tsx
@@ -0,0 +1,30 @@
+"use client"
+
+import { useEffect, useState } from "react"
+
+import { Button } from "@/components/ui/button"
+
+import { NewTicketDialog } from "./new-ticket-dialog"
+
+export function NewTicketDialogDeferred() {
+ const [mounted, setMounted] = useState(false)
+
+ useEffect(() => {
+ setMounted(true)
+ }, [])
+
+ if (!mounted) {
+ return (
+
+ )
+ }
+
+ return
+}
diff --git a/src/components/ui/dropzone.tsx b/src/components/ui/dropzone.tsx
index 635bd43..e2ec5f0 100644
--- a/src/components/ui/dropzone.tsx
+++ b/src/components/ui/dropzone.tsx
@@ -31,18 +31,19 @@ export function Dropzone({
const startUpload = useCallback(async (files: FileList | File[]) => {
const list = Array.from(files).slice(0, maxFiles);
- const url = await generateUrl({});
const uploaded: Uploaded[] = [];
for (const file of list) {
if (file.size > maxSize) continue;
+ const url = await generateUrl({});
const id = `${file.name}-${file.size}-${Date.now()}`;
const localPreview = file.type.startsWith("image/") ? URL.createObjectURL(file) : undefined;
setItems((prev) => [...prev, { id, name: file.name, progress: 0, status: "uploading" }]);
await new Promise((resolve) => {
- const form = new FormData();
- form.append("file", file);
const xhr = new XMLHttpRequest();
xhr.open("POST", url);
+ if (file.type) {
+ xhr.setRequestHeader("Content-Type", file.type);
+ }
xhr.upload.onprogress = (e) => {
if (!e.lengthComputable) return;
const progress = Math.round((e.loaded / e.total) * 100);
@@ -66,7 +67,7 @@ export function Dropzone({
setItems((prev) => prev.map((it) => (it.id === id ? { ...it, status: "error" } : it)));
resolve();
};
- xhr.send(form);
+ xhr.send(file);
});
}
if (uploaded.length) onUploaded?.(uploaded);