Implement company provisioning codes and session tweaks
This commit is contained in:
parent
0fb9bf59b2
commit
2cba553efa
28 changed files with 1407 additions and 534 deletions
|
|
@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
|||
import { formatDistanceToNow } from "date-fns"
|
||||
import { ptBR } from "date-fns/locale"
|
||||
import { IconLock, IconMessage, IconFileText } from "@tabler/icons-react"
|
||||
import { FileIcon, Image as ImageIcon, PencilLine, Trash2, X } from "lucide-react"
|
||||
import { Download, FileIcon, Image as ImageIcon, PencilLine, Trash2, X } from "lucide-react"
|
||||
import { useAction, useMutation, useQuery } from "convex/react"
|
||||
import { api } from "@/convex/_generated/api"
|
||||
import { useAuth } from "@/lib/auth-client"
|
||||
|
|
@ -43,7 +43,7 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
|
|||
const [attachmentsToSend, setAttachmentsToSend] = useState<Array<{ storageId: string; name: string; size?: number; type?: string; previewUrl?: string }>>([])
|
||||
const [preview, setPreview] = useState<string | null>(null)
|
||||
const [pending, setPending] = useState<Pick<TicketWithDetails["comments"][number], "id" | "author" | "visibility" | "body" | "attachments" | "createdAt" | "updatedAt">[]>([])
|
||||
const [visibility, setVisibility] = useState<"PUBLIC" | "INTERNAL">("PUBLIC")
|
||||
const [visibility, setVisibility] = useState<"PUBLIC" | "INTERNAL">("INTERNAL")
|
||||
const [attachmentToRemove, setAttachmentToRemove] = useState<{ commentId: string; attachmentId: string; name: string } | null>(null)
|
||||
const [removingAttachment, setRemovingAttachment] = useState(false)
|
||||
const [editingComment, setEditingComment] = useState<{ id: string; value: string } | null>(null)
|
||||
|
|
@ -228,16 +228,17 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
|
|||
const canEdit = Boolean(convexUserId && String(comment.author.id) === convexUserId && !isPending)
|
||||
const hasBody = bodyPlain.length > 0 || isEditing
|
||||
const isInternal = comment.visibility === "INTERNAL" && canSeeInternalComments
|
||||
const containerClass = isInternal
|
||||
const isPublic = comment.visibility === "PUBLIC"
|
||||
const containerClass = isPublic
|
||||
? "group/comment flex gap-3 rounded-2xl border border-amber-200/80 bg-amber-50/80 px-3 py-3 shadow-[0_0_0_1px_rgba(217,119,6,0.15)]"
|
||||
: "group/comment flex gap-3"
|
||||
const bodyClass = isInternal
|
||||
: "group/comment flex gap-3 rounded-2xl border border-slate-200 bg-white px-3 py-3"
|
||||
const bodyClass = isPublic
|
||||
? "relative break-words rounded-xl border border-amber-200/80 bg-white px-3 py-2 text-sm leading-relaxed text-neutral-700 shadow-[0_0_0_1px_rgba(217,119,6,0.08)]"
|
||||
: "relative break-words rounded-xl border border-slate-200 bg-white px-3 py-2 text-sm leading-relaxed text-neutral-700"
|
||||
const bodyEditButtonClass = isInternal
|
||||
const bodyEditButtonClass = isPublic
|
||||
? "absolute right-2 top-2 inline-flex size-7 items-center justify-center rounded-full border border-amber-200 bg-white text-amber-700 opacity-0 transition hover:border-amber-500 hover:text-amber-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500/30 group-hover/comment:opacity-100"
|
||||
: "absolute right-2 top-2 inline-flex size-7 items-center justify-center rounded-full border border-slate-300 bg-white text-neutral-700 opacity-0 transition hover:border-black hover:text-black focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black/20 group-hover/comment:opacity-100"
|
||||
const addContentButtonClass = isInternal
|
||||
const addContentButtonClass = isPublic
|
||||
? "inline-flex items-center gap-2 text-sm font-medium text-amber-700 transition hover:text-amber-900"
|
||||
: "inline-flex items-center gap-2 text-sm font-medium text-neutral-600 transition hover:text-neutral-900"
|
||||
|
||||
|
|
@ -263,6 +264,10 @@ export function TicketComments({ ticket }: TicketCommentsProps) {
|
|||
<span className="text-xs font-semibold tracking-wide text-amber-700/80">
|
||||
Comentário interno — visível apenas para administradores e agentes
|
||||
</span>
|
||||
) : comment.visibility === "PUBLIC" ? (
|
||||
<span className="text-xs font-semibold tracking-wide text-amber-700/80">
|
||||
Comentário visível para o cliente
|
||||
</span>
|
||||
) : null}
|
||||
{isEditing ? (
|
||||
<div
|
||||
|
|
@ -596,10 +601,20 @@ function CommentAttachmentCard({
|
|||
|
||||
const handleDownload = useCallback(async () => {
|
||||
const target = url ?? (await ensureUrl())
|
||||
if (target) {
|
||||
if (!target) return
|
||||
try {
|
||||
const link = document.createElement("a")
|
||||
link.href = target
|
||||
link.download = attachment.name ?? "anexo"
|
||||
link.rel = "noopener noreferrer"
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
} catch (error) {
|
||||
console.error("Failed to download attachment", error)
|
||||
window.open(target, "_blank", "noopener,noreferrer")
|
||||
}
|
||||
}, [ensureUrl, url])
|
||||
}, [attachment.name, ensureUrl, url])
|
||||
|
||||
const name = attachment.name ?? ""
|
||||
const urlLooksImage = url ? /\.(png|jpe?g|gif|webp|svg)$/i.test(url) : false
|
||||
|
|
@ -642,6 +657,14 @@ function CommentAttachmentCard({
|
|||
</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleDownload}
|
||||
aria-label={`Baixar ${name}`}
|
||||
className="absolute left-1.5 top-1.5 inline-flex size-7 items-center justify-center rounded-full border border-slate-300 bg-white text-neutral-700 opacity-0 transition hover:border-black hover:text-black focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-black/20 focus-visible:opacity-100 group-hover:opacity-100"
|
||||
>
|
||||
<Download className="size-3.5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onRequestRemoval}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue