Files
asaas-checkout/components/cupom-table.tsx
Felipe Carvalho 038ce3f556 feat: initial commit — asaas-checkout template white-label
Template genérico de checkout com ASAAS, parametrizado via env vars.
Inclui fluxo completo: checkout → pedido → polling → webhook → admin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 06:40:41 +02:00

240 lines
8.2 KiB
TypeScript

"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Badge } from "@/components/ui/badge"
import { Switch } from "@/components/ui/switch"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { useToast } from "@/components/ui/use-toast"
import { criarCupom, atualizarCupom, deletarCupom } from "@/lib/actions"
import type { Cupom } from "@/lib/supabase"
import { Plus, Trash2, Star, Tag } from "lucide-react"
import { useRouter } from "next/navigation"
interface CupomTableProps {
cupons: Cupom[]
}
function NovoCupomDialog() {
const [open, setOpen] = useState(false)
const [loading, setLoading] = useState(false)
const { toast } = useToast()
const router = useRouter()
const [form, setForm] = useState({
codigo: "",
descricao: "",
percentual: "15",
validade: "",
destaque: false,
})
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
try {
await criarCupom({
codigo: form.codigo,
descricao: form.descricao,
percentual: Number(form.percentual),
validade: form.validade,
destaque: form.destaque,
})
toast({ title: "Cupom criado!" })
setOpen(false)
setForm({ codigo: "", descricao: "", percentual: "15", validade: "", destaque: false })
router.refresh()
} catch (err) {
toast({ title: "Erro", description: err instanceof Error ? err.message : "Erro ao criar cupom", variant: "destructive" })
} finally {
setLoading(false)
}
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button className="gap-2"><Plus className="w-4 h-4" /> Novo Cupom</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Criar cupom de desconto</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4 mt-2">
<div className="space-y-1">
<Label>Código</Label>
<Input
required
placeholder="VIXCERT20"
value={form.codigo}
onChange={(e) => setForm({ ...form, codigo: e.target.value.toUpperCase() })}
/>
</div>
<div className="space-y-1">
<Label>Descrição</Label>
<Input
required
placeholder="Ex: Renovação de Certificado"
value={form.descricao}
onChange={(e) => setForm({ ...form, descricao: e.target.value })}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-1">
<Label>Desconto (%)</Label>
<Input
required
type="number"
min="1"
max="100"
value={form.percentual}
onChange={(e) => setForm({ ...form, percentual: e.target.value })}
/>
</div>
<div className="space-y-1">
<Label>Válido até</Label>
<Input
required
type="date"
value={form.validade}
onChange={(e) => setForm({ ...form, validade: e.target.value })}
/>
</div>
</div>
<div className="flex items-center gap-3">
<Switch
id="destaque"
checked={form.destaque}
onCheckedChange={(v) => setForm({ ...form, destaque: v })}
/>
<Label htmlFor="destaque" className="cursor-pointer flex items-center gap-1">
<Star className="w-4 h-4 text-yellow-500" /> Exibir em destaque no site
</Label>
</div>
<Button type="submit" className="w-full" disabled={loading}>
{loading ? "Criando..." : "Criar cupom"}
</Button>
</form>
</DialogContent>
</Dialog>
)
}
export function CupomTable({ cupons }: CupomTableProps) {
const { toast } = useToast()
const router = useRouter()
const handleToggleAtivo = async (cupom: Cupom) => {
try {
await atualizarCupom(cupom.id, { ativo: !cupom.ativo })
router.refresh()
} catch {
toast({ title: "Erro ao atualizar", variant: "destructive" })
}
}
const handleToggleDestaque = async (cupom: Cupom) => {
try {
await atualizarCupom(cupom.id, { destaque: !cupom.destaque })
router.refresh()
} catch {
toast({ title: "Erro ao atualizar", variant: "destructive" })
}
}
const handleDeletar = async (id: string) => {
if (!confirm("Deletar este cupom?")) return
try {
await deletarCupom(id)
toast({ title: "Cupom deletado" })
router.refresh()
} catch {
toast({ title: "Erro ao deletar", variant: "destructive" })
}
}
const hoje = new Date().toISOString().slice(0, 10)
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between">
<CardTitle className="flex items-center gap-2">
<Tag className="w-5 h-5" /> Cupons de Desconto
</CardTitle>
<NovoCupomDialog />
</CardHeader>
<CardContent>
{cupons.length === 0 ? (
<p className="text-center text-gray-500 py-8">Nenhum cupom cadastrado.</p>
) : (
<div className="space-y-3">
{cupons.map((cupom) => {
const vencido = cupom.validade < hoje
const validadeFormatada = new Date(cupom.validade + "T12:00:00").toLocaleDateString("pt-BR")
return (
<div
key={cupom.id}
className={`flex items-center justify-between p-4 rounded-lg border ${
cupom.destaque ? "border-secondary/50 bg-secondary/5" : "border-gray-200"
}`}
>
<div className="flex items-center gap-4">
<div>
<div className="flex items-center gap-2 mb-1">
<span className="font-mono font-bold text-gray-800">{cupom.codigo}</span>
<Badge variant="secondary">{cupom.percentual}% OFF</Badge>
{cupom.destaque && (
<Badge className="bg-yellow-100 text-yellow-800 border-yellow-300">
<Star className="w-3 h-3 mr-1" /> Destaque
</Badge>
)}
{vencido && <Badge variant="destructive">Vencido</Badge>}
{!cupom.ativo && <Badge variant="outline">Inativo</Badge>}
</div>
<div className="text-sm text-gray-500">{cupom.descricao} · Até {validadeFormatada}</div>
</div>
</div>
<div className="flex items-center gap-3">
<div className="flex items-center gap-1 text-xs text-gray-500">
<Switch
checked={cupom.destaque}
onCheckedChange={() => handleToggleDestaque(cupom)}
title="Exibir em destaque"
/>
<span>Destaque</span>
</div>
<div className="flex items-center gap-1 text-xs text-gray-500">
<Switch
checked={cupom.ativo}
onCheckedChange={() => handleToggleAtivo(cupom)}
title="Ativo/Inativo"
/>
<span>Ativo</span>
</div>
<Button
variant="ghost"
size="icon"
className="text-red-500 hover:text-red-700"
onClick={() => handleDeletar(cupom.id)}
>
<Trash2 className="w-4 h-4" />
</Button>
</div>
</div>
)
})}
</div>
)}
</CardContent>
</Card>
)
}