From b4f1a5abce095f818d1319caf3f468d569e767ed Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Thu, 16 Nov 2023 07:10:13 +0000 Subject: [PATCH 01/45] feat: handle download file error with toast --- .../documents/data-table-action-button.tsx | 67 +++++++++++-------- 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx b/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx index 17b577c13..0d767d388 100644 --- a/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx +++ b/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx @@ -12,6 +12,7 @@ import { DocumentStatus, SigningStatus } from '@documenso/prisma/client'; import type { DocumentWithData } from '@documenso/prisma/types/document-with-data'; import { trpc as trpcClient } from '@documenso/trpc/client'; import { Button } from '@documenso/ui/primitives/button'; +import { toast } from '@documenso/ui/primitives/use-toast'; export type DataTableActionButtonProps = { row: Document & { @@ -37,38 +38,48 @@ export const DataTableActionButton = ({ row }: DataTableActionButtonProps) => { const isSigned = recipient?.signingStatus === SigningStatus.SIGNED; const onDownloadClick = async () => { - let document: DocumentWithData | null = null; + try { + let document: DocumentWithData | null = null; - if (!recipient) { - document = await trpcClient.document.getDocumentById.query({ - id: row.id, + if (!recipient) { + document = await trpcClient.document.getDocumentById.query({ + id: row.id, + }); + } else { + document = await trpcClient.document.getDocumentByToken.query({ + token: recipient.token, + }); + } + + const documentData = document?.documentData; + + console.log(documentData); + + if (!documentData) { + return; + } + + const documentBytes = await getFile({ data: documentData.data, type: documentData.type }); + + const blob = new Blob([documentBytes], { + type: 'application/pdf', }); - } else { - document = await trpcClient.document.getDocumentByToken.query({ - token: recipient.token, + + const link = window.document.createElement('a'); + + link.href = window.URL.createObjectURL(blob); + link.download = row.title || 'document.pdf'; + + link.click(); + + window.URL.revokeObjectURL(link.href); + } catch (error) { + toast({ + title: 'Something went wrong', + description: 'An error occurred while trying to download file.', + variant: 'destructive', }); } - - const documentData = document?.documentData; - - if (!documentData) { - return; - } - - const documentBytes = await getFile(documentData); - - const blob = new Blob([documentBytes], { - type: 'application/pdf', - }); - - const link = window.document.createElement('a'); - - link.href = window.URL.createObjectURL(blob); - link.download = row.title || 'document.pdf'; - - link.click(); - - window.URL.revokeObjectURL(link.href); }; return match({ From fdf5b3908df581131dabb061b7c9e9e905c8cf69 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Fri, 24 Nov 2023 23:50:51 +0000 Subject: [PATCH 02/45] feat: add more posthog analytics to the application --- apps/web/src/app/(dashboard)/documents/page.tsx | 2 +- .../src/app/(dashboard)/documents/upload-document.tsx | 11 ++++++++++- apps/web/src/app/(signing)/sign/[token]/form.tsx | 10 +++++++++- apps/web/src/components/forms/signup.tsx | 7 +++++++ .../stripe/webhook/on-early-adopters-checkout.ts | 2 +- packages/lib/universal/get-feature-flag.ts | 10 ++++++---- 6 files changed, 34 insertions(+), 8 deletions(-) diff --git a/apps/web/src/app/(dashboard)/documents/page.tsx b/apps/web/src/app/(dashboard)/documents/page.tsx index f38668fd9..cd7f868ad 100644 --- a/apps/web/src/app/(dashboard)/documents/page.tsx +++ b/apps/web/src/app/(dashboard)/documents/page.tsx @@ -63,7 +63,7 @@ export default async function DocumentsPage({ searchParams = {} }: DocumentsPage return (
- +

Documents

diff --git a/apps/web/src/app/(dashboard)/documents/upload-document.tsx b/apps/web/src/app/(dashboard)/documents/upload-document.tsx index 644c9017a..eec881e67 100644 --- a/apps/web/src/app/(dashboard)/documents/upload-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/upload-document.tsx @@ -8,6 +8,7 @@ import { useRouter } from 'next/navigation'; import { Loader } from 'lucide-react'; import { useLimits } from '@documenso/ee/server-only/limits/provider/client'; +import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics'; import { createDocumentData } from '@documenso/lib/server-only/document-data/create-document-data'; import { putFile } from '@documenso/lib/universal/upload/put-file'; import { TRPCClientError } from '@documenso/trpc/client'; @@ -18,10 +19,12 @@ import { useToast } from '@documenso/ui/primitives/use-toast'; export type UploadDocumentProps = { className?: string; + userId?: number; }; -export const UploadDocument = ({ className }: UploadDocumentProps) => { +export const UploadDocument = ({ className, userId }: UploadDocumentProps) => { const router = useRouter(); + const analytics = useAnalytics(); const { toast } = useToast(); @@ -53,6 +56,12 @@ export const UploadDocument = ({ className }: UploadDocumentProps) => { duration: 5000, }); + analytics.capture('App: Document Uploaded', { + userId, + documentId: id, + timestamp: new Date().toISOString(), + }); + router.push(`/documents/${id}`); } catch (error) { console.error(error); diff --git a/apps/web/src/app/(signing)/sign/[token]/form.tsx b/apps/web/src/app/(signing)/sign/[token]/form.tsx index 5c6779c62..034b93c6d 100644 --- a/apps/web/src/app/(signing)/sign/[token]/form.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/form.tsx @@ -7,9 +7,10 @@ import { useRouter } from 'next/navigation'; import { useSession } from 'next-auth/react'; import { useForm } from 'react-hook-form'; +import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics'; import { completeDocumentWithToken } from '@documenso/lib/server-only/document/complete-document-with-token'; import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields'; -import { Document, Field, Recipient } from '@documenso/prisma/client'; +import type { Document, Field, Recipient } from '@documenso/prisma/client'; import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; @@ -29,6 +30,7 @@ export type SigningFormProps = { export const SigningForm = ({ document, recipient, fields }: SigningFormProps) => { const router = useRouter(); + const analytics = useAnalytics(); const { data: session } = useSession(); const { fullName, signature, setFullName, setSignature } = useRequiredSigningContext(); @@ -57,6 +59,12 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) = documentId: document.id, }); + analytics.capture('App: Document Signing Complete', { + signerId: recipient.id, + documentId: document.id, + timestamp: new Date().toISOString(), + }); + router.push(`/sign/${recipient.token}/complete`); }; diff --git a/apps/web/src/components/forms/signup.tsx b/apps/web/src/components/forms/signup.tsx index fc85510f3..8eb2ac0cc 100644 --- a/apps/web/src/components/forms/signup.tsx +++ b/apps/web/src/components/forms/signup.tsx @@ -8,6 +8,7 @@ import { signIn } from 'next-auth/react'; import { Controller, useForm } from 'react-hook-form'; import { z } from 'zod'; +import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics'; import { TRPCClientError } from '@documenso/trpc/client'; import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; @@ -33,6 +34,7 @@ export type SignUpFormProps = { export const SignUpForm = ({ className }: SignUpFormProps) => { const { toast } = useToast(); + const analytics = useAnalytics(); const [showPassword, setShowPassword] = useState(false); const { @@ -61,6 +63,11 @@ export const SignUpForm = ({ className }: SignUpFormProps) => { password, callbackUrl: '/', }); + + analytics.capture('App: User Sign Up', { + email, + timestamp: new Date().toISOString(), + }); } catch (err) { if (err instanceof TRPCClientError && err.data?.code === 'BAD_REQUEST') { toast({ diff --git a/packages/ee/server-only/stripe/webhook/on-early-adopters-checkout.ts b/packages/ee/server-only/stripe/webhook/on-early-adopters-checkout.ts index a8403e05a..22f60069e 100644 --- a/packages/ee/server-only/stripe/webhook/on-early-adopters-checkout.ts +++ b/packages/ee/server-only/stripe/webhook/on-early-adopters-checkout.ts @@ -1,4 +1,4 @@ -import Stripe from 'stripe'; +import type Stripe from 'stripe'; import { hashSync } from '@documenso/lib/server-only/auth/hash'; import { sealDocument } from '@documenso/lib/server-only/document/seal-document'; diff --git a/packages/lib/universal/get-feature-flag.ts b/packages/lib/universal/get-feature-flag.ts index 38707d41b..bf79f79ce 100644 --- a/packages/lib/universal/get-feature-flag.ts +++ b/packages/lib/universal/get-feature-flag.ts @@ -1,9 +1,7 @@ import { z } from 'zod'; -import { - TFeatureFlagValue, - ZFeatureFlagValueSchema, -} from '@documenso/lib/client-only/providers/feature-flag.types'; +import type { TFeatureFlagValue } from '@documenso/lib/client-only/providers/feature-flag.types'; +import { ZFeatureFlagValueSchema } from '@documenso/lib/client-only/providers/feature-flag.types'; import { APP_BASE_URL } from '@documenso/lib/constants/app'; import { LOCAL_FEATURE_FLAGS, isFeatureFlagEnabled } from '@documenso/lib/constants/feature-flags'; @@ -20,6 +18,10 @@ export const getFlag = async ( ): Promise => { const requestHeaders = options?.requestHeaders ?? {}; + if (!LOCAL_FEATURE_FLAGS[flag]) { + return LOCAL_FEATURE_FLAGS[flag]; + } + if (!isFeatureFlagEnabled()) { return LOCAL_FEATURE_FLAGS[flag] ?? true; } From d347359d2fc10d20cbd14190a804b21442b67d2e Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Sat, 25 Nov 2023 22:09:52 +0000 Subject: [PATCH 03/45] chore: changes from code review --- .../app/(dashboard)/documents/data-table-action-button.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx b/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx index 0d767d388..e0a56f83d 100644 --- a/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx +++ b/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx @@ -12,7 +12,7 @@ import { DocumentStatus, SigningStatus } from '@documenso/prisma/client'; import type { DocumentWithData } from '@documenso/prisma/types/document-with-data'; import { trpc as trpcClient } from '@documenso/trpc/client'; import { Button } from '@documenso/ui/primitives/button'; -import { toast } from '@documenso/ui/primitives/use-toast'; +import { useToast } from '@documenso/ui/primitives/use-toast'; export type DataTableActionButtonProps = { row: Document & { @@ -23,6 +23,7 @@ export type DataTableActionButtonProps = { export const DataTableActionButton = ({ row }: DataTableActionButtonProps) => { const { data: session } = useSession(); + const toast = useToast(); if (!session) { return null; @@ -53,8 +54,6 @@ export const DataTableActionButton = ({ row }: DataTableActionButtonProps) => { const documentData = document?.documentData; - console.log(documentData); - if (!documentData) { return; } From 0e40658201f7f33c61fd3f8eb7239f4c6c1458ae Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Sun, 26 Nov 2023 19:33:45 +0000 Subject: [PATCH 04/45] feat: track when the signing of a document has completed --- apps/web/src/app/(signing)/sign/[token]/form.tsx | 2 +- packages/lib/server-only/document/seal-document.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/(signing)/sign/[token]/form.tsx b/apps/web/src/app/(signing)/sign/[token]/form.tsx index 034b93c6d..57b737c0c 100644 --- a/apps/web/src/app/(signing)/sign/[token]/form.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/form.tsx @@ -59,7 +59,7 @@ export const SigningForm = ({ document, recipient, fields }: SigningFormProps) = documentId: document.id, }); - analytics.capture('App: Document Signing Complete', { + analytics.capture('App: Recipient has completed signing', { signerId: recipient.id, documentId: document.id, timestamp: new Date().toISOString(), diff --git a/packages/lib/server-only/document/seal-document.ts b/packages/lib/server-only/document/seal-document.ts index 318d540b8..c2e68277b 100644 --- a/packages/lib/server-only/document/seal-document.ts +++ b/packages/lib/server-only/document/seal-document.ts @@ -1,8 +1,10 @@ 'use server'; +import { nanoid } from 'nanoid'; import path from 'node:path'; import { PDFDocument } from 'pdf-lib'; +import PostHogServerClient from '@documenso/lib/server-only/feature-flags/get-post-hog-server-client'; import { prisma } from '@documenso/prisma'; import { DocumentStatus, SigningStatus } from '@documenso/prisma/client'; import { signPdf } from '@documenso/signing'; @@ -83,6 +85,18 @@ export const sealDocument = async ({ documentId, sendEmail = true }: SealDocumen arrayBuffer: async () => Promise.resolve(pdfBuffer), }); + const postHog = PostHogServerClient(); + + if (postHog) { + postHog.capture({ + distinctId: nanoid(), + event: 'App: Document Signed', + properties: { + documentId: document.id, + }, + }); + } + await prisma.documentData.update({ where: { id: documentData.id, From c46a69f865c8e69a9bf3376574dd5ac2171e64bc Mon Sep 17 00:00:00 2001 From: mikezzb Date: Sat, 2 Dec 2023 22:30:10 -0500 Subject: [PATCH 05/45] feat: stepper component --- packages/ui/primitives/stepper.tsx | 80 ++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 packages/ui/primitives/stepper.tsx diff --git a/packages/ui/primitives/stepper.tsx b/packages/ui/primitives/stepper.tsx new file mode 100644 index 000000000..e4d87a7ba --- /dev/null +++ b/packages/ui/primitives/stepper.tsx @@ -0,0 +1,80 @@ +import React, { useEffect, useState } from 'react'; +import type { FC } from 'react'; + +type StepProps = { + readonly useStep: () => { + stepIndex: number; + currentStep: number; + totalSteps: number; + isFirst: boolean; + isLast: boolean; + nextStep: () => void; + previousStep: () => void; + }; +}; + +export type WithStep = T & StepProps; + +type StepperProps = { + children: React.ReactNode; + onComplete?: () => void; + onStepChanged?: (currentStep: number) => void; + currentStep?: number; + setCurrentStep?: (step: number) => void; +}; + +export const Stepper: FC = ({ + children, + onComplete, + onStepChanged, + currentStep: propCurrentStep, + setCurrentStep: propSetCurrentStep, +}) => { + const [stateCurrentStep, stateSetCurrentStep] = useState(1); + + // Determine if props are provided, otherwise use state + const isControlled = propCurrentStep !== undefined && propSetCurrentStep !== undefined; + const currentStep = isControlled ? propCurrentStep : stateCurrentStep; + const setCurrentStep = isControlled ? propSetCurrentStep : stateSetCurrentStep; + + const totalSteps = React.Children.count(children); + + const nextStep = () => { + if (currentStep < totalSteps) { + setCurrentStep(currentStep + 1); + } else { + onComplete && onComplete(); + } + }; + + const previousStep = () => { + if (currentStep > 1) { + setCurrentStep(currentStep - 1); + } + }; + + useEffect(() => { + onStepChanged && onStepChanged(currentStep); + }, [currentStep, onStepChanged]); + + const useStep = (stepIndex: number) => ({ + stepIndex, + currentStep, + totalSteps, + isFirst: currentStep === 1, + isLast: currentStep === totalSteps, + nextStep, + previousStep, + }); + + const renderStep = (child: React.ReactNode, index: number) => { + if (!React.isValidElement(child)) return null; + return index + 1 === currentStep + ? React.cloneElement(child, { + useStep: () => useStep(index), + }) + : null; + }; + + return <>{React.Children.toArray(children).map(renderStep)}; +}; From a98b429052f08accfaaebf249263f14c1160ced9 Mon Sep 17 00:00:00 2001 From: mikezzb Date: Sat, 2 Dec 2023 22:42:59 -0500 Subject: [PATCH 06/45] feat: stepper refactor example --- .../documents/[id]/edit-document.tsx | 75 ++++++++----------- .../primitives/document-flow/add-fields.tsx | 21 +++--- .../primitives/document-flow/add-signers.tsx | 24 +++--- .../primitives/document-flow/add-subject.tsx | 22 +++--- .../ui/primitives/document-flow/add-title.tsx | 15 ++-- .../document-flow/document-flow-root.tsx | 3 +- packages/ui/primitives/document-flow/types.ts | 2 +- 7 files changed, 80 insertions(+), 82 deletions(-) diff --git a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx index e775bffdc..8aface5a6 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx @@ -5,7 +5,6 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; import type { DocumentData, Field, Recipient, User } from '@documenso/prisma/client'; -import { DocumentStatus } from '@documenso/prisma/client'; import type { DocumentWithData } from '@documenso/prisma/types/document-with-data'; import { trpc } from '@documenso/trpc/react'; import { cn } from '@documenso/ui/lib/utils'; @@ -24,6 +23,7 @@ import { } from '@documenso/ui/primitives/document-flow/document-flow-root'; import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types'; import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer'; +import { Stepper } from '@documenso/ui/primitives/stepper'; import { useToast } from '@documenso/ui/primitives/use-toast'; export type EditDocumentFormProps = { @@ -35,7 +35,12 @@ export type EditDocumentFormProps = { documentData: DocumentData; }; -type EditDocumentStep = 'title' | 'signers' | 'fields' | 'subject'; +enum EditDocumentStepEnum { + TITLE, + SIGNERS, + FIELDS, + SUBJECT, +} export const EditDocumentForm = ({ className, @@ -48,42 +53,35 @@ export const EditDocumentForm = ({ const { toast } = useToast(); const router = useRouter(); - const [step, setStep] = useState( - document.status === DocumentStatus.DRAFT ? 'title' : 'signers', - ); + // controlled stepper state + const [stepIdx, setStepIdx] = useState(0); const { mutateAsync: addTitle } = trpc.document.setTitleForDocument.useMutation(); const { mutateAsync: addFields } = trpc.field.addFields.useMutation(); const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation(); const { mutateAsync: sendDocument } = trpc.document.sendDocument.useMutation(); - const documentFlow: Record = { - title: { + // controlled stepper next + const nextStep = () => setStepIdx(stepIdx + 1); + + const documentFlow: DocumentFlowStep[] = [ + { title: 'Add Title', description: 'Add the title to the document.', - stepIndex: 1, }, - signers: { + { title: 'Add Signers', description: 'Add the people who will sign the document.', - stepIndex: 2, - onBackStep: () => document.status === DocumentStatus.DRAFT && setStep('title'), }, - fields: { + { title: 'Add Fields', description: 'Add all relevant fields for each recipient.', - stepIndex: 3, - onBackStep: () => setStep('signers'), }, - subject: { + { title: 'Add Subject', description: 'Add the subject and message you wish to send to signers.', - stepIndex: 4, - onBackStep: () => setStep('fields'), }, - }; - - const currentDocumentFlow = documentFlow[step]; + ]; const onAddTitleFormSubmit = async (data: TAddTitleFormSchema) => { try { @@ -95,7 +93,7 @@ export const EditDocumentForm = ({ router.refresh(); - setStep('signers'); + nextStep(); } catch (err) { console.error(err); @@ -116,8 +114,7 @@ export const EditDocumentForm = ({ }); router.refresh(); - - setStep('fields'); + nextStep(); } catch (err) { console.error(err); @@ -138,8 +135,7 @@ export const EditDocumentForm = ({ }); router.refresh(); - - setStep('subject'); + nextStep(); } catch (err) { console.error(err); @@ -181,6 +177,8 @@ export const EditDocumentForm = ({ } }; + const currentDocumentFlow = documentFlow[stepIdx]; + return (
- - {step === 'title' && ( + setStepIdx(step - 1)}> - )} - - {step === 'signers' && ( - )} - - {step === 'fields' && ( - )} - - {step === 'subject' && ( - )} +
diff --git a/packages/ui/primitives/document-flow/add-fields.tsx b/packages/ui/primitives/document-flow/add-fields.tsx index f662dca8b..801070d15 100644 --- a/packages/ui/primitives/document-flow/add-fields.tsx +++ b/packages/ui/primitives/document-flow/add-fields.tsx @@ -11,7 +11,8 @@ import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-c import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element'; import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer'; import { nanoid } from '@documenso/lib/universal/id'; -import { Field, FieldType, Recipient, SendStatus } from '@documenso/prisma/client'; +import type { Field, Recipient } from '@documenso/prisma/client'; +import { FieldType, SendStatus } from '@documenso/prisma/client'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; import { Card, CardContent } from '@documenso/ui/primitives/card'; @@ -25,7 +26,8 @@ import { import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover'; import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip'; -import { TAddFieldsFormSchema } from './add-fields.types'; +import type { WithStep } from '../stepper'; +import type { TAddFieldsFormSchema } from './add-fields.types'; import { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, @@ -33,7 +35,8 @@ import { DocumentFlowFormContainerStep, } from './document-flow-root'; import { FieldItem } from './field-item'; -import { DocumentFlowStep, FRIENDLY_FIELD_TYPE } from './types'; +import type { DocumentFlowStep } from './types'; +import { FRIENDLY_FIELD_TYPE } from './types'; const fontCaveat = Caveat({ weight: ['500'], @@ -53,7 +56,6 @@ export type AddFieldsFormProps = { hideRecipients?: boolean; recipients: Recipient[]; fields: Field[]; - numberOfSteps: number; onSubmit: (_data: TAddFieldsFormSchema) => void; }; @@ -62,10 +64,11 @@ export const AddFieldsFormPartial = ({ hideRecipients = false, recipients, fields, - numberOfSteps, onSubmit, -}: AddFieldsFormProps) => { + useStep, // Stepper +}: WithStep) => { const { isWithinPageBounds, getFieldPosition, getPage } = useDocumentElement(); + const { currentStep, totalSteps, nextStep, previousStep } = useStep(); const { control, @@ -513,15 +516,15 @@ export const AddFieldsFormPartial = ({ { - documentFlow.onBackStep?.(); + previousStep(); remove(); }} onGoNextClick={() => void onFormSubmit()} diff --git a/packages/ui/primitives/document-flow/add-signers.tsx b/packages/ui/primitives/document-flow/add-signers.tsx index b623b0d4e..977f95bdd 100644 --- a/packages/ui/primitives/document-flow/add-signers.tsx +++ b/packages/ui/primitives/document-flow/add-signers.tsx @@ -9,45 +9,49 @@ import { Controller, useFieldArray, useForm } from 'react-hook-form'; import { useLimits } from '@documenso/ee/server-only/limits/provider/client'; import { nanoid } from '@documenso/lib/universal/id'; -import { DocumentStatus, Field, Recipient, SendStatus } from '@documenso/prisma/client'; -import { DocumentWithData } from '@documenso/prisma/types/document-with-data'; +import type { Field, Recipient } from '@documenso/prisma/client'; +import { DocumentStatus, SendStatus } from '@documenso/prisma/client'; +import type { DocumentWithData } from '@documenso/prisma/types/document-with-data'; import { Button } from '@documenso/ui/primitives/button'; import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message'; import { Input } from '@documenso/ui/primitives/input'; import { Label } from '@documenso/ui/primitives/label'; import { useToast } from '@documenso/ui/primitives/use-toast'; -import { TAddSignersFormSchema, ZAddSignersFormSchema } from './add-signers.types'; +import type { WithStep } from '../stepper'; +import type { TAddSignersFormSchema } from './add-signers.types'; +import { ZAddSignersFormSchema } from './add-signers.types'; import { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, DocumentFlowFormContainerStep, } from './document-flow-root'; -import { DocumentFlowStep } from './types'; +import type { DocumentFlowStep } from './types'; export type AddSignersFormProps = { documentFlow: DocumentFlowStep; recipients: Recipient[]; fields: Field[]; document: DocumentWithData; - numberOfSteps: number; onSubmit: (_data: TAddSignersFormSchema) => void; }; export const AddSignersFormPartial = ({ documentFlow, - numberOfSteps, recipients, document, fields: _fields, onSubmit, -}: AddSignersFormProps) => { + useStep, // Stepper +}: WithStep) => { const { toast } = useToast(); const { remaining } = useLimits(); const initialId = useId(); + const { currentStep, totalSteps, nextStep, previousStep } = useStep(); + const { control, handleSubmit, @@ -221,15 +225,15 @@ export const AddSignersFormPartial = ({ void onFormSubmit()} /> diff --git a/packages/ui/primitives/document-flow/add-subject.tsx b/packages/ui/primitives/document-flow/add-subject.tsx index 1bf3b2cb4..e2a10afa3 100644 --- a/packages/ui/primitives/document-flow/add-subject.tsx +++ b/packages/ui/primitives/document-flow/add-subject.tsx @@ -2,28 +2,29 @@ import { useForm } from 'react-hook-form'; -import { DocumentStatus, Field, Recipient } from '@documenso/prisma/client'; -import { DocumentWithData } from '@documenso/prisma/types/document-with-data'; +import type { Field, Recipient } from '@documenso/prisma/client'; +import { DocumentStatus } from '@documenso/prisma/client'; +import type { DocumentWithData } from '@documenso/prisma/types/document-with-data'; import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message'; import { Input } from '@documenso/ui/primitives/input'; import { Label } from '@documenso/ui/primitives/label'; import { Textarea } from '@documenso/ui/primitives/textarea'; -import { TAddSubjectFormSchema } from './add-subject.types'; +import type { WithStep } from '../stepper'; +import type { TAddSubjectFormSchema } from './add-subject.types'; import { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, DocumentFlowFormContainerStep, } from './document-flow-root'; -import { DocumentFlowStep } from './types'; +import type { DocumentFlowStep } from './types'; export type AddSubjectFormProps = { documentFlow: DocumentFlowStep; recipients: Recipient[]; fields: Field[]; document: DocumentWithData; - numberOfSteps: number; onSubmit: (_data: TAddSubjectFormSchema) => void; }; @@ -32,9 +33,9 @@ export const AddSubjectFormPartial = ({ recipients: _recipients, fields: _fields, document, - numberOfSteps, onSubmit, -}: AddSubjectFormProps) => { + useStep, +}: WithStep) => { const { register, handleSubmit, @@ -49,6 +50,7 @@ export const AddSubjectFormPartial = ({ }); const onFormSubmit = handleSubmit(onSubmit); + const { currentStep, totalSteps, nextStep, previousStep } = useStep(); return ( <> @@ -124,15 +126,15 @@ export const AddSubjectFormPartial = ({ void onFormSubmit()} /> diff --git a/packages/ui/primitives/document-flow/add-title.tsx b/packages/ui/primitives/document-flow/add-title.tsx index 3ec44b17d..75cd47e93 100644 --- a/packages/ui/primitives/document-flow/add-title.tsx +++ b/packages/ui/primitives/document-flow/add-title.tsx @@ -8,6 +8,7 @@ import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-messa import { Input } from '@documenso/ui/primitives/input'; import { Label } from '@documenso/ui/primitives/label'; +import type { WithStep } from '../stepper'; import type { TAddTitleFormSchema } from './add-title.types'; import { DocumentFlowFormContainerActions, @@ -22,7 +23,6 @@ export type AddTitleFormProps = { recipients: Recipient[]; fields: Field[]; document: DocumentWithData; - numberOfSteps: number; onSubmit: (_data: TAddTitleFormSchema) => void; }; @@ -31,9 +31,9 @@ export const AddTitleFormPartial = ({ recipients: _recipients, fields: _fields, document, - numberOfSteps, onSubmit, -}: AddTitleFormProps) => { + useStep, +}: WithStep) => { const { register, handleSubmit, @@ -46,6 +46,8 @@ export const AddTitleFormPartial = ({ const onFormSubmit = handleSubmit(onSubmit); + const { stepIndex, currentStep, totalSteps, previousStep } = useStep(); + return ( <> @@ -72,14 +74,15 @@ export const AddTitleFormPartial = ({ void onFormSubmit()} /> diff --git a/packages/ui/primitives/document-flow/document-flow-root.tsx b/packages/ui/primitives/document-flow/document-flow-root.tsx index aec74dd6c..9142f4258 100644 --- a/packages/ui/primitives/document-flow/document-flow-root.tsx +++ b/packages/ui/primitives/document-flow/document-flow-root.tsx @@ -1,6 +1,7 @@ 'use client'; -import React, { HTMLAttributes } from 'react'; +import type { HTMLAttributes } from 'react'; +import React from 'react'; import { motion } from 'framer-motion'; diff --git a/packages/ui/primitives/document-flow/types.ts b/packages/ui/primitives/document-flow/types.ts index c9244ad05..677dc931b 100644 --- a/packages/ui/primitives/document-flow/types.ts +++ b/packages/ui/primitives/document-flow/types.ts @@ -53,7 +53,7 @@ export const FRIENDLY_FIELD_TYPE: Record = { export interface DocumentFlowStep { title: string; description: string; - stepIndex: number; + stepIndex?: number; onBackStep?: () => unknown; onNextStep?: () => unknown; } From eccf63dcfdf22a0b3cd8941ca94207f1223cb0c5 Mon Sep 17 00:00:00 2001 From: mikezzb Date: Sat, 2 Dec 2023 23:56:07 -0500 Subject: [PATCH 07/45] chore: refactor --- packages/ui/primitives/stepper.tsx | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/ui/primitives/stepper.tsx b/packages/ui/primitives/stepper.tsx index e4d87a7ba..f827139e0 100644 --- a/packages/ui/primitives/stepper.tsx +++ b/packages/ui/primitives/stepper.tsx @@ -57,8 +57,8 @@ export const Stepper: FC = ({ onStepChanged && onStepChanged(currentStep); }, [currentStep, onStepChanged]); - const useStep = (stepIndex: number) => ({ - stepIndex, + const useStep = () => ({ + stepIndex: currentStep - 1, currentStep, totalSteps, isFirst: currentStep === 1, @@ -67,14 +67,13 @@ export const Stepper: FC = ({ previousStep, }); - const renderStep = (child: React.ReactNode, index: number) => { - if (!React.isValidElement(child)) return null; - return index + 1 === currentStep - ? React.cloneElement(child, { - useStep: () => useStep(index), - }) - : null; - }; + // empty stepper + if (totalSteps === 0) return null; - return <>{React.Children.toArray(children).map(renderStep)}; + const currentChild = React.Children.toArray(children)[currentStep - 1]; + + // type validation + if (!React.isValidElement(currentChild)) return null; + + return <>{React.cloneElement(currentChild, { useStep })}; }; From 40a4ec4436224b2616a3e2c339bddff90f5675cb Mon Sep 17 00:00:00 2001 From: mikezzb Date: Sun, 3 Dec 2023 01:15:59 -0500 Subject: [PATCH 08/45] refactor: useContext & remove enum --- .../documents/[id]/edit-document.tsx | 53 ++++++++++--------- .../primitives/document-flow/add-fields.tsx | 7 ++- .../primitives/document-flow/add-signers.tsx | 7 ++- .../primitives/document-flow/add-subject.tsx | 7 ++- .../ui/primitives/document-flow/add-title.tsx | 5 +- packages/ui/primitives/stepper.tsx | 52 +++++++++--------- 6 files changed, 66 insertions(+), 65 deletions(-) diff --git a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx index 8aface5a6..73ec10a56 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx @@ -4,6 +4,7 @@ import { useState } from 'react'; import { useRouter } from 'next/navigation'; +import { DocumentStatus } from '@documenso/prisma/client'; import type { DocumentData, Field, Recipient, User } from '@documenso/prisma/client'; import type { DocumentWithData } from '@documenso/prisma/types/document-with-data'; import { trpc } from '@documenso/trpc/react'; @@ -35,12 +36,8 @@ export type EditDocumentFormProps = { documentData: DocumentData; }; -enum EditDocumentStepEnum { - TITLE, - SIGNERS, - FIELDS, - SUBJECT, -} +type EditDocumentStep = 'title' | 'signers' | 'fields' | 'subject'; +const EditDocumentSteps: EditDocumentStep[] = ['title', 'signers', 'fields', 'subject']; export const EditDocumentForm = ({ className, @@ -54,34 +51,37 @@ export const EditDocumentForm = ({ const router = useRouter(); // controlled stepper state - const [stepIdx, setStepIdx] = useState(0); + const [step, setStep] = useState( + document.status === DocumentStatus.DRAFT ? 'title' : 'signers', + ); const { mutateAsync: addTitle } = trpc.document.setTitleForDocument.useMutation(); const { mutateAsync: addFields } = trpc.field.addFields.useMutation(); const { mutateAsync: addSigners } = trpc.recipient.addSigners.useMutation(); const { mutateAsync: sendDocument } = trpc.document.sendDocument.useMutation(); - // controlled stepper next - const nextStep = () => setStepIdx(stepIdx + 1); - - const documentFlow: DocumentFlowStep[] = [ - { + const documentFlow: Record = { + title: { title: 'Add Title', description: 'Add the title to the document.', + stepIndex: 1, }, - { + signers: { title: 'Add Signers', description: 'Add the people who will sign the document.', + stepIndex: 2, }, - { + fields: { title: 'Add Fields', description: 'Add all relevant fields for each recipient.', + stepIndex: 3, }, - { + subject: { title: 'Add Subject', description: 'Add the subject and message you wish to send to signers.', + stepIndex: 4, }, - ]; + }; const onAddTitleFormSubmit = async (data: TAddTitleFormSchema) => { try { @@ -93,7 +93,7 @@ export const EditDocumentForm = ({ router.refresh(); - nextStep(); + setStep('signers'); } catch (err) { console.error(err); @@ -114,7 +114,7 @@ export const EditDocumentForm = ({ }); router.refresh(); - nextStep(); + setStep('fields'); } catch (err) { console.error(err); @@ -135,7 +135,7 @@ export const EditDocumentForm = ({ }); router.refresh(); - nextStep(); + setStep('subject'); } catch (err) { console.error(err); @@ -177,7 +177,7 @@ export const EditDocumentForm = ({ } }; - const currentDocumentFlow = documentFlow[stepIdx]; + const currentDocumentFlow = documentFlow[step]; return (
@@ -199,10 +199,13 @@ export const EditDocumentForm = ({ title={currentDocumentFlow.title} description={currentDocumentFlow.description} /> - setStepIdx(step - 1)}> + setStep(EditDocumentSteps[step - 1])} + > ) => { +}: AddFieldsFormProps) => { const { isWithinPageBounds, getFieldPosition, getPage } = useDocumentElement(); - const { currentStep, totalSteps, nextStep, previousStep } = useStep(); + const { currentStep, totalSteps, previousStep } = useStep(); const { control, diff --git a/packages/ui/primitives/document-flow/add-signers.tsx b/packages/ui/primitives/document-flow/add-signers.tsx index 977f95bdd..f549b7220 100644 --- a/packages/ui/primitives/document-flow/add-signers.tsx +++ b/packages/ui/primitives/document-flow/add-signers.tsx @@ -18,7 +18,7 @@ import { Input } from '@documenso/ui/primitives/input'; import { Label } from '@documenso/ui/primitives/label'; import { useToast } from '@documenso/ui/primitives/use-toast'; -import type { WithStep } from '../stepper'; +import { useStep } from '../stepper'; import type { TAddSignersFormSchema } from './add-signers.types'; import { ZAddSignersFormSchema } from './add-signers.types'; import { @@ -43,14 +43,13 @@ export const AddSignersFormPartial = ({ document, fields: _fields, onSubmit, - useStep, // Stepper -}: WithStep) => { +}: AddSignersFormProps) => { const { toast } = useToast(); const { remaining } = useLimits(); const initialId = useId(); - const { currentStep, totalSteps, nextStep, previousStep } = useStep(); + const { currentStep, totalSteps, previousStep } = useStep(); const { control, diff --git a/packages/ui/primitives/document-flow/add-subject.tsx b/packages/ui/primitives/document-flow/add-subject.tsx index e2a10afa3..e5456fb43 100644 --- a/packages/ui/primitives/document-flow/add-subject.tsx +++ b/packages/ui/primitives/document-flow/add-subject.tsx @@ -10,7 +10,7 @@ import { Input } from '@documenso/ui/primitives/input'; import { Label } from '@documenso/ui/primitives/label'; import { Textarea } from '@documenso/ui/primitives/textarea'; -import type { WithStep } from '../stepper'; +import { useStep } from '../stepper'; import type { TAddSubjectFormSchema } from './add-subject.types'; import { DocumentFlowFormContainerActions, @@ -34,8 +34,7 @@ export const AddSubjectFormPartial = ({ fields: _fields, document, onSubmit, - useStep, -}: WithStep) => { +}: AddSubjectFormProps) => { const { register, handleSubmit, @@ -50,7 +49,7 @@ export const AddSubjectFormPartial = ({ }); const onFormSubmit = handleSubmit(onSubmit); - const { currentStep, totalSteps, nextStep, previousStep } = useStep(); + const { currentStep, totalSteps, previousStep } = useStep(); return ( <> diff --git a/packages/ui/primitives/document-flow/add-title.tsx b/packages/ui/primitives/document-flow/add-title.tsx index 75cd47e93..27b6aff03 100644 --- a/packages/ui/primitives/document-flow/add-title.tsx +++ b/packages/ui/primitives/document-flow/add-title.tsx @@ -8,7 +8,7 @@ import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-messa import { Input } from '@documenso/ui/primitives/input'; import { Label } from '@documenso/ui/primitives/label'; -import type { WithStep } from '../stepper'; +import { useStep } from '../stepper'; import type { TAddTitleFormSchema } from './add-title.types'; import { DocumentFlowFormContainerActions, @@ -32,8 +32,7 @@ export const AddTitleFormPartial = ({ fields: _fields, document, onSubmit, - useStep, -}: WithStep) => { +}: AddTitleFormProps) => { const { register, handleSubmit, diff --git a/packages/ui/primitives/stepper.tsx b/packages/ui/primitives/stepper.tsx index f827139e0..795304e8c 100644 --- a/packages/ui/primitives/stepper.tsx +++ b/packages/ui/primitives/stepper.tsx @@ -1,26 +1,24 @@ -import React, { useEffect, useState } from 'react'; +import React, { createContext, useContext, useEffect, useState } from 'react'; import type { FC } from 'react'; -type StepProps = { - readonly useStep: () => { - stepIndex: number; - currentStep: number; - totalSteps: number; - isFirst: boolean; - isLast: boolean; - nextStep: () => void; - previousStep: () => void; - }; +type StepContextType = { + stepIndex: number; + currentStep: number; + totalSteps: number; + isFirst: boolean; + isLast: boolean; + nextStep: () => void; + previousStep: () => void; }; -export type WithStep = T & StepProps; +const StepContext = createContext(null); type StepperProps = { children: React.ReactNode; onComplete?: () => void; onStepChanged?: (currentStep: number) => void; - currentStep?: number; - setCurrentStep?: (step: number) => void; + currentStep?: number; // external control prop + setCurrentStep?: (step: number) => void; // external control function }; export const Stepper: FC = ({ @@ -57,7 +55,12 @@ export const Stepper: FC = ({ onStepChanged && onStepChanged(currentStep); }, [currentStep, onStepChanged]); - const useStep = () => ({ + // Empty stepper + if (totalSteps === 0) return null; + + const currentChild = React.Children.toArray(children)[currentStep - 1]; + + const stepContextValue: StepContextType = { stepIndex: currentStep - 1, currentStep, totalSteps, @@ -65,15 +68,14 @@ export const Stepper: FC = ({ isLast: currentStep === totalSteps, nextStep, previousStep, - }); + }; - // empty stepper - if (totalSteps === 0) return null; - - const currentChild = React.Children.toArray(children)[currentStep - 1]; - - // type validation - if (!React.isValidElement(currentChild)) return null; - - return <>{React.cloneElement(currentChild, { useStep })}; + return {currentChild}; +}; + +/** Hook for children to use the step context */ +export const useStep = (): StepContextType => { + const context = useContext(StepContext); + if (!context) throw new Error('useStep must be used within a Stepper'); + return context; }; From 43b1a14415850fbe8b3758b86b2edf55506cd6b4 Mon Sep 17 00:00:00 2001 From: mikezzb Date: Sun, 3 Dec 2023 11:21:51 -0500 Subject: [PATCH 09/45] chore: let code breath --- packages/ui/primitives/stepper.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/ui/primitives/stepper.tsx b/packages/ui/primitives/stepper.tsx index 795304e8c..35086ff17 100644 --- a/packages/ui/primitives/stepper.tsx +++ b/packages/ui/primitives/stepper.tsx @@ -56,7 +56,9 @@ export const Stepper: FC = ({ }, [currentStep, onStepChanged]); // Empty stepper - if (totalSteps === 0) return null; + if (totalSteps === 0) { + return null; + } const currentChild = React.Children.toArray(children)[currentStep - 1]; @@ -76,6 +78,8 @@ export const Stepper: FC = ({ /** Hook for children to use the step context */ export const useStep = (): StepContextType => { const context = useContext(StepContext); - if (!context) throw new Error('useStep must be used within a Stepper'); + if (!context) { + throw new Error('useStep must be used within a Stepper'); + } return context; }; From 340c9298064bb1f667376ea0ea01142d1c4d52dc Mon Sep 17 00:00:00 2001 From: mikezzb Date: Sun, 3 Dec 2023 11:36:18 -0500 Subject: [PATCH 10/45] refactor: edit doc --- .../src/app/(dashboard)/documents/[id]/edit-document.tsx | 9 +-------- packages/ui/primitives/document-flow/add-fields.tsx | 5 +++++ packages/ui/primitives/document-flow/add-signers.tsx | 5 +++++ packages/ui/primitives/document-flow/add-subject.tsx | 5 +++++ packages/ui/primitives/document-flow/add-title.tsx | 5 +++++ 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx index 73ec10a56..53da2d353 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx @@ -18,10 +18,7 @@ import { AddSubjectFormPartial } from '@documenso/ui/primitives/document-flow/ad import type { TAddSubjectFormSchema } from '@documenso/ui/primitives/document-flow/add-subject.types'; import { AddTitleFormPartial } from '@documenso/ui/primitives/document-flow/add-title'; import type { TAddTitleFormSchema } from '@documenso/ui/primitives/document-flow/add-title.types'; -import { - DocumentFlowFormContainer, - DocumentFlowFormContainerHeader, -} from '@documenso/ui/primitives/document-flow/document-flow-root'; +import { DocumentFlowFormContainer } from '@documenso/ui/primitives/document-flow/document-flow-root'; import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types'; import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer'; import { Stepper } from '@documenso/ui/primitives/stepper'; @@ -195,10 +192,6 @@ export const EditDocumentForm = ({ className="lg:h-[calc(100vh-6rem)]" onSubmit={(e) => e.preventDefault()} > - setStep(EditDocumentSteps[step - 1])} diff --git a/packages/ui/primitives/document-flow/add-fields.tsx b/packages/ui/primitives/document-flow/add-fields.tsx index 9ffcd5d80..112f2f849 100644 --- a/packages/ui/primitives/document-flow/add-fields.tsx +++ b/packages/ui/primitives/document-flow/add-fields.tsx @@ -32,6 +32,7 @@ import { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, + DocumentFlowFormContainerHeader, DocumentFlowFormContainerStep, } from './document-flow-root'; import { FieldItem } from './field-item'; @@ -289,6 +290,10 @@ export const AddFieldsFormPartial = ({ return ( <> +
{selectedField && ( diff --git a/packages/ui/primitives/document-flow/add-signers.tsx b/packages/ui/primitives/document-flow/add-signers.tsx index f549b7220..13af03d26 100644 --- a/packages/ui/primitives/document-flow/add-signers.tsx +++ b/packages/ui/primitives/document-flow/add-signers.tsx @@ -25,6 +25,7 @@ import { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, + DocumentFlowFormContainerHeader, DocumentFlowFormContainerStep, } from './document-flow-root'; import type { DocumentFlowStep } from './types'; @@ -129,6 +130,10 @@ export const AddSignersFormPartial = ({ return ( <> +
diff --git a/packages/ui/primitives/document-flow/add-subject.tsx b/packages/ui/primitives/document-flow/add-subject.tsx index e5456fb43..e9e761af0 100644 --- a/packages/ui/primitives/document-flow/add-subject.tsx +++ b/packages/ui/primitives/document-flow/add-subject.tsx @@ -16,6 +16,7 @@ import { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, + DocumentFlowFormContainerHeader, DocumentFlowFormContainerStep, } from './document-flow-root'; import type { DocumentFlowStep } from './types'; @@ -53,6 +54,10 @@ export const AddSubjectFormPartial = ({ return ( <> +
diff --git a/packages/ui/primitives/document-flow/add-title.tsx b/packages/ui/primitives/document-flow/add-title.tsx index 27b6aff03..2b91e1033 100644 --- a/packages/ui/primitives/document-flow/add-title.tsx +++ b/packages/ui/primitives/document-flow/add-title.tsx @@ -14,6 +14,7 @@ import { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, + DocumentFlowFormContainerHeader, DocumentFlowFormContainerStep, } from './document-flow-root'; import type { DocumentFlowStep } from './types'; @@ -49,6 +50,10 @@ export const AddTitleFormPartial = ({ return ( <> +
From 859b789018a4a7ff654e6e7f7ac0f2cd9a00c306 Mon Sep 17 00:00:00 2001 From: mikezzb Date: Sun, 3 Dec 2023 12:50:56 -0500 Subject: [PATCH 11/45] feat: isCompleting --- packages/ui/primitives/stepper.tsx | 38 +++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/packages/ui/primitives/stepper.tsx b/packages/ui/primitives/stepper.tsx index 35086ff17..71a4d025c 100644 --- a/packages/ui/primitives/stepper.tsx +++ b/packages/ui/primitives/stepper.tsx @@ -1,7 +1,8 @@ -import React, { createContext, useContext, useEffect, useState } from 'react'; +import React, { createContext, useContext, useState } from 'react'; import type { FC } from 'react'; type StepContextType = { + isCompleting: boolean; stepIndex: number; currentStep: number; totalSteps: number; @@ -15,10 +16,11 @@ const StepContext = createContext(null); type StepperProps = { children: React.ReactNode; - onComplete?: () => void; + onComplete?: () => void | Promise; onStepChanged?: (currentStep: number) => void; currentStep?: number; // external control prop setCurrentStep?: (step: number) => void; // external control function + isAsyncComplete?: boolean; }; export const Stepper: FC = ({ @@ -27,8 +29,10 @@ export const Stepper: FC = ({ onStepChanged, currentStep: propCurrentStep, setCurrentStep: propSetCurrentStep, + isAsyncComplete, }) => { const [stateCurrentStep, stateSetCurrentStep] = useState(1); + const [isCompleting, setIsCompleting] = useState(false); // Determine if props are provided, otherwise use state const isControlled = propCurrentStep !== undefined && propSetCurrentStep !== undefined; @@ -37,24 +41,39 @@ export const Stepper: FC = ({ const totalSteps = React.Children.count(children); + const handleComplete = async () => { + if (!onComplete) { + return; + } + if (!isAsyncComplete) { + void onComplete(); + return; + } + setIsCompleting(true); + await onComplete(); + // handle async complete action + setIsCompleting(false); + }; + + const handleStepChange = (nextStep: number) => { + setCurrentStep(nextStep); + onStepChanged && onStepChanged(nextStep); + }; + const nextStep = () => { if (currentStep < totalSteps) { - setCurrentStep(currentStep + 1); + void handleStepChange(currentStep + 1); } else { - onComplete && onComplete(); + void handleComplete(); } }; const previousStep = () => { if (currentStep > 1) { - setCurrentStep(currentStep - 1); + void handleStepChange(currentStep - 1); } }; - useEffect(() => { - onStepChanged && onStepChanged(currentStep); - }, [currentStep, onStepChanged]); - // Empty stepper if (totalSteps === 0) { return null; @@ -63,6 +82,7 @@ export const Stepper: FC = ({ const currentChild = React.Children.toArray(children)[currentStep - 1]; const stepContextValue: StepContextType = { + isCompleting, stepIndex: currentStep - 1, currentStep, totalSteps, From f310139a136488d9e57f0b8ac3506af5b19e4b51 Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Mon, 4 Dec 2023 00:27:17 +0530 Subject: [PATCH 12/45] feat: add pr labeler workflow Signed-off-by: Adithya Krishna --- .github/pr-labeler.yml | 52 ++++++++++++++++++++++++++++++++ .github/workflows/pr-labeler.yml | 20 ++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 .github/pr-labeler.yml create mode 100644 .github/workflows/pr-labeler.yml diff --git a/.github/pr-labeler.yml b/.github/pr-labeler.yml new file mode 100644 index 000000000..fc980b678 --- /dev/null +++ b/.github/pr-labeler.yml @@ -0,0 +1,52 @@ +'apps: marketing': + - apps/marketing/** + +'apps: web': + - apps/web/** + +'docker': + - docker/** + +'scripts': + - scripts/** + +'migrations': + - packages/prisma/migrations/**/migration.sql + +'e2e tests changes': + - packages/app-tests/e2e/** + +'pkg: assets': + - packages/assets/** + +'pkg: email': + - packages/email/** + +'pkg: eslint-config': + - packages/eslint-config/** + +'pkg: lib': + - packages/lib/** + +'pkg: prettier-config': + - packages/prettier-config/** + +'pkg: prisma': + - packages/prisma/** + +'pkg: signing': + - packages/signing/** + +'pkg: tailwind-config': + - packages/tailwind-config/** + +'pkg: trpc': + - packages/trpc/** + +'pkg: tsconfig': + - packages/tsconfig/** + +'pkg: ui': + - packages/ui/** + + diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml new file mode 100644 index 000000000..0ec8a6231 --- /dev/null +++ b/.github/workflows/pr-labeler.yml @@ -0,0 +1,20 @@ +name: "PR Labeler" + +on: + - pull_request_target + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v4 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + sync-labels: "" \ No newline at end of file From 0f11cc0b4beacb644231344a8c86a50db35f5026 Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Mon, 4 Dec 2023 00:27:49 +0530 Subject: [PATCH 13/45] feat: add first interaction workflow Signed-off-by: Adithya Krishna --- .github/workflows/first-interaction.yml | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/first-interaction.yml diff --git a/.github/workflows/first-interaction.yml b/.github/workflows/first-interaction.yml new file mode 100644 index 000000000..ce6bb25c8 --- /dev/null +++ b/.github/workflows/first-interaction.yml @@ -0,0 +1,29 @@ +name: "Welcome New Contributors" + +on: + pull_request: + types: opened + issues: + types: opened + +permissions: + pull-requests: write + issues: write + +jobs: + welcome-message: + name: Welcome Contributors + runs-on: ubuntu-latest + timeout-minutes: 10 + if: github.event.action == 'opened' + steps: + - uses: actions/first-interaction@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + pr-message: | + Thank you for creating your first Pull Request and for being a part of the open signing revolution! 💚🚀 +
Feel free to hop into our community in [Discord](https://documen.so/discord) + issue-message: | + Thank you for opening your first issue and for being a part of the open signing revolution! +
One of our team members will review it and get back to you as soon as it possible 💚 +
Meanwhile, please feel free to hop into our community in [Discord](https://documen.so/discord) \ No newline at end of file From a43be0432bbc789eef38766c026c5fa4e492db15 Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Mon, 4 Dec 2023 00:28:27 +0530 Subject: [PATCH 14/45] feat: add triage issue workflow Signed-off-by: Adithya Krishna --- .github/workflows/issue-opened.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/issue-opened.yml diff --git a/.github/workflows/issue-opened.yml b/.github/workflows/issue-opened.yml new file mode 100644 index 000000000..3a5b05b86 --- /dev/null +++ b/.github/workflows/issue-opened.yml @@ -0,0 +1,24 @@ +name: "Label Issues" + +on: + issues: + types: + - reopened + - opened + +jobs: + label_issues: + runs-on: ubuntu-latest + if: github.repository == 'documenso/documenso' + permissions: + issues: write + steps: + - uses: actions/github-script@v6 + with: + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ["needs triage"] + }) \ No newline at end of file From 88dc79742319d12c4d311dba89e9705341e01b91 Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Mon, 4 Dec 2023 00:29:15 +0530 Subject: [PATCH 15/45] chore: update semantic pr workflow Signed-off-by: Adithya Krishna --- .github/workflows/semantic-pull-requests.yml | 23 ++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/.github/workflows/semantic-pull-requests.yml b/.github/workflows/semantic-pull-requests.yml index af6e624c6..f04b753dc 100644 --- a/.github/workflows/semantic-pull-requests.yml +++ b/.github/workflows/semantic-pull-requests.yml @@ -17,5 +17,28 @@ jobs: runs-on: ubuntu-latest steps: - uses: amannn/action-semantic-pull-request@v5 + id: lint_pr_title env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: marocchino/sticky-pull-request-comment@v2 + if: always() && (steps.lint_pr_title.outputs.error_message != null) + with: + header: pr-title-lint-error + message: | + Hey There! and thank you for opening this pull request! 📝👋🏼 + + We require pull request titles to follow the [Conventional Commits Spec](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted. + + Details: + + ``` + ${{ steps.lint_pr_title.outputs.error_message }} + ``` + + - if: ${{ steps.lint_pr_title.outputs.error_message == null }} + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: pr-title-lint-error + message: | + Thank you for following the naming conventions for pull request titles! 💚🚀 \ No newline at end of file From 68120794f80d1e98adb3362d6270b118a44a2846 Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Mon, 4 Dec 2023 00:29:30 +0530 Subject: [PATCH 16/45] feat: added stale workflow Signed-off-by: Adithya Krishna --- .github/workflows/stale.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/stale.yml diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..5ad43c839 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,27 @@ +name: "Mark Stale Issues and PRs" + +on: + schedule: + - cron: '0 */8 * * *' + +jobs: + stale: + + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v3 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-pr-stale: 30 + days-before-issue-stale: 30 + stale-issue-message: 'This issue has not seen activity for a while. It will be closed in 30 days unless further activity is detected' + stale-pr-message: 'This PR has not seen activitiy for a while. It will be closed in 30 days unless further activity is detected.' + close-issue-message: 'This issue has been closed because of inactivity.' + close-pr-message: 'This PR has been closed because of inactivity.' + exempt-pr-labels: 'WIP, on-hold, needs review' + + \ No newline at end of file From ef84f5ba98a83ebbd7358f0a54fba5d65bcbece9 Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Mon, 4 Dec 2023 12:28:05 +0530 Subject: [PATCH 17/45] chore: added EOLs Signed-off-by: Adithya Krishna --- .github/pr-labeler.yml | 2 -- .github/workflows/first-interaction.yml | 2 +- .github/workflows/issue-opened.yml | 2 +- .github/workflows/pr-labeler.yml | 2 +- .github/workflows/semantic-pull-requests.yml | 2 +- .github/workflows/stale.yml | 2 -- 6 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/pr-labeler.yml b/.github/pr-labeler.yml index fc980b678..b9695c993 100644 --- a/.github/pr-labeler.yml +++ b/.github/pr-labeler.yml @@ -48,5 +48,3 @@ 'pkg: ui': - packages/ui/** - - diff --git a/.github/workflows/first-interaction.yml b/.github/workflows/first-interaction.yml index ce6bb25c8..2f488dd9c 100644 --- a/.github/workflows/first-interaction.yml +++ b/.github/workflows/first-interaction.yml @@ -26,4 +26,4 @@ jobs: issue-message: | Thank you for opening your first issue and for being a part of the open signing revolution!
One of our team members will review it and get back to you as soon as it possible 💚 -
Meanwhile, please feel free to hop into our community in [Discord](https://documen.so/discord) \ No newline at end of file +
Meanwhile, please feel free to hop into our community in [Discord](https://documen.so/discord) diff --git a/.github/workflows/issue-opened.yml b/.github/workflows/issue-opened.yml index 3a5b05b86..c656ce74d 100644 --- a/.github/workflows/issue-opened.yml +++ b/.github/workflows/issue-opened.yml @@ -21,4 +21,4 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, labels: ["needs triage"] - }) \ No newline at end of file + }) diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index 0ec8a6231..e7873f1db 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -17,4 +17,4 @@ jobs: - uses: actions/labeler@v4 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - sync-labels: "" \ No newline at end of file + sync-labels: "" diff --git a/.github/workflows/semantic-pull-requests.yml b/.github/workflows/semantic-pull-requests.yml index f04b753dc..ef0a87542 100644 --- a/.github/workflows/semantic-pull-requests.yml +++ b/.github/workflows/semantic-pull-requests.yml @@ -41,4 +41,4 @@ jobs: with: header: pr-title-lint-error message: | - Thank you for following the naming conventions for pull request titles! 💚🚀 \ No newline at end of file + Thank you for following the naming conventions for pull request titles! 💚🚀 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 5ad43c839..827c32a06 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -23,5 +23,3 @@ jobs: close-issue-message: 'This issue has been closed because of inactivity.' close-pr-message: 'This PR has been closed because of inactivity.' exempt-pr-labels: 'WIP, on-hold, needs review' - - \ No newline at end of file From d8588b780aa8b70f5d6eddb70514969433b1ab9e Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Mon, 4 Dec 2023 15:36:38 +0530 Subject: [PATCH 18/45] feat: added issue count workflow Signed-off-by: Adithya Krishna --- .github/workflows/issue-count.yml | 67 +++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/issue-count.yml diff --git a/.github/workflows/issue-count.yml b/.github/workflows/issue-count.yml new file mode 100644 index 000000000..f78bcab83 --- /dev/null +++ b/.github/workflows/issue-count.yml @@ -0,0 +1,67 @@ +name: "Issue Count" + +on: + issue_comment: + types: [created] + +permissions: + issues: write + +jobs: + countIssues: + runs-on: ubuntu-latest + env: + MY_ENV_VARIABLE: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Install Octokit + run: npm install @octokit/rest@18 + + - name: Parse comment and count issues + id: parse-comment + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { Octokit } = require("@octokit/rest"); + const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); + + const comment = context.payload.comment.body.trim(); + const regex = /^\/issue-count @(\S+)/; + const match = comment.match(regex); + + if (match) { + const username = match[1]; + console.log(`Username extracted: ${username}`); + + const { data: issues } = await octokit.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + assignee: username, + state: 'open' + }); + + const issueCount = issues.length; + console.log(`Issue count for ${username}: ${issueCount}`); + + const issueCommentId = context.payload.comment.id; + console.log(`Issue comment ID: ${issueCommentId}`); + + const issueCountMessage = `@${username} has ${issueCount} open issues assigned.`; + + await octokit.request('POST /repos/{owner}/{repo}/issues/{issue_number}/comments', { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: issueCountMessage, + headers: { + 'Authorization': `token ${{ secrets.GITHUB_TOKEN }}`, + } + }); + } else { + console.log('No valid username found in the comment'); + } From 36e48e67eefa588245e3896a3cc268c173155e50 Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Mon, 4 Dec 2023 16:07:51 +0530 Subject: [PATCH 19/45] chore: updated issue count workflow Signed-off-by: Adithya Krishna --- .github/workflows/issue-count.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/issue-count.yml b/.github/workflows/issue-count.yml index f78bcab83..ceb4ffabc 100644 --- a/.github/workflows/issue-count.yml +++ b/.github/workflows/issue-count.yml @@ -1,3 +1,4 @@ +# Triggered with '/issue-count @username' name: "Issue Count" on: @@ -9,6 +10,7 @@ permissions: jobs: countIssues: + if: ${{ !github.event.issue.pull_request }} runs-on: ubuntu-latest env: MY_ENV_VARIABLE: ${{ secrets.GITHUB_TOKEN }} @@ -61,7 +63,7 @@ jobs: headers: { 'Authorization': `token ${{ secrets.GITHUB_TOKEN }}`, } - }); + }); } else { console.log('No valid username found in the comment'); } From 02e96bbd0a98bf1ca17262ad401ee35bfa5c9230 Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Mon, 4 Dec 2023 16:08:04 +0530 Subject: [PATCH 20/45] feat: added pr count workflow Signed-off-by: Adithya Krishna --- .github/workflows/pr-count.yml | 69 ++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 .github/workflows/pr-count.yml diff --git a/.github/workflows/pr-count.yml b/.github/workflows/pr-count.yml new file mode 100644 index 000000000..673ac65e8 --- /dev/null +++ b/.github/workflows/pr-count.yml @@ -0,0 +1,69 @@ +# Triggered with '/pr-count @username' +name: "PR Count" + +on: + issue_comment: + types: [created] + +permissions: + pull-requests: write + +jobs: + countPRs: + if: ${{ github.event.issue.pull_request }} + runs-on: ubuntu-latest + env: + MY_ENV_VARIABLE: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Install Octokit + run: npm install @octokit/rest@18 + + - name: Parse comment and count PRs + id: parse-comment + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { Octokit } = require("@octokit/rest"); + const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); + + const comment = context.payload.comment.body.trim(); + const regex = /^\/pr-count @(\S+)/; + const match = comment.match(regex); + + if (match) { + const username = match[1]; + console.log(`Username extracted: ${username}`); + + const { data: pullRequests } = await octokit.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + creator: username, + state: 'open' + }); + + const prCount = pullRequests.length; + console.log(`PR count for ${username}: ${prCount}`); + + const issueCommentId = context.payload.comment.id; + console.log(`Issue comment ID: ${issueCommentId}`); + + const prCountMessage = `@${username} has ${prCount} open pull requests created.`; + + await octokit.request('POST /repos/{owner}/{repo}/issues/{issue_number}/comments', { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: prCountMessage, + headers: { + 'Authorization': `token ${{ secrets.GITHUB_TOKEN }}`, + } + }); + } else { + console.log('No valid username found in the comment'); + } From f181099e742756a596045474b46957f1d98fb943 Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Mon, 4 Dec 2023 16:20:02 +0530 Subject: [PATCH 21/45] chore: updated workflow permissions and run conditions Signed-off-by: Adithya Krishna --- .github/workflows/ci.yml | 1 + .github/workflows/codeql-analysis.yml | 1 + .github/workflows/first-interaction.yml | 2 +- .github/workflows/issue-count.yml | 2 +- .github/workflows/pr-count.yml | 4 ++-- .github/workflows/pr-labeler.yml | 1 + .github/workflows/semantic-pull-requests.yml | 1 + .github/workflows/stale.yml | 2 +- 8 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa29ae591..7e940d1b3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ env: jobs: build_app: name: Build App + if: github.repository == 'documenso/documenso' runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d47c37a00..281cc432c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -9,6 +9,7 @@ on: jobs: analyze: + if: github.repository == 'documenso/documenso' name: Analyze runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/first-interaction.yml b/.github/workflows/first-interaction.yml index 2f488dd9c..2f6f59e4a 100644 --- a/.github/workflows/first-interaction.yml +++ b/.github/workflows/first-interaction.yml @@ -13,9 +13,9 @@ permissions: jobs: welcome-message: name: Welcome Contributors + if: github.repository == 'documenso/documenso' && github.event.action == 'opened' runs-on: ubuntu-latest timeout-minutes: 10 - if: github.event.action == 'opened' steps: - uses: actions/first-interaction@v1 with: diff --git a/.github/workflows/issue-count.yml b/.github/workflows/issue-count.yml index ceb4ffabc..cab8676c4 100644 --- a/.github/workflows/issue-count.yml +++ b/.github/workflows/issue-count.yml @@ -10,7 +10,7 @@ permissions: jobs: countIssues: - if: ${{ !github.event.issue.pull_request }} + if: ${{ !github.event.issue.pull_request }} && github.repository == 'documenso/documenso' && github.event.comment.author_association == 'MEMBER' || 'COLLABORATOR' || 'OWNER' runs-on: ubuntu-latest env: MY_ENV_VARIABLE: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr-count.yml b/.github/workflows/pr-count.yml index 673ac65e8..8c4904945 100644 --- a/.github/workflows/pr-count.yml +++ b/.github/workflows/pr-count.yml @@ -6,11 +6,11 @@ on: types: [created] permissions: - pull-requests: write + pull-requests: write jobs: countPRs: - if: ${{ github.event.issue.pull_request }} + if: ${{ github.event.issue.pull_request }} && github.repository == 'documenso/documenso' && github.event.comment.author_association == 'MEMBER' || 'COLLABORATOR' || 'OWNER' runs-on: ubuntu-latest env: MY_ENV_VARIABLE: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index e7873f1db..e968a028e 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -9,6 +9,7 @@ concurrency: jobs: labeler: + if: github.repository == 'documenso/documenso' permissions: contents: read pull-requests: write diff --git a/.github/workflows/semantic-pull-requests.yml b/.github/workflows/semantic-pull-requests.yml index ef0a87542..08d3739fc 100644 --- a/.github/workflows/semantic-pull-requests.yml +++ b/.github/workflows/semantic-pull-requests.yml @@ -13,6 +13,7 @@ permissions: jobs: validate-pr: + if: github.repository == 'documenso/documenso' name: Validate PR title runs-on: ubuntu-latest steps: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 827c32a06..1fe91e9ab 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -6,7 +6,7 @@ on: jobs: stale: - + if: github.repository == 'documenso/documenso' runs-on: ubuntu-latest permissions: issues: write From ac529a89fc164596a672533145d78424d0dceb68 Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Mon, 4 Dec 2023 18:00:28 +0530 Subject: [PATCH 22/45] feat: check assignee and pr review reminder Signed-off-by: Adithya Krishna --- .github/workflows/issue-assignee-check.yml | 60 +++++++++++++++++++ .github/workflows/issue-count.yml | 69 ---------------------- .github/workflows/pr-count.yml | 69 ---------------------- .github/workflows/pr-review-reminder.yml | 61 +++++++++++++++++++ 4 files changed, 121 insertions(+), 138 deletions(-) create mode 100644 .github/workflows/issue-assignee-check.yml delete mode 100644 .github/workflows/issue-count.yml delete mode 100644 .github/workflows/pr-count.yml create mode 100644 .github/workflows/pr-review-reminder.yml diff --git a/.github/workflows/issue-assignee-check.yml b/.github/workflows/issue-assignee-check.yml new file mode 100644 index 000000000..7c415de7c --- /dev/null +++ b/.github/workflows/issue-assignee-check.yml @@ -0,0 +1,60 @@ +name: "Issue Assignee Check" + +on: + issues: + types: [assigned] + +permissions: + issues: write + +jobs: + countIssues: + if: ${{ github.event.issue.assignee }} && github.repository == 'documenso/documenso' && github.event.action == 'assigned' && github.event.sender.type == 'User' + runs-on: ubuntu-latest + env: + MY_ENV_VARIABLE: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Install Octokit + run: npm install @octokit/rest@18 + + - name: Check Assigned User's Issue Count + id: parse-comment + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { Octokit } = require("@octokit/rest"); + const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); + + const username = context.payload.issue.assignee.login; + console.log(`Username Extracted: ${username}`); + + const { data: issues } = await octokit.issues.listForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + assignee: username, + state: 'open' + }); + + const issueCount = issues.length; + console.log(`Issue Count For ${username}: ${issueCount}`); + + if (issueCount > 3) { + let issueCountMessage = `### 🚨 Documenso Police 🚨`; + issueCountMessage += `\n@${username} has ${issueCount} open issues assigned already. Consider whether this issue should be assigned to them or left open for another contributor.`; + + await octokit.request('POST /repos/{owner}/{repo}/issues/{issue_number}/comments', { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: issueCountMessage, + headers: { + 'Authorization': `token ${{ secrets.GITHUB_TOKEN }}`, + } + }); + } diff --git a/.github/workflows/issue-count.yml b/.github/workflows/issue-count.yml deleted file mode 100644 index cab8676c4..000000000 --- a/.github/workflows/issue-count.yml +++ /dev/null @@ -1,69 +0,0 @@ -# Triggered with '/issue-count @username' -name: "Issue Count" - -on: - issue_comment: - types: [created] - -permissions: - issues: write - -jobs: - countIssues: - if: ${{ !github.event.issue.pull_request }} && github.repository == 'documenso/documenso' && github.event.comment.author_association == 'MEMBER' || 'COLLABORATOR' || 'OWNER' - runs-on: ubuntu-latest - env: - MY_ENV_VARIABLE: ${{ secrets.GITHUB_TOKEN }} - steps: - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '18' - - - name: Install Octokit - run: npm install @octokit/rest@18 - - - name: Parse comment and count issues - id: parse-comment - uses: actions/github-script@v5 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { Octokit } = require("@octokit/rest"); - const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); - - const comment = context.payload.comment.body.trim(); - const regex = /^\/issue-count @(\S+)/; - const match = comment.match(regex); - - if (match) { - const username = match[1]; - console.log(`Username extracted: ${username}`); - - const { data: issues } = await octokit.issues.listForRepo({ - owner: context.repo.owner, - repo: context.repo.repo, - assignee: username, - state: 'open' - }); - - const issueCount = issues.length; - console.log(`Issue count for ${username}: ${issueCount}`); - - const issueCommentId = context.payload.comment.id; - console.log(`Issue comment ID: ${issueCommentId}`); - - const issueCountMessage = `@${username} has ${issueCount} open issues assigned.`; - - await octokit.request('POST /repos/{owner}/{repo}/issues/{issue_number}/comments', { - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: issueCountMessage, - headers: { - 'Authorization': `token ${{ secrets.GITHUB_TOKEN }}`, - } - }); - } else { - console.log('No valid username found in the comment'); - } diff --git a/.github/workflows/pr-count.yml b/.github/workflows/pr-count.yml deleted file mode 100644 index 8c4904945..000000000 --- a/.github/workflows/pr-count.yml +++ /dev/null @@ -1,69 +0,0 @@ -# Triggered with '/pr-count @username' -name: "PR Count" - -on: - issue_comment: - types: [created] - -permissions: - pull-requests: write - -jobs: - countPRs: - if: ${{ github.event.issue.pull_request }} && github.repository == 'documenso/documenso' && github.event.comment.author_association == 'MEMBER' || 'COLLABORATOR' || 'OWNER' - runs-on: ubuntu-latest - env: - MY_ENV_VARIABLE: ${{ secrets.GITHUB_TOKEN }} - steps: - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '18' - - - name: Install Octokit - run: npm install @octokit/rest@18 - - - name: Parse comment and count PRs - id: parse-comment - uses: actions/github-script@v5 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { Octokit } = require("@octokit/rest"); - const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); - - const comment = context.payload.comment.body.trim(); - const regex = /^\/pr-count @(\S+)/; - const match = comment.match(regex); - - if (match) { - const username = match[1]; - console.log(`Username extracted: ${username}`); - - const { data: pullRequests } = await octokit.pulls.list({ - owner: context.repo.owner, - repo: context.repo.repo, - creator: username, - state: 'open' - }); - - const prCount = pullRequests.length; - console.log(`PR count for ${username}: ${prCount}`); - - const issueCommentId = context.payload.comment.id; - console.log(`Issue comment ID: ${issueCommentId}`); - - const prCountMessage = `@${username} has ${prCount} open pull requests created.`; - - await octokit.request('POST /repos/{owner}/{repo}/issues/{issue_number}/comments', { - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: prCountMessage, - headers: { - 'Authorization': `token ${{ secrets.GITHUB_TOKEN }}`, - } - }); - } else { - console.log('No valid username found in the comment'); - } diff --git a/.github/workflows/pr-review-reminder.yml b/.github/workflows/pr-review-reminder.yml new file mode 100644 index 000000000..f0928dfcb --- /dev/null +++ b/.github/workflows/pr-review-reminder.yml @@ -0,0 +1,61 @@ +name: "PR Review Reminder" + +on: + pull_request: + types: [opened, reopened, ready_for_review, review_requested] + +permissions: + pull-requests: write + +jobs: + checkPRs: + if: ${{ github.event.pull_request.user.login }} && github.repository == 'documenso/documenso' && github.event.action == ('opened' || 'reopened' || 'ready_for_review' || 'review_requested') + runs-on: ubuntu-latest + env: + MY_ENV_VARIABLE: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + + - name: Install Octokit + run: npm install @octokit/rest@18 + + - name: Check user's PRs awaiting review + id: parse-prs + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { Octokit } = require("@octokit/rest"); + const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); + + const username = context.payload.pull_request.user.login; + console.log(`Username Extracted: ${username}`); + + const { data: pullRequests } = await octokit.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + sort: 'created', + direction: 'asc', + }); + + const userPullRequests = pullRequests.filter(pr => pr.user.login === username && (pr.state === 'open' || pr.state === 'ready_for_review')); + const prCount = userPullRequests.length; + console.log(`PR Count for ${username}: ${prCount}`); + + if (prCount > 3) { + let prReminderMessage = `🚨 @${username} has ${prCount} pull requests awaiting review. Please consider reviewing them when possible. 🚨`; + + await octokit.request('POST /repos/{owner}/{repo}/issues/{issue_number}/comments', { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.payload.pull_request.number, + body: prReminderMessage, + headers: { + 'Authorization': `token ${{ secrets.GITHUB_TOKEN }}`, + } + }); + } From 02d91d9cd459bac7234c22297cd02c34f5c81d89 Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Mon, 4 Dec 2023 18:04:05 +0530 Subject: [PATCH 23/45] chore: updated workflows Signed-off-by: Adithya Krishna --- .github/workflows/ci.yml | 6 +++--- .github/workflows/codeql-analysis.yml | 4 ++-- .github/workflows/e2e-tests.yml | 5 +++-- .github/workflows/issue-assignee-check.yml | 1 + .github/workflows/pr-review-reminder.yml | 1 + .github/workflows/stale.yml | 2 +- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e940d1b3..104dc3440 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,12 +20,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 2 - name: Install Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: 18 cache: npm @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 2 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 281cc432c..79919f7a5 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -24,9 +24,9 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 18 cache: npm diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index a37f001d1..c059e0317 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -22,10 +22,11 @@ jobs: ports: - 5432:5432 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: 18 + cache: npm - name: Install dependencies run: npm ci - name: Copy env diff --git a/.github/workflows/issue-assignee-check.yml b/.github/workflows/issue-assignee-check.yml index 7c415de7c..9ecc23339 100644 --- a/.github/workflows/issue-assignee-check.yml +++ b/.github/workflows/issue-assignee-check.yml @@ -18,6 +18,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: '18' + cache: npm - name: Install Octokit run: npm install @octokit/rest@18 diff --git a/.github/workflows/pr-review-reminder.yml b/.github/workflows/pr-review-reminder.yml index f0928dfcb..6b16c611f 100644 --- a/.github/workflows/pr-review-reminder.yml +++ b/.github/workflows/pr-review-reminder.yml @@ -18,6 +18,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: '18' + cache: npm - name: Install Octokit run: npm install @octokit/rest@18 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 1fe91e9ab..4c0fa757c 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,7 +13,7 @@ jobs: pull-requests: write steps: - - uses: actions/stale@v3 + - uses: actions/stale@v4 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-pr-stale: 30 From 52e696c90eea9458a70160c7e05fa100e700d6c0 Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Mon, 4 Dec 2023 18:12:43 +0530 Subject: [PATCH 24/45] chore: update pr labeler workflow Signed-off-by: Adithya Krishna --- .github/pr-labeler.yml | 47 ++++++++---------------------------------- 1 file changed, 9 insertions(+), 38 deletions(-) diff --git a/.github/pr-labeler.yml b/.github/pr-labeler.yml index b9695c993..4fb68aa2f 100644 --- a/.github/pr-labeler.yml +++ b/.github/pr-labeler.yml @@ -4,47 +4,18 @@ 'apps: web': - apps/web/** -'docker': - - docker/** +'version bump 👀': + - '**/package.json' + - '**/package-lock.json' -'scripts': - - scripts/** - -'migrations': +'🚨 migrations 🚨': - packages/prisma/migrations/**/migration.sql -'e2e tests changes': +'🚨 e2e changes 🚨': - packages/app-tests/e2e/** -'pkg: assets': - - packages/assets/** +"🚨 .env changes 🚨": + - .env.example -'pkg: email': - - packages/email/** - -'pkg: eslint-config': - - packages/eslint-config/** - -'pkg: lib': - - packages/lib/** - -'pkg: prettier-config': - - packages/prettier-config/** - -'pkg: prisma': - - packages/prisma/** - -'pkg: signing': - - packages/signing/** - -'pkg: tailwind-config': - - packages/tailwind-config/** - -'pkg: trpc': - - packages/trpc/** - -'pkg: tsconfig': - - packages/tsconfig/** - -'pkg: ui': - - packages/ui/** +'pkg: ee changes': + - packages/ee/** From bfedabdc10da834536cf75936f5b2720473c1995 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 5 Dec 2023 03:54:41 +0100 Subject: [PATCH 25/45] fix: increase e2e test timeout (#682) --- .github/workflows/e2e-tests.yml | 28 ++++++++----------- apps/web/package.json | 1 + package-lock.json | 4 +-- packages/app-tests/package.json | 2 +- packages/app-tests/playwright.config.ts | 4 +++ .../migration.sql | 5 ++++ packages/prisma/schema.prisma | 2 +- 7 files changed, 25 insertions(+), 21 deletions(-) create mode 100644 packages/prisma/migrations/20231205000309_add_cascade_delete_for_verification_tokens/migration.sql diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index a37f001d1..f2342f446 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -8,19 +8,6 @@ jobs: e2e_tests: timeout-minutes: 60 runs-on: ubuntu-latest - services: - postgres: - image: postgres - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 @@ -28,24 +15,31 @@ jobs: node-version: 18 - name: Install dependencies run: npm ci + - name: Copy env run: cp .env.example .env + + - name: Start Services + run: npm run dx:up + - name: Install Playwright Browsers run: npx playwright install --with-deps + - name: Generate Prisma Client run: npm run prisma:generate -w @documenso/prisma + - name: Create the database run: npm run prisma:migrate-dev + - name: Run Playwright tests run: npm run ci + - uses: actions/upload-artifact@v3 if: always() with: - name: playwright-report - path: playwright-report/ + name: test-results + path: "packages/app-tests/**/test-results/*" retention-days: 30 env: - NEXT_PRIVATE_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/documenso - NEXT_PRIVATE_DIRECT_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/documenso TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }} diff --git a/apps/web/package.json b/apps/web/package.json index 47d94fb63..150982c2d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -8,6 +8,7 @@ "build": "next build", "start": "next start", "lint": "next lint", + "e2e:prepare": "next build && next start", "lint:fix": "next lint --fix", "clean": "rimraf .next && rimraf node_modules", "copy:pdfjs": "node ../../scripts/copy-pdfjs.cjs" diff --git a/package-lock.json b/package-lock.json index 4d4be5be4..80f31d7a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ }, "apps/marketing": { "name": "@documenso/marketing", - "version": "1.2.1", + "version": "1.2.3", "license": "AGPL-3.0", "dependencies": { "@documenso/assets": "*", @@ -85,7 +85,7 @@ }, "apps/web": { "name": "@documenso/web", - "version": "1.2.1", + "version": "1.2.3", "license": "AGPL-3.0", "dependencies": { "@documenso/assets": "*", diff --git a/packages/app-tests/package.json b/packages/app-tests/package.json index 92cfd169d..e6f09a0fc 100644 --- a/packages/app-tests/package.json +++ b/packages/app-tests/package.json @@ -6,7 +6,7 @@ "main": "index.js", "scripts": { "test:dev": "playwright test", - "test:e2e": "start-server-and-test \"(cd ../../apps/web && npm run start)\" http://localhost:3000 \"playwright test\"" + "test:e2e": "start-server-and-test \"npm run start -w @documenso/web\" http://localhost:3000 \"playwright test\"" }, "keywords": [], "author": "", diff --git a/packages/app-tests/playwright.config.ts b/packages/app-tests/playwright.config.ts index 463b6f97d..672c2f7ef 100644 --- a/packages/app-tests/playwright.config.ts +++ b/packages/app-tests/playwright.config.ts @@ -28,8 +28,12 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', + + video: 'retain-on-failure', }, + timeout: 30_000, + /* Configure projects for major browsers */ projects: [ { diff --git a/packages/prisma/migrations/20231205000309_add_cascade_delete_for_verification_tokens/migration.sql b/packages/prisma/migrations/20231205000309_add_cascade_delete_for_verification_tokens/migration.sql new file mode 100644 index 000000000..26d7cce51 --- /dev/null +++ b/packages/prisma/migrations/20231205000309_add_cascade_delete_for_verification_tokens/migration.sql @@ -0,0 +1,5 @@ +-- DropForeignKey +ALTER TABLE "VerificationToken" DROP CONSTRAINT "VerificationToken_userId_fkey"; + +-- AddForeignKey +ALTER TABLE "VerificationToken" ADD CONSTRAINT "VerificationToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 7407bc5c0..2c6f1113e 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -60,7 +60,7 @@ model VerificationToken { expires DateTime createdAt DateTime @default(now()) userId Int - user User @relation(fields: [userId], references: [id]) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) } enum SubscriptionStatus { From 8ab1b0cf6b088e16e37ae6d0aa6594edc06f9434 Mon Sep 17 00:00:00 2001 From: Kritarth Sharma Date: Tue, 5 Dec 2023 08:30:48 +0530 Subject: [PATCH 26/45] fix: add workspace settings for eol and tabs (#725) --- .vscode/settings.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 38d6f1e73..97d5d1948 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,8 @@ "eslint.validate": ["typescript", "typescriptreact", "javascript", "javascriptreact"], "javascript.preferences.importModuleSpecifier": "non-relative", "javascript.preferences.useAliasesForRenames": false, - "typescript.enablePromptUseWorkspaceTsdk": true + "typescript.enablePromptUseWorkspaceTsdk": true, + "files.eol": "\n", + "editor.tabSize": 2, + "editor.insertSpaces": true } From 520522bef796c431a735619f3ff848e92d2ac0ca Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Tue, 5 Dec 2023 12:05:47 +0530 Subject: [PATCH 27/45] chore: removed repo condition for codeql Signed-off-by: Adithya Krishna --- .github/workflows/codeql-analysis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 79919f7a5..c51216165 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -9,7 +9,6 @@ on: jobs: analyze: - if: github.repository == 'documenso/documenso' name: Analyze runs-on: ubuntu-latest permissions: From 741201822ae6e823821f09404aa4e40507294afd Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Tue, 5 Dec 2023 10:12:28 +0000 Subject: [PATCH 28/45] fix: use useSession instead of prop drilling --- apps/web/src/app/(dashboard)/documents/page.tsx | 2 +- apps/web/src/app/(dashboard)/documents/upload-document.tsx | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/web/src/app/(dashboard)/documents/page.tsx b/apps/web/src/app/(dashboard)/documents/page.tsx index cd7f868ad..f38668fd9 100644 --- a/apps/web/src/app/(dashboard)/documents/page.tsx +++ b/apps/web/src/app/(dashboard)/documents/page.tsx @@ -63,7 +63,7 @@ export default async function DocumentsPage({ searchParams = {} }: DocumentsPage return (
- +

Documents

diff --git a/apps/web/src/app/(dashboard)/documents/upload-document.tsx b/apps/web/src/app/(dashboard)/documents/upload-document.tsx index eec881e67..7c59ffb4c 100644 --- a/apps/web/src/app/(dashboard)/documents/upload-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/upload-document.tsx @@ -6,6 +6,7 @@ import Link from 'next/link'; import { useRouter } from 'next/navigation'; import { Loader } from 'lucide-react'; +import { useSession } from 'next-auth/react'; import { useLimits } from '@documenso/ee/server-only/limits/provider/client'; import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics'; @@ -19,12 +20,12 @@ import { useToast } from '@documenso/ui/primitives/use-toast'; export type UploadDocumentProps = { className?: string; - userId?: number; }; -export const UploadDocument = ({ className, userId }: UploadDocumentProps) => { +export const UploadDocument = ({ className }: UploadDocumentProps) => { const router = useRouter(); const analytics = useAnalytics(); + const { data: session } = useSession(); const { toast } = useToast(); @@ -57,7 +58,7 @@ export const UploadDocument = ({ className, userId }: UploadDocumentProps) => { }); analytics.capture('App: Document Uploaded', { - userId, + userId: session?.user.id, documentId: id, timestamp: new Date().toISOString(), }); From 0baa2696b41764ac1ead0354a8bae8ec43437115 Mon Sep 17 00:00:00 2001 From: Ephraim Atta-Duncan Date: Tue, 5 Dec 2023 10:13:24 +0000 Subject: [PATCH 29/45] fix: removed unused code --- packages/lib/universal/get-feature-flag.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/lib/universal/get-feature-flag.ts b/packages/lib/universal/get-feature-flag.ts index bf79f79ce..0fc0aa131 100644 --- a/packages/lib/universal/get-feature-flag.ts +++ b/packages/lib/universal/get-feature-flag.ts @@ -18,10 +18,6 @@ export const getFlag = async ( ): Promise => { const requestHeaders = options?.requestHeaders ?? {}; - if (!LOCAL_FEATURE_FLAGS[flag]) { - return LOCAL_FEATURE_FLAGS[flag]; - } - if (!isFeatureFlagEnabled()) { return LOCAL_FEATURE_FLAGS[flag] ?? true; } From 2068d980ffca0db1b079c476b993866328c5953e Mon Sep 17 00:00:00 2001 From: Sushant Date: Wed, 6 Dec 2023 05:41:51 +0530 Subject: [PATCH 30/45] feat: allow for the deletion of any document (#711) Allow for the deletion of any document with notifications of document cancellation for pending documents. --- .github/workflows/e2e-tests.yml | 3 + .../documents/data-table-action-dropdown.tsx | 7 +- .../app/(dashboard)/documents/data-table.tsx | 15 +- ...-dialog.tsx => delete-document-dialog.tsx} | 63 +++-- .../(signing)/sign/[token]/complete/page.tsx | 24 +- .../sign/[token]/no-longer-available.tsx | 66 ++++++ .../src/app/(signing)/sign/[token]/page.tsx | 14 ++ .../(dashboard)/layout/profile-dropdown.tsx | 8 +- .../e2e/pr-711-deletion-of-documents.spec.ts | 192 +++++++++++++++ packages/app-tests/e2e/test-auth-flow.spec.ts | 8 +- packages/app-tests/package.json | 1 + packages/app-tests/tsconfig.json | 8 + .../template-document-cancel.tsx | 34 +++ packages/email/templates/document-cancel.tsx | 66 ++++++ .../server-only/document/delete-document.ts | 88 +++++++ .../document/delete-draft-document.ts | 13 -- .../server-only/document/find-documents.ts | 20 +- .../lib/server-only/document/get-stats.ts | 33 ++- .../field/sign-field-with-token.ts | 4 + .../migration.sql | 2 + packages/prisma/schema.prisma | 1 + packages/prisma/seed-database.ts | 76 +----- packages/prisma/seed/initial-seed.ts | 67 ++++++ .../seed/pr-711-deletion-of-documents.ts | 221 ++++++++++++++++++ .../trpc/server/document-router/router.ts | 8 +- .../trpc/server/document-router/schema.ts | 3 +- 26 files changed, 913 insertions(+), 132 deletions(-) rename apps/web/src/app/(dashboard)/documents/{delete-draft-document-dialog.tsx => delete-document-dialog.tsx} (53%) create mode 100644 apps/web/src/app/(signing)/sign/[token]/no-longer-available.tsx create mode 100644 packages/app-tests/e2e/pr-711-deletion-of-documents.spec.ts create mode 100644 packages/app-tests/tsconfig.json create mode 100644 packages/email/template-components/template-document-cancel.tsx create mode 100644 packages/email/templates/document-cancel.tsx create mode 100644 packages/lib/server-only/document/delete-document.ts delete mode 100644 packages/lib/server-only/document/delete-draft-document.ts create mode 100644 packages/prisma/migrations/20231202134005_deletedocuments/migration.sql create mode 100644 packages/prisma/seed/initial-seed.ts create mode 100644 packages/prisma/seed/pr-711-deletion-of-documents.ts diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index f2342f446..50c25f923 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -31,6 +31,9 @@ jobs: - name: Create the database run: npm run prisma:migrate-dev + - name: Seed the database + run: npm run prisma:seed + - name: Run Playwright tests run: npm run ci diff --git a/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx b/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx index c3e1f971c..9c3532f88 100644 --- a/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx +++ b/apps/web/src/app/(dashboard)/documents/data-table-action-dropdown.tsx @@ -32,7 +32,7 @@ import { } from '@documenso/ui/primitives/dropdown-menu'; import { ResendDocumentActionItem } from './_action-items/resend-document'; -import { DeleteDraftDocumentDialog } from './delete-draft-document-dialog'; +import { DeleteDocumentDialog } from './delete-document-dialog'; import { DuplicateDocumentDialog } from './duplicate-document-dialog'; export type DataTableActionDropdownProps = { @@ -60,7 +60,7 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) = // const isPending = row.status === DocumentStatus.PENDING; const isComplete = row.status === DocumentStatus.COMPLETED; // const isSigned = recipient?.signingStatus === SigningStatus.SIGNED; - const isDocumentDeletable = isOwner && row.status === DocumentStatus.DRAFT; + const isDocumentDeletable = isOwner; const onDownloadClick = async () => { let document: DocumentWithData | null = null; @@ -161,8 +161,9 @@ export const DataTableActionDropdown = ({ row }: DataTableActionDropdownProps) = {isDocumentDeletable && ( - diff --git a/apps/web/src/app/(dashboard)/documents/data-table.tsx b/apps/web/src/app/(dashboard)/documents/data-table.tsx index 9d07b8278..c8adb1422 100644 --- a/apps/web/src/app/(dashboard)/documents/data-table.tsx +++ b/apps/web/src/app/(dashboard)/documents/data-table.tsx @@ -8,6 +8,7 @@ import { useSession } from 'next-auth/react'; import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-update-search-params'; import type { FindResultSet } from '@documenso/lib/types/find-result-set'; import type { Document, Recipient, User } from '@documenso/prisma/client'; +import { ExtendedDocumentStatus } from '@documenso/prisma/types/extended-document-status'; import { DataTable } from '@documenso/ui/primitives/data-table'; import { DataTablePagination } from '@documenso/ui/primitives/data-table-pagination'; @@ -74,12 +75,14 @@ export const DocumentsDataTable = ({ results }: DocumentsDataTableProps) => { }, { header: 'Actions', - cell: ({ row }) => ( -
- - -
- ), + cell: ({ row }) => + (!row.original.deletedAt || + row.original.status === ExtendedDocumentStatus.COMPLETED) && ( +
+ + +
+ ), }, ]} data={results.data} diff --git a/apps/web/src/app/(dashboard)/documents/delete-draft-document-dialog.tsx b/apps/web/src/app/(dashboard)/documents/delete-document-dialog.tsx similarity index 53% rename from apps/web/src/app/(dashboard)/documents/delete-draft-document-dialog.tsx rename to apps/web/src/app/(dashboard)/documents/delete-document-dialog.tsx index 1a458a13d..5b4a84286 100644 --- a/apps/web/src/app/(dashboard)/documents/delete-draft-document-dialog.tsx +++ b/apps/web/src/app/(dashboard)/documents/delete-document-dialog.tsx @@ -1,5 +1,8 @@ +import { useState } from 'react'; + import { useRouter } from 'next/navigation'; +import { DocumentStatus } from '@documenso/prisma/client'; import { trpc as trpcReact } from '@documenso/trpc/react'; import { Button } from '@documenso/ui/primitives/button'; import { @@ -10,41 +13,46 @@ import { DialogHeader, DialogTitle, } from '@documenso/ui/primitives/dialog'; +import { Input } from '@documenso/ui/primitives/input'; import { useToast } from '@documenso/ui/primitives/use-toast'; type DeleteDraftDocumentDialogProps = { id: number; open: boolean; onOpenChange: (_open: boolean) => void; + status: DocumentStatus; }; -export const DeleteDraftDocumentDialog = ({ +export const DeleteDocumentDialog = ({ id, open, onOpenChange, + status, }: DeleteDraftDocumentDialogProps) => { const router = useRouter(); const { toast } = useToast(); - const { mutateAsync: deleteDocument, isLoading } = - trpcReact.document.deleteDraftDocument.useMutation({ - onSuccess: () => { - router.refresh(); + const [inputValue, setInputValue] = useState(''); + const [isDeleteEnabled, setIsDeleteEnabled] = useState(status === DocumentStatus.DRAFT); - toast({ - title: 'Document deleted', - description: 'Your document has been successfully deleted.', - duration: 5000, - }); + const { mutateAsync: deleteDocument, isLoading } = trpcReact.document.deleteDocument.useMutation({ + onSuccess: () => { + router.refresh(); - onOpenChange(false); - }, - }); + toast({ + title: 'Document deleted', + description: 'Your document has been successfully deleted.', + duration: 5000, + }); - const onDraftDelete = async () => { + onOpenChange(false); + }, + }); + + const onDelete = async () => { try { - await deleteDocument({ id }); + await deleteDocument({ id, status }); } catch { toast({ title: 'Something went wrong', @@ -55,6 +63,11 @@ export const DeleteDraftDocumentDialog = ({ } }; + const onInputChange = (event: React.ChangeEvent) => { + setInputValue(event.target.value); + setIsDeleteEnabled(event.target.value === 'delete'); + }; + return ( !isLoading && onOpenChange(value)}> @@ -67,6 +80,17 @@ export const DeleteDraftDocumentDialog = ({ + {status !== DocumentStatus.DRAFT && ( +
+ +
+ )} +
diff --git a/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx b/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx index b9a8ba6d7..54757667a 100644 --- a/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/complete/page.tsx @@ -67,18 +67,24 @@ export default async function CompletedSigningPage({ />
- {match(document.status) - .with(DocumentStatus.COMPLETED, () => ( + {match({ status: document.status, deletedAt: document.deletedAt }) + .with({ status: DocumentStatus.COMPLETED }, () => (
Everyone has signed
)) - .otherwise(() => ( + .with({ deletedAt: null }, () => (
Waiting for others to sign
+ )) + .otherwise(() => ( +
+ + Document no longer available to sign +
))}

@@ -86,16 +92,22 @@ export default async function CompletedSigningPage({ "{document.title}"

- {match(document.status) - .with(DocumentStatus.COMPLETED, () => ( + {match({ status: document.status, deletedAt: document.deletedAt }) + .with({ status: DocumentStatus.COMPLETED }, () => (

Everyone has signed! You will receive an Email copy of the signed document.

)) - .otherwise(() => ( + .with({ deletedAt: null }, () => (

You will receive an Email copy of the signed document once everyone has signed.

+ )) + .otherwise(() => ( +

+ This document has been cancelled by the owner and is no longer available for others to + sign. +

))}
diff --git a/apps/web/src/app/(signing)/sign/[token]/no-longer-available.tsx b/apps/web/src/app/(signing)/sign/[token]/no-longer-available.tsx new file mode 100644 index 000000000..8c7051caa --- /dev/null +++ b/apps/web/src/app/(signing)/sign/[token]/no-longer-available.tsx @@ -0,0 +1,66 @@ +import React from 'react'; + +import Link from 'next/link'; + +import { Clock8 } from 'lucide-react'; +import { useSession } from 'next-auth/react'; + +import signingCelebration from '@documenso/assets/images/signing-celebration.png'; +import type { Document, Signature } from '@documenso/prisma/client'; +import { SigningCard3D } from '@documenso/ui/components/signing-card'; + +type NoLongerAvailableProps = { + document: Document; + recipientName: string; + recipientSignature: Signature; +}; + +export const NoLongerAvailable = ({ + document, + recipientName, + recipientSignature, +}: NoLongerAvailableProps) => { + const { data: session } = useSession(); + + return ( +
+ + +
+
+ + Document Cancelled +
+ +

+ "{document.title}" + is no longer available to sign +

+ +

+ This document has been cancelled by the owner. +

+ + {session?.user ? ( + + Go Back Home + + ) : ( +

+ Want to send slick signing links like this one?{' '} + + Check out Documenso. + +

+ )} +
+
+ ); +}; diff --git a/apps/web/src/app/(signing)/sign/[token]/page.tsx b/apps/web/src/app/(signing)/sign/[token]/page.tsx index 67e679412..17789453e 100644 --- a/apps/web/src/app/(signing)/sign/[token]/page.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/page.tsx @@ -8,6 +8,7 @@ import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document import { viewedDocument } from '@documenso/lib/server-only/document/viewed-document'; import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token'; import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token'; +import { getRecipientSignatures } from '@documenso/lib/server-only/recipient/get-recipient-signatures'; import { DocumentStatus, FieldType, SigningStatus } from '@documenso/prisma/client'; import { Card, CardContent } from '@documenso/ui/primitives/card'; import { ElementVisible } from '@documenso/ui/primitives/element-visible'; @@ -17,6 +18,7 @@ import { DateField } from './date-field'; import { EmailField } from './email-field'; import { SigningForm } from './form'; import { NameField } from './name-field'; +import { NoLongerAvailable } from './no-longer-available'; import { SigningProvider } from './provider'; import { SignatureField } from './signature-field'; @@ -55,6 +57,18 @@ export default async function SigningPage({ params: { token } }: SigningPageProp redirect(`/sign/${token}/complete`); } + const recipientSignature = (await getRecipientSignatures({ recipientId: recipient.id }))[0]; + + if (document.deletedAt) { + return ( + + ); + } + return ( { return ( -
diff --git a/apps/marketing/src/app/(marketing)/singleplayer/client.tsx b/apps/marketing/src/app/(marketing)/singleplayer/client.tsx index 1dcb2d76b..b7654c7cf 100644 --- a/apps/marketing/src/app/(marketing)/singleplayer/client.tsx +++ b/apps/marketing/src/app/(marketing)/singleplayer/client.tsx @@ -17,15 +17,14 @@ import { AddFieldsFormPartial } from '@documenso/ui/primitives/document-flow/add import type { TAddFieldsFormSchema } from '@documenso/ui/primitives/document-flow/add-fields.types'; import { AddSignatureFormPartial } from '@documenso/ui/primitives/document-flow/add-signature'; import type { TAddSignatureFormSchema } from '@documenso/ui/primitives/document-flow/add-signature.types'; -import { - DocumentFlowFormContainer, - DocumentFlowFormContainerHeader, -} from '@documenso/ui/primitives/document-flow/document-flow-root'; +import { DocumentFlowFormContainer } from '@documenso/ui/primitives/document-flow/document-flow-root'; import type { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types'; import { LazyPDFViewer } from '@documenso/ui/primitives/lazy-pdf-viewer'; +import { Stepper } from '@documenso/ui/primitives/stepper'; import { useToast } from '@documenso/ui/primitives/use-toast'; -type SinglePlayerModeStep = 'fields' | 'sign'; +const SinglePlayerModeSteps = ['fields', 'sign'] as const; +type SinglePlayerModeStep = (typeof SinglePlayerModeSteps)[number]; // !: This entire file is a hack to get around failed prerendering of // !: the Single Player Mode page. This regression was introduced during @@ -226,37 +225,35 @@ export const SinglePlayerClient = () => {
- e.preventDefault()}> - - - {/* Add fields to PDF page. */} - {step === 'fields' && ( + e.preventDefault()} + > + setStep(SinglePlayerModeSteps[step - 1])} + > + {/* Add fields to PDF page. */}
- )} - {/* Enter user details and signature. */} - {step === 'sign' && ( + {/* Enter user details and signature. */} + field.type === 'NAME'))} requireSignature={Boolean(fields.find((field) => field.type === 'SIGNATURE'))} /> - )} +
diff --git a/apps/marketing/src/pages/api/trpc/[trpc].ts b/apps/marketing/src/pages/api/trpc/[trpc].ts index 0bc991a98..c43291ea1 100644 --- a/apps/marketing/src/pages/api/trpc/[trpc].ts +++ b/apps/marketing/src/pages/api/trpc/[trpc].ts @@ -4,6 +4,11 @@ import { appRouter } from '@documenso/trpc/server/router'; export const config = { maxDuration: 60, + api: { + bodyParser: { + sizeLimit: '50mb', + }, + }, }; export default trpcNext.createNextApiHandler({ diff --git a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx index 53da2d353..ffce3bd6c 100644 --- a/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx +++ b/apps/web/src/app/(dashboard)/documents/[id]/edit-document.tsx @@ -204,6 +204,7 @@ export const EditDocumentForm = ({ document={document} onSubmit={onAddTitleFormSubmit} /> + & { disabled?: boolean; diff --git a/packages/ui/components/document/document-share-button.tsx b/packages/ui/components/document/document-share-button.tsx index 5b6e9006a..b366123fb 100644 --- a/packages/ui/components/document/document-share-button.tsx +++ b/packages/ui/components/document/document-share-button.tsx @@ -13,8 +13,9 @@ import { } from '@documenso/lib/constants/toast'; import { generateTwitterIntent } from '@documenso/lib/universal/generate-twitter-intent'; import { trpc } from '@documenso/trpc/react'; -import { cn } from '@documenso/ui/lib/utils'; -import { Button } from '@documenso/ui/primitives/button'; + +import { cn } from '../../lib/utils'; +import { Button } from '../../primitives/button'; import { Dialog, DialogContent, @@ -22,8 +23,8 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from '@documenso/ui/primitives/dialog'; -import { useToast } from '@documenso/ui/primitives/use-toast'; +} from '../../primitives/dialog'; +import { useToast } from '../../primitives/use-toast'; export type DocumentShareButtonProps = HTMLAttributes & { token?: string; diff --git a/packages/ui/components/field/field-tooltip.tsx b/packages/ui/components/field/field-tooltip.tsx index 446b14d2d..3966e9c0c 100644 --- a/packages/ui/components/field/field-tooltip.tsx +++ b/packages/ui/components/field/field-tooltip.tsx @@ -1,17 +1,18 @@ import { TooltipArrow } from '@radix-ui/react-tooltip'; -import { VariantProps, cva } from 'class-variance-authority'; +import type { VariantProps } from 'class-variance-authority'; +import { cva } from 'class-variance-authority'; import { createPortal } from 'react-dom'; import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords'; -import { cn } from '@documenso/ui/lib/utils'; + +import { cn } from '../..//lib/utils'; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, -} from '@documenso/ui/primitives/tooltip'; - -import { Field } from '.prisma/client'; +} from '../..//primitives/tooltip'; +import type { Field } from '.prisma/client'; const tooltipVariants = cva('font-semibold', { variants: { diff --git a/packages/ui/components/field/field.tsx b/packages/ui/components/field/field.tsx index 054cc6376..e40b2e3d9 100644 --- a/packages/ui/components/field/field.tsx +++ b/packages/ui/components/field/field.tsx @@ -5,9 +5,10 @@ import React, { useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import { useFieldPageCoords } from '@documenso/lib/client-only/hooks/use-field-page-coords'; -import { Field } from '@documenso/prisma/client'; -import { cn } from '@documenso/ui/lib/utils'; -import { Card, CardContent } from '@documenso/ui/primitives/card'; +import type { Field } from '@documenso/prisma/client'; + +import { cn } from '../../lib/utils'; +import { Card, CardContent } from '../../primitives/card'; export type FieldRootContainerProps = { field: Field; diff --git a/packages/ui/components/signing-card.tsx b/packages/ui/components/signing-card.tsx index ab057c4e5..cda0c31c3 100644 --- a/packages/ui/components/signing-card.tsx +++ b/packages/ui/components/signing-card.tsx @@ -2,14 +2,16 @@ import { useCallback, useEffect, useRef, useState } from 'react'; -import Image, { StaticImageData } from 'next/image'; +import type { StaticImageData } from 'next/image'; +import Image from 'next/image'; import { animate, motion, useMotionTemplate, useMotionValue, useTransform } from 'framer-motion'; import { P, match } from 'ts-pattern'; -import { Signature } from '@documenso/prisma/client'; -import { cn } from '@documenso/ui/lib/utils'; -import { Card, CardContent } from '@documenso/ui/primitives/card'; +import type { Signature } from '@documenso/prisma/client'; + +import { cn } from '../lib/utils'; +import { Card, CardContent } from '../primitives/card'; export type SigningCardProps = { className?: string; diff --git a/packages/ui/primitives/combobox.tsx b/packages/ui/primitives/combobox.tsx index 899ccd61d..85f86056d 100644 --- a/packages/ui/primitives/combobox.tsx +++ b/packages/ui/primitives/combobox.tsx @@ -3,16 +3,11 @@ import * as React from 'react'; import { Check, ChevronsUpDown } from 'lucide-react'; import { Role } from '@documenso/prisma/client'; -import { cn } from '@documenso/ui/lib/utils'; -import { Button } from '@documenso/ui/primitives/button'; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, -} from '@documenso/ui/primitives/command'; -import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover'; + +import { cn } from '../lib/utils'; +import { Button } from './button'; +import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from './command'; +import { Popover, PopoverContent, PopoverTrigger } from './popover'; type ComboboxProps = { listValues: string[]; diff --git a/packages/ui/primitives/document-dropzone.tsx b/packages/ui/primitives/document-dropzone.tsx index 6987e9872..d81a3a7de 100644 --- a/packages/ui/primitives/document-dropzone.tsx +++ b/packages/ui/primitives/document-dropzone.tsx @@ -1,12 +1,14 @@ 'use client'; -import { Variants, motion } from 'framer-motion'; +import type { Variants } from 'framer-motion'; +import { motion } from 'framer-motion'; import { Plus } from 'lucide-react'; import { useDropzone } from 'react-dropzone'; import { megabytesToBytes } from '@documenso/lib/universal/unit-convertions'; -import { cn } from '@documenso/ui/lib/utils'; -import { Card, CardContent } from '@documenso/ui/primitives/card'; + +import { cn } from '../lib/utils'; +import { Card, CardContent } from './card'; const DocumentDropzoneContainerVariants: Variants = { initial: { diff --git a/packages/ui/primitives/document-flow/add-fields.tsx b/packages/ui/primitives/document-flow/add-fields.tsx index 112f2f849..a8ae9f0e3 100644 --- a/packages/ui/primitives/document-flow/add-fields.tsx +++ b/packages/ui/primitives/document-flow/add-fields.tsx @@ -13,20 +13,14 @@ import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer'; import { nanoid } from '@documenso/lib/universal/id'; import type { Field, Recipient } from '@documenso/prisma/client'; import { FieldType, SendStatus } from '@documenso/prisma/client'; -import { cn } from '@documenso/ui/lib/utils'; -import { Button } from '@documenso/ui/primitives/button'; -import { Card, CardContent } from '@documenso/ui/primitives/card'; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, -} from '@documenso/ui/primitives/command'; -import { Popover, PopoverContent, PopoverTrigger } from '@documenso/ui/primitives/popover'; -import { Tooltip, TooltipContent, TooltipTrigger } from '@documenso/ui/primitives/tooltip'; +import { cn } from '../../lib/utils'; +import { Button } from '../button'; +import { Card, CardContent } from '../card'; +import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem } from '../command'; +import { Popover, PopoverContent, PopoverTrigger } from '../popover'; import { useStep } from '../stepper'; +import { Tooltip, TooltipContent, TooltipTrigger } from '../tooltip'; import type { TAddFieldsFormSchema } from './add-fields.types'; import { DocumentFlowFormContainerActions, diff --git a/packages/ui/primitives/document-flow/add-signature.tsx b/packages/ui/primitives/document-flow/add-signature.tsx index aed252083..e4e5d9253 100644 --- a/packages/ui/primitives/document-flow/add-signature.tsx +++ b/packages/ui/primitives/document-flow/add-signature.tsx @@ -9,35 +9,38 @@ import { match } from 'ts-pattern'; import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer'; import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields'; -import { Field, FieldType } from '@documenso/prisma/client'; -import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature'; -import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip'; -import { cn } from '@documenso/ui/lib/utils'; -import { Card, CardContent } from '@documenso/ui/primitives/card'; -import { TAddSignatureFormSchema } from '@documenso/ui/primitives/document-flow/add-signature.types'; +import type { Field } from '@documenso/prisma/client'; +import { FieldType } from '@documenso/prisma/client'; +import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature'; + +import { FieldToolTip } from '../../components/field/field-tooltip'; +import { cn } from '../../lib/utils'; +import { Card, CardContent } from '../card'; +import { ElementVisible } from '../element-visible'; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../form/form'; +import { Input } from '../input'; +import { SignaturePad } from '../signature-pad'; +import { useStep } from '../stepper'; +import type { TAddSignatureFormSchema } from './add-signature.types'; +import { ZAddSignatureFormSchema } from './add-signature.types'; import { DocumentFlowFormContainerActions, DocumentFlowFormContainerContent, DocumentFlowFormContainerFooter, + DocumentFlowFormContainerHeader, DocumentFlowFormContainerStep, -} from '@documenso/ui/primitives/document-flow/document-flow-root'; -import { DocumentFlowStep } from '@documenso/ui/primitives/document-flow/types'; -import { ElementVisible } from '@documenso/ui/primitives/element-visible'; -import { Input } from '@documenso/ui/primitives/input'; -import { SignaturePad } from '@documenso/ui/primitives/signature-pad'; - -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../form/form'; -import { ZAddSignatureFormSchema } from './add-signature.types'; +} from './document-flow-root'; import { SinglePlayerModeCustomTextField, SinglePlayerModeSignatureField, } from './single-player-mode-fields'; +import type { DocumentFlowStep } from './types'; export type AddSignatureFormProps = { defaultValues?: TAddSignatureFormSchema; documentFlow: DocumentFlowStep; fields: FieldWithSignature[]; - numberOfSteps: number; + onSubmit: (_data: TAddSignatureFormSchema) => Promise | void; requireName?: boolean; requireSignature?: boolean; @@ -47,11 +50,13 @@ export const AddSignatureFormPartial = ({ defaultValues, documentFlow, fields, - numberOfSteps, + onSubmit, requireName = false, requireSignature = true, }: AddSignatureFormProps) => { + const { currentStep, totalSteps } = useStep(); + const [validateUninsertedFields, setValidateUninsertedFields] = useState(false); // Refined schema which takes into account whether to allow an empty name or signature. @@ -206,46 +211,30 @@ export const AddSignatureFormPartial = ({ }; return ( -
-
- -
- ( - - Email - - { - onFormValueChange(FieldType.EMAIL); - field.onChange(value); - }} - /> - - - - )} - /> + <> + - {requireName && ( + +
+ +
( - Name + Email { - onFormValueChange(FieldType.NAME); + onFormValueChange(FieldType.EMAIL); field.onChange(value); }} /> @@ -254,91 +243,114 @@ export const AddSignatureFormPartial = ({ )} /> - )} - {requireSignature && ( - ( - - Signature - - - - { - onFormValueChange(FieldType.SIGNATURE); - field.onChange(value); - }} - /> - - - - - - )} - /> - )} -
-
+ {requireName && ( + ( + + Name + + { + onFormValueChange(FieldType.NAME); + field.onChange(value); + }} + /> + + + + )} + /> + )} - - + {requireSignature && ( + ( + + Signature + + + + { + onFormValueChange(FieldType.SIGNATURE); + field.onChange(value); + }} + /> + + + + + + )} + /> + )} +
+
- - -
+ + - {validateUninsertedFields && uninsertedFields[0] && ( - - Click to insert field - - )} + + + - - {localFields.map((field) => - match(field.type) - .with(FieldType.DATE, FieldType.EMAIL, FieldType.NAME, () => { - return ( - + Click to insert field + + )} + + + {localFields.map((field) => + match(field.type) + .with(FieldType.DATE, FieldType.EMAIL, FieldType.NAME, () => { + return ( + + ); + }) + .with(FieldType.SIGNATURE, () => ( + - ); - }) - .with(FieldType.SIGNATURE, () => ( - - )) - .otherwise(() => { - return null; - }), - )} - - + )) + .otherwise(() => { + return null; + }), + )} +
+ + ); }; diff --git a/packages/ui/primitives/document-flow/add-signers.tsx b/packages/ui/primitives/document-flow/add-signers.tsx index 13af03d26..71be1c069 100644 --- a/packages/ui/primitives/document-flow/add-signers.tsx +++ b/packages/ui/primitives/document-flow/add-signers.tsx @@ -12,13 +12,13 @@ import { nanoid } from '@documenso/lib/universal/id'; import type { Field, Recipient } from '@documenso/prisma/client'; import { DocumentStatus, SendStatus } from '@documenso/prisma/client'; import type { DocumentWithData } from '@documenso/prisma/types/document-with-data'; -import { Button } from '@documenso/ui/primitives/button'; -import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message'; -import { Input } from '@documenso/ui/primitives/input'; -import { Label } from '@documenso/ui/primitives/label'; -import { useToast } from '@documenso/ui/primitives/use-toast'; +import { Button } from '../button'; +import { FormErrorMessage } from '../form/form-error-message'; +import { Input } from '../input'; +import { Label } from '../label'; import { useStep } from '../stepper'; +import { useToast } from '../use-toast'; import type { TAddSignersFormSchema } from './add-signers.types'; import { ZAddSignersFormSchema } from './add-signers.types'; import { diff --git a/packages/ui/primitives/document-flow/add-subject.tsx b/packages/ui/primitives/document-flow/add-subject.tsx index e9e761af0..881d59c74 100644 --- a/packages/ui/primitives/document-flow/add-subject.tsx +++ b/packages/ui/primitives/document-flow/add-subject.tsx @@ -5,12 +5,12 @@ import { useForm } from 'react-hook-form'; import type { Field, Recipient } from '@documenso/prisma/client'; import { DocumentStatus } from '@documenso/prisma/client'; import type { DocumentWithData } from '@documenso/prisma/types/document-with-data'; -import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message'; -import { Input } from '@documenso/ui/primitives/input'; -import { Label } from '@documenso/ui/primitives/label'; -import { Textarea } from '@documenso/ui/primitives/textarea'; +import { FormErrorMessage } from '../form/form-error-message'; +import { Input } from '../input'; +import { Label } from '../label'; import { useStep } from '../stepper'; +import { Textarea } from '../textarea'; import type { TAddSubjectFormSchema } from './add-subject.types'; import { DocumentFlowFormContainerActions, diff --git a/packages/ui/primitives/document-flow/add-title.tsx b/packages/ui/primitives/document-flow/add-title.tsx index 2b91e1033..8c2a9dc7a 100644 --- a/packages/ui/primitives/document-flow/add-title.tsx +++ b/packages/ui/primitives/document-flow/add-title.tsx @@ -4,10 +4,10 @@ import { useForm } from 'react-hook-form'; import type { Field, Recipient } from '@documenso/prisma/client'; import type { DocumentWithData } from '@documenso/prisma/types/document-with-data'; -import { FormErrorMessage } from '@documenso/ui/primitives/form/form-error-message'; -import { Input } from '@documenso/ui/primitives/input'; -import { Label } from '@documenso/ui/primitives/label'; +import { FormErrorMessage } from '../form/form-error-message'; +import { Input } from '../input'; +import { Label } from '../label'; import { useStep } from '../stepper'; import type { TAddTitleFormSchema } from './add-title.types'; import { diff --git a/packages/ui/primitives/document-flow/document-flow-root.tsx b/packages/ui/primitives/document-flow/document-flow-root.tsx index 9142f4258..42b70c58a 100644 --- a/packages/ui/primitives/document-flow/document-flow-root.tsx +++ b/packages/ui/primitives/document-flow/document-flow-root.tsx @@ -5,8 +5,8 @@ import React from 'react'; import { motion } from 'framer-motion'; -import { cn } from '@documenso/ui/lib/utils'; -import { Button } from '@documenso/ui/primitives/button'; +import { cn } from '../../lib/utils'; +import { Button } from '../button'; export type DocumentFlowFormContainerProps = HTMLAttributes & { children?: React.ReactNode; diff --git a/packages/ui/primitives/document-flow/field-item.tsx b/packages/ui/primitives/document-flow/field-item.tsx index 48e52b9a7..7583bd4b9 100644 --- a/packages/ui/primitives/document-flow/field-item.tsx +++ b/packages/ui/primitives/document-flow/field-item.tsx @@ -7,10 +7,11 @@ import { createPortal } from 'react-dom'; import { Rnd } from 'react-rnd'; import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer'; -import { cn } from '@documenso/ui/lib/utils'; -import { Card, CardContent } from '@documenso/ui/primitives/card'; -import { FRIENDLY_FIELD_TYPE, TDocumentFlowFormSchema } from './types'; +import { cn } from '../../lib/utils'; +import { Card, CardContent } from '../card'; +import type { TDocumentFlowFormSchema } from './types'; +import { FRIENDLY_FIELD_TYPE } from './types'; type Field = TDocumentFlowFormSchema['fields'][0]; diff --git a/packages/ui/primitives/document-flow/send-document-action-dialog.tsx b/packages/ui/primitives/document-flow/send-document-action-dialog.tsx index f295dadfc..a70282800 100644 --- a/packages/ui/primitives/document-flow/send-document-action-dialog.tsx +++ b/packages/ui/primitives/document-flow/send-document-action-dialog.tsx @@ -2,7 +2,8 @@ import { useState } from 'react'; import { Loader } from 'lucide-react'; -import { Button, ButtonProps } from '@documenso/ui/primitives/button'; +import type { ButtonProps } from '../button'; +import { Button } from '../button'; import { Dialog, DialogContent, @@ -11,7 +12,7 @@ import { DialogHeader, DialogTitle, DialogTrigger, -} from '@documenso/ui/primitives/dialog'; +} from '../dialog'; export type SendDocumentActionDialogProps = ButtonProps & { loading?: boolean; diff --git a/packages/ui/primitives/document-flow/single-player-mode-fields.tsx b/packages/ui/primitives/document-flow/single-player-mode-fields.tsx index 04c093efc..7cecd7131 100644 --- a/packages/ui/primitives/document-flow/single-player-mode-fields.tsx +++ b/packages/ui/primitives/document-flow/single-player-mode-fields.tsx @@ -13,9 +13,11 @@ import { MIN_HANDWRITING_FONT_SIZE, MIN_STANDARD_FONT_SIZE, } from '@documenso/lib/constants/pdf'; -import { Field, FieldType } from '@documenso/prisma/client'; -import { FieldWithSignature } from '@documenso/prisma/types/field-with-signature'; -import { FieldRootContainer } from '@documenso/ui/components/field/field'; +import type { Field } from '@documenso/prisma/client'; +import { FieldType } from '@documenso/prisma/client'; +import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature'; + +import { FieldRootContainer } from '../../components/field/field'; export type SinglePlayerModeFieldContainerProps = { field: FieldWithSignature; diff --git a/packages/ui/primitives/form/form-error-message.tsx b/packages/ui/primitives/form/form-error-message.tsx index bb555b7f7..e429799da 100644 --- a/packages/ui/primitives/form/form-error-message.tsx +++ b/packages/ui/primitives/form/form-error-message.tsx @@ -1,6 +1,6 @@ import { AnimatePresence, motion } from 'framer-motion'; -import { cn } from '@documenso/ui/lib/utils'; +import { cn } from '../../lib/utils'; export type FormErrorMessageProps = { className?: string; diff --git a/packages/ui/primitives/form/form.tsx b/packages/ui/primitives/form/form.tsx index 9467de3af..f500accae 100644 --- a/packages/ui/primitives/form/form.tsx +++ b/packages/ui/primitives/form/form.tsx @@ -1,19 +1,12 @@ import * as React from 'react'; -import * as LabelPrimitive from '@radix-ui/react-label'; +import type * as LabelPrimitive from '@radix-ui/react-label'; import { Slot } from '@radix-ui/react-slot'; import { AnimatePresence, motion } from 'framer-motion'; -import { - Controller, - ControllerProps, - FieldPath, - FieldValues, - FormProvider, - useFormContext, -} from 'react-hook-form'; - -import { cn } from '@documenso/ui/lib/utils'; +import type { ControllerProps, FieldPath, FieldValues } from 'react-hook-form'; +import { Controller, FormProvider, useFormContext } from 'react-hook-form'; +import { cn } from '../../lib/utils'; import { Label } from '../label'; const Form = FormProvider; diff --git a/packages/ui/primitives/pdf-viewer.tsx b/packages/ui/primitives/pdf-viewer.tsx index 62b08d2f9..07cdaf1e2 100644 --- a/packages/ui/primitives/pdf-viewer.tsx +++ b/packages/ui/primitives/pdf-viewer.tsx @@ -3,16 +3,16 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Loader } from 'lucide-react'; -import { PDFDocumentProxy } from 'pdfjs-dist'; +import type { PDFDocumentProxy } from 'pdfjs-dist'; import { Document as PDFDocument, Page as PDFPage, pdfjs } from 'react-pdf'; import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; import 'react-pdf/dist/esm/Page/TextLayer.css'; import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer'; import { getFile } from '@documenso/lib/universal/upload/get-file'; -import { DocumentData } from '@documenso/prisma/client'; -import { cn } from '@documenso/ui/lib/utils'; +import type { DocumentData } from '@documenso/prisma/client'; +import { cn } from '../lib/utils'; import { useToast } from './use-toast'; export type LoadedPDFDocument = PDFDocumentProxy; diff --git a/packages/ui/primitives/signature-pad/signature-pad.tsx b/packages/ui/primitives/signature-pad/signature-pad.tsx index 107627240..3497418d7 100644 --- a/packages/ui/primitives/signature-pad/signature-pad.tsx +++ b/packages/ui/primitives/signature-pad/signature-pad.tsx @@ -1,20 +1,12 @@ 'use client'; -import { - HTMLAttributes, - MouseEvent, - PointerEvent, - TouchEvent, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import type { HTMLAttributes, MouseEvent, PointerEvent, TouchEvent } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; -import { StrokeOptions, getStroke } from 'perfect-freehand'; - -import { cn } from '@documenso/ui/lib/utils'; +import type { StrokeOptions } from 'perfect-freehand'; +import { getStroke } from 'perfect-freehand'; +import { cn } from '../../lib/utils'; import { getSvgPathFromStroke } from './helper'; import { Point } from './point'; From cd6184406d133af049582fb8008bdba3344bb29b Mon Sep 17 00:00:00 2001 From: Mythie Date: Thu, 7 Dec 2023 15:08:16 +1100 Subject: [PATCH 36/45] chore: add e2e test for stepper --- .../e2e/pr-718-add-stepper-component.spec.ts | 75 +++++++++++++++++++ .../seed/pr-718-add-stepper-component.ts | 28 +++++++ 2 files changed, 103 insertions(+) create mode 100644 packages/app-tests/e2e/pr-718-add-stepper-component.spec.ts create mode 100644 packages/prisma/seed/pr-718-add-stepper-component.ts diff --git a/packages/app-tests/e2e/pr-718-add-stepper-component.spec.ts b/packages/app-tests/e2e/pr-718-add-stepper-component.spec.ts new file mode 100644 index 000000000..6e03979c0 --- /dev/null +++ b/packages/app-tests/e2e/pr-718-add-stepper-component.spec.ts @@ -0,0 +1,75 @@ +import { expect, test } from '@playwright/test'; +import path from 'node:path'; + +import { TEST_USER } from '@documenso/prisma/seed/pr-718-add-stepper-component'; + +test(`[PR-718]: should be able to create a document`, async ({ page }) => { + await page.goto('/signin'); + + const documentTitle = `example-${Date.now()}.pdf`; + + // Sign in + await page.getByLabel('Email').fill(TEST_USER.email); + await page.getByLabel('Password', { exact: true }).fill(TEST_USER.password); + await page.getByRole('button', { name: 'Sign In' }).click(); + + // Upload document + const [fileChooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.locator('input[type=file]').evaluate((e) => { + if (e instanceof HTMLInputElement) { + e.click(); + } + }), + ]); + + await fileChooser.setFiles(path.join(__dirname, '../../../assets/example.pdf')); + + // Wait to be redirected to the edit page + await page.waitForURL(/\/documents\/\d+/); + + // Set title + await expect(page.getByRole('heading', { name: 'Add Title' })).toBeVisible(); + + await page.getByLabel('Title').fill(documentTitle); + + await page.getByRole('button', { name: 'Continue' }).click(); + + // Add signers + await expect(page.getByRole('heading', { name: 'Add Signers' })).toBeVisible(); + + await page.getByLabel('Email*').fill('user1@example.com'); + await page.getByLabel('Name').fill('User 1'); + + await page.getByRole('button', { name: 'Continue' }).click(); + + // Add fields + await expect(page.getByRole('heading', { name: 'Add Fields' })).toBeVisible(); + + await page.getByRole('button', { name: 'User 1 Signature' }).click(); + await page.locator('canvas').click({ + position: { + x: 100, + y: 100, + }, + }); + + await page.getByRole('button', { name: 'Email Email' }).click(); + await page.locator('canvas').click({ + position: { + x: 100, + y: 200, + }, + }); + + await page.getByRole('button', { name: 'Continue' }).click(); + + // Add subject and send + await expect(page.getByRole('heading', { name: 'Add Subject' })).toBeVisible(); + await page.getByRole('button', { name: 'Send' }).click(); + + await page.waitForURL('/documents'); + + // Assert document was created + await expect(page.getByRole('link', { name: documentTitle })).toBeVisible(); +}); diff --git a/packages/prisma/seed/pr-718-add-stepper-component.ts b/packages/prisma/seed/pr-718-add-stepper-component.ts new file mode 100644 index 000000000..57a0ddc61 --- /dev/null +++ b/packages/prisma/seed/pr-718-add-stepper-component.ts @@ -0,0 +1,28 @@ +import { hashSync } from '@documenso/lib/server-only/auth/hash'; + +import { prisma } from '..'; + +// +// https://github.com/documenso/documenso/pull/713 +// + +const PULL_REQUEST_NUMBER = 718; + +const EMAIL_DOMAIN = `pr-${PULL_REQUEST_NUMBER}.documenso.com`; + +export const TEST_USER = { + name: 'User 1', + email: `user1@${EMAIL_DOMAIN}`, + password: 'Password123', +} as const; + +export const seedDatabase = async () => { + await prisma.user.create({ + data: { + name: TEST_USER.name, + email: TEST_USER.email, + password: hashSync(TEST_USER.password), + emailVerified: new Date(), + }, + }); +}; From 9a7e5d333dbabeeeb50a32b361b0f667328ce97a Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Thu, 7 Dec 2023 15:45:44 +1100 Subject: [PATCH 37/45] fix: don't expand documentData --- .../src/app/(dashboard)/documents/data-table-action-button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx b/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx index e0a56f83d..3fc9e2d42 100644 --- a/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx +++ b/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx @@ -58,7 +58,7 @@ export const DataTableActionButton = ({ row }: DataTableActionButtonProps) => { return; } - const documentBytes = await getFile({ data: documentData.data, type: documentData.type }); + const documentBytes = await getFile(documentData); const blob = new Blob([documentBytes], { type: 'application/pdf', From d58433c8a01569581e09121e2517dfd1b62e9581 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Thu, 7 Dec 2023 15:50:34 +1100 Subject: [PATCH 38/45] fix: destructure toast --- .../src/app/(dashboard)/documents/data-table-action-button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx b/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx index fb10888de..54a8f6184 100644 --- a/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx +++ b/apps/web/src/app/(dashboard)/documents/data-table-action-button.tsx @@ -23,7 +23,7 @@ export type DataTableActionButtonProps = { export const DataTableActionButton = ({ row }: DataTableActionButtonProps) => { const { data: session } = useSession(); - const toast = useToast(); + const { toast } = useToast(); if (!session) { return null; From c313da5028e43bb8f4d86f8ebbab54cf120d42b8 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Thu, 7 Dec 2023 16:29:20 +1100 Subject: [PATCH 39/45] fix: update seal event --- packages/lib/server-only/document/seal-document.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lib/server-only/document/seal-document.ts b/packages/lib/server-only/document/seal-document.ts index c2e68277b..5fa4b1a00 100644 --- a/packages/lib/server-only/document/seal-document.ts +++ b/packages/lib/server-only/document/seal-document.ts @@ -90,7 +90,7 @@ export const sealDocument = async ({ documentId, sendEmail = true }: SealDocumen if (postHog) { postHog.capture({ distinctId: nanoid(), - event: 'App: Document Signed', + event: 'App: Document Sealed', properties: { documentId: document.id, }, From 2c5d547cdfcdc9506033135f5728629d7b947d02 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Thu, 7 Dec 2023 18:00:54 +1100 Subject: [PATCH 40/45] fix: add missing import --- apps/web/src/app/(signing)/sign/[token]/form.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/web/src/app/(signing)/sign/[token]/form.tsx b/apps/web/src/app/(signing)/sign/[token]/form.tsx index 66f3cb5a7..29cd77995 100644 --- a/apps/web/src/app/(signing)/sign/[token]/form.tsx +++ b/apps/web/src/app/(signing)/sign/[token]/form.tsx @@ -10,6 +10,7 @@ import { useForm } from 'react-hook-form'; import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics'; import { sortFieldsByPosition, validateFieldsInserted } from '@documenso/lib/utils/fields'; import type { Document, Field, Recipient } from '@documenso/prisma/client'; +import { trpc } from '@documenso/trpc/react'; import { FieldToolTip } from '@documenso/ui/components/field/field-tooltip'; import { cn } from '@documenso/ui/lib/utils'; import { Button } from '@documenso/ui/primitives/button'; From 935601ad163ccc7d5fe24ba916edde106cbf27cc Mon Sep 17 00:00:00 2001 From: Mythie Date: Thu, 7 Dec 2023 18:46:57 +1100 Subject: [PATCH 41/45] fix(ee): add handling for incomplete expired checkouts --- .../ee/server-only/stripe/webhook/handler.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/ee/server-only/stripe/webhook/handler.ts b/packages/ee/server-only/stripe/webhook/handler.ts index dd2079122..3058ed261 100644 --- a/packages/ee/server-only/stripe/webhook/handler.ts +++ b/packages/ee/server-only/stripe/webhook/handler.ts @@ -1,9 +1,10 @@ -import { NextApiRequest, NextApiResponse } from 'next'; +import type { NextApiRequest, NextApiResponse } from 'next'; import { buffer } from 'micro'; import { match } from 'ts-pattern'; -import { Stripe, stripe } from '@documenso/lib/server-only/stripe'; +import type { Stripe } from '@documenso/lib/server-only/stripe'; +import { stripe } from '@documenso/lib/server-only/stripe'; import { getFlag } from '@documenso/lib/universal/get-feature-flag'; import { prisma } from '@documenso/prisma'; @@ -174,6 +175,13 @@ export const stripeWebhookHandler = async ( const subscription = await stripe.subscriptions.retrieve(subscriptionId); + if (subscription.status === 'incomplete_expired') { + return res.status(200).json({ + success: true, + message: 'Webhook received', + }); + } + const result = await prisma.subscription.findFirst({ select: { userId: true, @@ -218,6 +226,13 @@ export const stripeWebhookHandler = async ( const subscription = await stripe.subscriptions.retrieve(subscriptionId); + if (subscription.status === 'incomplete_expired') { + return res.status(200).json({ + success: true, + message: 'Webhook received', + }); + } + const result = await prisma.subscription.findFirst({ select: { userId: true, From 18310849703e586ad68db42fd018d3ba11558bc9 Mon Sep 17 00:00:00 2001 From: Mythie Date: Fri, 8 Dec 2023 09:29:47 +1100 Subject: [PATCH 42/45] chore: update ci --- .github/workflows/deploy.yml | 26 ++++++++++++++++++++++++++ .github/workflows/e2e-tests.yml | 1 + 2 files changed, 27 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000..118cb673f --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,26 @@ +name: Deploy to Production + +on: + push: + tags: + - '*' + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + + - name: Push to release branch + run: | + git config user.name "GitHub Actions" + git config user.email "actions@github.com" + + git checkout release || git checkout -b release + git merge --ff-only main + git push origin release diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 50c25f923..506abbc5f 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -6,6 +6,7 @@ on: branches: [ "main" ] jobs: e2e_tests: + name: "E2E Tests" timeout-minutes: 60 runs-on: ubuntu-latest steps: From 4e799e68ef1eaa46e71b241366eba7d9a15e167a Mon Sep 17 00:00:00 2001 From: Mythie Date: Fri, 8 Dec 2023 09:44:36 +1100 Subject: [PATCH 43/45] chore: update ci --- .github/workflows/deploy.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 118cb673f..80d188964 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -15,12 +15,10 @@ jobs: with: ref: main fetch-depth: 0 + token: ${{ secrets.GH_TOKEN }} - name: Push to release branch run: | - git config user.name "GitHub Actions" - git config user.email "actions@github.com" - git checkout release || git checkout -b release git merge --ff-only main git push origin release From 7feba02e08e2e742c6d6a8ada2e9351c25da1adc Mon Sep 17 00:00:00 2001 From: Mythie Date: Fri, 8 Dec 2023 13:01:08 +1100 Subject: [PATCH 44/45] chore: update ci and formatting --- .github/ISSUE_TEMPLATE/bug-report.yml | 7 ++-- .github/ISSUE_TEMPLATE/feature-request.yml | 6 +-- .github/ISSUE_TEMPLATE/improvement.yml | 4 +- .github/dependabot.yml | 32 +++++++------- .github/pr-labeler.yml | 4 +- .github/workflows/ci.yml | 8 ++-- .github/workflows/codeql-analysis.yml | 44 ++++++++++---------- .github/workflows/e2e-tests.yml | 4 +- .github/workflows/first-interaction.yml | 10 ++--- .github/workflows/issue-assignee-check.yml | 20 ++++----- .github/workflows/issue-opened.yml | 7 +--- .github/workflows/pr-labeler.yml | 5 +-- .github/workflows/pr-review-reminder.yml | 16 ++++--- .github/workflows/semantic-pull-requests.yml | 1 - .github/workflows/stale.yml | 24 +++++------ 15 files changed, 90 insertions(+), 102 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index b835896d0..4fcde0ea3 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -1,11 +1,10 @@ -name: "Bug Report" -labels: ["bug"] +name: 'Bug Report' +labels: ['bug'] description: Create a bug report to help us improve body: - type: markdown attributes: - value: - Thank you for reporting an issue. + value: Thank you for reporting an issue. Please fill in as much of the form below as you're able to. - type: textarea attributes: diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index f323f9475..ab21e8828 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -1,9 +1,9 @@ -name: "Feature Request" +name: 'Feature Request' description: Suggest a new idea or enhancement for this project body: - type: markdown attributes: - value: Please provide a clear and concise title for your feature request + value: Please provide a clear and concise title for your feature request - type: textarea attributes: label: Feature Description @@ -32,4 +32,4 @@ body: - label: I have provided a detailed description of the requested feature. - label: I have explained the use case or scenario for this feature. - label: I have included any relevant technical details or design suggestions. - - label: I understand that this is a suggestion and that there is no guarantee of implementation. \ No newline at end of file + - label: I understand that this is a suggestion and that there is no guarantee of implementation. diff --git a/.github/ISSUE_TEMPLATE/improvement.yml b/.github/ISSUE_TEMPLATE/improvement.yml index bebcb4cb5..058a025e7 100644 --- a/.github/ISSUE_TEMPLATE/improvement.yml +++ b/.github/ISSUE_TEMPLATE/improvement.yml @@ -1,4 +1,4 @@ -name: "General Improvement" +name: 'General Improvement' description: Suggest a minor enhancement or improvement for this project body: - type: markdown @@ -32,4 +32,4 @@ body: - label: I have provided a clear description of the improvement being suggested. - label: I have explained the rationale behind this improvement. - label: I have included any relevant technical details or design suggestions. - - label: I understand that this is a suggestion and that there is no guarantee of implementation. \ No newline at end of file + - label: I understand that this is a suggestion and that there is no guarantee of implementation. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d9013f408..9a20ae923 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,29 +4,29 @@ updates: - package-ecosystem: 'github-actions' directory: '/' schedule: - interval: "weekly" - target-branch: "main" + interval: 'weekly' + target-branch: 'main' labels: - - "ci dependencies" - - "ci" + - 'ci dependencies' + - 'ci' open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/apps/marketing" + - package-ecosystem: 'npm' + directory: '/apps/marketing' schedule: - interval: "weekly" - target-branch: "main" + interval: 'weekly' + target-branch: 'main' labels: - - "npm dependencies" - - "frontend" + - 'npm dependencies' + - 'frontend' open-pull-requests-limit: 0 - - package-ecosystem: "npm" - directory: "/apps/web" + - package-ecosystem: 'npm' + directory: '/apps/web' schedule: - interval: "weekly" - target-branch: "main" + interval: 'weekly' + target-branch: 'main' labels: - - "npm dependencies" - - "frontend" + - 'npm dependencies' + - 'frontend' open-pull-requests-limit: 0 diff --git a/.github/pr-labeler.yml b/.github/pr-labeler.yml index 4fb68aa2f..e6ad018a3 100644 --- a/.github/pr-labeler.yml +++ b/.github/pr-labeler.yml @@ -1,7 +1,7 @@ 'apps: marketing': - apps/marketing/** -'apps: web': +'apps: web': - apps/web/** 'version bump 👀': @@ -14,7 +14,7 @@ '🚨 e2e changes 🚨': - packages/app-tests/e2e/** -"🚨 .env changes 🚨": +'🚨 .env changes 🚨': - .env.example 'pkg: ee changes': diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 104dc3440..deda53ff0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,10 @@ -name: "Continuous Integration" +name: 'Continuous Integration' on: push: - branches: [ "main" ] + branches: ['main'] pull_request: - branches: [ "main" ] + branches: ['main'] concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -16,7 +16,6 @@ env: jobs: build_app: name: Build App - if: github.repository == 'documenso/documenso' runs-on: ubuntu-latest steps: - name: Checkout @@ -50,4 +49,3 @@ jobs: - name: Build Docker Image run: ./docker/build.sh - diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c51216165..465041c0a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -1,11 +1,11 @@ -name: "CodeQL" +name: 'CodeQL' on: workflow_dispatch: push: - branches: [ "main" ] + branches: ['main'] pull_request: - branches: [ "main" ] + branches: ['main'] jobs: analyze: @@ -19,30 +19,30 @@ jobs: strategy: fail-fast: true matrix: - language: [ 'javascript' ] + language: ['javascript'] steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 18 - cache: npm + - uses: actions/setup-node@v4 + with: + node-version: 18 + cache: npm - - name: Install Dependencies - run: npm ci + - name: Install Dependencies + run: npm ci - - name: Copy env - run: cp .env.example .env + - name: Copy env + run: cp .env.example .env - - name: Build Documenso - run: npm run build + - name: Build Documenso + run: npm run build - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index c059e0317..2f8df180b 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -1,9 +1,9 @@ name: Playwright Tests on: push: - branches: [ "main" ] + branches: ['main'] pull_request: - branches: [ "main" ] + branches: ['main'] jobs: e2e_tests: timeout-minutes: 60 diff --git a/.github/workflows/first-interaction.yml b/.github/workflows/first-interaction.yml index 2f6f59e4a..5f53eb280 100644 --- a/.github/workflows/first-interaction.yml +++ b/.github/workflows/first-interaction.yml @@ -1,10 +1,10 @@ -name: "Welcome New Contributors" +name: 'Welcome New Contributors' on: pull_request: - types: opened + types: ['opened'] issues: - types: opened + types: ['opened'] permissions: pull-requests: write @@ -13,7 +13,7 @@ permissions: jobs: welcome-message: name: Welcome Contributors - if: github.repository == 'documenso/documenso' && github.event.action == 'opened' + if: github.event.action == 'opened' runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -24,6 +24,6 @@ jobs: Thank you for creating your first Pull Request and for being a part of the open signing revolution! 💚🚀
Feel free to hop into our community in [Discord](https://documen.so/discord) issue-message: | - Thank you for opening your first issue and for being a part of the open signing revolution! + Thank you for opening your first issue and for being a part of the open signing revolution!
One of our team members will review it and get back to you as soon as it possible 💚
Meanwhile, please feel free to hop into our community in [Discord](https://documen.so/discord) diff --git a/.github/workflows/issue-assignee-check.yml b/.github/workflows/issue-assignee-check.yml index 9ecc23339..1ce7a02be 100644 --- a/.github/workflows/issue-assignee-check.yml +++ b/.github/workflows/issue-assignee-check.yml @@ -1,28 +1,26 @@ -name: "Issue Assignee Check" +name: 'Issue Assignee Check' on: issues: - types: [assigned] + types: ['assigned'] permissions: issues: write jobs: countIssues: - if: ${{ github.event.issue.assignee }} && github.repository == 'documenso/documenso' && github.event.action == 'assigned' && github.event.sender.type == 'User' + if: ${{ github.event.issue.assignee }} && github.event.action == 'assigned' && github.event.sender.type == 'User' runs-on: ubuntu-latest - env: - MY_ENV_VARIABLE: ${{ secrets.GITHUB_TOKEN }} steps: - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: '18' cache: npm - + - name: Install Octokit run: npm install @octokit/rest@18 - + - name: Check Assigned User's Issue Count id: parse-comment uses: actions/github-script@v5 @@ -31,20 +29,20 @@ jobs: script: | const { Octokit } = require("@octokit/rest"); const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); - + const username = context.payload.issue.assignee.login; console.log(`Username Extracted: ${username}`); - + const { data: issues } = await octokit.issues.listForRepo({ owner: context.repo.owner, repo: context.repo.repo, assignee: username, state: 'open' }); - + const issueCount = issues.length; console.log(`Issue Count For ${username}: ${issueCount}`); - + if (issueCount > 3) { let issueCountMessage = `### 🚨 Documenso Police 🚨`; issueCountMessage += `\n@${username} has ${issueCount} open issues assigned already. Consider whether this issue should be assigned to them or left open for another contributor.`; diff --git a/.github/workflows/issue-opened.yml b/.github/workflows/issue-opened.yml index c656ce74d..ed9f2811a 100644 --- a/.github/workflows/issue-opened.yml +++ b/.github/workflows/issue-opened.yml @@ -1,15 +1,12 @@ -name: "Label Issues" +name: 'Label Issues' on: issues: - types: - - reopened - - opened + types: ['opened', 'reopened'] jobs: label_issues: runs-on: ubuntu-latest - if: github.repository == 'documenso/documenso' permissions: issues: write steps: diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index e968a028e..1a5afd359 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -1,4 +1,4 @@ -name: "PR Labeler" +name: 'PR Labeler' on: - pull_request_target @@ -9,7 +9,6 @@ concurrency: jobs: labeler: - if: github.repository == 'documenso/documenso' permissions: contents: read pull-requests: write @@ -18,4 +17,4 @@ jobs: - uses: actions/labeler@v4 with: repo-token: ${{ secrets.GITHUB_TOKEN }} - sync-labels: "" + sync-labels: '' diff --git a/.github/workflows/pr-review-reminder.yml b/.github/workflows/pr-review-reminder.yml index 6b16c611f..cc272fbfe 100644 --- a/.github/workflows/pr-review-reminder.yml +++ b/.github/workflows/pr-review-reminder.yml @@ -1,28 +1,26 @@ -name: "PR Review Reminder" +name: 'PR Review Reminder' on: pull_request: - types: [opened, reopened, ready_for_review, review_requested] + types: ['opened', 'reopened', 'ready_for_review', 'review_requested'] permissions: pull-requests: write jobs: checkPRs: - if: ${{ github.event.pull_request.user.login }} && github.repository == 'documenso/documenso' && github.event.action == ('opened' || 'reopened' || 'ready_for_review' || 'review_requested') + if: ${{ github.event.pull_request.user.login }} && github.event.action == ('opened' || 'reopened' || 'ready_for_review' || 'review_requested') runs-on: ubuntu-latest - env: - MY_ENV_VARIABLE: ${{ secrets.GITHUB_TOKEN }} steps: - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: '18' cache: npm - + - name: Install Octokit run: npm install @octokit/rest@18 - + - name: Check user's PRs awaiting review id: parse-prs uses: actions/github-script@v5 @@ -31,10 +29,10 @@ jobs: script: | const { Octokit } = require("@octokit/rest"); const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN }); - + const username = context.payload.pull_request.user.login; console.log(`Username Extracted: ${username}`); - + const { data: pullRequests } = await octokit.pulls.list({ owner: context.repo.owner, repo: context.repo.repo, diff --git a/.github/workflows/semantic-pull-requests.yml b/.github/workflows/semantic-pull-requests.yml index 08d3739fc..ef0a87542 100644 --- a/.github/workflows/semantic-pull-requests.yml +++ b/.github/workflows/semantic-pull-requests.yml @@ -13,7 +13,6 @@ permissions: jobs: validate-pr: - if: github.repository == 'documenso/documenso' name: Validate PR title runs-on: ubuntu-latest steps: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 4c0fa757c..82beed6e2 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,4 +1,4 @@ -name: "Mark Stale Issues and PRs" +name: 'Mark Stale Issues and PRs' on: schedule: @@ -6,20 +6,20 @@ on: jobs: stale: - if: github.repository == 'documenso/documenso' runs-on: ubuntu-latest permissions: issues: write pull-requests: write steps: - - uses: actions/stale@v4 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - days-before-pr-stale: 30 - days-before-issue-stale: 30 - stale-issue-message: 'This issue has not seen activity for a while. It will be closed in 30 days unless further activity is detected' - stale-pr-message: 'This PR has not seen activitiy for a while. It will be closed in 30 days unless further activity is detected.' - close-issue-message: 'This issue has been closed because of inactivity.' - close-pr-message: 'This PR has been closed because of inactivity.' - exempt-pr-labels: 'WIP, on-hold, needs review' + - uses: actions/stale@v4 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-pr-stale: 30 + days-before-issue-stale: 30 + stale-issue-message: 'This issue has not seen activity for a while. It will be closed in 30 days unless further activity is detected' + stale-pr-message: 'This PR has not seen activitiy for a while. It will be closed in 30 days unless further activity is detected.' + close-issue-message: 'This issue has been closed because of inactivity.' + close-pr-message: 'This PR has been closed because of inactivity.' + exempt-pr-labels: 'WIP,on-hold,needs review' + exempt-issue-labels: 'WIP,on-hold,needs review,roadmap' From 48f6765e761ab6b56eff5a3f29850401b3c8c24b Mon Sep 17 00:00:00 2001 From: Mythie Date: Fri, 8 Dec 2023 13:01:59 +1100 Subject: [PATCH 45/45] chore: add yml to lint-staged --- lint-staged.config.cjs | 1 + 1 file changed, 1 insertion(+) diff --git a/lint-staged.config.cjs b/lint-staged.config.cjs index 802b44c31..a975cb594 100644 --- a/lint-staged.config.cjs +++ b/lint-staged.config.cjs @@ -1,3 +1,4 @@ module.exports = { '**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts,mdx}': ['prettier --write'], + '**/*.yml': ['prettier --write'], };