diff --git a/apps/remix/app/components/dialogs/template-create-dialog.tsx b/apps/remix/app/components/dialogs/template-create-dialog.tsx deleted file mode 100644 index 676537e4c..000000000 --- a/apps/remix/app/components/dialogs/template-create-dialog.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { useState } from 'react'; - -import { msg } from '@lingui/core/macro'; -import { useLingui } from '@lingui/react'; -import { Trans } from '@lingui/react/macro'; -import { FilePlus, Loader } from 'lucide-react'; -import { useNavigate } from 'react-router'; - -import { useSession } from '@documenso/lib/client-only/providers/session'; -import { formatTemplatesPath } from '@documenso/lib/utils/teams'; -import { trpc } from '@documenso/trpc/react'; -import type { TCreateTemplatePayloadSchema } from '@documenso/trpc/server/template-router/schema'; -import { Button } from '@documenso/ui/primitives/button'; -import { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger, -} from '@documenso/ui/primitives/dialog'; -import { DocumentDropzone } from '@documenso/ui/primitives/document-dropzone'; -import { useToast } from '@documenso/ui/primitives/use-toast'; - -import { useCurrentTeam } from '~/providers/team'; - -type TemplateCreateDialogProps = { - folderId?: string; -}; - -export const TemplateCreateDialog = ({ folderId }: TemplateCreateDialogProps) => { - const navigate = useNavigate(); - - const { user } = useSession(); - const { toast } = useToast(); - const { _ } = useLingui(); - - const team = useCurrentTeam(); - - const { mutateAsync: createTemplate } = trpc.template.createTemplate.useMutation(); - - const [showTemplateCreateDialog, setShowTemplateCreateDialog] = useState(false); - const [isUploadingFile, setIsUploadingFile] = useState(false); - - const onFileDrop = async (files: File[]) => { - const file = files[0]; - - if (isUploadingFile) { - return; - } - - setIsUploadingFile(true); - - try { - const payload = { - title: file.name, - folderId: folderId, - } satisfies TCreateTemplatePayloadSchema; - - const formData = new FormData(); - - formData.append('payload', JSON.stringify(payload)); - formData.append('file', file); - - const { envelopeId: id } = await createTemplate(formData); - - toast({ - title: _(msg`Template document uploaded`), - description: _( - msg`Your document has been uploaded successfully. You will be redirected to the template page.`, - ), - duration: 5000, - }); - - setShowTemplateCreateDialog(false); - - await navigate(`${formatTemplatesPath(team.url)}/${id}/edit`); - } catch { - toast({ - title: _(msg`Something went wrong`), - description: _(msg`Please try again later.`), - variant: 'destructive', - }); - - setIsUploadingFile(false); - } - }; - - return ( - !isUploadingFile && setShowTemplateCreateDialog(value)} - > - - - - - - - - New Template - - - - Templates allow you to quickly generate documents with pre-filled recipients and - fields. - - - - -
- - - {isUploadingFile && ( -
- -
- )} -
- - - - - - -
-
- ); -}; diff --git a/apps/remix/app/components/general/document/document-upload-button-legacy.tsx b/apps/remix/app/components/general/document/document-upload-button-legacy.tsx index f4f13aaf0..03f4f5068 100644 --- a/apps/remix/app/components/general/document/document-upload-button-legacy.tsx +++ b/apps/remix/app/components/general/document/document-upload-button-legacy.tsx @@ -14,9 +14,10 @@ import { useSession } from '@documenso/lib/client-only/providers/session'; import { APP_DOCUMENT_UPLOAD_SIZE_LIMIT } from '@documenso/lib/constants/app'; import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; -import { formatDocumentsPath } from '@documenso/lib/utils/teams'; +import { formatDocumentsPath, formatTemplatesPath } from '@documenso/lib/utils/teams'; import { trpc } from '@documenso/trpc/react'; import type { TCreateDocumentPayloadSchema } from '@documenso/trpc/server/document-router/create-document.types'; +import type { TCreateTemplatePayloadSchema } from '@documenso/trpc/server/template-router/schema'; import { cn } from '@documenso/ui/lib/utils'; import { DocumentUploadButton as DocumentUploadButtonPrimitive } from '@documenso/ui/primitives/document-upload-button'; import { @@ -31,9 +32,13 @@ import { useCurrentTeam } from '~/providers/team'; export type DocumentUploadButtonLegacyProps = { className?: string; + type: EnvelopeType; }; -export const DocumentUploadButtonLegacy = ({ className }: DocumentUploadButtonLegacyProps) => { +export const DocumentUploadButtonLegacy = ({ + className, + type, +}: DocumentUploadButtonLegacyProps) => { const { _ } = useLingui(); const { toast } = useToast(); const { user } = useSession(); @@ -54,8 +59,18 @@ export const DocumentUploadButtonLegacy = ({ className }: DocumentUploadButtonLe const [isLoading, setIsLoading] = useState(false); const { mutateAsync: createDocument } = trpc.document.create.useMutation(); + const { mutateAsync: createTemplate } = trpc.template.createTemplate.useMutation(); const disabledMessage = useMemo(() => { + if (!user.emailVerified) { + return msg`Verify your email to upload documents.`; + } + + // No errors for templates. + if (type === EnvelopeType.TEMPLATE) { + return; + } + if (organisation.subscription && remaining.documents === 0) { return msg`Document upload disabled due to unpaid invoices`; } @@ -64,11 +79,8 @@ export const DocumentUploadButtonLegacy = ({ className }: DocumentUploadButtonLe return msg`You have reached your document limit.`; } - if (!user.emailVerified) { - return msg`Verify your email to upload documents.`; - } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [remaining.documents, user.emailVerified, team]); + }, [remaining.documents, user.emailVerified, team, type]); const onFileDrop = async (file: File) => { try { @@ -80,30 +92,48 @@ export const DocumentUploadButtonLegacy = ({ className }: DocumentUploadButtonLe meta: { timezone: userTimezone, }, - } satisfies TCreateDocumentPayloadSchema; + } satisfies TCreateDocumentPayloadSchema | TCreateTemplatePayloadSchema; const formData = new FormData(); formData.append('payload', JSON.stringify(payload)); formData.append('file', file); - const { envelopeId: id } = await createDocument(formData); + // Handle legacy document creation. + if (type === EnvelopeType.DOCUMENT) { + const { envelopeId: id } = await createDocument(formData); - void refreshLimits(); + void refreshLimits(); - await navigate(`${formatDocumentsPath(team.url)}/${id}/edit`); + await navigate(`${formatDocumentsPath(team.url)}/${id}/edit`); - toast({ - title: _(msg`Document uploaded`), - description: _(msg`Your document has been uploaded successfully.`), - duration: 5000, - }); + toast({ + title: _(msg`Document uploaded`), + description: _(msg`Your document has been uploaded successfully.`), + duration: 5000, + }); - analytics.capture('App: Document Uploaded', { - userId: user.id, - documentId: id, - timestamp: new Date().toISOString(), - }); + analytics.capture('App: Document Uploaded', { + userId: user.id, + documentId: id, + timestamp: new Date().toISOString(), + }); + } + + // Handle legacy template creation. + if (type === EnvelopeType.TEMPLATE) { + const { envelopeId: id } = await createTemplate(formData); + + await navigate(`${formatTemplatesPath(team.url)}/${id}/edit`); + + toast({ + title: _(msg`Template document uploaded`), + description: _( + msg`Your document has been uploaded successfully. You will be redirected to the template page.`, + ), + duration: 5000, + }); + } } catch (err) { const error = AppError.parseError(err); @@ -149,17 +179,18 @@ export const DocumentUploadButtonLegacy = ({ className }: DocumentUploadButtonLe
onFileDrop(files[0])} onDropRejected={onFileDropRejected} - type={EnvelopeType.DOCUMENT} + type={type} internalVersion="1" />
{team?.id === undefined && + type === EnvelopeType.DOCUMENT && remaining.documents > 0 && Number.isFinite(remaining.documents) && ( diff --git a/apps/remix/app/components/general/folder/folder-grid.tsx b/apps/remix/app/components/general/folder/folder-grid.tsx index e8ef9dc3d..5d4c8aa12 100644 --- a/apps/remix/app/components/general/folder/folder-grid.tsx +++ b/apps/remix/app/components/general/folder/folder-grid.tsx @@ -15,7 +15,6 @@ import { FolderCreateDialog } from '~/components/dialogs/folder-create-dialog'; import { FolderDeleteDialog } from '~/components/dialogs/folder-delete-dialog'; import { FolderMoveDialog } from '~/components/dialogs/folder-move-dialog'; import { FolderUpdateDialog } from '~/components/dialogs/folder-update-dialog'; -import { TemplateCreateDialog } from '~/components/dialogs/template-create-dialog'; import { DocumentUploadButtonLegacy } from '~/components/general/document/document-upload-button-legacy'; import { FolderCard, FolderCardEmpty } from '~/components/general/folder/folder-card'; import { useCurrentTeam } from '~/providers/team'; @@ -70,7 +69,7 @@ export const FolderGrid = ({ type, parentId }: FolderGridProps) => {
@@ -100,10 +99,9 @@ export const FolderGrid = ({ type, parentId }: FolderGridProps) => {
- {type === FolderType.DOCUMENT ? ( - // If you delete this, delete the component as well. - ) : ( - // If you delete this, delete the component as well. + {/* If you delete this, delete the component as well. */} + {organisation.organisationClaim.flags.allowLegacyEnvelopes && ( + )} @@ -113,7 +111,7 @@ export const FolderGrid = ({ type, parentId }: FolderGridProps) => { {isPending ? (
{Array.from({ length: 4 }).map((_, index) => ( -
+
@@ -194,7 +192,7 @@ export const FolderGrid = ({ type, parentId }: FolderGridProps) => { {foldersData.folders.length > 12 && (
View all folders diff --git a/packages/app-tests/e2e/folders/team-account-folders.spec.ts b/packages/app-tests/e2e/folders/team-account-folders.spec.ts index 494ff28e1..43811a65a 100644 --- a/packages/app-tests/e2e/folders/team-account-folders.spec.ts +++ b/packages/app-tests/e2e/folders/team-account-folders.spec.ts @@ -364,6 +364,25 @@ test('[TEAMS]: can create a template subfolder inside a template folder', async test('[TEAMS]: can create a template inside a template folder', async ({ page }) => { const { team, teamOwner } = await seedTeamDocuments(); + const organisationClaim = await prisma.organisationClaim.findFirstOrThrow({ + where: { + organisation: { + id: team.organisationId, + }, + }, + }); + + await prisma.organisationClaim.update({ + where: { + id: organisationClaim.id, + }, + data: { + flags: { + allowLegacyEnvelopes: true, + }, + }, + }); + const folder = await seedBlankFolder(teamOwner, team.id, { createFolderOptions: { name: 'Team Client Templates', @@ -380,16 +399,15 @@ test('[TEAMS]: can create a template inside a template folder', async ({ page }) await expect(page.getByText('Team Client Templates')).toBeVisible(); - await page.getByRole('button', { name: 'Template (Legacy)' }).click(); + // Upload document. + const [fileChooser] = await Promise.all([ + page.waitForEvent('filechooser'), + page.getByRole('button', { name: 'Template (Legacy)' }).click(), + ]); - await page.getByText('Upload Template Document').click(); - - await page.locator('input[type="file"]').nth(0).waitFor({ state: 'attached' }); - - await page - .locator('input[type="file"]') - .nth(0) - .setInputFiles(path.join(__dirname, '../../../assets/documenso-supporter-pledge.pdf')); + await fileChooser.setFiles( + path.join(__dirname, '../../../assets/documenso-supporter-pledge.pdf'), + ); await page.waitForTimeout(3000); diff --git a/packages/lib/types/subscription.ts b/packages/lib/types/subscription.ts index c35dc10ca..eba408cd8 100644 --- a/packages/lib/types/subscription.ts +++ b/packages/lib/types/subscription.ts @@ -31,7 +31,7 @@ export const ZClaimFlagsSchema = z.object({ authenticationPortal: z.boolean().optional(), - allowEnvelopes: z.boolean().optional(), + allowLegacyEnvelopes: z.boolean().optional(), }); export type TClaimFlags = z.infer; @@ -84,9 +84,9 @@ export const SUBSCRIPTION_CLAIM_FEATURE_FLAGS: Record< key: 'authenticationPortal', label: 'Authentication portal', }, - allowEnvelopes: { - key: 'allowEnvelopes', - label: 'Allow envelopes', + allowLegacyEnvelopes: { + key: 'allowLegacyEnvelopes', + label: 'Allow Legacy Envelopes', }, }; diff --git a/packages/prisma/seed/users.ts b/packages/prisma/seed/users.ts index 7776fcff8..9447dbd73 100644 --- a/packages/prisma/seed/users.ts +++ b/packages/prisma/seed/users.ts @@ -60,6 +60,18 @@ export const seedUser = async ({ }, include: { teams: true, + organisationClaim: true, + }, + }); + + await prisma.organisationClaim.update({ + where: { + id: organisation.organisationClaim.id, + }, + data: { + flags: { + allowLegacyEnvelopes: true, + }, }, }); diff --git a/packages/ui/components/pdf-viewer/pdf-viewer-konva-lazy.tsx b/packages/ui/components/pdf-viewer/pdf-viewer-konva-lazy.tsx index 2d5d38658..ab85b329d 100644 --- a/packages/ui/components/pdf-viewer/pdf-viewer-konva-lazy.tsx +++ b/packages/ui/components/pdf-viewer/pdf-viewer-konva-lazy.tsx @@ -17,7 +17,7 @@ const EnvelopePdfViewer = lazy(async () => import('./pdf-viewer-konva')); export const PDFViewerKonvaLazy = (props: PDFViewerProps) => { return ( - Loading client component...
}> + Loading...
}> );