Fix auth trustedOrigins and improve emprestimos page layout
- Add production domain to trustedOrigins explicitly - Add health API endpoint for diagnostics - Improve emprestimos page layout to match tickets design - Use correct primary color for buttons - Fix segmented control styling with rounded borders - Use Empty component for empty state 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
326da8dae6
commit
e9a658341f
3 changed files with 110 additions and 42 deletions
69
src/app/api/health/route.ts
Normal file
69
src/app/api/health/route.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { NextResponse } from "next/server"
|
||||||
|
|
||||||
|
export const runtime = "nodejs"
|
||||||
|
export const dynamic = "force-dynamic"
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
const diagnostics: Record<string, unknown> = {
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
nodeVersion: process.version,
|
||||||
|
platform: process.platform,
|
||||||
|
arch: process.arch,
|
||||||
|
env: {
|
||||||
|
NODE_ENV: process.env.NODE_ENV,
|
||||||
|
BETTER_AUTH_URL: process.env.BETTER_AUTH_URL ? "set" : "not set",
|
||||||
|
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL ? "set" : "not set",
|
||||||
|
DATABASE_URL: process.env.DATABASE_URL ? "set" : "not set",
|
||||||
|
BETTER_AUTH_SECRET: process.env.BETTER_AUTH_SECRET ? "set" : "not set",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test SQLite binding
|
||||||
|
try {
|
||||||
|
const Database = (await import("better-sqlite3")).default
|
||||||
|
const testDb = new Database(":memory:")
|
||||||
|
testDb.exec("SELECT 1")
|
||||||
|
testDb.close()
|
||||||
|
diagnostics.sqlite = { status: "ok", message: "better-sqlite3 binding works" }
|
||||||
|
} catch (error) {
|
||||||
|
diagnostics.sqlite = {
|
||||||
|
status: "error",
|
||||||
|
message: error instanceof Error ? error.message : String(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Prisma connection
|
||||||
|
try {
|
||||||
|
const { prisma } = await import("@/lib/prisma")
|
||||||
|
await prisma.$queryRaw`SELECT 1`
|
||||||
|
diagnostics.prisma = { status: "ok", message: "Prisma connection works" }
|
||||||
|
} catch (error) {
|
||||||
|
diagnostics.prisma = {
|
||||||
|
status: "error",
|
||||||
|
message: error instanceof Error ? error.message : String(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test auth config
|
||||||
|
try {
|
||||||
|
const { auth } = await import("@/lib/auth")
|
||||||
|
diagnostics.auth = {
|
||||||
|
status: "ok",
|
||||||
|
baseURL: (auth as { options?: { baseURL?: string } }).options?.baseURL ?? "unknown",
|
||||||
|
trustedOrigins: "configured",
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
diagnostics.auth = {
|
||||||
|
status: "error",
|
||||||
|
message: error instanceof Error ? error.message : String(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasErrors = Object.values(diagnostics).some(
|
||||||
|
(v) => typeof v === "object" && v !== null && "status" in v && v.status === "error"
|
||||||
|
)
|
||||||
|
|
||||||
|
return NextResponse.json(diagnostics, {
|
||||||
|
status: hasErrors ? 500 : 200,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -53,6 +53,8 @@ import { DateRangeButton, type DateRangeValue } from "@/components/date-range-bu
|
||||||
import { DatePicker } from "@/components/ui/date-picker"
|
import { DatePicker } from "@/components/ui/date-picker"
|
||||||
import { Textarea } from "@/components/ui/textarea"
|
import { Textarea } from "@/components/ui/textarea"
|
||||||
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
|
||||||
|
import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription } from "@/components/ui/empty"
|
||||||
|
import { Plus } from "lucide-react"
|
||||||
|
|
||||||
const EQUIPAMENTO_TIPOS = [
|
const EQUIPAMENTO_TIPOS = [
|
||||||
"NOTEBOOK",
|
"NOTEBOOK",
|
||||||
|
|
@ -401,12 +403,12 @@ export function EmprestimosPageClient() {
|
||||||
const fieldTrigger =
|
const fieldTrigger =
|
||||||
"h-10 w-full rounded-2xl border border-slate-300 bg-slate-50/80 px-3 text-left text-sm font-semibold text-neutral-700 focus:ring-neutral-300 flex items-center gap-2"
|
"h-10 w-full rounded-2xl border border-slate-300 bg-slate-50/80 px-3 text-left text-sm font-semibold text-neutral-700 focus:ring-neutral-300 flex items-center gap-2"
|
||||||
const segmentedRoot =
|
const segmentedRoot =
|
||||||
"flex h-10 min-w-[200px] items-stretch rounded-full border border-slate-200 bg-slate-50/70 p-1 gap-1"
|
"flex h-10 min-w-[220px] flex-1 items-stretch rounded-full border border-slate-200 bg-slate-50/70 p-1 gap-1"
|
||||||
const segmentedItem =
|
const segmentedItem =
|
||||||
"inline-flex h-full flex-1 items-center justify-center rounded-full px-4 text-sm font-semibold text-neutral-600 transition-colors hover:bg-slate-100 data-[state=on]:bg-cyan-600 data-[state=on]:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-300"
|
"inline-flex h-full flex-1 items-center justify-center rounded-full first:rounded-full last:rounded-full px-4 text-sm font-semibold text-neutral-600 transition-colors hover:bg-slate-100 data-[state=on]:bg-neutral-900 data-[state=on]:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-300"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 px-4 lg:px-6">
|
<div className="mx-auto max-w-6xl space-y-6 px-4 lg:px-6">
|
||||||
{/* Stats Cards */}
|
{/* Stats Cards */}
|
||||||
<div className="grid gap-4 md:grid-cols-4">
|
<div className="grid gap-4 md:grid-cols-4">
|
||||||
<div className="rounded-2xl border border-slate-200 bg-white/90 p-4 shadow-sm transition-colors hover:border-cyan-200/60 hover:bg-cyan-50/30">
|
<div className="rounded-2xl border border-slate-200 bg-white/90 p-4 shadow-sm transition-colors hover:border-cyan-200/60 hover:bg-cyan-50/30">
|
||||||
|
|
@ -429,24 +431,17 @@ export function EmprestimosPageClient() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filters Section */}
|
{/* Action Button */}
|
||||||
<section className="rounded-3xl border border-slate-200 bg-white/90 p-4 shadow-sm">
|
<div className="flex justify-end">
|
||||||
<div className="flex flex-col gap-4">
|
<Button size="sm" onClick={() => setIsCreateDialogOpen(true)}>
|
||||||
{/* Header with title and action */}
|
<Plus className="size-4" />
|
||||||
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
|
|
||||||
<div>
|
|
||||||
<h2 className="text-lg font-semibold text-neutral-900">Empréstimos de Equipamentos</h2>
|
|
||||||
<p className="text-sm text-neutral-500">Gerencie o empréstimo de equipamentos para clientes.</p>
|
|
||||||
</div>
|
|
||||||
<Button
|
|
||||||
onClick={() => setIsCreateDialogOpen(true)}
|
|
||||||
className="h-10 gap-2 rounded-full bg-cyan-600 px-5 font-semibold text-white shadow-sm transition-colors hover:bg-cyan-700"
|
|
||||||
>
|
|
||||||
<IconPlus className="size-4" />
|
|
||||||
Novo empréstimo
|
Novo empréstimo
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Filters Section */}
|
||||||
|
<section className="rounded-3xl border border-slate-200 bg-white/90 p-4 shadow-sm">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
{/* Search and Date Range */}
|
{/* Search and Date Range */}
|
||||||
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:gap-4">
|
<div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:gap-4">
|
||||||
<div className="flex flex-1 flex-col gap-2">
|
<div className="flex flex-1 flex-col gap-2">
|
||||||
|
|
@ -517,14 +512,22 @@ export function EmprestimosPageClient() {
|
||||||
{/* Table Section */}
|
{/* Table Section */}
|
||||||
<section className="rounded-3xl border border-slate-200 bg-white/90 shadow-sm overflow-hidden">
|
<section className="rounded-3xl border border-slate-200 bg-white/90 shadow-sm overflow-hidden">
|
||||||
{!emprestimos ? (
|
{!emprestimos ? (
|
||||||
<div className="flex items-center justify-center py-16">
|
<div className="flex items-center justify-center gap-2 py-16">
|
||||||
<Spinner className="size-8" />
|
<Spinner className="size-5 text-neutral-500" />
|
||||||
|
<span className="text-sm text-neutral-600">Carregando empréstimos...</span>
|
||||||
</div>
|
</div>
|
||||||
) : filteredEmprestimos.length === 0 ? (
|
) : filteredEmprestimos.length === 0 ? (
|
||||||
<div className="flex flex-col items-center justify-center py-16 text-center">
|
<Empty className="m-4 border-dashed">
|
||||||
<IconPackage className="mb-4 size-12 text-neutral-300" />
|
<EmptyHeader>
|
||||||
<p className="text-neutral-500">Nenhum empréstimo encontrado.</p>
|
<EmptyMedia variant="icon" className="bg-slate-100 text-slate-600">
|
||||||
</div>
|
<IconPackage className="size-5" />
|
||||||
|
</EmptyMedia>
|
||||||
|
<EmptyTitle>Nenhum empréstimo encontrado</EmptyTitle>
|
||||||
|
<EmptyDescription>
|
||||||
|
Ajuste os filtros ou crie um novo empréstimo para começar.
|
||||||
|
</EmptyDescription>
|
||||||
|
</EmptyHeader>
|
||||||
|
</Empty>
|
||||||
) : (
|
) : (
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<Table>
|
<Table>
|
||||||
|
|
@ -571,14 +574,14 @@ export function EmprestimosPageClient() {
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell className="text-right">
|
||||||
{emp.status === "ATIVO" && (
|
{emp.status === "ATIVO" && (
|
||||||
<Button
|
<button
|
||||||
size="sm"
|
type="button"
|
||||||
className="h-8 gap-1.5 rounded-full bg-emerald-600 px-3 text-xs font-semibold text-white shadow-sm transition-colors hover:bg-emerald-700"
|
|
||||||
onClick={() => openDevolverDialog(emp.id)}
|
onClick={() => openDevolverDialog(emp.id)}
|
||||||
|
className="inline-flex h-9 items-center gap-2 rounded-full border border-emerald-200 bg-emerald-50 px-3 text-sm font-semibold text-emerald-700 transition hover:border-emerald-300 hover:bg-emerald-100"
|
||||||
>
|
>
|
||||||
<IconCircleCheck className="size-3.5" />
|
<IconCircleCheck className="size-4" />
|
||||||
Devolver
|
Devolver
|
||||||
</Button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
|
@ -767,14 +770,10 @@ export function EmprestimosPageClient() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)} className="rounded-full">
|
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)}>
|
||||||
Cancelar
|
Cancelar
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button onClick={handleCreate} disabled={isSubmitting}>
|
||||||
onClick={handleCreate}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
className="gap-2 rounded-full bg-cyan-600 font-semibold text-white hover:bg-cyan-700"
|
|
||||||
>
|
|
||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
<>
|
<>
|
||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
|
|
@ -811,14 +810,10 @@ export function EmprestimosPageClient() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<Button variant="outline" onClick={() => setIsDevolverDialogOpen(false)} className="rounded-full">
|
<Button variant="outline" onClick={() => setIsDevolverDialogOpen(false)}>
|
||||||
Cancelar
|
Cancelar
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button onClick={handleDevolver} disabled={isSubmitting}>
|
||||||
onClick={handleDevolver}
|
|
||||||
disabled={isSubmitting}
|
|
||||||
className="gap-2 rounded-full bg-emerald-600 font-semibold text-white hover:bg-emerald-700"
|
|
||||||
>
|
|
||||||
{isSubmitting ? (
|
{isSubmitting ? (
|
||||||
<>
|
<>
|
||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,16 @@ import { prisma } from "./prisma"
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
secret: env.BETTER_AUTH_SECRET,
|
secret: env.BETTER_AUTH_SECRET,
|
||||||
baseURL: env.BETTER_AUTH_URL,
|
baseURL: env.BETTER_AUTH_URL,
|
||||||
// Permite login tanto no domínio de produção quanto no localhost em DEV
|
// Permite login no dominio de producao e localhost em DEV
|
||||||
trustedOrigins: Array.from(
|
trustedOrigins: Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
[
|
[
|
||||||
|
// Dominio de producao (sempre incluido)
|
||||||
|
"https://tickets.esdrasrenan.com.br",
|
||||||
|
// URLs das variaveis de ambiente
|
||||||
env.BETTER_AUTH_URL,
|
env.BETTER_AUTH_URL,
|
||||||
env.NEXT_PUBLIC_APP_URL,
|
env.NEXT_PUBLIC_APP_URL,
|
||||||
|
// Localhost apenas em desenvolvimento
|
||||||
process.env.NODE_ENV !== "production" ? "http://localhost:3000" : undefined,
|
process.env.NODE_ENV !== "production" ? "http://localhost:3000" : undefined,
|
||||||
process.env.NODE_ENV !== "production" ? "http://127.0.0.1:3000" : undefined,
|
process.env.NODE_ENV !== "production" ? "http://127.0.0.1:3000" : undefined,
|
||||||
process.env.NODE_ENV !== "production" ? "http://localhost:3001" : undefined,
|
process.env.NODE_ENV !== "production" ? "http://localhost:3001" : undefined,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue