diff --git a/apps/marketing/src/app/(marketing)/layout.tsx b/apps/marketing/src/app/(marketing)/layout.tsx
index 4a0c4ccd8..eebb794bb 100644
--- a/apps/marketing/src/app/(marketing)/layout.tsx
+++ b/apps/marketing/src/app/(marketing)/layout.tsx
@@ -9,7 +9,7 @@ export type MarketingLayoutProps = {
export default function MarketingLayout({ children }: MarketingLayoutProps) {
return (
-
+
diff --git a/apps/marketing/src/app/(marketing)/single-player-mode/page.tsx b/apps/marketing/src/app/(marketing)/single-player-mode/page.tsx
index 0b977d01b..62f8cf265 100644
--- a/apps/marketing/src/app/(marketing)/single-player-mode/page.tsx
+++ b/apps/marketing/src/app/(marketing)/single-player-mode/page.tsx
@@ -204,7 +204,7 @@ export default function SinglePlayerModePage() {
-
e.preventDefault()}>
+ e.preventDefault()}>
{
+ const [validateUninsertedFields, setValidateUninsertedFields] = useState(false);
+
// Refined schema which takes into account whether to allow an empty name or signature.
const refinedSchema = ZAddSignatureFormSchema.superRefine((val, ctx) => {
if (requireName && val.name.length === 0) {
@@ -81,72 +84,101 @@ export const AddSignatureFormPartial = ({
/**
* A local copy of the provided fields to modify.
*/
- const [localFields, setLocalFields] = useState(
- fields.map((field) => {
- let customText = field.customText;
+ const [localFields, setLocalFields] = useState(JSON.parse(JSON.stringify(fields)));
- if (field.type === FieldType.DATE) {
- customText = DateTime.now().toFormat('yyyy-MM-dd hh:mm a');
+ const uninsertedFields = useMemo(() => {
+ const fields = localFields.filter((field) => !field.inserted);
+
+ return fields.sort((a, b) => {
+ if (a.page < b.page) {
+ return -1;
}
- const inserted = match(field.type)
- .with(FieldType.DATE, () => true)
- .with(FieldType.NAME, () => form.getValues('name').length > 0)
- .with(FieldType.EMAIL, () => form.getValues('email').length > 0)
- .with(FieldType.SIGNATURE, () => form.getValues('signature').length > 0)
- .otherwise(() => true);
+ if (a.page > b.page) {
+ return 1;
+ }
- return { ...field, inserted, customText };
- }),
- );
+ const aTop = a.positionY;
+ const bTop = b.positionY;
- const onEmailInputBlur = () => {
- setLocalFields((prev) =>
- prev.map((field) => {
- if (field.type !== FieldType.EMAIL) {
- return field;
- }
+ if (aTop < bTop) {
+ return -1;
+ }
- const value = form.getValues('email');
+ if (aTop > bTop) {
+ return 1;
+ }
- return {
- ...field,
- customText: value,
- inserted: value.length > 0,
- };
- }),
- );
+ return 0;
+ });
+ }, [localFields]);
+
+ const onValidateFields = async (values: TAddSignatureFormSchema) => {
+ setValidateUninsertedFields(true);
+
+ const firstUninsertedField = uninsertedFields[0];
+
+ const firstUninsertedFieldElement =
+ firstUninsertedField && document.getElementById(`field-${firstUninsertedField.id}`);
+
+ if (firstUninsertedFieldElement) {
+ firstUninsertedFieldElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ return;
+ }
+
+ await onSubmit(values);
};
- const onNameInputBlur = () => {
- setLocalFields((prev) =>
- prev.map((field) => {
- if (field.type !== FieldType.NAME) {
- return field;
- }
+ /**
+ * Validates whether the corresponding form for a given field type is valid.
+ *
+ * @returns `true` if the form associated with the provided field is valid, `false` otherwise.
+ */
+ const validateFieldForm = async (fieldType: Field['type']): Promise => {
+ if (fieldType === FieldType.SIGNATURE) {
+ await form.trigger('signature');
+ return !form.formState.errors.signature;
+ }
- const value = form.getValues('name');
+ if (fieldType === FieldType.NAME) {
+ await form.trigger('name');
+ return !form.formState.errors.name;
+ }
- return {
- ...field,
- customText: value,
- inserted: value.length > 0,
- };
- }),
- );
+ if (fieldType === FieldType.EMAIL) {
+ await form.trigger('email');
+ return !form.formState.errors.email;
+ }
+
+ return true;
};
- const onSignatureInputChange = (value: string) => {
- setLocalFields((prev) =>
- prev.map((field) => {
- if (field.type !== FieldType.SIGNATURE) {
- return field;
- }
+ /**
+ * Insert the corresponding form value into a given field.
+ */
+ const insertFormValueIntoField = (field: Field) => {
+ return match(field.type)
+ .with(FieldType.DATE, () => ({
+ ...field,
+ customText: DateTime.now().toFormat('yyyy-MM-dd hh:mm a'),
+ inserted: true,
+ }))
+ .with(FieldType.EMAIL, () => ({
+ ...field,
+ customText: form.getValues('email'),
+ inserted: true,
+ }))
+ .with(FieldType.NAME, () => ({
+ ...field,
+ customText: form.getValues('name'),
+ inserted: true,
+ }))
+ .with(FieldType.SIGNATURE, () => {
+ const value = form.getValues('signature');
return {
...field,
- value: value ?? '',
- inserted: true,
+ value,
Signature: {
id: -1,
recipientId: -1,
@@ -155,7 +187,27 @@ export const AddSignatureFormPartial = ({
signatureImageAsBase64: value,
typedSignature: null,
},
+ inserted: true,
};
+ })
+ .otherwise(() => {
+ throw new Error('Unsupported field');
+ });
+ };
+
+ const insertField = (field: Field) => async () => {
+ const isFieldFormValid = await validateFieldForm(field.type);
+ if (!isFieldFormValid) {
+ return;
+ }
+
+ setLocalFields((prev) =>
+ prev.map((prevField) => {
+ if (prevField.id !== field.id) {
+ return prevField;
+ }
+
+ return insertFormValueIntoField(field);
}),
);
};
@@ -172,16 +224,7 @@ export const AddSignatureFormPartial = ({
Email
- {
- field.onBlur();
- onEmailInputBlur();
- }}
- />
+
@@ -196,14 +239,7 @@ export const AddSignatureFormPartial = ({
Name
- {
- field.onBlur();
- onNameInputBlur();
- }}
- />
+
@@ -231,10 +267,7 @@ export const AddSignatureFormPartial = ({
{
- field.onChange(value ?? '');
- onSignatureInputChange(value ?? '');
- }}
+ {...field}
/>
@@ -258,19 +291,37 @@ export const AddSignatureFormPartial = ({
loading={form.formState.isSubmitting}
disabled={form.formState.isSubmitting}
onGoBackClick={documentFlow.onBackStep}
- onGoNextClick={async () => await form.handleSubmit(onSubmit)()}
+ onGoNextClick={form.handleSubmit(onValidateFields)}
/>
+ {validateUninsertedFields && uninsertedFields[0] && (
+
+ Click to insert field
+
+ )}
+
{localFields.map((field) =>
match(field.type)
.with(FieldType.DATE, FieldType.EMAIL, FieldType.NAME, () => {
- return ;
+ return (
+
+ );
})
.with(FieldType.SIGNATURE, () => (
-
+
))
.otherwise(() => {
return null;
diff --git a/packages/ui/primitives/document-flow/document-flow-root.tsx b/packages/ui/primitives/document-flow/document-flow-root.tsx
index 300f7d4b3..96a0e18b1 100644
--- a/packages/ui/primitives/document-flow/document-flow-root.tsx
+++ b/packages/ui/primitives/document-flow/document-flow-root.tsx
@@ -21,7 +21,7 @@ export const DocumentFlowFormContainer = ({