'use client'; import { HTMLAttributes, KeyboardEvent, useMemo, useState } from 'react'; import { zodResolver } from '@hookform/resolvers/zod'; import { AnimatePresence, motion } from 'framer-motion'; import { Loader } from 'lucide-react'; import { usePlausible } from 'next-plausible'; import { Controller, useForm } from 'react-hook-form'; import { z } from 'zod'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; import { Card, CardContent } from '@documenso/ui/primitives/card'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@documenso/ui/primitives/dialog'; import { Input } from '@documenso/ui/primitives/input'; import { useToast } from '@documenso/ui/primitives/use-toast'; import { claimPlan } from '~/api/claim-plan/fetcher'; import { FormErrorMessage } from '../form/form-error-message'; import { SignaturePad } from '../signature-pad'; const ZWidgetFormSchema = z .object({ email: z.string().email({ message: 'Please enter a valid email address.' }), name: z.string().min(3, { message: 'Please enter a valid name.' }), }) .and( z.union([ z.object({ signatureDataUrl: z.string().min(1), signatureText: z.null().or(z.string().max(0)), }), z.object({ signatureDataUrl: z.null().or(z.string().max(0)), signatureText: z.string().min(1), }), ]), ); export type TWidgetFormSchema = z.infer; export type WidgetProps = HTMLAttributes; export const Widget = ({ className, children, ...props }: WidgetProps) => { const { toast } = useToast(); const event = usePlausible(); const [step, setStep] = useState<'EMAIL' | 'NAME' | 'SIGN'>('EMAIL'); const [showSigningDialog, setShowSigningDialog] = useState(false); const [draftSignatureDataUrl, setDraftSignatureDataUrl] = useState(null); const { control, register, handleSubmit, setValue, trigger, watch, formState: { errors, isSubmitting, isValid }, } = useForm({ mode: 'onChange', defaultValues: { email: '', name: '', signatureDataUrl: null, signatureText: '', }, resolver: zodResolver(ZWidgetFormSchema), }); const signatureDataUrl = watch('signatureDataUrl'); const signatureText = watch('signatureText'); const stepsRemaining = useMemo(() => { if (step === 'NAME') { return 2; } if (step === 'SIGN') { return 1; } return 3; }, [step]); const onNextStepClick = () => { if (step === 'EMAIL') { setStep('NAME'); setTimeout(() => { document.querySelector('#name')?.focus(); }, 0); } if (step === 'NAME') { setStep('SIGN'); setTimeout(() => { document.querySelector('#signatureText')?.focus(); }, 0); } }; const onEnterPress = (callback: () => void) => { return (e: KeyboardEvent) => { if (e.key === 'Enter') { e.preventDefault(); callback(); } }; }; const onSignatureConfirmClick = () => { setValue('signatureDataUrl', draftSignatureDataUrl); setValue('signatureText', ''); trigger('signatureDataUrl'); setShowSigningDialog(false); }; const onFormSubmit = async ({ email, name, signatureDataUrl, signatureText, }: TWidgetFormSchema) => { try { const delay = new Promise((resolve) => setTimeout(resolve, 1000)); // eslint-disable-next-line turbo/no-undeclared-env-vars const planId = process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID; const claimPlanInput = signatureDataUrl ? { name, email, planId, signatureDataUrl: signatureDataUrl!, signatureText: null, } : { name, email, planId, signatureDataUrl: null, signatureText: signatureText!, }; const [result] = await Promise.all([claimPlan(claimPlanInput), delay]); event('claim-plan-widget'); window.location.href = result; } catch (error) { event('claim-plan-failed'); toast({ title: 'Something went wrong', description: error instanceof Error ? error.message : 'Please try again later.', variant: 'destructive', }); } }; return ( <> {children} Sign up for the community plan with Timur Ercan & Lucas Smith from Documenso What’s your email? ( field.value !== '' && !errors.email?.message && onEnterPress(onNextStepClick)(e) } {...field} /> onNextStepClick()} > Next )} /> {(step === 'NAME' || step === 'SIGN') && ( and your name? ( field.value !== '' && !errors.name?.message && onEnterPress(onNextStepClick)(e) } {...field} /> onNextStepClick()} > Next )} /> )} {stepsRemaining} step(s) until signed Minimise contract setShowSigningDialog(true)} > {!signatureText && signatureDataUrl && ( )} {signatureText && ( {signatureText} )} e.stopPropagation()} > { if (e.target.value !== '') { setValue('signatureDataUrl', null); } }, })} /> {isSubmitting && } Sign Add your signature By signing you signal your support of Documenso's mission in a non-legally binding, but heartfelt way. You also unlock the option to purchase the early supporter plan including everything we build this year for fixed price. setShowSigningDialog(false)}> Cancel onSignatureConfirmClick()}>Confirm > ); };
with Timur Ercan & Lucas Smith from Documenso
{stepsRemaining} step(s) until signed
Minimise contract
{signatureText}