add petition success dialog

This commit is contained in:
Francisco Ignácio Fontoura Solla 2026-05-01 01:09:31 -03:00
parent 01779dfd71
commit 918d6f62a2
3 changed files with 214 additions and 1 deletions

View file

@ -11,7 +11,7 @@ import {
import { Petition } from '@/payload-types'
import { zodResolver } from '@hookform/resolvers/zod'
import { FormProvider, type SubmitHandler, useForm } from 'react-hook-form'
import { useTransition } from 'react'
import { useState, useTransition } from 'react'
import { Textarea } from './ui/textarea'
import { Button } from './ui/button'
import { petitionFormSchema, type PetitionFormInput } from '@/lib/schemas/petition-form'
@ -22,6 +22,7 @@ import { PhoneInput } from './PhoneInput'
import { StateSelect } from './StateSelect'
import { CitySelect } from './CitySelect'
import { PostalCodeInput } from './PostalCodeInput'
import { PetitionSuccessDialog } from './PetitionSuccessDialog'
interface PetitionFormProps {
id: string
@ -44,6 +45,7 @@ export const PetitionForm = ({ id, petition, consentHTML }: PetitionFormProps) =
})
const [isSubmitting, startTransition] = useTransition()
const [isSuccessOpen, setIsSuccessOpen] = useState(false)
const onSubmit: SubmitHandler<PetitionFormInput> = (input) => {
const consentId =
@ -57,6 +59,7 @@ export const PetitionForm = ({ id, petition, consentHTML }: PetitionFormProps) =
consentId,
})
methods.reset()
setIsSuccessOpen(true)
} catch {
methods.setError('root', {
message: 'Falha ao enviar assinatura. Tente novamente.',
@ -67,6 +70,11 @@ export const PetitionForm = ({ id, petition, consentHTML }: PetitionFormProps) =
return (
<FormProvider {...methods}>
<PetitionSuccessDialog
open={isSuccessOpen}
onOpenChange={setIsSuccessOpen}
petitionTitle={petition.title}
/>
<form id={id} onSubmit={methods.handleSubmit(onSubmit)}>
<FieldSet>
{petition.form.title ? <FieldLegend>{petition.form.title}</FieldLegend> : null}

View file

@ -0,0 +1,73 @@
'use client'
import { useEffect, useState } from 'react'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogTitle,
} from '@/components/ui/dialog'
import { WhatsAppIcon } from '@/components/socialIcons'
import { XIcon } from 'lucide-react'
interface PetitionSuccessDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
petitionTitle: string
}
export const PetitionSuccessDialog = ({
open,
onOpenChange,
petitionTitle,
}: PetitionSuccessDialogProps) => {
const [shareUrl, setShareUrl] = useState('')
useEffect(() => {
if (typeof window !== 'undefined') {
setShareUrl(window.location.href)
}
}, [])
const message = `Acabei de assinar o abaixo-assinado "${petitionTitle}". Some sua voz também: ${shareUrl}`
const whatsappHref = `https://wa.me/?text=${encodeURIComponent(message)}`
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
data-theme="petition"
showCloseButton={false}
className="gap-0 overflow-hidden border-0 p-0 sm:p-0"
>
<div className="relative bg-[var(--petition-hero)] px-6 py-6 text-[var(--petition-hero-foreground)] sm:px-8 sm:py-7">
<DialogClose
aria-label="Fechar"
className="absolute right-3 top-3 inline-flex size-8 items-center justify-center rounded-md text-[var(--petition-hero-foreground)] transition-colors hover:bg-white/15 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/60"
>
<XIcon className="size-4" />
</DialogClose>
<DialogTitle className="border-none pb-0 text-xl font-bold leading-tight sm:text-2xl">
Obrigado por assinar!
</DialogTitle>
</div>
<div className="flex flex-col gap-4 bg-card px-6 py-6 text-card-foreground sm:px-8 sm:py-7">
<DialogDescription>
Sua assinatura foi registrada. Compartilhe o abaixo-assinado no WhatsApp para somar mais
apoiadores.
</DialogDescription>
<Button
asChild
className="w-full bg-[#25D366] font-semibold text-white hover:bg-[#1ebe5a] hover:text-white"
>
<a href={whatsappHref} target="_blank" rel="noopener noreferrer">
<WhatsAppIcon className="size-5" />
Compartilhar no WhatsApp
</a>
</Button>
</div>
</DialogContent>
</Dialog>
)
}

View file

@ -0,0 +1,132 @@
'use client'
import * as React from 'react'
import { Dialog as DialogPrimitive } from 'radix-ui'
import { XIcon } from 'lucide-react'
import { cn } from '@/lib/utils'
function Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {
return <DialogPrimitive.Root data-slot="dialog" {...props} />
}
function DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
}
function DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
}
function DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
}
function DialogOverlay({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
return (
<DialogPrimitive.Overlay
data-slot="dialog-overlay"
className={cn(
'fixed inset-0 z-50 bg-black/60 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0',
className,
)}
{...props}
/>
)
}
function DialogContent({
className,
children,
showCloseButton = true,
...props
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean
}) {
return (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
data-slot="dialog-content"
className={cn(
'fixed left-1/2 top-1/2 z-50 grid w-[calc(100vw-2rem)] max-w-sm -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl border border-border bg-background p-6 text-foreground shadow-lg duration-200 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 sm:max-w-md sm:p-8',
className,
)}
{...props}
>
{children}
{showCloseButton ? (
<DialogPrimitive.Close
data-slot="dialog-close"
className="absolute right-3 top-3 inline-flex size-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/50 disabled:pointer-events-none"
aria-label="Fechar"
>
<XIcon className="size-4" />
</DialogPrimitive.Close>
) : null}
</DialogPrimitive.Content>
</DialogPortal>
)
}
function DialogHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="dialog-header"
className={cn('flex flex-col gap-1.5 text-center sm:text-left', className)}
{...props}
/>
)
}
function DialogFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="dialog-footer"
className={cn('flex flex-col gap-2 sm:flex-row sm:justify-end', className)}
{...props}
/>
)
}
function DialogTitle({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
return (
<DialogPrimitive.Title
data-slot="dialog-title"
className={cn('text-lg font-semibold leading-tight', className)}
{...props}
/>
)
}
function DialogDescription({
className,
...props
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
return (
<DialogPrimitive.Description
data-slot="dialog-description"
className={cn('text-sm leading-relaxed text-muted-foreground', className)}
{...props}
/>
)
}
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
}