feat: improve company forms, phone input, and auth redirects
This commit is contained in:
parent
6962d5e5b5
commit
604216ddec
3 changed files with 57 additions and 17 deletions
|
|
@ -15,7 +15,7 @@ export async function middleware(request: NextRequest) {
|
||||||
return new NextResponse("Invalid Host header", { status: 403 })
|
return new NextResponse("Invalid Host header", { status: 403 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const { pathname, search } = request.nextUrl
|
const { pathname, searchParams, search } = request.nextUrl
|
||||||
|
|
||||||
if (pathname.startsWith("/api")) {
|
if (pathname.startsWith("/api")) {
|
||||||
return NextResponse.next()
|
return NextResponse.next()
|
||||||
|
|
@ -49,6 +49,18 @@ export async function middleware(request: NextRequest) {
|
||||||
? ((session.user as unknown as { machinePersona?: string }).machinePersona ?? "").toLowerCase()
|
? ((session.user as unknown as { machinePersona?: string }).machinePersona ?? "").toLowerCase()
|
||||||
: null
|
: null
|
||||||
|
|
||||||
|
if (pathname === "/login") {
|
||||||
|
const callback = searchParams.get("callbackUrl") ?? undefined
|
||||||
|
const defaultDestination =
|
||||||
|
role === "machine"
|
||||||
|
? machinePersona === "manager"
|
||||||
|
? "/dashboard"
|
||||||
|
: "/portal/tickets"
|
||||||
|
: APP_HOME
|
||||||
|
const target = callback && !callback.startsWith("/login") ? callback : defaultDestination
|
||||||
|
return NextResponse.redirect(new URL(target, request.url))
|
||||||
|
}
|
||||||
|
|
||||||
// Ajusta destinos conforme persona da máquina para evitar loops login<->dashboard
|
// Ajusta destinos conforme persona da máquina para evitar loops login<->dashboard
|
||||||
if (role === "machine") {
|
if (role === "machine") {
|
||||||
// Evita enviar colaborador ao dashboard; redireciona para o Portal
|
// Evita enviar colaborador ao dashboard; redireciona para o Portal
|
||||||
|
|
@ -56,11 +68,6 @@ export async function middleware(request: NextRequest) {
|
||||||
return NextResponse.redirect(new URL("/portal/tickets", request.url))
|
return NextResponse.redirect(new URL("/portal/tickets", request.url))
|
||||||
}
|
}
|
||||||
// Evita mostrar login quando já há sessão de máquina
|
// Evita mostrar login quando já há sessão de máquina
|
||||||
if (pathname === "/login") {
|
|
||||||
const target = machinePersona === "manager" ? "/dashboard" : "/portal/tickets"
|
|
||||||
const url = new URL(target, request.url)
|
|
||||||
return NextResponse.redirect(url)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAdmin = role === "admin"
|
const isAdmin = role === "admin"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState, useTransition } from "react"
|
import { useCallback, useEffect, useMemo, useRef, useState, useTransition, useId } from "react"
|
||||||
import { formatDistanceToNow } from "date-fns"
|
import { formatDistanceToNow } from "date-fns"
|
||||||
import { ptBR } from "date-fns/locale"
|
import { ptBR } from "date-fns/locale"
|
||||||
import { useQuery } from "convex/react"
|
import { useQuery } from "convex/react"
|
||||||
|
|
@ -86,6 +86,15 @@ export function AdminCompaniesManager({ initialCompanies }: { initialCompanies:
|
||||||
const [searchTerm, setSearchTerm] = useState("")
|
const [searchTerm, setSearchTerm] = useState("")
|
||||||
const isMobile = useIsMobile()
|
const isMobile = useIsMobile()
|
||||||
|
|
||||||
|
const nameId = useId()
|
||||||
|
const slugId = useId()
|
||||||
|
const descriptionId = useId()
|
||||||
|
const cnpjId = useId()
|
||||||
|
const domainId = useId()
|
||||||
|
const phoneId = useId()
|
||||||
|
const addressId = useId()
|
||||||
|
const hoursId = useId()
|
||||||
|
|
||||||
const machinesQuery = useQuery(api.machines.listByTenant, { includeMetadata: false }) as MachineSummary[] | undefined
|
const machinesQuery = useQuery(api.machines.listByTenant, { includeMetadata: false }) as MachineSummary[] | undefined
|
||||||
const machinesByCompanyId = useMemo(() => {
|
const machinesByCompanyId = useMemo(() => {
|
||||||
const map = new Map<string, MachineSummary[]>()
|
const map = new Map<string, MachineSummary[]>()
|
||||||
|
|
@ -304,60 +313,80 @@ export function AdminCompaniesManager({ initialCompanies }: { initialCompanies:
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<form onSubmit={handleSubmit} className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
<form onSubmit={handleSubmit} className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>Nome</Label>
|
<Label htmlFor={nameId}>Nome</Label>
|
||||||
<Input
|
<Input
|
||||||
|
id={nameId}
|
||||||
|
name="companyName"
|
||||||
value={form.name ?? ""}
|
value={form.name ?? ""}
|
||||||
onChange={(e) => setForm((p) => ({ ...p, name: e.target.value }))}
|
onChange={(e) => setForm((p) => ({ ...p, name: e.target.value }))}
|
||||||
placeholder="Nome da empresa ou apelido interno"
|
placeholder="Nome da empresa ou apelido interno"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>Slug</Label>
|
<Label htmlFor={slugId}>Slug</Label>
|
||||||
<Input
|
<Input
|
||||||
|
id={slugId}
|
||||||
|
name="companySlug"
|
||||||
value={form.slug ?? ""}
|
value={form.slug ?? ""}
|
||||||
onChange={(e) => setForm((p) => ({ ...p, slug: e.target.value }))}
|
onChange={(e) => setForm((p) => ({ ...p, slug: e.target.value }))}
|
||||||
placeholder="empresa-exemplo"
|
placeholder="empresa-exemplo"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2 md:col-span-2">
|
<div className="grid gap-2 md:col-span-2">
|
||||||
<Label>Descrição</Label>
|
<Label htmlFor={descriptionId}>Descrição</Label>
|
||||||
<Input value={form.description ?? ""} onChange={(e) => setForm((p) => ({ ...p, description: e.target.value }))} placeholder="Resumo, segmento ou observações internas" />
|
<Input
|
||||||
|
id={descriptionId}
|
||||||
|
name="companyDescription"
|
||||||
|
value={form.description ?? ""}
|
||||||
|
onChange={(e) => setForm((p) => ({ ...p, description: e.target.value }))}
|
||||||
|
placeholder="Resumo, segmento ou observações internas"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>CNPJ</Label>
|
<Label htmlFor={cnpjId}>CNPJ</Label>
|
||||||
<Input
|
<Input
|
||||||
|
id={cnpjId}
|
||||||
|
name="companyCnpj"
|
||||||
value={form.cnpj ?? ""}
|
value={form.cnpj ?? ""}
|
||||||
onChange={(e) => setForm((p) => ({ ...p, cnpj: e.target.value }))}
|
onChange={(e) => setForm((p) => ({ ...p, cnpj: e.target.value }))}
|
||||||
placeholder="00.000.000/0000-00"
|
placeholder="00.000.000/0000-00"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>Domínio</Label>
|
<Label htmlFor={domainId}>Domínio</Label>
|
||||||
<Input
|
<Input
|
||||||
|
id={domainId}
|
||||||
|
name="companyDomain"
|
||||||
value={form.domain ?? ""}
|
value={form.domain ?? ""}
|
||||||
onChange={(e) => setForm((p) => ({ ...p, domain: e.target.value }))}
|
onChange={(e) => setForm((p) => ({ ...p, domain: e.target.value }))}
|
||||||
placeholder="empresa.com.br"
|
placeholder="empresa.com.br"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>Telefone</Label>
|
<Label htmlFor={phoneId}>Telefone</Label>
|
||||||
<PhoneInput
|
<PhoneInput
|
||||||
|
id={phoneId}
|
||||||
|
name="companyPhone"
|
||||||
value={form.phone ?? ""}
|
value={form.phone ?? ""}
|
||||||
onChange={(value) => setForm((p) => ({ ...p, phone: value || null }))}
|
onChange={(value) => setForm((p) => ({ ...p, phone: value || null }))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2 md:col-span-2">
|
<div className="grid gap-2 md:col-span-2">
|
||||||
<Label>Endereço</Label>
|
<Label htmlFor={addressId}>Endereço</Label>
|
||||||
<Input
|
<Input
|
||||||
|
id={addressId}
|
||||||
|
name="companyAddress"
|
||||||
value={form.address ?? ""}
|
value={form.address ?? ""}
|
||||||
onChange={(e) => setForm((p) => ({ ...p, address: e.target.value }))}
|
onChange={(e) => setForm((p) => ({ ...p, address: e.target.value }))}
|
||||||
placeholder="Rua, número, bairro, cidade/UF"
|
placeholder="Rua, número, bairro, cidade/UF"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<Label>Horas contratadas/mês</Label>
|
<Label htmlFor={hoursId}>Horas contratadas/mês</Label>
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
|
id={hoursId}
|
||||||
|
name="companyHours"
|
||||||
min={0}
|
min={0}
|
||||||
step="0.25"
|
step="0.25"
|
||||||
value={form.contractedHoursPerMonth ?? ""}
|
value={form.contractedHoursPerMonth ?? ""}
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ function placeholderFor(country: CountryOption): string {
|
||||||
return "Número de telefone"
|
return "Número de telefone"
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PhoneInput({ value, onChange, className }: PhoneInputProps) {
|
export function PhoneInput({ value, onChange, className, id, name }: PhoneInputProps) {
|
||||||
const [selectedCountry, setSelectedCountry] = useState<CountryOption>(DEFAULT_COUNTRY)
|
const [selectedCountry, setSelectedCountry] = useState<CountryOption>(DEFAULT_COUNTRY)
|
||||||
const [localDigits, setLocalDigits] = useState<string>("")
|
const [localDigits, setLocalDigits] = useState<string>("")
|
||||||
|
|
||||||
|
|
@ -144,6 +144,8 @@ export function PhoneInput({ value, onChange, className }: PhoneInputProps) {
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Input
|
<Input
|
||||||
|
id={id}
|
||||||
|
name={name}
|
||||||
type="tel"
|
type="tel"
|
||||||
value={displayValue}
|
value={displayValue}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
|
|
@ -158,6 +160,8 @@ export type PhoneInputProps = {
|
||||||
value?: string | null
|
value?: string | null
|
||||||
onChange?: (value: string) => void
|
onChange?: (value: string) => void
|
||||||
className?: string
|
className?: string
|
||||||
|
id?: string
|
||||||
|
name?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatPhoneDisplay(rawValue?: string | null): string | null {
|
export function formatPhoneDisplay(rawValue?: string | null): string | null {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue