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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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 520522bef796c431a735619f3ff848e92d2ac0ca Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Tue, 5 Dec 2023 12:05:47 +0530 Subject: [PATCH 25/47] 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 26/47] 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 27/47] 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 684e5272d2438bc88bbe4af19412046403ea9b3b Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Thu, 7 Dec 2023 00:52:36 +0000 Subject: [PATCH 28/47] fix(webapp): use checkout for expired plans --- .../app/(dashboard)/settings/billing/create-checkout.action.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/(dashboard)/settings/billing/create-checkout.action.ts b/apps/web/src/app/(dashboard)/settings/billing/create-checkout.action.ts index 0552c55ec..465d662a1 100644 --- a/apps/web/src/app/(dashboard)/settings/billing/create-checkout.action.ts +++ b/apps/web/src/app/(dashboard)/settings/billing/create-checkout.action.ts @@ -23,7 +23,7 @@ export const createCheckout = async ({ priceId }: CreateCheckoutOptions) => { let stripeCustomer: Stripe.Customer | null = null; // Find the Stripe customer for the current user subscription. - if (existingSubscription) { + if (existingSubscription?.periodEnd && existingSubscription.periodEnd >= new Date()) { stripeCustomer = await getStripeCustomerById(existingSubscription.customerId); if (!stripeCustomer) { From 3ff7b188d7dc49abce7a5ae31c8f9948dbc13e0a Mon Sep 17 00:00:00 2001 From: Mythie Date: Thu, 7 Dec 2023 15:06:49 +1100 Subject: [PATCH 29/47] fix(ui): tidy stepper code --- packages/ui/primitives/stepper.tsx | 38 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/packages/ui/primitives/stepper.tsx b/packages/ui/primitives/stepper.tsx index 71a4d025c..d38de2eb1 100644 --- a/packages/ui/primitives/stepper.tsx +++ b/packages/ui/primitives/stepper.tsx @@ -1,7 +1,7 @@ import React, { createContext, useContext, useState } from 'react'; import type { FC } from 'react'; -type StepContextType = { +type StepContextValue = { isCompleting: boolean; stepIndex: number; currentStep: number; @@ -12,7 +12,7 @@ type StepContextType = { previousStep: () => void; }; -const StepContext = createContext(null); +const StepContext = createContext(null); type StepperProps = { children: React.ReactNode; @@ -20,7 +20,6 @@ type StepperProps = { onStepChanged?: (currentStep: number) => void; currentStep?: number; // external control prop setCurrentStep?: (step: number) => void; // external control function - isAsyncComplete?: boolean; }; export const Stepper: FC = ({ @@ -29,7 +28,6 @@ export const Stepper: FC = ({ onStepChanged, currentStep: propCurrentStep, setCurrentStep: propSetCurrentStep, - isAsyncComplete, }) => { const [stateCurrentStep, stateSetCurrentStep] = useState(1); const [isCompleting, setIsCompleting] = useState(false); @@ -42,22 +40,26 @@ export const Stepper: FC = ({ const totalSteps = React.Children.count(children); const handleComplete = async () => { - if (!onComplete) { - return; + try { + if (!onComplete) { + return; + } + + setIsCompleting(true); + + await onComplete(); + + setIsCompleting(false); + } catch (error) { + setIsCompleting(false); + + throw error; } - if (!isAsyncComplete) { - void onComplete(); - return; - } - setIsCompleting(true); - await onComplete(); - // handle async complete action - setIsCompleting(false); }; const handleStepChange = (nextStep: number) => { setCurrentStep(nextStep); - onStepChanged && onStepChanged(nextStep); + onStepChanged?.(nextStep); }; const nextStep = () => { @@ -81,7 +83,7 @@ export const Stepper: FC = ({ const currentChild = React.Children.toArray(children)[currentStep - 1]; - const stepContextValue: StepContextType = { + const stepContextValue: StepContextValue = { isCompleting, stepIndex: currentStep - 1, currentStep, @@ -96,10 +98,12 @@ export const Stepper: FC = ({ }; /** Hook for children to use the step context */ -export const useStep = (): StepContextType => { +export const useStep = (): StepContextValue => { const context = useContext(StepContext); + if (!context) { throw new Error('useStep must be used within a Stepper'); } + return context; }; From 1a34f9fa7a728e71e3fa685550568ab1efca88f7 Mon Sep 17 00:00:00 2001 From: Mythie Date: Thu, 7 Dec 2023 15:08:00 +1100 Subject: [PATCH 30/47] fix: import updates and api route body sizes --- apps/marketing/src/app/(marketing)/layout.tsx | 4 +- .../app/(marketing)/singleplayer/client.tsx | 35 ++- apps/marketing/src/pages/api/trpc/[trpc].ts | 5 + .../documents/[id]/edit-document.tsx | 1 + apps/web/src/pages/api/trpc/[trpc].ts | 5 + .../document/document-download-button.tsx | 5 +- .../document/document-share-button.tsx | 9 +- .../ui/components/field/field-tooltip.tsx | 11 +- packages/ui/components/field/field.tsx | 7 +- packages/ui/components/signing-card.tsx | 10 +- packages/ui/primitives/combobox.tsx | 15 +- packages/ui/primitives/document-dropzone.tsx | 8 +- .../primitives/document-flow/add-fields.tsx | 18 +- .../document-flow/add-signature.tsx | 258 +++++++++--------- .../primitives/document-flow/add-signers.tsx | 10 +- .../primitives/document-flow/add-subject.tsx | 8 +- .../ui/primitives/document-flow/add-title.tsx | 6 +- .../document-flow/document-flow-root.tsx | 4 +- .../primitives/document-flow/field-item.tsx | 7 +- .../send-document-action-dialog.tsx | 5 +- .../single-player-mode-fields.tsx | 8 +- .../ui/primitives/form/form-error-message.tsx | 2 +- packages/ui/primitives/form/form.tsx | 15 +- packages/ui/primitives/pdf-viewer.tsx | 6 +- .../signature-pad/signature-pad.tsx | 18 +- 25 files changed, 243 insertions(+), 237 deletions(-) diff --git a/apps/marketing/src/app/(marketing)/layout.tsx b/apps/marketing/src/app/(marketing)/layout.tsx index 365d8a5d1..248414b33 100644 --- a/apps/marketing/src/app/(marketing)/layout.tsx +++ b/apps/marketing/src/app/(marketing)/layout.tsx @@ -29,7 +29,7 @@ export default function MarketingLayout({ children }: MarketingLayoutProps) { return (
@@ -41,7 +41,7 @@ export default function MarketingLayout({ children }: MarketingLayoutProps) {
-
{children}
+
{children}
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 31/47] 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 32/47] 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 33/47] 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 34/47] 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 35/47] 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 36/47] 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 37/47] 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 38/47] 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 39/47] 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 40/47] 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'], }; From dfec8df31e883467392db8cd3b9a8e3bcbcc3810 Mon Sep 17 00:00:00 2001 From: Mythie Date: Fri, 8 Dec 2023 20:18:28 +1100 Subject: [PATCH 41/47] fix: ensure command menu results are distinct --- apps/web/src/components/(dashboard)/common/command-menu.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/web/src/components/(dashboard)/common/command-menu.tsx b/apps/web/src/components/(dashboard)/common/command-menu.tsx index ab0bba6af..2e352b45a 100644 --- a/apps/web/src/components/(dashboard)/common/command-menu.tsx +++ b/apps/web/src/components/(dashboard)/common/command-menu.tsx @@ -79,8 +79,7 @@ export function CommandMenu({ open, onOpenChange }: CommandMenuProps) { return searchDocumentsData.map((document) => ({ label: document.title, path: `/documents/${document.id}`, - value: - document.title + ' ' + document.Recipient.map((recipient) => recipient.email).join(' '), + value: [document.id, document.title, ...document.Recipient.map((r) => r.email)].join(' '), })); }, [searchDocumentsData]); From d8d36ae8e2fc6eeeb843dc24e48c353cd2b40bb5 Mon Sep 17 00:00:00 2001 From: Mythie Date: Fri, 8 Dec 2023 20:41:47 +1100 Subject: [PATCH 42/47] fix: sticky header z-positioning --- apps/web/src/components/(dashboard)/layout/header.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/(dashboard)/layout/header.tsx b/apps/web/src/components/(dashboard)/layout/header.tsx index bea7f4aee..25f260575 100644 --- a/apps/web/src/components/(dashboard)/layout/header.tsx +++ b/apps/web/src/components/(dashboard)/layout/header.tsx @@ -1,10 +1,11 @@ 'use client'; -import { HTMLAttributes, useEffect, useState } from 'react'; +import type { HTMLAttributes } from 'react'; +import { useEffect, useState } from 'react'; import Link from 'next/link'; -import { User } from '@documenso/prisma/client'; +import type { User } from '@documenso/prisma/client'; import { cn } from '@documenso/ui/lib/utils'; import { Logo } from '~/components/branding/logo'; @@ -32,7 +33,7 @@ export const Header = ({ className, user, ...props }: HeaderProps) => { return (
5 && 'border-b-border', className, )} From 09dcc2cac012ff1156a0a1be0861ad6f333c0ecd Mon Sep 17 00:00:00 2001 From: Mythie Date: Fri, 8 Dec 2023 20:44:28 +1100 Subject: [PATCH 43/47] chore: update github actions --- .github/{pr-labeler.yml => labeler.yml} | 0 .github/workflows/semantic-pull-requests.yml | 4 ++-- .github/workflows/stale.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename .github/{pr-labeler.yml => labeler.yml} (100%) diff --git a/.github/pr-labeler.yml b/.github/labeler.yml similarity index 100% rename from .github/pr-labeler.yml rename to .github/labeler.yml diff --git a/.github/workflows/semantic-pull-requests.yml b/.github/workflows/semantic-pull-requests.yml index ef0a87542..7685562b5 100644 --- a/.github/workflows/semantic-pull-requests.yml +++ b/.github/workflows/semantic-pull-requests.yml @@ -1,4 +1,4 @@ -name: "Validate PR Name" +name: 'Validate PR Name' on: pull_request_target: @@ -9,7 +9,7 @@ on: - synchronize permissions: - pull-requests: read + pull-requests: write jobs: validate-pr: diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 82beed6e2..efd681a71 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -22,4 +22,4 @@ 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' - exempt-issue-labels: 'WIP,on-hold,needs review,roadmap' + exempt-issue-labels: 'WIP,on-hold,needs review,roadmap,assigned' From 38e5b1d3ce8acbb792d18dba509e7cdfeb53cd56 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Fri, 8 Dec 2023 20:49:08 +1100 Subject: [PATCH 44/47] chore: use minio as s3 storage for document during development (#588) --- .env.example | 10 +++++----- README.md | 4 +++- docker/compose-services.yml | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 45c26f6be..968dd05e5 100644 --- a/.env.example +++ b/.env.example @@ -29,15 +29,15 @@ E2E_TEST_AUTHENTICATE_USER_PASSWORD="test_password" # OPTIONAL: Defines the storage transport to use. Available options: database (default) | s3 NEXT_PUBLIC_UPLOAD_TRANSPORT="database" # OPTIONAL: Defines the endpoint to use for the S3 storage transport. Relevant when using third-party S3-compatible providers. -NEXT_PRIVATE_UPLOAD_ENDPOINT= +NEXT_PRIVATE_UPLOAD_ENDPOINT="http://127.0.0.1:9002" # OPTIONAL: Defines the region to use for the S3 storage transport. Defaults to us-east-1. -NEXT_PRIVATE_UPLOAD_REGION= +NEXT_PRIVATE_UPLOAD_REGION="unknown" # REQUIRED: Defines the bucket to use for the S3 storage transport. -NEXT_PRIVATE_UPLOAD_BUCKET= +NEXT_PRIVATE_UPLOAD_BUCKET="documenso" # OPTIONAL: Defines the access key ID to use for the S3 storage transport. -NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID= +NEXT_PRIVATE_UPLOAD_ACCESS_KEY_ID="documenso" # OPTIONAL: Defines the secret access key to use for the S3 storage transport. -NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY= +NEXT_PRIVATE_UPLOAD_SECRET_ACCESS_KEY="password" # [[SMTP]] # OPTIONAL: Defines the transport to use for sending emails. Available options: smtp-auth (default) | smtp-api | mailchannels diff --git a/README.md b/README.md index 89f44a926..24d932858 100644 --- a/README.md +++ b/README.md @@ -139,11 +139,13 @@ npm run d 1. **App** - http://localhost:3000 2. **Incoming Mail Access** - http://localhost:9000 - 3. **Database Connection Details** + - **Port**: 54320 - **Connection**: Use your favorite database client to connect using the provided port. +4. **S3 Storage Dashboard** - http://localhost:9001 + ## Developer Setup ### Manual Setup diff --git a/docker/compose-services.yml b/docker/compose-services.yml index 67c193dbc..85f06a8d7 100644 --- a/docker/compose-services.yml +++ b/docker/compose-services.yml @@ -17,3 +17,20 @@ services: - 9000:9000 - 2500:2500 - 1100:1100 + + minio: + image: minio/minio + container_name: minio + ports: + - 9002:9002 + - 9001:9001 + volumes: + - minio:/data + environment: + MINIO_ROOT_USER: documenso + MINIO_ROOT_PASSWORD: password + entrypoint: sh + command: -c 'mkdir -p /data/documenso && minio server /data --console-address ":9001" --address ":9002"' + +volumes: + minio: From 7d22957404b933c3b45dc7ab4596cfc21a4d736a Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Fri, 8 Dec 2023 21:27:38 +0530 Subject: [PATCH 45/47] chore: add eslint fix command --- lint-staged.config.cjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lint-staged.config.cjs b/lint-staged.config.cjs index a975cb594..b8fd3a0b4 100644 --- a/lint-staged.config.cjs +++ b/lint-staged.config.cjs @@ -1,4 +1,4 @@ module.exports = { - '**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts,mdx}': ['prettier --write'], - '**/*.yml': ['prettier --write'], + '**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts}': ['prettier --write', 'eslint --fix'], + '**/*.{yml,mdx}': ['prettier --write'], }; From 497d9140d28e0a7a2b02c47a5f70e3dff04492d7 Mon Sep 17 00:00:00 2001 From: Aditya Deshlahre <132184385+adityadeshlahre@users.noreply.github.com> Date: Sat, 9 Dec 2023 06:00:15 +0530 Subject: [PATCH 46/47] chore: add lint-staged task for dependency changes (#548) --- lint-staged.config.cjs | 1 + package.json | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lint-staged.config.cjs b/lint-staged.config.cjs index a975cb594..de7417140 100644 --- a/lint-staged.config.cjs +++ b/lint-staged.config.cjs @@ -1,4 +1,5 @@ module.exports = { '**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts,mdx}': ['prettier --write'], '**/*.yml': ['prettier --write'], + '**/*/package.json': ['npm run precommit'], }; diff --git a/package.json b/package.json index 2e708363f..780f76793 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "prisma:seed": "npm run with:env -- npm run prisma:seed -w @documenso/prisma", "prisma:studio": "npm run with:env -- npx prisma studio --schema packages/prisma/schema.prisma", "with:env": "dotenv -e .env -e .env.local --", - "reset:hard": "npm run clean && npm i && npm run prisma:generate" + "reset:hard": "npm run clean && npm i && npm run prisma:generate", + "precommit": "npm install && git add package.json package-lock.json" }, "engines": { "npm": ">=8.6.0", From e1d3874e79d0e8e8ebeba9d94bff60e61b4e4f63 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Sat, 9 Dec 2023 11:40:23 +1100 Subject: [PATCH 47/47] fix: do not lint js files --- lint-staged.config.cjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lint-staged.config.cjs b/lint-staged.config.cjs index b8fd3a0b4..3dd4bf238 100644 --- a/lint-staged.config.cjs +++ b/lint-staged.config.cjs @@ -1,4 +1,5 @@ module.exports = { - '**/*.{js,jsx,cjs,mjs,ts,tsx,cts,mts}': ['prettier --write', 'eslint --fix'], + '**/*.{ts,tsx,cts,mts}': ['eslint --fix'], + '**/*.{js,jsx,cjs,mjs}': ['prettier --write'], '**/*.{yml,mdx}': ['prettier --write'], };