Skip to content

Commit

Permalink
Add "package download" pattern, which provides a submit button to dow…
Browse files Browse the repository at this point in the history
…nload a PDF package.
  • Loading branch information
danielnaab committed Oct 29, 2024
1 parent 576aa19 commit dd7596c
Show file tree
Hide file tree
Showing 20 changed files with 1,787 additions and 1,452 deletions.
5 changes: 5 additions & 0 deletions packages/common/src/locales/en/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export const en = {
displayName: 'Short answer',
maxLength: 'Maximum length',
},
packageDownload: {
...defaults,
displayName: 'Package download',
fieldLabel: 'Package download label',
},
page: {
fieldLabel: 'Page title',
},
Expand Down
16 changes: 16 additions & 0 deletions packages/design/src/Form/components/PackageDownload/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';

import { type PackageDownloadProps } from '@atj/forms';

import { type PatternComponent } from '../../../Form/index.js';
import ActionBar from '../../../Form/ActionBar/index.js';

const PackageDownload: PatternComponent<PackageDownloadProps> = props => {
return (
<>
<p className="maxw-tablet">{props.text}</p>
<ActionBar actions={props.actions} />
</>
);
};
export default PackageDownload;
2 changes: 2 additions & 0 deletions packages/design/src/Form/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Address from './Address/index.js';
import Checkbox from './Checkbox/index.js';
import Fieldset from './Fieldset/index.js';
import FormSummary from './FormSummary/index.js';
import PackageDownload from './PackageDownload/index.js';
import Page from './Page/index.js';
import PageSet from './PageSet/index.js';
import Paragraph from './Paragraph/index.js';
Expand All @@ -19,6 +20,7 @@ export const defaultPatternComponents: ComponentForPattern = {
fieldset: Fieldset as PatternComponent,
'form-summary': FormSummary as PatternComponent,
input: TextInput as PatternComponent,
'package-download': PackageDownload as PatternComponent,
page: Page as PatternComponent,
'page-set': PageSet as PatternComponent,
paragraph: Paragraph as PatternComponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ const sidebarPatterns: DropdownPattern[] = [
['paragraph', defaultFormConfig.patterns['paragraph']],
['rich-text', defaultFormConfig.patterns['rich-text']],
['radio-group', defaultFormConfig.patterns['radio-group']],
['package-download', defaultFormConfig.patterns['package-download']]
] as const;
export const fieldsetPatterns: DropdownPattern[] = [
['form-summary', defaultFormConfig.patterns['form-summary']],
Expand All @@ -102,6 +103,7 @@ export const fieldsetPatterns: DropdownPattern[] = [
['paragraph', defaultFormConfig.patterns['paragraph']],
['rich-text', defaultFormConfig.patterns['rich-text']],
['radio-group', defaultFormConfig.patterns['radio-group']],
['package-download', defaultFormConfig.patterns['package-download']]
] as const;

export const SidebarAddPatternMenuItem = ({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import classnames from 'classnames';
import React from 'react';

import { enLocale as message } from '@atj/common';
import {
type PackageDownloadPattern,
type PackageDownloadProps,
type PatternId,
} from '@atj/forms';

import PackageDownload from '../../../Form/components/PackageDownload/index.js';
import { PatternEditComponent } from '../types.js';

import { PatternEditActions } from './common/PatternEditActions.js';
import { PatternEditForm } from './common/PatternEditForm.js';
import { usePatternEditFormContext } from './common/hooks.js';
import { useFormManagerStore } from '../../store.js';

const PackageDownloadPatternEdit: PatternEditComponent<
PackageDownloadProps
> = ({ focus, previewProps }) => {
return (
<>
{focus ? (
<PatternEditForm
pattern={focus.pattern}
editComponent={<EditComponent patternId={focus.pattern.id} />}
></PatternEditForm>
) : (
<div className="padding-left-3 padding-bottom-3 padding-right-3">
<PackageDownload {...previewProps} />
</div>
)}
</>
);
};

const EditComponent = ({ patternId }: { patternId: PatternId }) => {
const pattern = useFormManagerStore<PackageDownloadPattern>(
state => state.session.form.patterns[patternId]
);
const { fieldId, getFieldState, register } =
usePatternEditFormContext<PackageDownloadPattern>(patternId);
const text = getFieldState('text');

return (
<div className="grid-row grid-gap-1">
<div className="tablet:grid-col-12">
<label
className={classnames('usa-label', {
'usa-label--error': text.error,
})}
htmlFor={fieldId('text')}
>
{message.patterns.packageDownload.fieldLabel}
</label>
{text.error ? (
<span className="usa-error-message" role="alert">
{text.error.message}
</span>
) : null}
<textarea
id={fieldId('text')}
className="usa-textarea bg-primary-lighter text-bold"
style={{ height: 'unset' }}
rows={4}
{...register('text')}
defaultValue={pattern.data.text}
autoFocus
></textarea>
</div>
<PatternEditActions />
</div>
);
};

export default PackageDownloadPatternEdit;
2 changes: 2 additions & 0 deletions packages/design/src/FormManager/FormEdit/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import CheckboxPatternEdit from './CheckboxPatternEdit.js';
import FieldsetEdit from './FieldsetEdit.js';
import FormSummaryEdit from './FormSummaryEdit.js';
import InputPatternEdit from './InputPatternEdit.js';
import PackageDownloadPatternEdit from './PackageDownloadPatternEdit.js';
import { PageEdit } from './PageEdit.js';
import PageSetEdit from './PageSetEdit.js';
import ParagraphPatternEdit from './ParagraphPatternEdit.js';
Expand All @@ -21,6 +22,7 @@ export const defaultPatternEditComponents: EditComponentForPattern = {
input: InputPatternEdit as PatternEditComponent,
'form-summary': FormSummaryEdit as PatternEditComponent,
fieldset: FieldsetEdit as PatternEditComponent,
'package-download': PackageDownloadPatternEdit as PatternEditComponent,
page: PageEdit as PatternEditComponent,
'page-set': PageSetEdit as PatternEditComponent,
'radio-group': RadioGroupPatternEdit as PatternEditComponent,
Expand Down
6 changes: 6 additions & 0 deletions packages/forms/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import {
import { type FormSession, nullSession, sessionIsComplete } from './session.js';
import { type ActionName } from './submission.js';

export type PackageDownloadProps = PatternProps<{
type: 'package-download';
text: string;
actions: PromptAction[];
}>;

export type TextInputProps = PatternProps<{
type: 'input';
inputId: string;
Expand Down
1,351 changes: 1,351 additions & 0 deletions packages/forms/src/documents/__tests__/test-documents.ts

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion packages/forms/src/documents/pdf/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const createFormOutputFieldData = (
return;
}
const outputFieldId = output.formFields[patternId];
console.log('***', patternId, docField, outputFieldId);
if (outputFieldId === '') {
console.error(`empty outputFieldId for field: ${patternId}: ${docField}`);
return;
Expand Down Expand Up @@ -65,7 +66,7 @@ export const fillPDF = async (
.sort((a, b) => a.name.localeCompare(b.name));

// Console log the resulting array
console.log('uniqueNamesArray:', uniqueNamesArray);
//console.log('uniqueNamesArray:', uniqueNamesArray);

// fields.map(field => {
// console.log('field name is:', field.getName());
Expand Down
7 changes: 5 additions & 2 deletions packages/forms/src/patterns/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { checkboxConfig } from './checkbox.js';
import { fieldsetConfig } from './fieldset/index.js';
import { formSummaryConfig } from './form-summary.js';
import { inputConfig } from './input/index.js';
import { packageDownloadConfig } from './package-download/index.js';
import { pageConfig } from './page/index.js';
import { pageSetConfig } from './page-set/index.js';
import { paragraphConfig } from './paragraph.js';
Expand All @@ -22,6 +23,7 @@ export const defaultFormConfig: FormConfig = {
checkbox: checkboxConfig,
fieldset: fieldsetConfig,
input: inputConfig,
'package-download': packageDownloadConfig,
page: pageConfig,
'page-set': pageSetConfig,
paragraph: paragraphConfig,
Expand All @@ -32,14 +34,15 @@ export const defaultFormConfig: FormConfig = {
} as const;

export * from './address/index.js';
export * from './checkbox.js';
export * from './fieldset/index.js';
export * from './form-summary.js';
export * from './input/index.js';
export * from './package-download/index.js';
export * from './page/index.js';
export { type PagePattern } from './page/config.js';
export * from './page-set/index.js';
export { type PageSetPattern } from './page-set/config.js';
export * from './checkbox.js';
export * from './form-summary.js';
export * from './paragraph.js';
export * from './radio-group.js';
export * from './sequence.js';
12 changes: 12 additions & 0 deletions packages/forms/src/patterns/package-download/builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { type PackageDownloadPattern } from '.';
import { PatternBuilder } from '../../pattern';

export class PackageDownload extends PatternBuilder<PackageDownloadPattern> {
toPattern(): PackageDownloadPattern {
return {
id: this.id,
type: 'page-set',
data: this.data,
};
}
}
44 changes: 44 additions & 0 deletions packages/forms/src/patterns/package-download/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as z from 'zod';

import { type Pattern, type PatternConfig } from '../../pattern.js';
import { type PackageDownloadProps } from '../../components.js';
import { type ActionName, getActionString } from '../../submission.js';
import { safeZodParseFormErrors } from '../../util/zod.js';

const configSchema = z.object({
text: z.string().min(1),
});
export type PackageDownloadPattern = Pattern<z.infer<typeof configSchema>>;

export const packageDownloadConfig: PatternConfig<PackageDownloadPattern> = {
displayName: 'Package download',
iconPath: 'block-icon.svg',
initial: {
text: 'Description text...',
},
parseConfigData: obj => safeZodParseFormErrors(configSchema, obj),
getChildren() {
return [];
},
createPrompt(_, session, pattern, options) {
const actionName: ActionName = getActionString({
handlerId: 'page-set',
patternId: pattern.id,
});
return {
props: {
_patternId: pattern.id,
type: 'package-download' as const,
actions: [
{
type: 'submit',
submitAction: actionName,
text: 'Download PDF',
},
],
text: pattern.data.text,
} as PackageDownloadProps,
children: [],
};
},
};
105 changes: 105 additions & 0 deletions packages/forms/src/patterns/package-download/submit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { describe, expect, it } from 'vitest';

import { failure, success } from '@atj/common';

import { Blueprint, getDocumentFieldData, type FormSession } from '../..';
import { defaultFormConfig } from '../../..';

import { downloadPackageHandler } from './submit';
import { PackageDownload } from './builder';
import { PageSet } from '../page-set/builder';
import { Page } from '../page/builder';
import { Input } from '../input/builder';
import { loadSamplePDF } from '../../documents/__tests__/sample-data';

describe('downloadPackageHandler', async () => {
it('returns failure when form is not complete', async () => {
const session: FormSession = {
form: await createTestForm(),
data: { errors: {}, values: {} },
route: { url: '#', params: {} },
};
const result = await downloadPackageHandler(defaultFormConfig, {
pattern: new PackageDownload({ text: 'Download now!' }).toPattern(),
session,
data: {},
});

expect(result).toEqual(failure('Form is not complete'));
});

it('returns attachments on complete form', async () => {
const session: FormSession = {
form: await createTestForm(),
data: {
errors: {},
values: {
'input-1': '53555',
},
},
route: { url: '#', params: {} },
};
const result = await downloadPackageHandler(defaultFormConfig, {
pattern: new PackageDownload({ text: 'Download now!' }).toPattern(),
session,
data: {},
});
expect(result).toEqual(
expect.objectContaining({
success: true,
data: {
session,
attachments: [
{
fileName: 'test.pdf',
data: expect.any(Uint8Array),
},
],
},
})
);
});
});

const createTestForm = async (): Promise<Blueprint> => {
const pdfBytes = await loadSamplePDF(
'doj-pardon-marijuana/application_for_certificate_of_pardon_for_simple_marijuana_possession.pdf'
);
const input1 = new Input(
{ label: 'Input 1', required: true, maxLength: 10 },
'input-1'
);
const page1 = new Page({ title: 'Page 1', patterns: [input1.id] }, 'page-1');
const pageSet = new PageSet({ pages: [page1.id] }, 'page-set');
return {
summary: {
title: 'Test Form',
description: 'A test form',
},
root: 'page-set',
patterns: {
'page-set': pageSet.toPattern(),
'page-1': page1.toPattern(),
'input-1': input1.toPattern(),
},
outputs: [
{
path: 'test.pdf',
data: new Uint8Array(pdfBytes),
fields: {
[input1.id]: {
type: 'TextField',
name: 'Zip Code',
label: 'Zip Code',
maxLength: 32,
value: 'zipCode',
required: true,
},
},
formFields: {
[input1.id]: 'Zip Code',
},
},
],
};
};
Loading

0 comments on commit dd7596c

Please sign in to comment.