Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aed55e9bcd |
@@ -13,7 +13,6 @@ export default {
|
||||
title: 'API & Integration Guides',
|
||||
},
|
||||
'public-api': 'Public API',
|
||||
embedding: 'Embedded Signing',
|
||||
'embedded-authoring': 'Embedded Authoring',
|
||||
embedding: 'Embedding',
|
||||
webhooks: 'Webhooks',
|
||||
};
|
||||
|
||||
@@ -7,4 +7,5 @@ export default {
|
||||
preact: 'Preact Integration',
|
||||
angular: 'Angular Integration',
|
||||
'css-variables': 'CSS Variables',
|
||||
authoring: 'Authoring',
|
||||
};
|
||||
|
||||
+1
-14
@@ -1,25 +1,12 @@
|
||||
---
|
||||
title: Embedded Authoring
|
||||
title: Authoring
|
||||
description: Learn how to use embedded authoring to create documents and templates in your application
|
||||
---
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
|
||||
<Callout type="info">
|
||||
The embedded authoring feature is an enterprise only feature. Please contact us if you are
|
||||
interested in using it.
|
||||
</Callout>
|
||||
|
||||
# Embedded Authoring
|
||||
|
||||
In addition to embedding signing experiences, Documenso now supports embedded authoring, allowing you to integrate document and template creation and editing directly within your application.
|
||||
|
||||
## Embedded Signing vs Embedded Authoring
|
||||
|
||||
Embedded signing allows you to embed your Documenso documents into your application for signing. Your users will be able to sign the document directly in your application.
|
||||
|
||||
Embedded authoring allows you to integrate Documenso's document and template creation and editing into your application. You will be able to create and edit documents and templates directly in your application.
|
||||
|
||||
## How Embedded Authoring Works
|
||||
|
||||
The embedded authoring feature enables your users to create and edit documents and templates without leaving your application. This process works through secure presign tokens that authenticate the embedding session and manage permissions.
|
||||
@@ -3,16 +3,10 @@ title: Get Started
|
||||
description: Learn how to use embedding to bring signing to your own website or application
|
||||
---
|
||||
|
||||
# Embedded Signing
|
||||
# Embedding
|
||||
|
||||
Our embedding feature lets you integrate our document signing experience into your own application or website. Whether you're building with React, Preact, Vue, Svelte, Solid, Angular, or using generalized web components, this guide will help you get started with embedding Documenso.
|
||||
|
||||
## Embedded Signing vs Embedded Authoring
|
||||
|
||||
Embedded signing allows you to embed your Documenso documents into your application for signing. Your users will be able to sign the document directly in your application.
|
||||
|
||||
Embedded authoring allows you to integrate Documenso's document and template creation and editing into your application. You will be able to create and edit documents and templates directly in your application.
|
||||
|
||||
## Availability
|
||||
|
||||
Embedding is currently available for all users on a **Teams Plan** and above, as well as **Early Adopter's** within a team (Early Adopters can create a team for free).
|
||||
|
||||
@@ -40,21 +40,13 @@ type TCreateFolderFormSchema = z.infer<typeof ZCreateFolderFormSchema>;
|
||||
export type FolderCreateDialogProps = {
|
||||
type: FolderType;
|
||||
trigger?: React.ReactNode;
|
||||
parentFolderId?: string | null;
|
||||
} & Omit<DialogPrimitive.DialogProps, 'children'>;
|
||||
|
||||
export const FolderCreateDialog = ({
|
||||
type,
|
||||
trigger,
|
||||
parentFolderId,
|
||||
...props
|
||||
}: FolderCreateDialogProps) => {
|
||||
export const FolderCreateDialog = ({ type, trigger, ...props }: FolderCreateDialogProps) => {
|
||||
const { t } = useLingui();
|
||||
const { toast } = useToast();
|
||||
const { folderId } = useParams();
|
||||
|
||||
const parentId = parentFolderId ?? folderId;
|
||||
|
||||
const [isCreateFolderOpen, setIsCreateFolderOpen] = useState(false);
|
||||
|
||||
const { mutateAsync: createFolder } = trpc.folder.createFolder.useMutation();
|
||||
@@ -70,7 +62,7 @@ export const FolderCreateDialog = ({
|
||||
try {
|
||||
await createFolder({
|
||||
name: data.name,
|
||||
parentId,
|
||||
parentId: folderId,
|
||||
type,
|
||||
});
|
||||
|
||||
|
||||
@@ -53,10 +53,6 @@ export const FolderGrid = ({ type, parentId }: FolderGridProps) => {
|
||||
const rootPath =
|
||||
type === FolderType.DOCUMENT ? formatDocumentsPath(team.url) : formatTemplatesPath(team.url);
|
||||
|
||||
if (parentId) {
|
||||
return `${rootPath}/folders?parentId=${parentId}`;
|
||||
}
|
||||
|
||||
return `${rootPath}/folders`;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { FolderIcon, HomeIcon, Loader2, SearchIcon } from 'lucide-react';
|
||||
import { Link, useSearchParams } from 'react-router';
|
||||
import { HomeIcon, Loader2, SearchIcon } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { FolderType } from '@documenso/lib/types/folder-type';
|
||||
import { formatDocumentsPath } from '@documenso/lib/utils/teams';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { type TFolderWithSubfolders } from '@documenso/trpc/server/folder-router/schema';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
|
||||
import { FolderCreateDialog } from '~/components/dialogs/folder-create-dialog';
|
||||
@@ -25,10 +26,8 @@ export function meta() {
|
||||
export default function DocumentsFoldersPage() {
|
||||
const { t } = useLingui();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const team = useCurrentTeam();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const parentId = searchParams.get('parentId');
|
||||
|
||||
const [isMovingFolder, setIsMovingFolder] = useState(false);
|
||||
const [folderToMove, setFolderToMove] = useState<TFolderWithSubfolders | null>(null);
|
||||
@@ -40,51 +39,44 @@ export default function DocumentsFoldersPage() {
|
||||
|
||||
const { data: foldersData, isLoading: isFoldersLoading } = trpc.folder.getFolders.useQuery({
|
||||
type: FolderType.DOCUMENT,
|
||||
parentId: parentId,
|
||||
parentId: null,
|
||||
});
|
||||
|
||||
const navigateToFolder = (folderId?: string | null) => {
|
||||
const documentsPath = formatDocumentsPath(team.url);
|
||||
|
||||
if (folderId) {
|
||||
void navigate(`${documentsPath}/f/${folderId}`);
|
||||
} else {
|
||||
void navigate(documentsPath);
|
||||
}
|
||||
};
|
||||
|
||||
const isFolderMatchingSearch = (folder: TFolderWithSubfolders) =>
|
||||
folder.name.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
|
||||
const formatBreadCrumbPath = (folderId: string) => {
|
||||
const documentsPath = formatDocumentsPath(team.url);
|
||||
|
||||
return `${documentsPath}/f/${folderId}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-screen-xl px-4 md:px-8">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="flex flex-1 items-center text-sm font-medium text-muted-foreground">
|
||||
<Link
|
||||
to={formatDocumentsPath(team.url)}
|
||||
className="flex items-center hover:text-muted-foreground/80"
|
||||
<div className="flex flex-1 items-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="flex items-center space-x-2 pl-0 hover:bg-transparent"
|
||||
onClick={() => navigateToFolder(null)}
|
||||
>
|
||||
<HomeIcon className="mr-2 h-4 w-4" />
|
||||
<Trans>Home</Trans>
|
||||
</Link>
|
||||
|
||||
{foldersData?.breadcrumbs.map((folder) => (
|
||||
<div key={folder.id} className="flex items-center">
|
||||
<span className="px-3">/</span>
|
||||
<Link
|
||||
to={formatBreadCrumbPath(folder.id)}
|
||||
className="flex items-center hover:text-muted-foreground/80"
|
||||
>
|
||||
<FolderIcon className="mr-2 h-4 w-4" />
|
||||
<span>{folder.name}</span>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
<HomeIcon className="h-4 w-4" />
|
||||
<span>Home</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-y-4 sm:flex-row sm:justify-end sm:gap-x-4">
|
||||
<FolderCreateDialog type={FolderType.DOCUMENT} parentFolderId={parentId} />
|
||||
<FolderCreateDialog type={FolderType.DOCUMENT} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full max-w-md py-6">
|
||||
<SearchIcon className="absolute left-2 top-9 h-4 w-4 text-muted-foreground" />
|
||||
<SearchIcon className="text-muted-foreground absolute left-2 top-9 h-4 w-4" />
|
||||
<Input
|
||||
placeholder={t`Search folders...`}
|
||||
value={searchTerm}
|
||||
@@ -99,7 +91,7 @@ export default function DocumentsFoldersPage() {
|
||||
|
||||
{isFoldersLoading ? (
|
||||
<div className="mt-6 flex justify-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
<Loader2 className="text-muted-foreground h-8 w-8 animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
@@ -134,14 +126,14 @@ export default function DocumentsFoldersPage() {
|
||||
|
||||
<div>
|
||||
{searchTerm && foldersData?.folders.filter(isFolderMatchingSearch).length === 0 && (
|
||||
<div className="mt-6 text-center text-muted-foreground">
|
||||
<div className="text-muted-foreground mt-6 text-center">
|
||||
<Trans>No folders found matching "{searchTerm}"</Trans>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
|
||||
{foldersData?.folders
|
||||
.filter((folder) => !folder.pinned && isFolderMatchingSearch(folder))
|
||||
.filter((folder) => !folder.pinned)
|
||||
.map((folder) => (
|
||||
<FolderCard
|
||||
key={folder.id}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { FolderIcon, HomeIcon, Loader2, SearchIcon } from 'lucide-react';
|
||||
import { Link, useSearchParams } from 'react-router';
|
||||
import { HomeIcon, Loader2, SearchIcon } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { FolderType } from '@documenso/lib/types/folder-type';
|
||||
import { formatTemplatesPath } from '@documenso/lib/utils/teams';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { type TFolderWithSubfolders } from '@documenso/trpc/server/folder-router/schema';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Input } from '@documenso/ui/primitives/input';
|
||||
|
||||
import { FolderCreateDialog } from '~/components/dialogs/folder-create-dialog';
|
||||
@@ -25,10 +26,8 @@ export function meta() {
|
||||
export default function TemplatesFoldersPage() {
|
||||
const { t } = useLingui();
|
||||
|
||||
const navigate = useNavigate();
|
||||
const team = useCurrentTeam();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const parentId = searchParams.get('parentId');
|
||||
|
||||
const [isMovingFolder, setIsMovingFolder] = useState(false);
|
||||
const [folderToMove, setFolderToMove] = useState<TFolderWithSubfolders | null>(null);
|
||||
@@ -40,51 +39,44 @@ export default function TemplatesFoldersPage() {
|
||||
|
||||
const { data: foldersData, isLoading: isFoldersLoading } = trpc.folder.getFolders.useQuery({
|
||||
type: FolderType.TEMPLATE,
|
||||
parentId: parentId,
|
||||
parentId: null,
|
||||
});
|
||||
|
||||
const navigateToFolder = (folderId?: string | null) => {
|
||||
const templatesPath = formatTemplatesPath(team.url);
|
||||
|
||||
if (folderId) {
|
||||
void navigate(`${templatesPath}/f/${folderId}`);
|
||||
} else {
|
||||
void navigate(templatesPath);
|
||||
}
|
||||
};
|
||||
|
||||
const isFolderMatchingSearch = (folder: TFolderWithSubfolders) =>
|
||||
folder.name.toLowerCase().includes(searchTerm.toLowerCase());
|
||||
|
||||
const formatBreadCrumbPath = (folderId: string) => {
|
||||
const templatesPath = formatTemplatesPath(team.url);
|
||||
|
||||
return `${templatesPath}/f/${folderId}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-screen-xl px-4 md:px-8">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<div className="flex flex-1 items-center text-sm font-medium text-muted-foreground">
|
||||
<Link
|
||||
to={formatTemplatesPath(team.url)}
|
||||
className="flex items-center hover:text-muted-foreground/80"
|
||||
<div className="flex flex-1 items-center">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="flex items-center space-x-2 pl-0 hover:bg-transparent"
|
||||
onClick={() => navigateToFolder(null)}
|
||||
>
|
||||
<HomeIcon className="mr-2 h-4 w-4" />
|
||||
<Trans>Home</Trans>
|
||||
</Link>
|
||||
|
||||
{foldersData?.breadcrumbs.map((folder) => (
|
||||
<div key={folder.id} className="flex items-center">
|
||||
<span className="px-3">/</span>
|
||||
<Link
|
||||
to={formatBreadCrumbPath(folder.id)}
|
||||
className="flex items-center hover:text-muted-foreground/80"
|
||||
>
|
||||
<FolderIcon className="mr-2 h-4 w-4" />
|
||||
<span>{folder.name}</span>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
<HomeIcon className="h-4 w-4" />
|
||||
<span>Home</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-y-4 sm:flex-row sm:justify-end sm:gap-x-4">
|
||||
<FolderCreateDialog type={FolderType.TEMPLATE} parentFolderId={parentId} />
|
||||
<FolderCreateDialog type={FolderType.TEMPLATE} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full max-w-md py-6">
|
||||
<SearchIcon className="absolute left-2 top-9 h-4 w-4 text-muted-foreground" />
|
||||
<SearchIcon className="text-muted-foreground absolute left-2 top-9 h-4 w-4" />
|
||||
<Input
|
||||
placeholder={t`Search folders...`}
|
||||
value={searchTerm}
|
||||
@@ -98,8 +90,8 @@ export default function TemplatesFoldersPage() {
|
||||
</h1>
|
||||
|
||||
{isFoldersLoading ? (
|
||||
<div className="mt-6 flex justify-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
<div className="mt- flex justify-center">
|
||||
<Loader2 className="text-muted-foreground h-8 w-8 animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
@@ -134,7 +126,7 @@ export default function TemplatesFoldersPage() {
|
||||
|
||||
<div>
|
||||
{searchTerm && foldersData?.folders.filter(isFolderMatchingSearch).length === 0 && (
|
||||
<div className="mt-6 text-center text-muted-foreground">
|
||||
<div className="text-muted-foreground mt-6 text-center">
|
||||
<Trans>No folders found matching "{searchTerm}"</Trans>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -388,9 +388,10 @@ const decorateAndSignPdf = async ({
|
||||
}
|
||||
}
|
||||
|
||||
// Should never run into issues with this flatten since all
|
||||
// arcoFields are created by pdf-lib itself.
|
||||
legacy_pdfLibDoc.getForm().flatten();
|
||||
// Flatten the form to bake checkbox/radio appearances into the PDF content
|
||||
// This ensures proper rendering when the PDF is processed by libpdf
|
||||
const form = legacy_pdfLibDoc.getForm();
|
||||
form.flatten();
|
||||
|
||||
await pdfDoc.reload(await legacy_pdfLibDoc.save());
|
||||
}
|
||||
|
||||
@@ -551,7 +551,6 @@ export const createDocumentFromTemplate = async ({
|
||||
visibility: template.visibility || settings.documentVisibility,
|
||||
useLegacyFieldInsertion: template.useLegacyFieldInsertion ?? false,
|
||||
documentMetaId: documentMeta.id,
|
||||
formValues: formValues ?? undefined,
|
||||
recipients: {
|
||||
createMany: {
|
||||
data: allFinalRecipients.map((recipient) => {
|
||||
|
||||
Reference in New Issue
Block a user