From 384a22c7f374398dcc313ce5fe2207b900b62cda Mon Sep 17 00:00:00 2001
From: Daniel Naab
Date: Fri, 7 Jun 2024 15:17:35 -0500
Subject: [PATCH] Ingest PDF element pages (#178)
* Place PDF ingested patterns on separate pages, corresponding to original location in source PDF.
* Remove page from PDF element type, for now - we don't need it to fill the pdf
* Add logging for submission validation errors
* Use session data rather than form post data when generating PDF
* Add placeholder back button to each page, which acts as a submit button.
* Move the actions definition into PromptComponent, and implement on the page-set pattern. This allows us to easily vary the available actions based on the currently selected page.
* Wire the back button up as a link instead of a submit button.
* Add new actions prop to pageset story
* Add name=action to the submit buttons, to distinguish between next and submit operations. Also, remove the unused actions array from the Prompt type. Prompts are now just a single component, so perhaps the type should just go away.
* remove comment
* Pdf pages (#181)
* Place PDF ingested patterns on separate pages, corresponding to original location in source PDF.
* Remove page from PDF element type, for now - we don't need it to fill the pdf
* Add logging for submission validation errors
* Use session data rather than form post data when generating PDF
* Add placeholder back button to each page, which acts as a submit button.
* Move the actions definition into PromptComponent, and implement on the page-set pattern. This allows us to easily vary the available actions based on the currently selected page.
* Wire the back button up as a link instead of a submit button.
* Add new actions prop to pageset story
* Add name=action to the submit buttons, to distinguish between next and submit operations. Also, remove the unused actions array from the Prompt type. Prompts are now just a single component, so perhaps the type should just go away.
---------
Co-authored-by: Daniel Naab
* Fix merge issue: remove appending of radio group to rootSequence.
* options don't need page
* options don't need page
* comment radio
* radio option page
* remove radio option page
---------
Co-authored-by: jimmoffet
---
packages/design/src/Form/ActionBar/index.tsx | 18 +-
.../components/PageSet/PageSet.stories.tsx | 8 +-
.../src/Form/components/PageSet/PageSet.tsx | 5 +-
packages/design/src/Form/index.tsx | 3 -
packages/forms/src/components.ts | 24 +-
.../src/documents/__tests__/document.test.ts | 17 +-
.../forms/src/documents/pdf/mock-response.ts | 271 ++++++++++++++----
.../forms/src/documents/pdf/parsing-api.ts | 57 ++--
.../forms/src/patterns/page-set/prompt.ts | 37 +++
packages/forms/src/response.ts | 11 +-
.../src/service/operations/submit-form.ts | 2 +-
packages/forms/src/session.ts | 6 +
12 files changed, 338 insertions(+), 121 deletions(-)
diff --git a/packages/design/src/Form/ActionBar/index.tsx b/packages/design/src/Form/ActionBar/index.tsx
index 8a597c279..49cef0a8d 100644
--- a/packages/design/src/Form/ActionBar/index.tsx
+++ b/packages/design/src/Form/ActionBar/index.tsx
@@ -8,10 +8,26 @@ export default function ActionBar({ actions }: { actions: PromptAction[] }) {
{actions.map((action, index) => {
if (action.type === 'submit') {
return (
-
diff --git a/packages/design/src/Form/components/PageSet/PageSet.stories.tsx b/packages/design/src/Form/components/PageSet/PageSet.stories.tsx
index 8e30e4c1e..c9f00228e 100644
--- a/packages/design/src/Form/components/PageSet/PageSet.stories.tsx
+++ b/packages/design/src/Form/components/PageSet/PageSet.stories.tsx
@@ -1,7 +1,9 @@
import React from 'react';
-
+import { MemoryRouter } from 'react-router-dom';
import type { Meta, StoryObj } from '@storybook/react';
+import { type PageSetProps } from '@atj/forms';
+
import { FormManagerProvider } from '../../../FormManager/store';
import {
createTestFormManagerContext,
@@ -10,7 +12,6 @@ import {
} from '../../../test-form';
import PageSet from './PageSet';
-import { MemoryRouter } from 'react-router-dom';
const meta: Meta = {
title: 'patterns/PageSet',
@@ -46,5 +47,6 @@ export const Basic = {
active: true,
},
],
- },
+ actions: [],
+ } satisfies PageSetProps,
} satisfies StoryObj;
diff --git a/packages/design/src/Form/components/PageSet/PageSet.tsx b/packages/design/src/Form/components/PageSet/PageSet.tsx
index acfed9eb7..ba7ac18ca 100644
--- a/packages/design/src/Form/components/PageSet/PageSet.tsx
+++ b/packages/design/src/Form/components/PageSet/PageSet.tsx
@@ -3,9 +3,11 @@ import React from 'react';
import { type PageSetProps } from '@atj/forms';
import { type PatternComponent } from '../..';
-import { PageMenu } from './PageMenu';
+import ActionBar from '../../../Form/ActionBar';
import { useRouteParams } from '../../../FormRouter/hooks';
+import { PageMenu } from './PageMenu';
+
const PageSet: PatternComponent = props => {
const { routeParams, pathname } = useRouteParams();
return (
@@ -25,6 +27,7 @@ const PageSet: PatternComponent = props => {
{props.children}
+
);
diff --git a/packages/design/src/Form/index.tsx b/packages/design/src/Form/index.tsx
index 0c2bb4c94..c552fa249 100644
--- a/packages/design/src/Form/index.tsx
+++ b/packages/design/src/Form/index.tsx
@@ -12,8 +12,6 @@ import {
type PromptComponent,
} from '@atj/forms';
-import ActionBar from './ActionBar';
-
export type FormUIContext = {
config: FormConfig;
components: ComponentForPattern;
@@ -224,7 +222,6 @@ const FormContents = ({
/>
);
})}
-
>
);
diff --git a/packages/forms/src/components.ts b/packages/forms/src/components.ts
index e8173485e..02321fdcf 100644
--- a/packages/forms/src/components.ts
+++ b/packages/forms/src/components.ts
@@ -56,6 +56,7 @@ export type CheckboxProps = PatternProps<{
export type PageSetProps = PatternProps<{
type: 'page-set';
pages: { title: string; active: boolean }[];
+ actions: PromptAction[];
}>;
export type PageProps = PatternProps<{
@@ -86,9 +87,15 @@ export type PatternProps = {
export type SubmitAction = {
type: 'submit';
- text: 'Submit';
+ submitAction: 'next' | 'submit';
+ text: string;
+};
+export type LinkAction = {
+ type: 'link';
+ text: string;
+ url: string;
};
-export type PromptAction = SubmitAction;
+export type PromptAction = SubmitAction | LinkAction;
export type PromptComponent = {
props: PatternProps;
@@ -96,7 +103,6 @@ export type PromptComponent = {
};
export type Prompt = {
- actions: PromptAction[];
components: PromptComponent[];
};
@@ -107,7 +113,6 @@ export const createPrompt = (
): Prompt => {
if (options.validate && sessionIsComplete(config, session)) {
return {
- actions: [],
components: [
{
props: {
@@ -137,12 +142,6 @@ export const createPrompt = (
const root = getRootPattern(session.form);
components.push(createPromptForPattern(config, session, root, options));
return {
- actions: [
- {
- type: 'submit',
- text: 'Submit',
- },
- ],
components,
};
};
@@ -169,10 +168,6 @@ export const createPromptForPattern: CreatePrompt = (
return patternConfig.createPrompt(config, session, pattern, options);
};
-export const isPromptAction = (prompt: Prompt, action: string) => {
- return prompt.actions.find(a => a.type === action);
-};
-
export const createNullPrompt = ({
config,
pattern,
@@ -187,6 +182,5 @@ export const createNullPrompt = ({
validate: false,
}),
],
- actions: [],
};
};
diff --git a/packages/forms/src/documents/__tests__/document.test.ts b/packages/forms/src/documents/__tests__/document.test.ts
index 0e2f04e4a..596123101 100644
--- a/packages/forms/src/documents/__tests__/document.test.ts
+++ b/packages/forms/src/documents/__tests__/document.test.ts
@@ -38,13 +38,14 @@ describe('addDocument document processing', () => {
console.error(JSON.stringify(errors, null, 2)); // Fix these
expect(rootPattern).toEqual(expect.objectContaining({ type: 'page-set' }));
- expect(rootPattern.data.pages.length).toEqual(1);
- const pagePattern = getPattern(
- updatedForm,
- rootPattern.data.pages[0]
- );
-
- // As a sanity check, just confirm that there is content on the first page.
- expect(pagePattern.data.patterns.length).toBeGreaterThan(1);
+ expect(rootPattern.data.pages.length).toEqual(4);
+ for (let page = 0; page < rootPattern.data.pages.length; page++) {
+ const pagePattern = getPattern(
+ updatedForm,
+ rootPattern.data.pages[page]
+ );
+ // As a sanity check, just confirm that there is content on the first page.
+ expect(pagePattern.data.patterns.length).toBeGreaterThanOrEqual(1);
+ }
});
});
diff --git a/packages/forms/src/documents/pdf/mock-response.ts b/packages/forms/src/documents/pdf/mock-response.ts
index 64257d9fc..c97b5e757 100644
--- a/packages/forms/src/documents/pdf/mock-response.ts
+++ b/packages/forms/src/documents/pdf/mock-response.ts
@@ -5,21 +5,24 @@ export const mockResponse = {
'OMB Control No: 1123-0014 Expires 03/31/2027 \nAPPLICATION FOR CERTIFICATE OF PARDON FOR THE OFFENSES OF \nSIMPLE POSSESSION, ATTEMPTED SIMPLE POSSESSION, OR USE OF \nMARIJUANA \nOn October 6, 2022, President Biden issued a presidential proclamation that pardoned many federal and D.C. \noffenses for simple marijuana possession. On December 22, 2023, President Biden issued another proclamation that \nexpanded the relief provided by the original proclamation by pardoning the federal offenses of simple possession, \nattempted possession, and use of marijuana. \nHow a pardon can help you \nA pardon is an expression of the President\u2019s forgiveness. It does not mean you are innocent or expunge your \nconviction. But it does remove civil disabilities\u2014such as restrictions on the right to vote, to hold office, or to sit \non a jury\u2014that are imposed because of the pardoned conviction. It may also be helpful in obtaining licenses, \nbonding, or employment. Learn more about the pardon. \nYou qualify for the pardon if: \n\u2022 On or before December 22, 2023, you were charged with or convicted of simple possession, attempted \npossession, or use of marijuana under the federal code, the District of Columbia code, or the Code of \nFederal Regulations \n\u2022 You were a U.S. citizen or lawfully present in the United States at the time of the offense \n\u2022 You were a U.S. citizen or lawful permanent resident on December 22, 2023 \nRequest a certificate to show proof of the pardon \nA Certificate of Pardon is proof that you were pardoned under the proclamation. The certificate is the only \ndocumentation you will receive of the pardon. Use the application below to start your request. \nWhat you\'ll need for the request \nAbout you \nYou can submit a request for yourself or someone else can submit on your behalf. You must provide \npersonal details, like name or citizenship status and either a mailing address, an email address or both to \ncontact you. We strongly recommend including an email address, if available, as we may not be able to \nrespond as quickly if you do not provide it. You can also use the mailing address or email address of \nanother person, if you do not have your own. \nAbout the charge or conviction \nYou must state whether it was a charge or conviction, the court district where it happened, and the date \n(month, day, year). If possible, you should also: \n\u2022 enter information about your case (docket or case number and the code section that was \ncharged) \n\u2022 upload your documents \no charging documents, like the indictment, complaint, criminal information, ticket or \ncitation; or \no conviction documents, like the judgment of conviction, the court docket sheet showing \nthe sentence and date it was imposed, or if you did not go to court, the receipt showing \npayment of fine \nIf you were charged by a ticket or citation and paid a fine instead of appearing in court, you should also provide the \ndate of conviction or the date the fine was paid. \nWithout this information, we can\'t guarantee that we\'ll be able to determine if you qualify for the pardon under \nthe proclamation. \n \nPage 1 of 4 \nUnited States Department of Justice Office of the Pardon Attorney Washington, D.C. 20530 January 2024 OMB Control No: 1123-0014 Expires 03/31/2027 \nAPPLICATION FOR CERTIFICATE OF PARDON FOR THE OFFENSES OF \nSIMPLE POSSESSION, ATTEMPTED SIMPLE POSSESSION, OR USE OF \nMARIJUANA \nInstructions: \nAn online version of this application is available at: Presidential Proclamation on Marijuana Possession \n(justice.gov). You can also complete and return this application with the required documents to \nUSPardon.Attorney@usdoj.gov or U.S. Department of Justice, Office of the Pardon Attorney, 950 Pennsylvania \nAvenue NW, Washington, DC 20530. \nPublic Burden Statement: \nThis collection meets the requirements of 44 U.S.C. \u00a7 3507, as amended by the Paperwork Reduction Act of 1995. \nWe estimate that it will take 120 minutes to read the instructions, gather the relevant materials, and answer \nquestions on the form. Send comments regarding the burden estimate or any other aspect of this collection of \ninformation, including suggestions for reducing this burden, to Office of the Pardon Attorney, U.S. Department of \nJustice, Attn: OMB Number 1123-0014, RFK Building, 950 Pennsylvania Avenue, N.W., Washington DC 20530. \nThe OMB Clearance number, 1123-0014, is currently valid. \nPrivacy Act Statement: \nThe Office of the Pardon Attorney has authority to collect this information under the U.S. Constitution, Article \nII, Section 2 (the pardon clause); Orders of the Attorney General Nos. 1798-93, 58 Fed. Reg. 53658 and 53659 \n(1993), 2317-2000, 65 Fed. Reg. 48381 (2000), and 2323-2000, 65 Fed. Reg. 58223 and 58224 (2000), codified in \n28 C.F.R. \u00a7\u00a7 1.1 et seq. (the rules governing petitions for executive clemency); and Order of the Attorney General \nNo. 1012-83, 48 Fed. Reg. 22290 (1983), as codified in 28 C.F.R. \u00a7\u00a7 0.35 and 0.36 (the authority of the Office of \nthe Pardon Attorney). The principal purpose for collecting this information is to enable the Office of the Pardon \nAttorney to issue an individual certificate of pardon to you. The routine uses which may be made of this \ninformation include provision of data to the President and his staff, other governmental entities, and the public. \nThe full list of routine uses for this correspondence can be found in the System of Records Notice titled, \u201cPrivacy \nAct of 1974; System of Records,\u201d published in Federal Register, September 15, 2011, Vol. 76, No. 179, at pages \n57078 through 57080; as amended by \u201cPrivacy Act of 1974; System of Records,\u201d published in the Federal \nRegister, May 25, 2017, Vol. 82, No. 100, at page 24161, and at the U.S. Department of Justice, Office of Privacy \nand Civil Liberties\' website at: https://www.justice.gov/opcl/doj-systems-records#OPA. \nBy signing the attached form, you consent to allowing the Office of the Pardon Attorney to obtain information \nregarding your citizenship and/or immigration status from the courts, from other government agencies, from other \ncomponents within the Department of Justice, and from the Department of Homeland Security, U.S. Citizenship \nand Immigration Services (DHS-USCIS), Systematic Alien Verification for Entitlements (SAVE) program. The \ninformation received from these sources will be used for the sole purposes of determining an applicant\'s \nqualification for a Certificate of Pardon under the December 22 proclamation and for record-keeping of those \ndeterminations. Further, please be aware that if the Office of the Pardon Attorney is unable to verify your \ncitizenship or immigration status based on the information provided below, we may contact you to obtain \nadditional verification information. Learn more about the DHS-USCIS\'s SAVE program and its ordinary uses. \nYour disclosure of information to the Office of the Pardon Attorney on this form is voluntary. If you do not \ncomplete all or some of the information fields in this form, however, the Office of the Pardon Attorney may not be \nable to effectively respond. Information regarding gender, race, or ethnicity is not required and will not affect the \nprocessing of the application. \nNote: Submit a separate form for each conviction or charge for which you are seeking a certificate of pardon. \nApplication Form on page 3. \nPage 2 of 4 \nUnited States Department of Justice Office of the Pardon Attorney Washington, D.C. 20530 January 2024 OMB Control No: 1123-0014 Expires 03/31/2027 \nAPPLICATION FOR CERTIFICATE OF PARDON FOR THE OFFENSES OF \nSIMPLE POSSESSION, ATTEMPTED SIMPLE POSSESSION, OR USE OF \nMARIJUANA \nComplete the following: \nName: \n(first) (middle) (last) \nName at Conviction: \n(if different) (first) (middle) (last) \nAddress: \n(number) (street) (apartment/unit no.) \n\n(city) (state) (Zip Code) \nEmail Address: Phone Number: \nDate of Birth: Gender: Are you Hispanic or Latino?: Yes No \nRace: Alaska Native or American Indian Asian Black or African American \nNative Hawaiian or Other Pacific Islander White Other \nCitizenship or Residency Status: \nU.S. citizen by birth \nU.S. naturalized citizen Date Naturalization Granted: \nLawful Permanent Resident Date Residency Granted: \nAlien Registration Number (A-Number), Certificate of Naturalization Number, or Citizenship Number \n(if applicant is a lawful permanent resident or naturalized citizen): \n(A-Number) \n1. Applicant was convicted on: in the U.S. District Court for the \n(month/day/year) (Northern, etc.) \nDistrict of (state) or D.C. Superior Court of simple possession of marijuana, under \nDocket No. : and Code Section: ; OR \n(docket number) (code section) \n2. Applicant was charged with Code Section: in the U.S. District Court for the \n(code section) (Eastern, etc.) \nDistrict of or D.C. Superior Court under Docket No: \n(state) (docket number) \n \nUnited States Department of Justice Office of the Pardon Attorney Page 3 of 4 Washington, D.C. 20530 January 2024 OMB Control No: 1123-0014 Expires 03/31/2027 \nAPPLICATION FOR CERTIFICATE OF PARDON FOR THE OFFENSES OF \nSIMPLE POSSESSION, ATTEMPTED SIMPLE POSSESSION, OR USE OF \nMARIJUANA \nWith knowledge of the penalties for false statements to Federal Agencies, as provided by 18 \nU.S.C. \u00a7 1001, and with knowledge that this statement is submitted by me to affect action by \nthe U.S. Department of Justice, I certify that: \n1. The applicant was either a U.S. citizen or lawfully present in the United States at the time of the \noffense. \n2. The applicant was a U.S. citizen or lawful permanent resident on December 22, 2023. \n3. The above statements, and accompanying documents, are true and complete to the \n best of my knowledge, information, and belief. \n4. I acknowledge that any certificate issued in reliance on the above information will be \nvoided, if the information is subsequently determined to be false. \n\n(date) (signature) \nPage 4 of 4 \nUnited States Department of Justice Office of the Pardon Attorney Washington, D.C. 20530 January 2024 ',
form_summary: {
component_type: 'form_summary',
- title: '',
- description: '',
+ title: 'My Form Title',
+ description: 'My Form Description',
},
elements: [
{
component_type: 'paragraph',
text: "OMB Control No: 1123-0014 Expires 03/31/2027 APPLICATION FOR CERTIFICATE OF PARDON FOR THE OFFENSES OF SIMPLE POSSESSION, ATTEMPTED SIMPLE POSSESSION, OR USE OF MARIJUANA On October 6, 2022, President Biden issued a presidential proclamation that pardoned many federal and D.C. offenses for simple marijuana possession. On December 22, 2023, President Biden issued another proclamation that expanded the relief provided by the original proclamation by pardoning the federal offenses of simple possession, attempted possession, and use of marijuana. How a pardon can help you A pardon is an expression of the President\u2019s forgiveness. It does not mean you are innocent or expunge your conviction. But it does remove civil disabilities\u2014such as restrictions on the right to vote, to hold office, or to sit on a jury\u2014that are imposed because of the pardoned conviction. It may also be helpful in obtaining licenses, bonding, or employment. Learn more about the pardon. You qualify for the pardon if: \u2022 On or before December 22, 2023, you were charged with or convicted of simple possession, attempted possession, or use of marijuana under the federal code, the District of Columbia code, or the Code of Federal Regulations \u2022 You were a U.S. citizen or lawfully present in the United States at the time of the offense \u2022 You were a U.S. citizen or lawful permanent resident on December 22, 2023 Request a certificate to show proof of the pardon A Certificate of Pardon is proof that you were pardoned under the proclamation. The certificate is the only documentation you will receive of the pardon. Use the application below to start your request. What you'll need for the request About you You can submit a request for yourself or someone else can submit on your behalf. You must provide personal details, like name or citizenship status and either a mailing address, an email address or both to contact you. We strongly recommend including an email address, if available, as we may not be able to respond as quickly if you do not provide it. You can also use the mailing address or email address of another person, if you do not have your own. About the charge or conviction You must state whether it was a charge or conviction, the court district where it happened, and the date (month, day, year). If possible, you should also: \u2022 enter information about your case (docket or case number and the code section that was charged) \u2022 upload your documents o charging documents, like the indictment, complaint, criminal information, ticket or citation; or o conviction documents, like the judgment of conviction, the court docket sheet showing the sentence and date it was imposed, or if you did not go to court, the receipt showing payment of fine If you were charged by a ticket or citation and paid a fine instead of appearing in court, you should also provide the date of conviction or the date the fine was paid. Without this information, we can't guarantee that we'll be able to determine if you qualify for the pardon under the proclamation. Page 1 of 4 United States Department of Justice Office of the Pardon Attorney Washington, D.C. 20530 January 2024",
+ page: 0,
},
{
component_type: 'paragraph',
text: "OMB Control No: 1123-0014 Expires 03/31/2027 APPLICATION FOR CERTIFICATE OF PARDON FOR THE OFFENSES OF SIMPLE POSSESSION, ATTEMPTED SIMPLE POSSESSION, OR USE OF MARIJUANA Instructions: An online version of this application is available at: Presidential Proclamation on Marijuana Possession (justice.gov). You can also complete and return this application with the required documents to USPardon.Attorney@usdoj.gov or U.S. Department of Justice, Office of the Pardon Attorney, 950 Pennsylvania Avenue NW, Washington, DC 20530. Public Burden Statement: This collection meets the requirements of 44 U.S.C. \u00a7 3507, as amended by the Paperwork Reduction Act of 1995. We estimate that it will take 120 minutes to read the instructions, gather the relevant materials, and answer questions on the form. Send comments regarding the burden estimate or any other aspect of this collection of information, including suggestions for reducing this burden, to Office of the Pardon Attorney, U.S. Department of Justice, Attn: OMB Number 1123-0014, RFK Building, 950 Pennsylvania Avenue, N.W., Washington DC 20530. The OMB Clearance number, 1123-0014, is currently valid. Privacy Act Statement: The Office of the Pardon Attorney has authority to collect this information under the U.S. Constitution, Article II, Section 2 (the pardon clause); Orders of the Attorney General Nos. 1798-93, 58 Fed. Reg. 53658 and 53659 (1993), 2317-2000, 65 Fed. Reg. 48381 (2000), and 2323-2000, 65 Fed. Reg. 58223 and 58224 (2000), codified in 28 C.F.R. \u00a7\u00a7 1.1 et seq. (the rules governing petitions for executive clemency); and Order of the Attorney General No. 1012-83, 48 Fed. Reg. 22290 (1983), as codified in 28 C.F.R. \u00a7\u00a7 0.35 and 0.36 (the authority of the Office of the Pardon Attorney). The principal purpose for collecting this information is to enable the Office of the Pardon Attorney to issue an individual certificate of pardon to you. The routine uses which may be made of this information include provision of data to the President and his staff, other governmental entities, and the public. The full list of routine uses for this correspondence can be found in the System of Records Notice titled, \u201cPrivacy Act of 1974; System of Records,\u201d published in Federal Register, September 15, 2011, Vol. 76, No. 179, at pages 57078 through 57080; as amended by \u201cPrivacy Act of 1974; System of Records,\u201d published in the Federal Register, May 25, 2017, Vol. 82, No. 100, at page 24161, and at the U.S. Department of Justice, Office of Privacy and Civil Liberties' website at: https://www.justice.gov/opcl/doj-systems-records#OPA. By signing the attached form, you consent to allowing the Office of the Pardon Attorney to obtain information regarding your citizenship and/or immigration status from the courts, from other government agencies, from other components within the Department of Justice, and from the Department of Homeland Security, U.S. Citizenship and Immigration Services (DHS-USCIS), Systematic Alien Verification for Entitlements (SAVE) program. The information received from these sources will be used for the sole purposes of determining an applicant's qualification for a Certificate of Pardon under the December 22 proclamation and for record-keeping of those determinations. Further, please be aware that if the Office of the Pardon Attorney is unable to verify your citizenship or immigration status based on the information provided below, we may contact you to obtain additional verification information. Learn more about the DHS-USCIS's SAVE program and its ordinary uses. Your disclosure of information to the Office of the Pardon Attorney on this form is voluntary. If you do not complete all or some of the information fields in this form, however, the Office of the Pardon Attorney may not be able to effectively respond. Information regarding gender, race, or ethnicity is not required and will not affect the processing of the application. Note: Submit a separate form for each conviction or charge for which you are seeking a certificate of pardon. Application Form on page 3. Page 2 of 4 United States Department of Justice Office of the Pardon Attorney Washington, D.C. 20530 January 2024",
+ page: 1,
},
{
component_type: 'paragraph',
text: 'OMB Control No: 1123-0014 Expires 03/31/2027 APPLICATION FOR CERTIFICATE OF PARDON FOR THE OFFENSES OF SIMPLE POSSESSION, ATTEMPTED SIMPLE POSSESSION, OR USE OF MARIJUANA Complete the following:',
+ page: 2,
},
{
component_type: 'fieldset',
@@ -31,6 +34,7 @@ export const mockResponse = {
label: 'First Name',
default_value: '',
required: true,
+ page: 2,
},
{
component_type: 'text_input',
@@ -38,6 +42,7 @@ export const mockResponse = {
label: 'Middle Name',
default_value: '',
required: true,
+ page: 2,
},
{
component_type: 'text_input',
@@ -45,12 +50,15 @@ export const mockResponse = {
label: 'Last Name',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'paragraph',
text: '(first) (middle) (last)',
+ page: 2,
},
{
component_type: 'fieldset',
@@ -62,6 +70,7 @@ export const mockResponse = {
label: 'First Name at Conviction',
default_value: '',
required: true,
+ page: 2,
},
{
component_type: 'text_input',
@@ -69,6 +78,7 @@ export const mockResponse = {
label: 'Middle Name at Conviction',
default_value: '',
required: true,
+ page: 2,
},
{
component_type: 'text_input',
@@ -76,12 +86,15 @@ export const mockResponse = {
label: 'Last Name at Conviction',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'paragraph',
text: '(if different) (first) (middle) (last)',
+ page: 2,
},
{
component_type: 'fieldset',
@@ -93,12 +106,15 @@ export const mockResponse = {
label: 'Address (number, street, apartment/unit number)',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'paragraph',
text: '(number) (street) (apartment/unit no.)',
+ page: 2,
},
{
component_type: 'fieldset',
@@ -110,6 +126,7 @@ export const mockResponse = {
label: 'City',
default_value: '',
required: true,
+ page: 2,
},
{
component_type: 'text_input',
@@ -117,6 +134,7 @@ export const mockResponse = {
label: 'State',
default_value: '',
required: true,
+ page: 2,
},
{
component_type: 'text_input',
@@ -124,12 +142,15 @@ export const mockResponse = {
label: '(Zip Code)',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'paragraph',
text: '(city) (state) (Zip Code)',
+ page: 2,
},
{
component_type: 'fieldset',
@@ -141,8 +162,10 @@ export const mockResponse = {
label: 'Email Address',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'fieldset',
@@ -154,12 +177,15 @@ export const mockResponse = {
label: 'Phone Number',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'paragraph',
text: 'Date of Birth: Gender:',
+ page: 2,
},
{
component_type: 'fieldset',
@@ -171,6 +197,7 @@ export const mockResponse = {
label: 'Date of Birth',
default_value: '',
required: true,
+ page: 2,
},
{
component_type: 'text_input',
@@ -178,100 +205,116 @@ export const mockResponse = {
label: 'Gender',
default_value: '',
required: true,
+ page: 2,
},
],
- },
- {
- component_type: 'paragraph',
- text: 'Yes',
+ page: 2,
},
{
component_type: 'radio_group',
legend: 'Are you Hispanic or Latino?: ',
options: [
{
- id: 'Ethnicity/Yes',
+ id: 'Yes',
label: 'Yes ',
- name: 'Ethnicity/Yes',
+ name: 'Yes',
default_checked: false,
+ page: 2,
},
{
- id: 'Ethnicity/No',
+ id: 'No',
label: 'No ',
- name: 'Ethnicity/No',
+ name: 'No',
default_checked: false,
+ page: 2,
},
],
id: 'Ethnicity',
+ page: 2,
},
{
- component_type: 'paragraph',
- text: 'Race:',
- },
- {
- component_type: 'checkbox',
- id: 'Nat Amer',
- label: 'Alaska Native or American Indian ',
- default_checked: false,
- },
- {
- component_type: 'checkbox',
- id: 'Asian',
- label: 'Asian ',
- default_checked: false,
- },
- {
- component_type: 'checkbox',
- id: 'Blck Amer',
- label: 'Black or African American ',
- default_checked: false,
- },
- {
- component_type: 'checkbox',
- id: 'Nat Haw Islander',
- label: 'Native Hawaiian or Other Pacific Islander ',
- default_checked: false,
- },
- {
- component_type: 'checkbox',
- id: 'White',
- label: 'White ',
- default_checked: false,
- },
- {
- component_type: 'checkbox',
- id: 'Other',
- label: 'Other ',
- default_checked: false,
+ component_type: 'fieldset',
+ legend: 'Race:',
+ fields: [
+ {
+ component_type: 'checkbox',
+ id: 'Nat Amer',
+ label: 'Alaska Native or American Indian ',
+ default_checked: false,
+ struct_parent: 20,
+ page: 2,
+ },
+ {
+ component_type: 'checkbox',
+ id: 'Asian',
+ label: 'Asian ',
+ default_checked: false,
+ struct_parent: 21,
+ page: 2,
+ },
+ {
+ component_type: 'checkbox',
+ id: 'Blck Amer',
+ label: 'Black or African American ',
+ default_checked: false,
+ struct_parent: 22,
+ page: 2,
+ },
+ {
+ component_type: 'checkbox',
+ id: 'Nat Haw Islander',
+ label: 'Native Hawaiian or Other Pacific Islander ',
+ default_checked: false,
+ struct_parent: 23,
+ page: 2,
+ },
+ {
+ component_type: 'checkbox',
+ id: 'White',
+ label: 'White ',
+ default_checked: false,
+ struct_parent: 24,
+ page: 2,
+ },
+ {
+ component_type: 'checkbox',
+ id: 'Other',
+ label: 'Other ',
+ default_checked: false,
+ struct_parent: 25,
+ page: 2,
+ },
+ ],
+ page: 2,
},
{
component_type: 'radio_group',
legend: 'Citizenship or Residency Status: ',
options: [
{
- id: 'Citizenship/Birth',
+ id: 'Birth',
label: 'U.S. citizen by birth ',
- name: 'Citizenship/Birth',
+ name: 'Birth',
default_checked: false,
+ page: 2,
},
{
- id: 'Citizenship/Naturalized',
+ id: 'Naturalized',
label: 'U.S. naturalized citizen ',
- name: 'Citizenship/Naturalized',
+ name: 'Naturalized',
default_checked: false,
+ page: 2,
},
{
- id: 'Citizenship/Permanent Resident',
+ id: 'Permanent_Resident',
label: 'Lawful Permanent Resident ',
- name: 'Citizenship/Permanent Resident',
+ name: 'Permanent_Resident',
default_checked: false,
+ page: 2,
},
],
id: 'Citizenship',
- },
- {
- component_type: 'paragraph',
- text: 'Date Naturalization Granted:',
+ page: 2,
},
{
component_type: 'fieldset',
@@ -283,6 +326,7 @@ export const mockResponse = {
label: 'Date Residency Granted (mm/dd/yyyy)',
default_value: '',
required: true,
+ page: 2,
},
{
component_type: 'text_input',
@@ -290,12 +334,15 @@ export const mockResponse = {
label: 'date naturalization granted',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'paragraph',
text: 'Date Residency Granted: Alien Registration Number (A-Number), Certificate of Naturalization Number, or Citizenship Number',
+ page: 2,
},
{
component_type: 'fieldset',
@@ -308,12 +355,15 @@ export const mockResponse = {
label: 'Alien Registration, Naturalization, or Citizenship Number',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'paragraph',
text: '(A-Number) 1.',
+ page: 2,
},
{
component_type: 'fieldset',
@@ -325,8 +375,10 @@ export const mockResponse = {
label: 'Convict Date',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'fieldset',
@@ -338,12 +390,15 @@ export const mockResponse = {
label: 'US District Court',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'paragraph',
text: '(month/day/year) (Northern, etc.)',
+ page: 2,
},
{
component_type: 'fieldset',
@@ -355,12 +410,15 @@ export const mockResponse = {
label: 'State',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'paragraph',
text: '(state) or D.C. Superior Court of simple possession of marijuana, under :',
+ page: 2,
},
{
component_type: 'fieldset',
@@ -372,12 +430,15 @@ export const mockResponse = {
label: 'Docket Number',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'paragraph',
text: ';',
+ page: 2,
},
{
component_type: 'fieldset',
@@ -389,12 +450,15 @@ export const mockResponse = {
label: 'Code Section',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'paragraph',
text: 'OR (docket number) (code section) 2.',
+ page: 2,
},
{
component_type: 'fieldset',
@@ -406,8 +470,10 @@ export const mockResponse = {
label: 'Code Section',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'fieldset',
@@ -419,12 +485,15 @@ export const mockResponse = {
label: 'U.S. District Court',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'paragraph',
text: '(code section) (Eastern, etc.)',
+ page: 2,
},
{
component_type: 'fieldset',
@@ -436,12 +505,15 @@ export const mockResponse = {
label: 'State',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'paragraph',
text: 'or',
+ page: 2,
},
{
component_type: 'fieldset',
@@ -453,16 +525,20 @@ export const mockResponse = {
label: 'Docket No 2',
default_value: '',
required: true,
+ page: 2,
},
],
+ page: 2,
},
{
component_type: 'paragraph',
text: '(state) (docket number) United States Department of Justice Office of the Pardon Attorney Page 3 of 4 Washington, D.C. 20530 January 2024',
+ page: 2,
},
{
component_type: 'paragraph',
text: 'OMB Control No: 1123-0014 Expires 03/31/2027 APPLICATION FOR CERTIFICATE OF PARDON FOR THE OFFENSES OF SIMPLE POSSESSION, ATTEMPTED SIMPLE POSSESSION, OR USE OF MARIJUANA With knowledge of the penalties for false statements to Federal Agencies, as provided by 18 U.S.C. \u00a7 1001, and with knowledge that this statement is submitted by me to affect action by the U.S. Department of Justice, I certify that: 1. The applicant was either a U.S. citizen or lawfully present in the United States at the time of the offense. 2. The applicant was a U.S. citizen or lawful permanent resident on December 22, 2023. 3. The above statements, and accompanying documents, are true and complete to the best of my knowledge, information, and belief. 4. I acknowledge that any certificate issued in reliance on the above information will be voided, if the information is subsequently determined to be false.',
+ page: 3,
},
{
component_type: 'fieldset',
@@ -474,12 +550,15 @@ export const mockResponse = {
label: 'Date',
default_value: '',
required: true,
+ page: 3,
},
],
+ page: 3,
},
{
component_type: 'paragraph',
text: '(date) (signature) Page 4 of 4 United States Department of Justice Office of the Pardon Attorney Washington, D.C. 20530 January 2024',
+ page: 3,
},
],
raw_fields: [
@@ -491,6 +570,7 @@ export const mockResponse = {
coordinates: [97.0, 636.960022, 233.279999, 659.640015],
field_label: 'Fst Name 1',
field_instructions: 'First Name',
+ struct_parent: 4,
font_info: '',
hidden: false,
child_fields: [],
@@ -498,6 +578,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Fst Name 1',
+ struct_parent: 4,
},
{
type: '/Tx',
@@ -505,6 +586,7 @@ export const mockResponse = {
field_dict: {
coordinates: [233.087006, 637.580994, 390.214996, 659.320007],
field_instructions: 'Middle Name',
+ struct_parent: 5,
name: 0,
field_type: '/Tx',
font_info: '',
@@ -515,6 +597,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Mid Name 1/0',
+ struct_parent: 5,
},
{
type: '/Tx',
@@ -522,6 +605,7 @@ export const mockResponse = {
field_dict: {
coordinates: [390.996002, 637.492981, 548.124023, 659.231995],
field_instructions: 'Last Name',
+ struct_parent: 6,
name: 0,
field_type: '/Tx',
font_info: '',
@@ -532,6 +616,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Lst Name 1/0',
+ struct_parent: 6,
},
{
type: '/Tx',
@@ -541,6 +626,7 @@ export const mockResponse = {
coordinates: [153.740005, 598.085022, 283.246002, 620.765015],
field_label: 'Conv Fst Name',
field_instructions: 'First Name at Conviction',
+ struct_parent: 7,
font_info: '',
hidden: false,
child_fields: [],
@@ -548,6 +634,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Conv Fst Name',
+ struct_parent: 7,
},
{
type: '/Tx',
@@ -557,6 +644,7 @@ export const mockResponse = {
coordinates: [282.497986, 598.164001, 410.80899, 620.843994],
field_label: 'Conv Mid Name',
field_instructions: 'Middle Name at Conviction',
+ struct_parent: 8,
font_info: '',
hidden: false,
child_fields: [],
@@ -564,6 +652,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Conv Mid Name',
+ struct_parent: 8,
},
{
type: '/Tx',
@@ -573,6 +662,7 @@ export const mockResponse = {
coordinates: [410.212006, 597.677002, 536.132019, 620.357971],
field_label: 'Conv Lst Name',
field_instructions: 'Last Name at Conviction',
+ struct_parent: 9,
font_info: '',
hidden: false,
child_fields: [],
@@ -580,6 +670,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Conv Lst Name',
+ struct_parent: 9,
},
{
type: '/Tx',
@@ -589,6 +680,7 @@ export const mockResponse = {
coordinates: [102.839996, 563.880005, 547.080017, 586.559998],
field_label: 'Address',
field_instructions: 'Address (number, street, apartment/unit number)',
+ struct_parent: 10,
font_info: '',
hidden: false,
child_fields: [],
@@ -596,6 +688,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Address',
+ struct_parent: 10,
},
{
type: '/Tx',
@@ -605,6 +698,7 @@ export const mockResponse = {
coordinates: [64.500504, 531.0, 269.519989, 551.880005],
field_label: 'City',
field_instructions: 'City',
+ struct_parent: 11,
font_info: '',
hidden: false,
child_fields: [],
@@ -612,6 +706,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'City',
+ struct_parent: 11,
},
{
type: '/Tx',
@@ -621,6 +716,7 @@ export const mockResponse = {
coordinates: [273.959991, 531.0, 440.519989, 551.880005],
field_label: 'State',
field_instructions: 'State',
+ struct_parent: 12,
font_info: '',
hidden: false,
child_fields: [],
@@ -628,6 +724,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'State',
+ struct_parent: 12,
},
{
type: '/Tx',
@@ -637,6 +734,7 @@ export const mockResponse = {
coordinates: [444.959991, 531.0, 552.719971, 551.880005],
field_label: 'Zip Code',
field_instructions: '(Zip Code)',
+ struct_parent: 13,
font_info: '',
hidden: false,
child_fields: [],
@@ -644,6 +742,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Zip Code',
+ struct_parent: 13,
},
{
type: '/Tx',
@@ -653,6 +752,7 @@ export const mockResponse = {
coordinates: [131.863998, 489.600006, 290.743988, 512.280029],
field_label: 'Email Address',
field_instructions: 'Email Address',
+ struct_parent: 14,
font_info: '',
hidden: false,
child_fields: [],
@@ -660,6 +760,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Email Address',
+ struct_parent: 14,
},
{
type: '/Tx',
@@ -669,6 +770,7 @@ export const mockResponse = {
coordinates: [385.679993, 489.600006, 549.599976, 512.280029],
field_label: 'Phone Number',
field_instructions: 'Phone Number',
+ struct_parent: 15,
font_info: '',
hidden: false,
child_fields: [],
@@ -676,6 +778,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Phone Number',
+ struct_parent: 15,
},
{
type: '/Tx',
@@ -685,6 +788,7 @@ export const mockResponse = {
coordinates: [126.480003, 451.679993, 197.880005, 474.359985],
field_label: 'Date of Birth',
field_instructions: 'Date of Birth',
+ struct_parent: 16,
font_info: '',
hidden: false,
child_fields: [],
@@ -692,6 +796,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Date of Birth',
+ struct_parent: 16,
},
{
type: '/Tx',
@@ -701,6 +806,7 @@ export const mockResponse = {
coordinates: [241.559998, 451.679993, 313.079987, 474.359985],
field_label: 'Gender',
field_instructions: 'Gender',
+ struct_parent: 17,
font_info: '',
hidden: false,
child_fields: [],
@@ -708,12 +814,14 @@ export const mockResponse = {
},
page_number: 2,
path: 'Gender',
+ struct_parent: 17,
},
{
type: '/Btn',
var_name: '',
field_dict: {
coordinates: [505.618988, 450.865997, 523.619019, 468.865997],
+ struct_parent: 18,
name: 'Yes',
field_type: '/Btn',
field_instructions: '',
@@ -729,12 +837,14 @@ export const mockResponse = {
},
page_number: 2,
path: 'Ethnicity/Yes',
+ struct_parent: 18,
},
{
type: '/Btn',
var_name: '',
field_dict: {
coordinates: [558.213013, 450.865997, 576.213013, 468.865997],
+ struct_parent: 19,
name: 'No',
field_type: '/Btn',
field_instructions: '',
@@ -750,6 +860,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Ethnicity/No',
+ struct_parent: 19,
},
{
type: '/Btn',
@@ -759,6 +870,7 @@ export const mockResponse = {
coordinates: [280.10199, 426.162994, 298.10199, 444.162994],
field_label: 'Nat Amer',
field_instructions: 'Alaska Native or American Indian',
+ struct_parent: 20,
font_info: '',
hidden: false,
child_fields: [],
@@ -766,6 +878,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Nat Amer',
+ struct_parent: 20,
},
{
type: '/Btn',
@@ -775,6 +888,7 @@ export const mockResponse = {
coordinates: [366.563995, 426.162994, 384.563995, 444.162994],
field_label: 'Asian',
field_instructions: 'Asian',
+ struct_parent: 21,
font_info: '',
hidden: false,
child_fields: [],
@@ -782,6 +896,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Asian',
+ struct_parent: 21,
},
{
type: '/Btn',
@@ -791,6 +906,7 @@ export const mockResponse = {
coordinates: [531.517029, 426.162994, 549.517029, 444.162994],
field_label: 'Blck Amer',
field_instructions: 'Black or African American',
+ struct_parent: 22,
font_info: '',
hidden: false,
child_fields: [],
@@ -798,6 +914,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Blck Amer',
+ struct_parent: 22,
},
{
type: '/Btn',
@@ -807,6 +924,7 @@ export const mockResponse = {
coordinates: [309.587006, 401.061005, 327.587006, 419.061005],
field_label: 'Nat Haw Islander',
field_instructions: 'Native Hawaiian or Other Pacific Islander',
+ struct_parent: 23,
font_info: '',
hidden: false,
child_fields: [],
@@ -814,6 +932,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Nat Haw Islander',
+ struct_parent: 23,
},
{
type: '/Btn',
@@ -823,6 +942,7 @@ export const mockResponse = {
coordinates: [438.681, 401.061005, 456.681, 419.061005],
field_label: 'White',
field_instructions: 'White',
+ struct_parent: 24,
font_info: '',
hidden: false,
child_fields: [],
@@ -830,6 +950,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'White',
+ struct_parent: 24,
},
{
type: '/Btn',
@@ -839,6 +960,7 @@ export const mockResponse = {
coordinates: [508.806, 401.061005, 526.80603, 419.061005],
field_label: 'Other',
field_instructions: 'Other',
+ struct_parent: 25,
font_info: '',
hidden: false,
child_fields: [],
@@ -846,6 +968,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Other',
+ struct_parent: 25,
},
{
type: '/Btn',
@@ -853,6 +976,7 @@ export const mockResponse = {
field_dict: {
coordinates: [98.414398, 349.662994, 116.414001, 367.662994],
field_instructions: 'U S Citizen by birth',
+ struct_parent: 26,
name: 'Birth',
field_type: '/Btn',
font_info: '',
@@ -867,6 +991,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Citizenship/Birth',
+ struct_parent: 26,
},
{
type: '/Btn',
@@ -874,6 +999,7 @@ export const mockResponse = {
field_dict: {
coordinates: [98.414398, 331.733002, 116.414001, 349.733002],
field_instructions: 'U S naturalized citizen',
+ struct_parent: 27,
name: 'Naturalized',
field_type: '/Btn',
font_info: '',
@@ -888,6 +1014,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Citizenship/Naturalized',
+ struct_parent: 27,
},
{
type: '/Btn',
@@ -895,6 +1022,7 @@ export const mockResponse = {
field_dict: {
coordinates: [98.414398, 313.006012, 116.414001, 331.006012],
field_instructions: 'Lawful Permenent Resident',
+ struct_parent: 29,
name: 'Permanent Resident',
field_type: '/Btn',
font_info: '',
@@ -909,6 +1037,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Citizenship/Permanent Resident',
+ struct_parent: 29,
},
{
type: '/Tx',
@@ -916,6 +1045,7 @@ export const mockResponse = {
field_dict: {
coordinates: [432.306, 331.979004, 489.425995, 352.92099],
field_instructions: 'date naturalization granted',
+ struct_parent: 28,
name: 0,
field_type: '/Tx',
font_info: '',
@@ -926,6 +1056,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Naturalization Date_af_date/0',
+ struct_parent: 28,
},
{
type: '/Tx',
@@ -935,6 +1066,7 @@ export const mockResponse = {
coordinates: [414.304993, 329.523987, 471.424988, 308.582001],
field_label: 'Residency Date_af_date',
field_instructions: 'Date Residency Granted (mm/dd/yyyy)',
+ struct_parent: 30,
font_info: '',
hidden: false,
child_fields: [],
@@ -942,6 +1074,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Residency Date_af_date',
+ struct_parent: 30,
},
{
type: '/Tx',
@@ -952,6 +1085,7 @@ export const mockResponse = {
field_label: 'A-Number',
field_instructions:
'Alien Registration, Naturalization, or Citizenship Number',
+ struct_parent: 31,
font_info: '',
hidden: false,
child_fields: [],
@@ -959,6 +1093,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'A-Number',
+ struct_parent: 31,
},
{
type: '/Tx',
@@ -968,6 +1103,7 @@ export const mockResponse = {
coordinates: [203.602005, 218.822006, 301.363007, 245.341995],
field_label: 'Convict-Date_af_date',
field_instructions: 'Convict Date',
+ struct_parent: 32,
font_info: '',
hidden: false,
child_fields: [],
@@ -975,6 +1111,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Convict-Date_af_date',
+ struct_parent: 32,
},
{
type: '/Tx',
@@ -984,6 +1121,7 @@ export const mockResponse = {
coordinates: [451.200012, 219.0, 522.719971, 241.679993],
field_label: 'US District Court',
field_instructions: 'US District Court',
+ struct_parent: 33,
font_info: '',
hidden: false,
child_fields: [],
@@ -991,6 +1129,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'US District Court',
+ struct_parent: 33,
},
{
type: '/Tx',
@@ -1000,6 +1139,7 @@ export const mockResponse = {
coordinates: [105.720001, 187.919998, 177.240005, 210.600006],
field_label: 'Dist State',
field_instructions: 'State',
+ struct_parent: 34,
font_info: '',
hidden: false,
child_fields: [],
@@ -1007,6 +1147,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Dist State',
+ struct_parent: 34,
},
{
type: '/Tx',
@@ -1016,6 +1157,7 @@ export const mockResponse = {
coordinates: [114.015999, 153.479996, 262.575989, 176.160004],
field_label: 'Docket No',
field_instructions: 'Docket Number',
+ struct_parent: 36,
font_info: '',
hidden: false,
child_fields: [],
@@ -1023,6 +1165,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Docket No',
+ struct_parent: 36,
},
{
type: '/Tx',
@@ -1032,6 +1175,7 @@ export const mockResponse = {
coordinates: [349.320007, 153.479996, 448.320007, 176.160004],
field_label: 'Code Section',
field_instructions: 'Code Section',
+ struct_parent: 37,
font_info: '',
hidden: false,
child_fields: [],
@@ -1039,6 +1183,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Code Section',
+ struct_parent: 37,
},
{
type: '/Tx',
@@ -1048,6 +1193,7 @@ export const mockResponse = {
coordinates: [266.640015, 121.440002, 316.200012, 144.119995],
field_label: 'Code Section_2',
field_instructions: 'Code Section',
+ struct_parent: 38,
font_info: '',
hidden: false,
child_fields: [],
@@ -1055,6 +1201,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Code Section_2',
+ struct_parent: 38,
},
{
type: '/Tx',
@@ -1064,6 +1211,7 @@ export const mockResponse = {
coordinates: [464.040009, 121.32, 542.039978, 144.0],
field_label: 'US District Court_2',
field_instructions: 'U.S. District Court',
+ struct_parent: 39,
font_info: '',
hidden: false,
child_fields: [],
@@ -1071,6 +1219,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'US District Court_2',
+ struct_parent: 39,
},
{
type: '/Tx',
@@ -1080,6 +1229,7 @@ export const mockResponse = {
coordinates: [105.720001, 86.760002, 188.160004, 109.440002],
field_label: 'District 2',
field_instructions: 'State',
+ struct_parent: 40,
font_info: '',
hidden: false,
child_fields: [],
@@ -1087,6 +1237,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'District 2',
+ struct_parent: 40,
},
{
type: '/Tx',
@@ -1096,6 +1247,7 @@ export const mockResponse = {
coordinates: [403.920013, 86.760002, 525.0, 109.440002],
field_label: 'Docket No 2',
field_instructions: 'Docket No 2',
+ struct_parent: 42,
font_info: '',
hidden: false,
child_fields: [],
@@ -1103,6 +1255,7 @@ export const mockResponse = {
},
page_number: 2,
path: 'Docket No 2',
+ struct_parent: 42,
},
{
type: '/Tx',
@@ -1112,6 +1265,7 @@ export const mockResponse = {
coordinates: [75.120003, 396.720001, 219.479996, 425.519989],
field_label: 'App Date',
field_instructions: 'Date',
+ struct_parent: 44,
font_info: '',
hidden: false,
child_fields: [],
@@ -1119,6 +1273,7 @@ export const mockResponse = {
},
page_number: 3,
path: 'App Date',
+ struct_parent: 44,
},
],
grouped_items: [],
diff --git a/packages/forms/src/documents/pdf/parsing-api.ts b/packages/forms/src/documents/pdf/parsing-api.ts
index 628c94365..7d886220e 100644
--- a/packages/forms/src/documents/pdf/parsing-api.ts
+++ b/packages/forms/src/documents/pdf/parsing-api.ts
@@ -87,6 +87,7 @@ const TxInput = z.object({
label: z.string(),
default_value: z.string(),
required: z.boolean(),
+ page: z.number(),
});
const Checkbox = z.object({
@@ -94,6 +95,7 @@ const Checkbox = z.object({
id: z.string(),
label: z.string(),
default_checked: z.boolean(),
+ page: z.number(),
});
const RadioGroupOption = z.object({
@@ -108,17 +110,20 @@ const RadioGroup = z.object({
component_type: z.literal('radio_group'),
legend: z.string(),
options: RadioGroupOption.array(),
+ page: z.number(),
});
const Paragraph = z.object({
component_type: z.literal('paragraph'),
text: z.string(),
+ page: z.number(),
});
const Fieldset = z.object({
component_type: z.literal('fieldset'),
legend: z.string(),
fields: z.union([TxInput, Checkbox]).array(),
+ page: z.number(),
});
const ExtractedObject = z.object({
@@ -172,6 +177,7 @@ export const fetchPdfApiResponse: FetchPdfApiResponse = async (
export const processApiResponse = async (json: any): Promise => {
const extracted: ExtractedObject = ExtractedObject.parse(json.parsed_pdf);
const rootSequence: PatternId[] = [];
+ const pagePatterns: Record = {};
const parsedPdf: ParsedPdf = {
patterns: {},
errors: [],
@@ -210,7 +216,9 @@ export const processApiResponse = async (json: any): Promise => {
}
);
if (paragraph) {
- rootSequence.push(paragraph.id);
+ pagePatterns[element.page] = (pagePatterns[element.page] || []).concat(
+ paragraph.id
+ );
}
continue;
}
@@ -226,7 +234,9 @@ export const processApiResponse = async (json: any): Promise => {
}
);
if (checkboxPattern) {
- rootSequence.push(checkboxPattern.id);
+ pagePatterns[element.page] = (pagePatterns[element.page] || []).concat(
+ checkboxPattern.id
+ );
parsedPdf.outputs[checkboxPattern.id] = {
type: 'CheckBox',
name: element.id,
@@ -245,7 +255,6 @@ export const processApiResponse = async (json: any): Promise => {
'radio-group',
{
label: element.legend,
- // outputId: element.id,
options: element.options.map(option => ({
id: option.id,
label: option.label,
@@ -255,7 +264,9 @@ export const processApiResponse = async (json: any): Promise => {
}
);
if (radioGroupPattern) {
- rootSequence.push(radioGroupPattern.id);
+ pagePatterns[element.page] = (pagePatterns[element.page] || []).concat(
+ radioGroupPattern.id
+ );
/*
parsedPdf.outputs[radioGroupPattern.id] = {
type: 'RadioGroup',
@@ -337,27 +348,30 @@ export const processApiResponse = async (json: any): Promise => {
}
);
if (fieldset) {
- rootSequence.push(fieldset.id);
+ pagePatterns[element.page] = (pagePatterns[element.page] || []).concat(
+ fieldset.id
+ );
}
}
}
// Create a pattern for the single, first page.
- const pagePattern = processPatternData(
- defaultFormConfig,
- parsedPdf,
- 'page',
- {
- title: 'Untitled Page',
- patterns: rootSequence,
- }
- );
-
- const pages: PatternId[] = [];
- if (pagePattern) {
- parsedPdf.patterns[pagePattern.id] = pagePattern;
- pages.push(pagePattern.id);
- }
+ const pages: PatternId[] = Object.entries(pagePatterns)
+ .map(([page, patterns]) => {
+ const pagePattern = processPatternData(
+ defaultFormConfig,
+ parsedPdf,
+ 'page',
+ {
+ title: `Page ${parseInt(page) + 1}`,
+ patterns,
+ },
+ undefined,
+ parseInt(page)
+ );
+ return pagePattern?.id;
+ })
+ .filter(page => page !== undefined) as PatternId[];
// Assign the page to the root page set.
const rootPattern = processPatternData(
@@ -380,7 +394,8 @@ const processPatternData = (
parsedPdf: ParsedPdf,
patternType: T['type'],
patternData: T['data'],
- patternId?: PatternId
+ patternId?: PatternId,
+ page?: number
) => {
const result = createPattern(config, patternType, patternData, patternId);
if (!result.success) {
diff --git a/packages/forms/src/patterns/page-set/prompt.ts b/packages/forms/src/patterns/page-set/prompt.ts
index 7d18eeeac..81fa4d6da 100644
--- a/packages/forms/src/patterns/page-set/prompt.ts
+++ b/packages/forms/src/patterns/page-set/prompt.ts
@@ -3,6 +3,7 @@ import { z } from 'zod';
import {
type CreatePrompt,
type PageSetProps,
+ type PromptAction,
createPromptForPattern,
getPattern,
} from '../..';
@@ -31,10 +32,12 @@ export const createPrompt: CreatePrompt = (
),
]
: [];
+ const actions = getActionsForPage(pattern.data.pages.length, activePage);
return {
props: {
_patternId: pattern.id,
type: 'page-set',
+ actions,
pages: pattern.data.pages.map((patternId, index) => {
const childPattern = getPattern(session.form, patternId) as PagePattern;
if (childPattern.type !== 'page') {
@@ -64,3 +67,37 @@ const parseRouteData = (pattern: PageSetPattern, routeParams?: RouteData) => {
const schema = getRouteParamSchema(pattern);
return safeZodParseFormErrors(schema, routeParams || {});
};
+
+const getActionsForPage = (
+ pageCount: number,
+ pageIndex: number | null
+): PromptAction[] => {
+ if (pageIndex === null) {
+ return [];
+ }
+ const actions: PromptAction[] = [];
+ if (pageIndex > 0) {
+ // FIXME: HACK! Don't do this here. We need to pass the form's ID, or its
+ // URL, to createPrompt.
+ const pathName = location.hash.split('?')[0];
+ actions.push({
+ type: 'link',
+ text: 'Back',
+ url: `${pathName}?page=${pageIndex - 1}`,
+ });
+ }
+ if (pageIndex < pageCount - 1) {
+ actions.push({
+ type: 'submit',
+ submitAction: 'next',
+ text: 'Next',
+ });
+ } else {
+ actions.push({
+ type: 'submit',
+ submitAction: 'submit',
+ text: 'Submit',
+ });
+ }
+ return actions;
+};
diff --git a/packages/forms/src/response.ts b/packages/forms/src/response.ts
index 8c2646317..1556419f0 100644
--- a/packages/forms/src/response.ts
+++ b/packages/forms/src/response.ts
@@ -6,7 +6,7 @@ import {
getPatternConfig,
validatePattern,
} from '.';
-import { type PromptAction, createPrompt, isPromptAction } from './components';
+import { type PromptAction } from './components';
import { type FormSession, updateSession, FormErrorMap } from './session';
export type PromptResponse = {
@@ -19,17 +19,8 @@ export const applyPromptResponse = (
session: FormSession,
response: PromptResponse
): Result => {
- // Get the current prompt for this session.
- const prompt = createPrompt(config, session, { validate: false });
- if (!isPromptAction(prompt, response.action)) {
- return {
- success: false,
- error: 'invalid action',
- };
- }
const { errors, values } = parsePromptResponse(session, config, response);
const newSession = updateSession(session, values, errors);
-
return {
success: true,
data: newSession,
diff --git a/packages/forms/src/service/operations/submit-form.ts b/packages/forms/src/service/operations/submit-form.ts
index 392efdf4a..3be1037ca 100644
--- a/packages/forms/src/service/operations/submit-form.ts
+++ b/packages/forms/src/service/operations/submit-form.ts
@@ -51,7 +51,7 @@ export const submitForm = async (
error: 'Session is not complete',
});
}
- return generateDocumentPackage(form, formData);
+ return generateDocumentPackage(form, newSessionResult.data.data.values);
};
const generateDocumentPackage = async (
diff --git a/packages/forms/src/session.ts b/packages/forms/src/session.ts
index 0ab611923..2e86ff18c 100644
--- a/packages/forms/src/session.ts
+++ b/packages/forms/src/session.ts
@@ -144,6 +144,12 @@ export const sessionIsComplete = (config: FormConfig, session: FormSession) => {
const patternConfig = getPatternConfig(config, pattern.type);
const value = getFormSessionValue(session, pattern.id);
const isValidResult = validatePattern(patternConfig, pattern, value);
+ if (!isValidResult.success) {
+ console.error({
+ pattern,
+ error: isValidResult.error,
+ });
+ }
return isValidResult.success;
});
};