From 34e962cc48e5bfdb296b7013f1e4bd90e8ad49de Mon Sep 17 00:00:00 2001 From: Mythie Date: Thu, 31 Aug 2023 14:06:19 +1000 Subject: [PATCH] fix: minor updates --- .../src/app/(dashboard)/dashboard/page.tsx | 29 +- .../sign/[token]/signature-field.tsx | 5 +- .../src/components/(marketing)/callout.tsx | 66 --- .../(marketing)/claim-plan-dialog.tsx | 150 ------- .../faster-smarter-beautiful-bento.tsx | 77 ---- .../web/src/components/(marketing)/footer.tsx | 63 --- .../web/src/components/(marketing)/header.tsx | 32 -- apps/web/src/components/(marketing)/hero.tsx | 225 ---------- .../(marketing)/open-build-template-bento.tsx | 74 ---- .../(marketing)/password-reveal.tsx | 33 -- .../components/(marketing)/pricing-table.tsx | 180 -------- .../share-connect-paid-widget-bento.tsx | 91 ---- .../web/src/components/(marketing)/widget.tsx | 402 ------------------ 13 files changed, 24 insertions(+), 1403 deletions(-) delete mode 100644 apps/web/src/components/(marketing)/callout.tsx delete mode 100644 apps/web/src/components/(marketing)/claim-plan-dialog.tsx delete mode 100644 apps/web/src/components/(marketing)/faster-smarter-beautiful-bento.tsx delete mode 100644 apps/web/src/components/(marketing)/footer.tsx delete mode 100644 apps/web/src/components/(marketing)/header.tsx delete mode 100644 apps/web/src/components/(marketing)/hero.tsx delete mode 100644 apps/web/src/components/(marketing)/open-build-template-bento.tsx delete mode 100644 apps/web/src/components/(marketing)/password-reveal.tsx delete mode 100644 apps/web/src/components/(marketing)/pricing-table.tsx delete mode 100644 apps/web/src/components/(marketing)/share-connect-paid-widget-bento.tsx delete mode 100644 apps/web/src/components/(marketing)/widget.tsx diff --git a/apps/web/src/app/(dashboard)/dashboard/page.tsx b/apps/web/src/app/(dashboard)/dashboard/page.tsx index a83b2e8cc..77b18b98c 100644 --- a/apps/web/src/app/(dashboard)/dashboard/page.tsx +++ b/apps/web/src/app/(dashboard)/dashboard/page.tsx @@ -5,6 +5,7 @@ import { Clock, File, FileCheck } from 'lucide-react'; import { getRequiredServerComponentSession } from '@documenso/lib/next-auth/get-server-session'; import { findDocuments } from '@documenso/lib/server-only/document/find-documents'; import { getStats } from '@documenso/lib/server-only/document/get-stats'; +import { DocumentStatus as InternalDocumentStatus } from '@documenso/prisma/client'; import { Table, TableBody, @@ -21,6 +22,24 @@ import { LocaleDate } from '~/components/formatter/locale-date'; import { UploadDocument } from './upload-document'; +const CARD_DATA = [ + { + icon: FileCheck, + title: 'Completed', + status: InternalDocumentStatus.COMPLETED, + }, + { + icon: File, + title: 'Drafts', + status: InternalDocumentStatus.DRAFT, + }, + { + icon: Clock, + title: 'Pending', + status: InternalDocumentStatus.PENDING, + }, +]; + export default async function DashboardPage() { const user = await getRequiredServerComponentSession(); @@ -34,20 +53,14 @@ export default async function DashboardPage() { }), ]); - const cardData = [ - { icon: FileCheck, title: 'Completed', status: stats.COMPLETED }, - { icon: File, title: 'Drafts', status: stats.DRAFT }, - { icon: Clock, title: 'Pending', status: stats.PENDING }, - ]; - return (

Dashboard

- {cardData.map((card) => ( + {CARD_DATA.map((card) => ( - + ))}
diff --git a/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx b/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx index cada25e06..68a61fb67 100644 --- a/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/signature-field.tsx @@ -139,9 +139,10 @@ export const SignatureField = ({ field, recipient }: SignatureFieldProps) => { /> )} - {state === 'signed-text' && signature?.typedSignature && ( + {state === 'signed-text' && (

- {signature.typedSignature} + {/* This optional chaining is intentional, we don't want to move the check into the condition above */} + {signature?.typedSignature}

)} diff --git a/apps/web/src/components/(marketing)/callout.tsx b/apps/web/src/components/(marketing)/callout.tsx deleted file mode 100644 index d83983141..000000000 --- a/apps/web/src/components/(marketing)/callout.tsx +++ /dev/null @@ -1,66 +0,0 @@ -'use client'; - -import Link from 'next/link'; - -import { Github } from 'lucide-react'; -import { usePlausible } from 'next-plausible'; - -import { Button } from '@documenso/ui/primitives/button'; - -export type CalloutProps = { - starCount?: number; - [key: string]: unknown; -}; - -export const Callout = ({ starCount }: CalloutProps) => { - const event = usePlausible(); - - const onSignUpClick = () => { - const el = document.getElementById('email'); - - if (el) { - const { top } = el.getBoundingClientRect(); - - window.scrollTo({ - top: top - 120, - behavior: 'smooth', - }); - - setTimeout(() => { - el.focus(); - }, 500); - } - }; - - return ( -
- - - event('view-github')} - > - - -
- ); -}; diff --git a/apps/web/src/components/(marketing)/claim-plan-dialog.tsx b/apps/web/src/components/(marketing)/claim-plan-dialog.tsx deleted file mode 100644 index 1f78c5292..000000000 --- a/apps/web/src/components/(marketing)/claim-plan-dialog.tsx +++ /dev/null @@ -1,150 +0,0 @@ -'use client'; - -import React, { useState } from 'react'; - -import { useSearchParams } from 'next/navigation'; - -import { zodResolver } from '@hookform/resolvers/zod'; -import { Info, Loader } from 'lucide-react'; -import { usePlausible } from 'next-plausible'; -import { useForm } from 'react-hook-form'; -import { z } from 'zod'; - -import { cn } from '@documenso/ui/lib/utils'; -import { Button } from '@documenso/ui/primitives/button'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '@documenso/ui/primitives/dialog'; -import { Input } from '@documenso/ui/primitives/input'; -import { Label } from '@documenso/ui/primitives/label'; -import { useToast } from '@documenso/ui/primitives/use-toast'; - -import { claimPlan } from '~/api/claim-plan/fetcher'; - -import { FormErrorMessage } from '../form/form-error-message'; - -export const ZClaimPlanDialogFormSchema = z.object({ - name: z.string().min(3), - email: z.string().email(), -}); - -export type TClaimPlanDialogFormSchema = z.infer; - -export type ClaimPlanDialogProps = { - className?: string; - planId: string; - children: React.ReactNode; -}; - -export const ClaimPlanDialog = ({ className, planId, children }: ClaimPlanDialogProps) => { - const params = useSearchParams(); - const { toast } = useToast(); - const event = usePlausible(); - - const [open, setOpen] = useState(() => params?.get('cancelled') === 'true'); - - const { - register, - handleSubmit, - formState: { errors, isSubmitting }, - } = useForm({ - mode: 'onBlur', - defaultValues: { - name: params?.get('name') ?? '', - email: params?.get('email') ?? '', - }, - resolver: zodResolver(ZClaimPlanDialogFormSchema), - }); - - const onFormSubmit = async ({ name, email }: TClaimPlanDialogFormSchema) => { - try { - const delay = new Promise((resolve) => { - setTimeout(resolve, 1000); - }); - - const [redirectUrl] = await Promise.all([ - claimPlan({ name, email, planId, signatureText: name, signatureDataUrl: null }), - delay, - ]); - - event('claim-plan-pricing'); - - window.location.href = redirectUrl; - } 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} - - - - Claim your plan - - - We're almost there! Please enter your email address and name to claim your plan. - - - -
- {params?.get('cancelled') === 'true' && ( -
-
-
- -
-
-

- You have cancelled the payment process. If you didn't mean to do this, please - try again. -

-
-
-
- )} - -
- - - - - -
- -
- - - - - -
- - -
-
-
- ); -}; diff --git a/apps/web/src/components/(marketing)/faster-smarter-beautiful-bento.tsx b/apps/web/src/components/(marketing)/faster-smarter-beautiful-bento.tsx deleted file mode 100644 index 2cbaaef53..000000000 --- a/apps/web/src/components/(marketing)/faster-smarter-beautiful-bento.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { HTMLAttributes } from 'react'; - -import Image from 'next/image'; - -import { cn } from '@documenso/ui/lib/utils'; -import { Card, CardContent } from '@documenso/ui/primitives/card'; - -import backgroundPattern from '~/assets/background-pattern.png'; -import cardBeautifulFigure from '~/assets/card-beautiful-figure.png'; -import cardFastFigure from '~/assets/card-fast-figure.png'; -import cardSmartFigure from '~/assets/card-smart-figure.png'; - -export type FasterSmarterBeautifulBentoProps = HTMLAttributes; - -export const FasterSmarterBeautifulBento = ({ - className, - ...props -}: FasterSmarterBeautifulBentoProps) => { - return ( -
-
- background pattern -
-

- A 10x better signing experience. - Faster, smarter and more beautiful. -

- -
- - -

- Fast. - When it comes to sending or receiving a contract, you can count on lightning-fast - speeds. -

- -
- its fast -
-
-
- - - -

- Beautiful. - Because signing should be celebrated. That’s why we care about the smallest detail in - our product. -

- -
- its fast -
-
-
- - - -

- Smart. - Our custom templates come with smart rules that can help you save time and energy. -

- -
- its fast -
-
-
-
-
- ); -}; diff --git a/apps/web/src/components/(marketing)/footer.tsx b/apps/web/src/components/(marketing)/footer.tsx deleted file mode 100644 index a5fadfcf8..000000000 --- a/apps/web/src/components/(marketing)/footer.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { HTMLAttributes } from 'react'; - -import Image from 'next/image'; -import Link from 'next/link'; - -import { Github, MessagesSquare, Twitter } from 'lucide-react'; - -import { cn } from '@documenso/ui/lib/utils'; - -export type FooterProps = HTMLAttributes; - -const SOCIAL_LINKS = [ - { href: 'https://twitter.com/documenso', icon: }, - { href: 'https://github.com/documenso/documenso', icon: }, - { href: 'https://documen.so/discord', icon: }, -]; - -const FOOTER_LINKS = [ - { href: '/pricing', text: 'Pricing' }, - { href: 'https://status.documenso.com', text: 'Status', target: '_blank' }, - { href: 'mailto:support@documenso.com', text: 'Support' }, - // { href: '/privacy', text: 'Privacy'} -]; - -export const Footer = ({ className, ...props }: FooterProps) => { - return ( -
-
-
- - Documenso Logo - - -
- {SOCIAL_LINKS.map((link, index) => ( - - {link.icon} - - ))} -
-
- -
- {FOOTER_LINKS.map((link, index) => ( - - {link.text} - - ))} -
-
-
-

- © {new Date().getFullYear()} Documenso, Inc. All rights reserved. -

-
-
- ); -}; diff --git a/apps/web/src/components/(marketing)/header.tsx b/apps/web/src/components/(marketing)/header.tsx deleted file mode 100644 index 5a1fa3b89..000000000 --- a/apps/web/src/components/(marketing)/header.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { HTMLAttributes } from 'react'; - -import Image from 'next/image'; -import Link from 'next/link'; - -import { cn } from '@documenso/ui/lib/utils'; - -export type HeaderProps = HTMLAttributes; - -export const Header = ({ className, ...props }: HeaderProps) => { - return ( -
- - Documenso Logo - - -
- - Pricing - - - - Sign in - -
-
- ); -}; diff --git a/apps/web/src/components/(marketing)/hero.tsx b/apps/web/src/components/(marketing)/hero.tsx deleted file mode 100644 index 7896a010e..000000000 --- a/apps/web/src/components/(marketing)/hero.tsx +++ /dev/null @@ -1,225 +0,0 @@ -'use client'; - -import Image from 'next/image'; -import Link from 'next/link'; - -import { Variants, motion } from 'framer-motion'; -import { Github } from 'lucide-react'; -import { usePlausible } from 'next-plausible'; - -import { cn } from '@documenso/ui/lib/utils'; -import { Button } from '@documenso/ui/primitives/button'; - -import backgroundPattern from '~/assets/background-pattern.png'; - -import { Widget } from './widget'; - -export type HeroProps = { - className?: string; - starCount?: number; - [key: string]: unknown; -}; - -const BackgroundPatternVariants: Variants = { - initial: { - opacity: 0, - }, - - animate: { - opacity: 1, - - transition: { - delay: 1, - duration: 1.2, - }, - }, -}; - -const HeroTitleVariants: Variants = { - initial: { - opacity: 0, - y: 60, - }, - animate: { - opacity: 1, - y: 0, - transition: { - duration: 0.5, - }, - }, -}; - -export const Hero = ({ className, starCount, ...props }: HeroProps) => { - const event = usePlausible(); - - const onSignUpClick = () => { - const el = document.getElementById('email'); - - if (el) { - const { top } = el.getBoundingClientRect(); - - window.scrollTo({ - top: top - 120, - behavior: 'smooth', - }); - - requestAnimationFrame(() => { - el.focus(); - }); - } - }; - - return ( - -
- - background pattern - -
- -
- - Document signing, - finally open source. - - - - - - event('view-github')}> - - - - -
- - - Documenso - The open source DocuSign alternative | Product Hunt - - -
- - - - Documenso Supporter Pledge -

- Our mission is to create an open signing infrastructure that empowers the world, - enabling businesses to embrace openness, cooperation, and transparency. We believe - that signing, as a fundamental act, should embody these values. By offering an - open-source signing solution, we aim to make document signing accessible, transparent, - and trustworthy. -

- -

- Through our platform, called Documenso, we strive to earn your trust by allowing - self-hosting and providing complete visibility into its inner workings. We value - inclusivity and foster an environment where diverse perspectives and contributions are - welcomed, even though we may not implement them all. -

- -

- At Documenso, we envision a web-enabled future for business and contracts, and we are - committed to being the leading provider of open signing infrastructure. By combining - exceptional product design with open-source principles, we aim to deliver a robust and - well-designed application that exceeds your expectations. -

- -

- We understand that exceptional products are born from exceptional communities, and we - invite you to join our open-source community. Your contributions, whether technical or - non-technical, will help shape the future of signing. Together, we can create a better - future for everyone. -

- -

- Today we invite you to join us on this journey: By signing this mission statement you - signal your support of Documenso's mission{' '} - - (in a non-legally binding, but heartfelt way) - {' '} - and lock in the early supporter plan for forever, including everything we build this - year. -

- -
-

Timur & Lucas

-
- -
- Timur Ercan & Lucas Smith -

Co-Founders, Documenso

-
-
-
-
-
- ); -}; diff --git a/apps/web/src/components/(marketing)/open-build-template-bento.tsx b/apps/web/src/components/(marketing)/open-build-template-bento.tsx deleted file mode 100644 index e7920500b..000000000 --- a/apps/web/src/components/(marketing)/open-build-template-bento.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { HTMLAttributes } from 'react'; - -import Image from 'next/image'; - -import { cn } from '@documenso/ui/lib/utils'; -import { Card, CardContent } from '@documenso/ui/primitives/card'; - -import backgroundPattern from '~/assets/background-pattern.png'; -import cardBuildFigure from '~/assets/card-build-figure.png'; -import cardOpenFigure from '~/assets/card-open-figure.png'; -import cardTemplateFigure from '~/assets/card-template-figure.png'; - -export type OpenBuildTemplateBentoProps = HTMLAttributes; - -export const OpenBuildTemplateBento = ({ className, ...props }: OpenBuildTemplateBentoProps) => { - return ( -
-
- background pattern -
-

- Truly your own. - Customise and expand. -

- -
- - -

- Open Source or Hosted. - It’s up to you. Either clone our repository or rely on our easy to use hosting - solution. -

- -
- its fast -
-
-
- - - -

- Build on top. - Make it your own through advanced customization and adjustability. -

- -
- its fast -
-
-
- - - -

- Template Store (Soon). - Choose a template from the community app store. Or submit your own template for others - to use. -

- -
- its fast -
-
-
-
-
- ); -}; diff --git a/apps/web/src/components/(marketing)/password-reveal.tsx b/apps/web/src/components/(marketing)/password-reveal.tsx deleted file mode 100644 index b31765943..000000000 --- a/apps/web/src/components/(marketing)/password-reveal.tsx +++ /dev/null @@ -1,33 +0,0 @@ -'use client'; - -import { useToast } from '@documenso/ui/primitives/use-toast'; - -import { useCopyToClipboard } from '~/hooks/use-copy-to-clipboard'; - -export type PasswordRevealProps = { - password: string; -}; - -export const PasswordReveal = ({ password }: PasswordRevealProps) => { - const { toast } = useToast(); - const [, copy] = useCopyToClipboard(); - - const onCopyClick = () => { - void copy(password).then(() => { - toast({ - title: 'Copied to clipboard', - description: 'Your password has been copied to your clipboard.', - }); - }); - }; - - return ( - - ); -}; diff --git a/apps/web/src/components/(marketing)/pricing-table.tsx b/apps/web/src/components/(marketing)/pricing-table.tsx deleted file mode 100644 index 73003abdc..000000000 --- a/apps/web/src/components/(marketing)/pricing-table.tsx +++ /dev/null @@ -1,180 +0,0 @@ -'use client'; - -import { HTMLAttributes, useMemo, useState } from 'react'; - -import Link from 'next/link'; -import { useSearchParams } from 'next/navigation'; - -import { AnimatePresence, motion } from 'framer-motion'; -import { usePlausible } from 'next-plausible'; - -import { cn } from '@documenso/ui/lib/utils'; -import { Button } from '@documenso/ui/primitives/button'; - -import { ClaimPlanDialog } from './claim-plan-dialog'; - -export type PricingTableProps = HTMLAttributes; - -const SELECTED_PLAN_BAR_LAYOUT_ID = 'selected-plan-bar'; - -export const PricingTable = ({ className, ...props }: PricingTableProps) => { - const params = useSearchParams(); - const event = usePlausible(); - - const [period, setPeriod] = useState<'MONTHLY' | 'YEARLY'>(() => - // eslint-disable-next-line turbo/no-undeclared-env-vars - params?.get('planId') === process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID - ? 'YEARLY' - : 'MONTHLY', - ); - - const planId = useMemo(() => { - if (period === 'MONTHLY') { - // eslint-disable-next-line turbo/no-undeclared-env-vars - return process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_MONTHLY_PRICE_ID; - } - - // eslint-disable-next-line turbo/no-undeclared-env-vars - return process.env.NEXT_PUBLIC_STRIPE_COMMUNITY_PLAN_YEARLY_PRICE_ID; - }, [period]); - - return ( -
-
- - setPeriod('MONTHLY')} - > - Monthly - {period === 'MONTHLY' && ( - - )} - - - setPeriod('YEARLY')} - > - Yearly -
- Save $60 -
- {period === 'YEARLY' && ( - - )} -
-
-
- -
-
-

Self Hosted

-

Free

- -

- For small teams and individuals who need a simple solution -

- - - -
-

Host your own instance

-

Full Control

-

Customizability

-

Docker Ready

-

Community Support

-

Free, Forever

-
-
- -
-

Community

-
- - {period === 'MONTHLY' && $30} - {period === 'YEARLY' && $300} - -
- -

- For fast-growing companies that aim to scale across multiple teams. -

- - - - - -
-

Documenso Early Adopter Deal:

-

Join the movement

-

Simple signing solution

-

Email and Slack assistance

-

- Includes all upcoming features -

-

Fixed, straightforward pricing

-
-
- -
-

Enterprise

-

Pricing on request

- -

- For large organizations that need extra flexibility and control. -

- - event('enterprise-contact')} - > - - - -
-

Everything in Community, plus:

-

Custom Subdomain

-

Compliance Check

-

Guaranteed Uptime

-

Reporting & Analysis

-

24/7 Support

-
-
-
-
- ); -}; diff --git a/apps/web/src/components/(marketing)/share-connect-paid-widget-bento.tsx b/apps/web/src/components/(marketing)/share-connect-paid-widget-bento.tsx deleted file mode 100644 index 05b6a3232..000000000 --- a/apps/web/src/components/(marketing)/share-connect-paid-widget-bento.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { HTMLAttributes } from 'react'; - -import Image from 'next/image'; - -import { cn } from '@documenso/ui/lib/utils'; -import { Card, CardContent } from '@documenso/ui/primitives/card'; - -import backgroundPattern from '~/assets/background-pattern.png'; -import cardConnectionsFigure from '~/assets/card-connections-figure.png'; -import cardPaidFigure from '~/assets/card-paid-figure.png'; -import cardSharingFigure from '~/assets/card-sharing-figure.png'; -import cardWidgetFigure from '~/assets/card-widget-figure.png'; - -export type ShareConnectPaidWidgetBentoProps = HTMLAttributes; - -export const ShareConnectPaidWidgetBento = ({ - className, - ...props -}: ShareConnectPaidWidgetBentoProps) => { - return ( -
-
- background pattern -
-

- Integrates with all your favourite tools. - Send, connect, receive and embed everywhere. -

- -
- - -

- Easy Sharing (Soon). - Receive your personal link to share with everyone you care about. -

- -
- its fast -
-
-
- - - -

- Connections (Soon). - Create connections and automations with Zapier and more to integrate with your - favorite tools. -

- -
- its fast -
-
-
- - - -

- Get paid (Soon). - Integrated payments with stripe so you don’t have to worry about getting paid. -

- -
- its fast -
-
-
- - - -

- React Widget (Soon). - Easily embed Documenso into your product. Simply copy and paste our react widget into - your application. -

- -
- its fast -
-
-
-
-
- ); -}; diff --git a/apps/web/src/components/(marketing)/widget.tsx b/apps/web/src/components/(marketing)/widget.tsx deleted file mode 100644 index 15e15d04c..000000000 --- a/apps/web/src/components/(marketing)/widget.tsx +++ /dev/null @@ -1,402 +0,0 @@ -'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 { SignaturePad } from '@documenso/ui/primitives/signature-pad'; -import { useToast } from '@documenso/ui/primitives/use-toast'; - -import { claimPlan } from '~/api/claim-plan/fetcher'; - -import { FormErrorMessage } from '../form/form-error-message'; - -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', ''); - - void 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 -

- -
- - - - - - ( -
- - field.value !== '' && - !errors.email?.message && - onEnterPress(onNextStepClick)(e) - } - {...field} - /> - -
- -
-
- )} - /> - - -
- - {(step === 'NAME' || step === 'SIGN') && ( - - - - ( -
- - field.value !== '' && - !errors.name?.message && - onEnterPress(onNextStepClick)(e) - } - {...field} - /> - -
- -
-
- )} - /> - - -
- )} -
- -
- -
-

{stepsRemaining} step(s) until signed

-

Minimise contract

-
- -
-
-
- - - setShowSigningDialog(true)} - > -
- {!signatureText && signatureDataUrl && ( - user signature - )} - - {signatureText && ( -

- {signatureText} -

- )} -
- -
e.stopPropagation()} - > - { - if (e.target.value !== '') { - setValue('signatureDataUrl', null); - } - }, - })} - /> - - -
-
-
- -
- - - - - - 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. -
- - - - - - - - -
-
- - ); -};