Log machine context errors in portal

This commit is contained in:
Esdras Renan 2025-10-14 18:49:58 -03:00
parent 545d5bea4b
commit 0fb95147f4
3 changed files with 82 additions and 5 deletions

View file

@ -23,7 +23,7 @@ const navItems = [
export function PortalShell({ children }: PortalShellProps) { export function PortalShell({ children }: PortalShellProps) {
const pathname = usePathname() const pathname = usePathname()
const router = useRouter() const router = useRouter()
const { session, machineContext } = useAuth() const { session, machineContext, machineContextError, machineContextLoading } = useAuth()
const [isSigningOut, setIsSigningOut] = useState(false) const [isSigningOut, setIsSigningOut] = useState(false)
const isMachineSession = session?.user.role === "machine" const isMachineSession = session?.user.role === "machine"
@ -136,7 +136,25 @@ export function PortalShell({ children }: PortalShellProps) {
</div> </div>
</header> </header>
<main className="mx-auto flex w-full max-w-6xl flex-1 flex-col gap-6 px-6 py-8"> <main className="mx-auto flex w-full max-w-6xl flex-1 flex-col gap-6 px-6 py-8">
{null} {machineContextError ? (
<div className="rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 shadow-sm">
<p className="font-semibold text-red-800">Falha ao carregar os dados do colaborador vinculado.</p>
<p className="mt-1 text-xs text-red-700/80">
{machineContextError.message}
{machineContextError.status ? ` (status ${machineContextError.status})` : null}
</p>
{machineContextError.details && Object.keys(machineContextError.details).length > 0 ? (
<pre className="mt-2 overflow-x-auto rounded-lg bg-red-100 px-3 py-2 text-[11px] leading-tight text-red-800">
{JSON.stringify(machineContextError.details, null, 2)}
</pre>
) : null}
</div>
) : null}
{!machineContextError && machineContextLoading ? (
<div className="rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-neutral-600 shadow-sm">
Recuperando dados do colaborador vinculado...
</div>
) : null}
{children} {children}
</main> </main>
<footer className="border-t border-slate-200 bg-white/70"> <footer className="border-t border-slate-200 bg-white/70">

View file

@ -31,7 +31,7 @@ function toHtml(text: string) {
export function PortalTicketForm() { export function PortalTicketForm() {
const router = useRouter() const router = useRouter()
const { convexUserId, session, machineContext } = useAuth() const { convexUserId, session, machineContext, machineContextError, machineContextLoading } = useAuth()
const createTicket = useMutation(api.tickets.create) const createTicket = useMutation(api.tickets.create)
const addComment = useMutation(api.tickets.addComment) const addComment = useMutation(api.tickets.addComment)
@ -49,13 +49,19 @@ export function PortalTicketForm() {
return Boolean(subject.trim() && description.trim() && categoryId && subcategoryId) return Boolean(subject.trim() && description.trim() && categoryId && subcategoryId)
}, [subject, description, categoryId, subcategoryId]) }, [subject, description, categoryId, subcategoryId])
const isViewerReady = Boolean(viewerId) const isViewerReady = Boolean(viewerId)
const viewerErrorMessage = useMemo(() => {
if (!machineContextError) return null
const suffix = machineContextError.status ? ` (status ${machineContextError.status})` : ""
return `${machineContextError.message}${suffix}`
}, [machineContextError])
async function handleSubmit(event: React.FormEvent) { async function handleSubmit(event: React.FormEvent) {
event.preventDefault() event.preventDefault()
if (isSubmitting || !isFormValid) return if (isSubmitting || !isFormValid) return
if (!viewerId) { if (!viewerId) {
const detail = viewerErrorMessage ? ` Detalhes: ${viewerErrorMessage}` : ""
toast.error( toast.error(
"Não foi possível identificar o colaborador vinculado a esta máquina. Tente abrir novamente o portal ou contate o suporte.", `N<EFBFBD>o foi poss<73>vel identificar o colaborador vinculado a esta m<>quina. Tente abrir novamente o portal ou contate o suporte.${detail}`,
{ id: "portal-new-ticket" } { id: "portal-new-ticket" }
) )
return return
@ -124,6 +130,14 @@ export function PortalTicketForm() {
{!isViewerReady ? ( {!isViewerReady ? (
<div className="rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-700"> <div className="rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-700">
Vincule esta máquina a um colaborador na aplicação desktop para enviar chamados em nome dele. Vincule esta máquina a um colaborador na aplicação desktop para enviar chamados em nome dele.
{machineContextLoading ? (
<p className="mt-2 text-xs text-amber-600">Carregando informa<EFBFBD><EFBFBD>es da m<EFBFBD>quina...</p>
) : null}
{viewerErrorMessage ? (
<p className="mt-2 text-xs text-amber-600">
Detalhes do erro: {viewerErrorMessage}
</p>
) : null}
</div> </div>
) : null} ) : null}
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">

View file

@ -37,6 +37,12 @@ type MachineContext = {
companyId: string | null companyId: string | null
} }
type MachineContextError = {
status: number
message: string
details?: Record<string, unknown> | null
}
const authClient = createAuthClient({ const authClient = createAuthClient({
plugins: [customSessionClient<AppAuth>()], plugins: [customSessionClient<AppAuth>()],
fetchOptions: { fetchOptions: {
@ -53,6 +59,8 @@ type AuthContextValue = {
isStaff: boolean isStaff: boolean
isCustomer: boolean isCustomer: boolean
machineContext: MachineContext | null machineContext: MachineContext | null
machineContextLoading: boolean
machineContextError: MachineContextError | null
} }
const AuthContext = createContext<AuthContextValue>({ const AuthContext = createContext<AuthContextValue>({
@ -64,6 +72,8 @@ const AuthContext = createContext<AuthContextValue>({
isStaff: false, isStaff: false,
isCustomer: false, isCustomer: false,
machineContext: null, machineContext: null,
machineContextLoading: false,
machineContextError: null,
}) })
export function useAuth() { export function useAuth() {
@ -77,6 +87,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const ensureUser = useMutation(api.users.ensureUser) const ensureUser = useMutation(api.users.ensureUser)
const [convexUserId, setConvexUserId] = useState<string | null>(null) const [convexUserId, setConvexUserId] = useState<string | null>(null)
const [machineContext, setMachineContext] = useState<MachineContext | null>(null) const [machineContext, setMachineContext] = useState<MachineContext | null>(null)
const [machineContextLoading, setMachineContextLoading] = useState(false)
const [machineContextError, setMachineContextError] = useState<MachineContextError | null>(null)
useEffect(() => { useEffect(() => {
if (!session?.user || session.user.role === "machine") { if (!session?.user || session.user.role === "machine") {
@ -87,16 +99,37 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
useEffect(() => { useEffect(() => {
if (!session?.user || session.user.role !== "machine") { if (!session?.user || session.user.role !== "machine") {
setMachineContext(null) setMachineContext(null)
setMachineContextError(null)
setMachineContextLoading(false)
return return
} }
let cancelled = false let cancelled = false
setMachineContextLoading(true)
setMachineContextError(null)
;(async () => { ;(async () => {
try { try {
const response = await fetch("/api/machines/session", { credentials: "include" }) const response = await fetch("/api/machines/session", { credentials: "include" })
if (!response.ok) { if (!response.ok) {
let payload: Record<string, unknown> | null = null
try {
const parsed = await response.clone().json()
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
payload = parsed as Record<string, unknown>
}
} catch {
payload = null
}
const fallbackMessage = "Falha ao carregar o contexto da m<>quina."
const message =
(payload && typeof payload.error === "string" && payload.error.trim()) || fallbackMessage
if (!cancelled) { if (!cancelled) {
setMachineContext(null) setMachineContext(null)
setMachineContextError({
status: response.status,
message,
details: payload,
})
} }
return return
} }
@ -122,11 +155,21 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
assignedUserRole: machine.assignedUserRole ?? null, assignedUserRole: machine.assignedUserRole ?? null,
companyId: machine.companyId ?? null, companyId: machine.companyId ?? null,
}) })
setMachineContextError(null)
} }
} catch (error) { } catch (error) {
console.error("Failed to load machine context", error) console.error("Failed to load machine context", error)
if (!cancelled) { if (!cancelled) {
setMachineContext(null) setMachineContext(null)
setMachineContextError({
status: 0,
message: "Erro ao carregar o contexto da m<>quina.",
details: error instanceof Error ? { message: error.message } : null,
})
}
} finally {
if (!cancelled) {
setMachineContextLoading(false)
} }
} }
})() })()
@ -184,8 +227,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
isStaff: isStaff(normalizedRole), isStaff: isStaff(normalizedRole),
isCustomer: normalizedRole === "collaborator", isCustomer: normalizedRole === "collaborator",
machineContext, machineContext,
machineContextLoading,
machineContextError,
}), }),
[session, isPending, effectiveConvexUserId, normalizedRole, machineContext] [session, isPending, effectiveConvexUserId, normalizedRole, machineContext, machineContextLoading, machineContextError]
) )
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider> return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>