add petition success dialog
This commit is contained in:
parent
01779dfd71
commit
918d6f62a2
3 changed files with 214 additions and 1 deletions
|
|
@ -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}
|
||||
|
|
|
|||
73
src/components/PetitionSuccessDialog.tsx
Normal file
73
src/components/PetitionSuccessDialog.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
132
src/components/ui/dialog.tsx
Normal file
132
src/components/ui/dialog.tsx
Normal 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,
|
||||
}
|
||||
Loading…
Reference in a new issue