Merge branch 'main' into feat/bin-tab

This commit is contained in:
Ephraim Duncan
2025-04-18 23:41:53 +00:00
committed by GitHub
174 changed files with 23672 additions and 2236 deletions

View File

@ -23,12 +23,12 @@ import {
getUsersWithSubscriptionsCount,
} from '@documenso/lib/server-only/admin/get-users-stats';
import { getSignerConversionMonthly } from '@documenso/lib/server-only/user/get-signer-conversion';
import { env } from '@documenso/lib/utils/env';
import { AdminStatsSignerConversionChart } from '~/components/general/admin-stats-signer-conversion-chart';
import { AdminStatsUsersWithDocumentsChart } from '~/components/general/admin-stats-users-with-documents';
import { CardMetric } from '~/components/general/metric-card';
import { version } from '../../../../package.json';
import type { Route } from './+types/stats';
export async function loader() {
@ -89,7 +89,7 @@ export default function AdminStatsPage({ loaderData }: Route.ComponentProps) {
value={usersWithSubscriptionsCount}
/>
<CardMetric icon={FileCog} title={_(msg`App Version`)} value={`v${env('APP_VERSION')}`} />
<CardMetric icon={FileCog} title={_(msg`App Version`)} value={`v${version}`} />
</div>
<div className="mt-16 gap-8">

View File

@ -6,6 +6,7 @@ import { redirect } from 'react-router';
import { match } from 'ts-pattern';
import { UAParser } from 'ua-parser-js';
import { isDocumentPlatform } from '@documenso/ee/server-only/util/is-document-platform';
import { APP_I18N_OPTIONS, ZSupportedLanguageCodeSchema } from '@documenso/lib/constants/i18n';
import {
RECIPIENT_ROLES_DESCRIPTION,
@ -59,6 +60,8 @@ export async function loader({ request }: Route.LoaderArgs) {
throw redirect('/');
}
const isPlatformDocument = await isDocumentPlatform(document);
const documentLanguage = ZSupportedLanguageCodeSchema.parse(document.documentMeta?.language);
const auditLogs = await getDocumentCertificateAuditLogs({
@ -70,6 +73,7 @@ export async function loader({ request }: Route.LoaderArgs) {
return {
document,
documentLanguage,
isPlatformDocument,
auditLogs,
messages,
};
@ -85,7 +89,7 @@ export async function loader({ request }: Route.LoaderArgs) {
* Update: Maybe <Trans> tags work now after RR7 migration.
*/
export default function SigningCertificate({ loaderData }: Route.ComponentProps) {
const { document, documentLanguage, auditLogs, messages } = loaderData;
const { document, documentLanguage, isPlatformDocument, auditLogs, messages } = loaderData;
const { i18n, _ } = useLingui();
@ -245,24 +249,24 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
{signature.secondaryId}
</span>
</p>
<p className="text-muted-foreground mt-2 text-sm print:text-xs">
<span className="font-medium">{_(msg`IP Address`)}:</span>{' '}
<span className="inline-block">
{logs.DOCUMENT_RECIPIENT_COMPLETED[0]?.ipAddress ?? _(msg`Unknown`)}
</span>
</p>
<p className="text-muted-foreground mt-1 text-sm print:text-xs">
<span className="font-medium">{_(msg`Device`)}:</span>{' '}
<span className="inline-block">
{getDevice(logs.DOCUMENT_RECIPIENT_COMPLETED[0]?.userAgent)}
</span>
</p>
</>
) : (
<p className="text-muted-foreground">N/A</p>
)}
<p className="text-muted-foreground mt-2 text-sm print:text-xs">
<span className="font-medium">{_(msg`IP Address`)}:</span>{' '}
<span className="inline-block">
{logs.DOCUMENT_RECIPIENT_COMPLETED[0]?.ipAddress ?? _(msg`Unknown`)}
</span>
</p>
<p className="text-muted-foreground mt-1 text-sm print:text-xs">
<span className="font-medium">{_(msg`Device`)}:</span>{' '}
<span className="inline-block">
{getDevice(logs.DOCUMENT_RECIPIENT_COMPLETED[0]?.userAgent)}
</span>
</p>
</TableCell>
<TableCell truncate={false} className="w-[min-content] align-top">
@ -337,15 +341,17 @@ export default function SigningCertificate({ loaderData }: Route.ComponentProps)
</CardContent>
</Card>
<div className="my-8 flex-row-reverse">
<div className="flex items-end justify-end gap-x-4">
<p className="flex-shrink-0 text-sm font-medium print:text-xs">
{_(msg`Signing certificate provided by`)}:
</p>
{isPlatformDocument && (
<div className="my-8 flex-row-reverse">
<div className="flex items-end justify-end gap-x-4">
<p className="flex-shrink-0 text-sm font-medium print:text-xs">
{_(msg`Signing certificate provided by`)}:
</p>
<BrandingLogo className="max-h-6 print:max-h-4" />
<BrandingLogo className="max-h-6 print:max-h-4" />
</div>
</div>
</div>
)}
</div>
);
}

View File

@ -79,7 +79,14 @@ export default function DirectTemplatePage() {
const { template, directTemplateRecipient } = data;
return (
<DocumentSigningProvider email={user?.email} fullName={user?.name} signature={user?.signature}>
<DocumentSigningProvider
email={user?.email}
fullName={user?.name}
signature={user?.signature}
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
uploadSignatureEnabled={template.templateMeta?.uploadSignatureEnabled}
drawSignatureEnabled={template.templateMeta?.drawSignatureEnabled}
>
<DocumentSigningAuthProvider
documentAuthOptions={template.authOptions}
recipient={directTemplateRecipient}

View File

@ -1,5 +1,5 @@
import { Trans } from '@lingui/react/macro';
import { DocumentStatus, RecipientRole, SigningStatus } from '@prisma/client';
import { DocumentSigningOrder, DocumentStatus, RecipientRole, SigningStatus } from '@prisma/client';
import { Clock8 } from 'lucide-react';
import { Link, redirect } from 'react-router';
import { getOptionalLoaderContext } from 'server/utils/get-loader-session';
@ -13,6 +13,7 @@ import { viewedDocument } from '@documenso/lib/server-only/document/viewed-docum
import { getCompletedFieldsForToken } from '@documenso/lib/server-only/field/get-completed-fields-for-token';
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
import { getIsRecipientsTurnToSign } from '@documenso/lib/server-only/recipient/get-is-recipient-turn';
import { getNextPendingRecipient } from '@documenso/lib/server-only/recipient/get-next-pending-recipient';
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
import { getRecipientSignatures } from '@documenso/lib/server-only/recipient/get-recipient-signatures';
import { getRecipientsForAssistant } from '@documenso/lib/server-only/recipient/get-recipients-for-assistant';
@ -72,7 +73,24 @@ export async function loader({ params, request }: Route.LoaderArgs) {
? await getRecipientsForAssistant({
token,
})
: [];
: [recipient];
if (
document.documentMeta?.signingOrder === DocumentSigningOrder.SEQUENTIAL &&
recipient.role !== RecipientRole.ASSISTANT
) {
const nextPendingRecipient = await getNextPendingRecipient({
documentId: document.id,
currentRecipientId: recipient.id,
});
if (nextPendingRecipient) {
allRecipients.push({
...nextPendingRecipient,
fields: [],
});
}
}
const { derivedRecipientAccessAuth } = extractDocumentAuthMethods({
documentAuth: document.authOptions,
@ -215,6 +233,9 @@ export default function SigningPage() {
email={recipient.email}
fullName={user?.email === recipient.email ? user?.name : recipient.name}
signature={user?.email === recipient.email ? user?.signature : undefined}
typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
uploadSignatureEnabled={document.documentMeta?.uploadSignatureEnabled}
drawSignatureEnabled={document.documentMeta?.drawSignatureEnabled}
>
<DocumentSigningAuthProvider
documentAuthOptions={document.authOptions}

View File

@ -6,7 +6,7 @@ import { isTokenExpired } from '@documenso/lib/utils/token-verification';
import { prisma } from '@documenso/prisma';
import { Button } from '@documenso/ui/primitives/button';
import type { Route } from './+types/team.verify.transfer.token';
import type { Route } from './+types/team.verify.transfer.$token';
export async function loader({ params }: Route.LoaderArgs) {
const { token } = params;

View File

@ -131,7 +131,14 @@ export default function EmbedDirectTemplatePage() {
} = useSuperLoaderData<typeof loader>();
return (
<DocumentSigningProvider email={user?.email} fullName={user?.name} signature={user?.signature}>
<DocumentSigningProvider
email={user?.email}
fullName={user?.name}
signature={user?.signature}
typedSignatureEnabled={template.templateMeta?.typedSignatureEnabled}
uploadSignatureEnabled={template.templateMeta?.uploadSignatureEnabled}
drawSignatureEnabled={template.templateMeta?.drawSignatureEnabled}
>
<DocumentSigningAuthProvider
documentAuthOptions={template.authOptions}
recipient={recipient}

View File

@ -8,6 +8,7 @@ import { isUserEnterprise } from '@documenso/ee/server-only/util/is-document-ent
import { isDocumentPlatform } from '@documenso/ee/server-only/util/is-document-platform';
import { IS_BILLING_ENABLED } from '@documenso/lib/constants/app';
import { getDocumentAndSenderByToken } from '@documenso/lib/server-only/document/get-document-by-token';
import { getCompletedFieldsForToken } from '@documenso/lib/server-only/field/get-completed-fields-for-token';
import { getFieldsForToken } from '@documenso/lib/server-only/field/get-fields-for-token';
import { getIsRecipientsTurnToSign } from '@documenso/lib/server-only/recipient/get-is-recipient-turn';
import { getRecipientByToken } from '@documenso/lib/server-only/recipient/get-recipient-by-token';
@ -33,7 +34,7 @@ export async function loader({ params, request }: Route.LoaderArgs) {
const { user } = await getOptionalSession(request);
const [document, fields, recipient] = await Promise.all([
const [document, fields, recipient, completedFields] = await Promise.all([
getDocumentAndSenderByToken({
token,
userId: user?.id,
@ -41,6 +42,7 @@ export async function loader({ params, request }: Route.LoaderArgs) {
}).catch(() => null),
getFieldsForToken({ token }),
getRecipientByToken({ token }).catch(() => null),
getCompletedFieldsForToken({ token }).catch(() => []),
]);
// `document.directLink` is always available but we're doing this to
@ -130,6 +132,7 @@ export async function loader({ params, request }: Route.LoaderArgs) {
allRecipients,
recipient,
fields,
completedFields,
hidePoweredBy,
isPlatformDocument,
isEnterpriseDocument,
@ -145,6 +148,7 @@ export default function EmbedSignDocumentPage() {
allRecipients,
recipient,
fields,
completedFields,
hidePoweredBy,
isPlatformDocument,
isEnterpriseDocument,
@ -156,6 +160,9 @@ export default function EmbedSignDocumentPage() {
email={recipient.email}
fullName={user?.email === recipient.email ? user?.name : recipient.name}
signature={user?.email === recipient.email ? user?.signature : undefined}
typedSignatureEnabled={document.documentMeta?.typedSignatureEnabled}
uploadSignatureEnabled={document.documentMeta?.uploadSignatureEnabled}
drawSignatureEnabled={document.documentMeta?.drawSignatureEnabled}
>
<DocumentSigningAuthProvider
documentAuthOptions={document.authOptions}
@ -168,6 +175,7 @@ export default function EmbedSignDocumentPage() {
documentData={document.documentData}
recipient={recipient}
fields={fields}
completedFields={completedFields}
metadata={document.documentMeta}
isCompleted={isDocumentCompleted(document.status)}
hidePoweredBy={

View File

@ -0,0 +1,66 @@
import { useLayoutEffect, useState } from 'react';
import { Outlet } from 'react-router';
import { TrpcProvider, trpc } from '@documenso/trpc/react';
import { EmbedClientLoading } from '~/components/embed/embed-client-loading';
import { ZBaseEmbedAuthoringSchema } from '~/types/embed-authoring-base-schema';
import { injectCss } from '~/utils/css-vars';
export default function AuthoringLayout() {
const [token, setToken] = useState('');
const {
mutateAsync: verifyEmbeddingPresignToken,
isPending: isVerifyingEmbeddingPresignToken,
data: isVerified,
} = trpc.embeddingPresign.verifyEmbeddingPresignToken.useMutation();
useLayoutEffect(() => {
try {
const hash = window.location.hash.slice(1);
const result = ZBaseEmbedAuthoringSchema.safeParse(
JSON.parse(decodeURIComponent(atob(hash))),
);
if (!result.success) {
return;
}
const { token, css, cssVars, darkModeDisabled } = result.data;
if (darkModeDisabled) {
document.documentElement.classList.add('dark-mode-disabled');
}
injectCss({
css,
cssVars,
});
void verifyEmbeddingPresignToken({ token }).then((result) => {
if (result.success) {
setToken(token);
}
});
} catch (err) {
console.error('Error verifying embedding presign token:', err);
}
}, []);
if (isVerifyingEmbeddingPresignToken) {
return <EmbedClientLoading />;
}
if (typeof isVerified !== 'undefined' && !isVerified.success) {
return <div>Invalid embedding presign token</div>;
}
return (
<TrpcProvider headers={{ authorization: `Bearer ${token}` }}>
<Outlet />
</TrpcProvider>
);
}

View File

@ -0,0 +1,53 @@
import { useEffect } from 'react';
import { Trans } from '@lingui/react/macro';
import { CheckCircle2 } from 'lucide-react';
import { useSearchParams } from 'react-router';
export default function EmbeddingAuthoringCompletedPage() {
const [searchParams] = useSearchParams();
// Get templateId and externalId from URL search params
const templateId = searchParams.get('templateId');
const documentId = searchParams.get('documentId');
const externalId = searchParams.get('externalId');
const id = Number(templateId || documentId);
const type = templateId ? 'template' : 'document';
// Send postMessage to parent window with the details
useEffect(() => {
if (!id || !window.parent || window.parent === window) {
return;
}
window.parent.postMessage(
{
type: `${type}-created`,
[`${type}Id`]: id,
externalId,
},
'*',
);
}, [id, type, externalId]);
return (
<div className="flex min-h-[100dvh] flex-col items-center justify-center p-6 text-center">
<div className="mx-auto w-full max-w-md">
<CheckCircle2 className="text-primary mx-auto h-16 w-16" />
<h1 className="mt-6 text-2xl font-bold">
{type === 'template' ? <Trans>Template Created</Trans> : <Trans>Document Created</Trans>}
</h1>
<p className="text-muted-foreground mt-2">
{type === 'template' ? (
<Trans>Your template has been created successfully</Trans>
) : (
<Trans>Your document has been created successfully</Trans>
)}
</p>
</div>
</div>
);
}

View File

@ -0,0 +1,184 @@
import { useLayoutEffect, useState } from 'react';
import { useLingui } from '@lingui/react';
import { useNavigate } from 'react-router';
import { DocumentSignatureType } from '@documenso/lib/constants/document';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { trpc } from '@documenso/trpc/react';
import { Stepper } from '@documenso/ui/primitives/stepper';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { ConfigureDocumentProvider } from '~/components/embed/authoring/configure-document-context';
import { ConfigureDocumentView } from '~/components/embed/authoring/configure-document-view';
import type { TConfigureEmbedFormSchema } from '~/components/embed/authoring/configure-document-view.types';
import {
ConfigureFieldsView,
type TConfigureFieldsFormSchema,
} from '~/components/embed/authoring/configure-fields-view';
import {
type TBaseEmbedAuthoringSchema,
ZBaseEmbedAuthoringSchema,
} from '~/types/embed-authoring-base-schema';
export default function EmbeddingAuthoringDocumentCreatePage() {
const { _ } = useLingui();
const { toast } = useToast();
const navigate = useNavigate();
const [configuration, setConfiguration] = useState<TConfigureEmbedFormSchema | null>(null);
const [fields, setFields] = useState<TConfigureFieldsFormSchema | null>(null);
const [features, setFeatures] = useState<TBaseEmbedAuthoringSchema['features'] | null>(null);
const [externalId, setExternalId] = useState<string | null>(null);
const [currentStep, setCurrentStep] = useState(1);
const { mutateAsync: createEmbeddingDocument } =
trpc.embeddingPresign.createEmbeddingDocument.useMutation();
const handleConfigurePageViewSubmit = (data: TConfigureEmbedFormSchema) => {
// Store the configuration data and move to the field placement stage
setConfiguration(data);
setCurrentStep(2);
};
const handleBackToConfig = (data: TConfigureFieldsFormSchema) => {
// Return to the configuration view but keep the data
setFields(data);
setCurrentStep(1);
};
const handleConfigureFieldsSubmit = async (data: TConfigureFieldsFormSchema) => {
try {
if (!configuration || !configuration.documentData) {
toast({
variant: 'destructive',
title: _('Error'),
description: _('Please configure the document first'),
});
return;
}
const fields = data.fields;
const documentData = await putPdfFile({
arrayBuffer: async () => Promise.resolve(configuration.documentData!.data.buffer),
name: configuration.documentData.name,
type: configuration.documentData.type,
});
// Use the externalId from the URL fragment if available
const documentExternalId = externalId || configuration.meta.externalId;
const createResult = await createEmbeddingDocument({
title: configuration.title,
documentDataId: documentData.id,
externalId: documentExternalId,
meta: {
...configuration.meta,
drawSignatureEnabled:
configuration.meta.signatureTypes.length === 0 ||
configuration.meta.signatureTypes.includes(DocumentSignatureType.DRAW),
typedSignatureEnabled:
configuration.meta.signatureTypes.length === 0 ||
configuration.meta.signatureTypes.includes(DocumentSignatureType.TYPE),
uploadSignatureEnabled:
configuration.meta.signatureTypes.length === 0 ||
configuration.meta.signatureTypes.includes(DocumentSignatureType.UPLOAD),
},
recipients: configuration.signers.map((signer) => ({
name: signer.name,
email: signer.email,
role: signer.role,
fields: fields
.filter((field) => field.signerEmail === signer.email)
// There's a gnarly discriminated union that makes this hard to satisfy, we're casting for the second
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.map<any>((f) => ({
...f,
pageX: f.pageX,
pageY: f.pageY,
width: f.pageWidth,
height: f.pageHeight,
})),
})),
});
toast({
title: _('Success'),
description: _('Document created successfully'),
});
// Send a message to the parent window with the document details
if (window.parent !== window) {
window.parent.postMessage(
{
type: 'document-created',
documentId: createResult.documentId,
externalId: documentExternalId,
},
'*',
);
}
const hash = window.location.hash.slice(1);
// Navigate to the completion page instead of the document details page
await navigate(
`/embed/v1/authoring/create-completed?documentId=${createResult.documentId}&externalId=${documentExternalId}#${hash}`,
);
} catch (err) {
console.error('Error creating document:', err);
toast({
variant: 'destructive',
title: _('Error'),
description: _('Failed to create document'),
});
}
};
useLayoutEffect(() => {
try {
const hash = window.location.hash.slice(1);
const result = ZBaseEmbedAuthoringSchema.safeParse(
JSON.parse(decodeURIComponent(atob(hash))),
);
if (!result.success) {
return;
}
setFeatures(result.data.features);
// Extract externalId from the parsed data if available
if (result.data.externalId) {
setExternalId(result.data.externalId);
}
} catch (err) {
console.error('Error parsing embedding params:', err);
}
}, []);
return (
<div className="relative mx-auto flex min-h-[100dvh] max-w-screen-lg p-6">
<ConfigureDocumentProvider isTemplate={false} features={features ?? {}}>
<Stepper currentStep={currentStep} setCurrentStep={setCurrentStep}>
<ConfigureDocumentView
defaultValues={configuration ?? undefined}
onSubmit={handleConfigurePageViewSubmit}
/>
<ConfigureFieldsView
configData={configuration!}
defaultValues={fields ?? undefined}
onBack={handleBackToConfig}
onSubmit={handleConfigureFieldsSubmit}
/>
</Stepper>
</ConfigureDocumentProvider>
</div>
);
}

View File

@ -0,0 +1,175 @@
import { useLayoutEffect, useState } from 'react';
import { useLingui } from '@lingui/react';
import { useNavigate } from 'react-router';
import { putPdfFile } from '@documenso/lib/universal/upload/put-file';
import { trpc } from '@documenso/trpc/react';
import { Stepper } from '@documenso/ui/primitives/stepper';
import { useToast } from '@documenso/ui/primitives/use-toast';
import { ConfigureDocumentProvider } from '~/components/embed/authoring/configure-document-context';
import { ConfigureDocumentView } from '~/components/embed/authoring/configure-document-view';
import type { TConfigureEmbedFormSchema } from '~/components/embed/authoring/configure-document-view.types';
import {
ConfigureFieldsView,
type TConfigureFieldsFormSchema,
} from '~/components/embed/authoring/configure-fields-view';
import {
type TBaseEmbedAuthoringSchema,
ZBaseEmbedAuthoringSchema,
} from '~/types/embed-authoring-base-schema';
export default function EmbeddingAuthoringTemplateCreatePage() {
const { _ } = useLingui();
const { toast } = useToast();
const navigate = useNavigate();
const [configuration, setConfiguration] = useState<TConfigureEmbedFormSchema | null>(null);
const [fields, setFields] = useState<TConfigureFieldsFormSchema | null>(null);
const [features, setFeatures] = useState<TBaseEmbedAuthoringSchema['features'] | null>(null);
const [externalId, setExternalId] = useState<string | null>(null);
const [currentStep, setCurrentStep] = useState(1);
const { mutateAsync: createEmbeddingTemplate } =
trpc.embeddingPresign.createEmbeddingTemplate.useMutation();
const handleConfigurePageViewSubmit = (data: TConfigureEmbedFormSchema) => {
// Store the configuration data and move to the field placement stage
setConfiguration(data);
setCurrentStep(2);
};
const handleBackToConfig = (data: TConfigureFieldsFormSchema) => {
// Return to the configuration view but keep the data
setFields(data);
setCurrentStep(1);
};
const handleConfigureFieldsSubmit = async (data: TConfigureFieldsFormSchema) => {
try {
console.log('configuration', configuration);
console.log('data', data);
if (!configuration || !configuration.documentData) {
toast({
variant: 'destructive',
title: _('Error'),
description: _('Please configure the template first'),
});
return;
}
const fields = data.fields;
const documentData = await putPdfFile({
arrayBuffer: async () => Promise.resolve(configuration.documentData!.data.buffer),
name: configuration.documentData.name,
type: configuration.documentData.type,
});
// Use the externalId from the URL fragment if available
const metaWithExternalId = {
...configuration.meta,
externalId: externalId || configuration.meta.externalId,
};
const createResult = await createEmbeddingTemplate({
title: configuration.title,
documentDataId: documentData.id,
meta: metaWithExternalId,
recipients: configuration.signers.map((signer) => ({
name: signer.name,
email: signer.email,
role: signer.role,
fields: fields
.filter((field) => field.signerEmail === signer.email)
// There's a gnarly discriminated union that makes this hard to satisfy, we're casting for the second
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.map<any>((field) => ({
...field,
pageX: field.pageX,
pageY: field.pageY,
width: field.pageWidth,
height: field.pageHeight,
})),
})),
});
toast({
title: _('Success'),
description: _('Template created successfully'),
});
// Send a message to the parent window with the template details
if (window.parent !== window) {
window.parent.postMessage(
{
type: 'template-created',
templateId: createResult.templateId,
externalId: metaWithExternalId.externalId,
},
'*',
);
}
const hash = window.location.hash.slice(1);
// Navigate to the completion page instead of the template details page
await navigate(
`/embed/v1/authoring/create-completed?templateId=${createResult.templateId}&externalId=${metaWithExternalId.externalId}#${hash}`,
);
} catch (err) {
console.error('Error creating template:', err);
toast({
variant: 'destructive',
title: _('Error'),
description: _('Failed to create template'),
});
}
};
useLayoutEffect(() => {
try {
const hash = window.location.hash.slice(1);
const result = ZBaseEmbedAuthoringSchema.safeParse(
JSON.parse(decodeURIComponent(atob(hash))),
);
if (!result.success) {
return;
}
setFeatures(result.data.features);
// Extract externalId from the parsed data if available
if (result.data.externalId) {
setExternalId(result.data.externalId);
}
} catch (err) {
console.error('Error parsing embedding params:', err);
}
}, []);
return (
<div className="relative mx-auto flex min-h-[100dvh] max-w-screen-lg p-6">
<ConfigureDocumentProvider isTemplate={true} features={features ?? {}}>
<Stepper currentStep={currentStep} setCurrentStep={setCurrentStep}>
<ConfigureDocumentView
defaultValues={configuration ?? undefined}
onSubmit={handleConfigurePageViewSubmit}
/>
<ConfigureFieldsView
configData={configuration!}
defaultValues={fields ?? undefined}
onBack={handleBackToConfig}
onSubmit={handleConfigureFieldsSubmit}
/>
</Stepper>
</ConfigureDocumentProvider>
</div>
);
}