mirror of
https://github.com/documenso/documenso.git
synced 2025-11-19 19:21:39 +10:00
fix: embed editing updates (#2197)
Allows empty recipients for embed template authoring. Also allows fixing the step to editing fields only for embedded authoring updates.
This commit is contained in:
@ -81,33 +81,6 @@ export const ConfigureDocumentAdvancedSettings = ({
|
||||
|
||||
<TabsContent value="general" className="mt-0">
|
||||
<div className="flex flex-col space-y-6">
|
||||
{/* <FormField
|
||||
control={control}
|
||||
name="meta.externalId"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex flex-row items-center">
|
||||
<Trans>External ID</Trans>
|
||||
<Tooltip>
|
||||
<TooltipTrigger>
|
||||
<InfoIcon className="mx-2 h-4 w-4" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="text-muted-foreground max-w-xs">
|
||||
<Trans>
|
||||
Add an external ID to the document. This can be used to identify the
|
||||
document in external systems.
|
||||
</Trans>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input className="bg-background" {...field} disabled={isSubmitting} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/> */}
|
||||
|
||||
{features.allowConfigureSignatureTypes && (
|
||||
<FormField
|
||||
control={control}
|
||||
|
||||
@ -66,14 +66,13 @@ export const ConfigureDocumentRecipients = ({
|
||||
});
|
||||
|
||||
const onAddSigner = useCallback(() => {
|
||||
const signerNumber = signers.length + 1;
|
||||
const recipientSigningOrder =
|
||||
signers.length > 0 ? (signers[signers.length - 1]?.signingOrder || 0) + 1 : 1;
|
||||
|
||||
appendSigner({
|
||||
formId: nanoid(8),
|
||||
name: isTemplate ? `Recipient ${signerNumber}` : '',
|
||||
email: isTemplate ? `recipient.${signerNumber}@document.com` : '',
|
||||
name: '',
|
||||
email: '',
|
||||
role: RecipientRole.SIGNER,
|
||||
signingOrder:
|
||||
signingOrder === DocumentSigningOrder.SEQUENTIAL ? recipientSigningOrder : undefined,
|
||||
|
||||
@ -25,9 +25,11 @@ import { ConfigureDocumentUpload } from './configure-document-upload';
|
||||
import {
|
||||
type TConfigureEmbedFormSchema,
|
||||
ZConfigureEmbedFormSchema,
|
||||
ZConfigureTemplateEmbedFormSchema,
|
||||
} from './configure-document-view.types';
|
||||
|
||||
export interface ConfigureDocumentViewProps {
|
||||
type?: 'document' | 'template';
|
||||
onSubmit: (data: TConfigureEmbedFormSchema) => void | Promise<void>;
|
||||
defaultValues?: Partial<TConfigureEmbedFormSchema>;
|
||||
disableUpload?: boolean;
|
||||
@ -35,6 +37,7 @@ export interface ConfigureDocumentViewProps {
|
||||
}
|
||||
|
||||
export const ConfigureDocumentView = ({
|
||||
type = 'document',
|
||||
onSubmit,
|
||||
defaultValues,
|
||||
disableUpload,
|
||||
@ -42,14 +45,16 @@ export const ConfigureDocumentView = ({
|
||||
const { isTemplate } = useConfigureDocument();
|
||||
|
||||
const form = useForm<TConfigureEmbedFormSchema>({
|
||||
resolver: zodResolver(ZConfigureEmbedFormSchema),
|
||||
resolver: zodResolver(
|
||||
type === 'template' ? ZConfigureTemplateEmbedFormSchema : ZConfigureEmbedFormSchema,
|
||||
),
|
||||
defaultValues: {
|
||||
title: defaultValues?.title || '',
|
||||
signers: defaultValues?.signers || [
|
||||
{
|
||||
formId: nanoid(8),
|
||||
name: isTemplate ? `Recipient ${1}` : '',
|
||||
email: isTemplate ? `recipient.${1}@document.com` : '',
|
||||
name: '',
|
||||
email: '',
|
||||
role: RecipientRole.SIGNER,
|
||||
signingOrder: 1,
|
||||
disabled: false,
|
||||
|
||||
@ -17,7 +17,7 @@ export const ZConfigureEmbedFormSchema = z.object({
|
||||
z.object({
|
||||
nativeId: z.number().optional(),
|
||||
formId: z.string(),
|
||||
name: z.string().min(1, { message: 'Name is required' }),
|
||||
name: z.string(),
|
||||
email: z.string().email('Invalid email address'),
|
||||
role: z.enum(['SIGNER', 'CC', 'APPROVER', 'VIEWER', 'ASSISTANT']),
|
||||
signingOrder: z.number().optional(),
|
||||
@ -48,3 +48,17 @@ export const ZConfigureEmbedFormSchema = z.object({
|
||||
})
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const ZConfigureTemplateEmbedFormSchema = ZConfigureEmbedFormSchema.extend({
|
||||
signers: z.array(
|
||||
z.object({
|
||||
nativeId: z.number().optional(),
|
||||
formId: z.string(),
|
||||
name: z.string(),
|
||||
email: z.union([z.string().length(0), z.string().email('Invalid email address')]),
|
||||
role: z.enum(['SIGNER', 'CC', 'APPROVER', 'VIEWER', 'ASSISTANT']),
|
||||
signingOrder: z.number().optional(),
|
||||
disabled: z.boolean().optional(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
@ -42,7 +42,7 @@ export type ConfigureFieldsViewProps = {
|
||||
configData: TConfigureEmbedFormSchema;
|
||||
documentData?: DocumentData;
|
||||
defaultValues?: Partial<TConfigureFieldsFormSchema>;
|
||||
onBack: (data: TConfigureFieldsFormSchema) => void;
|
||||
onBack?: (data: TConfigureFieldsFormSchema) => void;
|
||||
onSubmit: (data: TConfigureFieldsFormSchema) => void;
|
||||
};
|
||||
|
||||
@ -481,15 +481,17 @@ export const ConfigureFieldsView = ({
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
className="flex-1"
|
||||
loading={form.formState.isSubmitting}
|
||||
onClick={() => onBack(form.getValues())}
|
||||
>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
{onBack && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
className="flex-1"
|
||||
loading={form.formState.isSubmitting}
|
||||
onClick={() => onBack(form.getValues())}
|
||||
>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
className="flex-1"
|
||||
@ -642,15 +644,17 @@ export const ConfigureFieldsView = ({
|
||||
</div>
|
||||
|
||||
<div className="mt-6 flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
className="flex-1"
|
||||
loading={form.formState.isSubmitting}
|
||||
onClick={() => onBack(form.getValues())}
|
||||
>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
{onBack && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
className="flex-1"
|
||||
loading={form.formState.isSubmitting}
|
||||
onClick={() => onBack(form.getValues())}
|
||||
>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
className="flex-1"
|
||||
|
||||
@ -36,14 +36,14 @@ export const loader = async ({ request }: Route.LoaderArgs) => {
|
||||
}
|
||||
|
||||
return {
|
||||
hasValidToken: !!result,
|
||||
token,
|
||||
hasValidToken: !!result,
|
||||
allowEmbedAuthoringWhiteLabel,
|
||||
};
|
||||
};
|
||||
|
||||
export default function AuthoringLayout() {
|
||||
const { hasValidToken, token, allowEmbedAuthoringWhiteLabel } = useLoaderData<typeof loader>();
|
||||
const { token, hasValidToken, allowEmbedAuthoringWhiteLabel } = useLoaderData<typeof loader>();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
try {
|
||||
|
||||
@ -27,7 +27,7 @@ import { ConfigureFieldsView } from '~/components/embed/authoring/configure-fiel
|
||||
import type { TConfigureFieldsFormSchema } from '~/components/embed/authoring/configure-fields-view.types';
|
||||
import {
|
||||
type TBaseEmbedAuthoringSchema,
|
||||
ZBaseEmbedAuthoringSchema,
|
||||
ZBaseEmbedAuthoringEditSchema,
|
||||
} from '~/types/embed-authoring-base-schema';
|
||||
|
||||
import type { Route } from './+types/document.edit.$id';
|
||||
@ -88,6 +88,8 @@ export default function EmbeddingAuthoringDocumentEditPage() {
|
||||
|
||||
const { document } = useLoaderData<typeof loader>();
|
||||
|
||||
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
||||
|
||||
const signatureTypes = useMemo(() => {
|
||||
const types: string[] = [];
|
||||
|
||||
@ -159,6 +161,7 @@ export default function EmbeddingAuthoringDocumentEditPage() {
|
||||
const [features, setFeatures] = useState<TBaseEmbedAuthoringSchema['features'] | null>(null);
|
||||
const [externalId, setExternalId] = useState<string | null>(null);
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [canGoBack, setCanGoBack] = useState(true);
|
||||
|
||||
const { mutateAsync: updateEmbeddingDocument } =
|
||||
trpc.embeddingPresign.updateEmbeddingDocument.useMutation();
|
||||
@ -177,6 +180,7 @@ export default function EmbeddingAuthoringDocumentEditPage() {
|
||||
fields: fieldData.fields.filter((field) => signerEmails.includes(field.signerEmail)),
|
||||
};
|
||||
});
|
||||
|
||||
setCurrentStep(2);
|
||||
};
|
||||
|
||||
@ -275,7 +279,7 @@ export default function EmbeddingAuthoringDocumentEditPage() {
|
||||
try {
|
||||
const hash = window.location.hash.slice(1);
|
||||
|
||||
const result = ZBaseEmbedAuthoringSchema.safeParse(
|
||||
const result = ZBaseEmbedAuthoringEditSchema.safeParse(
|
||||
JSON.parse(decodeURIComponent(atob(hash))),
|
||||
);
|
||||
|
||||
@ -285,15 +289,26 @@ export default function EmbeddingAuthoringDocumentEditPage() {
|
||||
|
||||
setFeatures(result.data.features);
|
||||
|
||||
if (result.data.onlyEditFields) {
|
||||
setCurrentStep(2);
|
||||
setCanGoBack(false);
|
||||
}
|
||||
|
||||
// Extract externalId from the parsed data if available
|
||||
if (result.data.externalId) {
|
||||
setExternalId(result.data.externalId);
|
||||
}
|
||||
|
||||
setHasFinishedInit(true);
|
||||
} catch (err) {
|
||||
console.error('Error parsing embedding params:', err);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!hasFinishedInit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative mx-auto flex min-h-[100dvh] max-w-screen-lg p-6">
|
||||
<ConfigureDocumentProvider isTemplate={false} features={features ?? {}}>
|
||||
@ -308,7 +323,7 @@ export default function EmbeddingAuthoringDocumentEditPage() {
|
||||
configData={configuration!}
|
||||
documentData={document.documentData}
|
||||
defaultValues={fields ?? undefined}
|
||||
onBack={handleBackToConfig}
|
||||
onBack={canGoBack ? handleBackToConfig : undefined}
|
||||
onSubmit={handleConfigureFieldsSubmit}
|
||||
/>
|
||||
</Stepper>
|
||||
|
||||
@ -154,6 +154,7 @@ export default function EmbeddingAuthoringTemplateCreatePage() {
|
||||
<ConfigureDocumentProvider isTemplate={true} features={features ?? {}}>
|
||||
<Stepper currentStep={currentStep} setCurrentStep={setCurrentStep}>
|
||||
<ConfigureDocumentView
|
||||
type="template"
|
||||
defaultValues={configuration ?? undefined}
|
||||
onSubmit={handleConfigurePageViewSubmit}
|
||||
/>
|
||||
|
||||
@ -27,7 +27,7 @@ import { ConfigureFieldsView } from '~/components/embed/authoring/configure-fiel
|
||||
import type { TConfigureFieldsFormSchema } from '~/components/embed/authoring/configure-fields-view.types';
|
||||
import {
|
||||
type TBaseEmbedAuthoringSchema,
|
||||
ZBaseEmbedAuthoringSchema,
|
||||
ZBaseEmbedAuthoringEditSchema,
|
||||
} from '~/types/embed-authoring-base-schema';
|
||||
|
||||
import type { Route } from './+types/document.edit.$id';
|
||||
@ -88,6 +88,8 @@ export default function EmbeddingAuthoringTemplateEditPage() {
|
||||
|
||||
const { template } = useLoaderData<typeof loader>();
|
||||
|
||||
const [hasFinishedInit, setHasFinishedInit] = useState(false);
|
||||
|
||||
const signatureTypes = useMemo(() => {
|
||||
const types: string[] = [];
|
||||
|
||||
@ -159,6 +161,7 @@ export default function EmbeddingAuthoringTemplateEditPage() {
|
||||
const [features, setFeatures] = useState<TBaseEmbedAuthoringSchema['features'] | null>(null);
|
||||
const [externalId, setExternalId] = useState<string | null>(null);
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [canGoBack, setCanGoBack] = useState(true);
|
||||
|
||||
const { mutateAsync: updateEmbeddingTemplate } =
|
||||
trpc.embeddingPresign.updateEmbeddingTemplate.useMutation();
|
||||
@ -230,7 +233,9 @@ export default function EmbeddingAuthoringTemplateEditPage() {
|
||||
signingOrder: signer.signingOrder,
|
||||
fields: fields
|
||||
.filter((field) => field.signerEmail === signer.email)
|
||||
.map((f) => ({
|
||||
// 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,
|
||||
id: f.nativeId,
|
||||
envelopeItemId: template.templateDocumentData.envelopeItemId,
|
||||
@ -273,7 +278,7 @@ export default function EmbeddingAuthoringTemplateEditPage() {
|
||||
try {
|
||||
const hash = window.location.hash.slice(1);
|
||||
|
||||
const result = ZBaseEmbedAuthoringSchema.safeParse(
|
||||
const result = ZBaseEmbedAuthoringEditSchema.safeParse(
|
||||
JSON.parse(decodeURIComponent(atob(hash))),
|
||||
);
|
||||
|
||||
@ -283,20 +288,32 @@ export default function EmbeddingAuthoringTemplateEditPage() {
|
||||
|
||||
setFeatures(result.data.features);
|
||||
|
||||
if (result.data.onlyEditFields) {
|
||||
setCurrentStep(2);
|
||||
setCanGoBack(false);
|
||||
}
|
||||
|
||||
// Extract externalId from the parsed data if available
|
||||
if (result.data.externalId) {
|
||||
setExternalId(result.data.externalId);
|
||||
}
|
||||
|
||||
setHasFinishedInit(true);
|
||||
} catch (err) {
|
||||
console.error('Error parsing embedding params:', err);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!hasFinishedInit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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
|
||||
type="template"
|
||||
defaultValues={configuration ?? undefined}
|
||||
disableUpload={true}
|
||||
onSubmit={handleConfigurePageViewSubmit}
|
||||
@ -306,7 +323,7 @@ export default function EmbeddingAuthoringTemplateEditPage() {
|
||||
configData={configuration!}
|
||||
documentData={template.templateDocumentData}
|
||||
defaultValues={fields ?? undefined}
|
||||
onBack={handleBackToConfig}
|
||||
onBack={canGoBack ? handleBackToConfig : undefined}
|
||||
onSubmit={handleConfigureFieldsSubmit}
|
||||
/>
|
||||
</Stepper>
|
||||
|
||||
@ -2,21 +2,24 @@ import { z } from 'zod';
|
||||
|
||||
import { ZBaseEmbedDataSchema } from './embed-base-schemas';
|
||||
|
||||
export const ZBaseEmbedAuthoringSchema = z
|
||||
.object({
|
||||
externalId: z.string().optional(),
|
||||
features: z
|
||||
.object({
|
||||
allowConfigureSignatureTypes: z.boolean().optional(),
|
||||
allowConfigureLanguage: z.boolean().optional(),
|
||||
allowConfigureDateFormat: z.boolean().optional(),
|
||||
allowConfigureTimezone: z.boolean().optional(),
|
||||
allowConfigureRedirectUrl: z.boolean().optional(),
|
||||
allowConfigureCommunication: z.boolean().optional(),
|
||||
})
|
||||
.optional()
|
||||
.default({}),
|
||||
})
|
||||
.and(ZBaseEmbedDataSchema);
|
||||
export const ZBaseEmbedAuthoringSchema = ZBaseEmbedDataSchema.extend({
|
||||
externalId: z.string().optional(),
|
||||
features: z
|
||||
.object({
|
||||
allowConfigureSignatureTypes: z.boolean().optional(),
|
||||
allowConfigureLanguage: z.boolean().optional(),
|
||||
allowConfigureDateFormat: z.boolean().optional(),
|
||||
allowConfigureTimezone: z.boolean().optional(),
|
||||
allowConfigureRedirectUrl: z.boolean().optional(),
|
||||
allowConfigureCommunication: z.boolean().optional(),
|
||||
})
|
||||
.optional()
|
||||
.default({}),
|
||||
});
|
||||
|
||||
export const ZBaseEmbedAuthoringEditSchema = ZBaseEmbedAuthoringSchema.extend({
|
||||
onlyEditFields: z.boolean().optional().default(false),
|
||||
});
|
||||
|
||||
export type TBaseEmbedAuthoringSchema = z.infer<typeof ZBaseEmbedAuthoringSchema>;
|
||||
export type TBaseEmbedAuthoringEditSchema = z.infer<typeof ZBaseEmbedAuthoringEditSchema>;
|
||||
|
||||
Reference in New Issue
Block a user