Skip to content

Commit

Permalink
Fix route parsing in form filler view. (#141)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielnaab committed May 25, 2024
1 parent 8f0509d commit 765a74c
Show file tree
Hide file tree
Showing 14 changed files with 150 additions and 76 deletions.
8 changes: 3 additions & 5 deletions packages/design/src/Form/components/PageSet/PageSet.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import React from 'react';
import { useLocation } from 'react-router-dom';

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

import { type PatternComponent } from '../..';
import { useFormManagerStore } from '../../../FormManager/store';
import { PageMenu } from './PageMenu';
import { useRouteParams } from '../../../FormRouter/hooks';

const PageSet: PatternComponent<PageSetProps> = props => {
const location = useLocation();
const routeParams = useFormManagerStore(state => state.session.routeParams);
const { routeParams, pathname } = useRouteParams();
return (
<div className="grid-row grid-gap">
<nav className="tablet:grid-col-3 bg-primary-lightest">
Expand All @@ -20,7 +18,7 @@ const PageSet: PatternComponent<PageSetProps> = props => {
return {
title: page.title,
selected: page.active,
url: `#${location.pathname}?page=${index}`,
url: `#${pathname}?page=${index}`,
};
})}
/>
Expand Down
25 changes: 25 additions & 0 deletions packages/design/src/FormRouter/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useLocation } from 'react-router-dom';

import {
type RouteData,
getRouteDataFromQueryString,
} from '@atj/forms/src/route-data';

export const useRouteParams = (): RouteData => {
const location = useLocation();
const queryString = location.search.startsWith('?')
? location.search.substring(1)
: location.search;
return {
routeParams: getRouteDataFromQueryString(queryString),
pathname: location.pathname,
};
};

export const useQueryString = (): string => {
const location = useLocation();
const queryString = location.search.startsWith('?')
? location.search.substring(1)
: location.search;
return queryString;
};
8 changes: 6 additions & 2 deletions packages/design/src/FormRouter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import React from 'react';
import { useParams, HashRouter, Route, Routes } from 'react-router-dom';

import { type FormService } from '@atj/form-service';
import Form, { type FormUIContext } from '../Form';
import { createFormSession } from '@atj/forms';

import Form, { type FormUIContext } from '../Form';
import { useQueryString } from './hooks';

// Wrapper around Form that includes a client-side router for loading forms.
export default function FormRouter({
context,
Expand All @@ -20,6 +22,7 @@ export default function FormRouter({
path="/:formId"
Component={() => {
const { formId } = useParams();
const queryString = useQueryString();
if (formId === undefined) {
return <div>formId is undefined</div>;
}
Expand All @@ -34,7 +37,8 @@ export default function FormRouter({
</div>
);
}
const session = createFormSession(result.data);

const session = createFormSession(result.data, queryString);
return (
<Form
context={context}
Expand Down
2 changes: 1 addition & 1 deletion packages/forms/src/pattern.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type PatternValueMap = Record<PatternId, PatternValue>;
export type PatternMap = Record<PatternId, Pattern>;
export type GetPattern = (form: Blueprint, id: PatternId) => Pattern;

type ParseUserInput<Pattern, PatternOutput> = (
export type ParseUserInput<Pattern, PatternOutput> = (
pattern: Pattern,
obj: unknown
) => Result<PatternOutput, FormError>;
Expand Down
3 changes: 2 additions & 1 deletion packages/forms/src/patterns/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as z from 'zod';

import { type Pattern, type PatternConfig, validatePattern } from '../pattern';
import { type CheckboxProps } from '../components';
import { getFormSessionValue } from '../session';
import { getFormSessionError, getFormSessionValue } from '../session';
import { safeZodParseFormErrors, safeZodParseToFormError } from '../util/zod';

const configSchema = z.object({
Expand Down Expand Up @@ -32,6 +32,7 @@ export const checkboxConfig: PatternConfig<CheckboxPattern, PatternOutput> = {
createPrompt(_, session, pattern, options) {
const extraAttributes: Record<string, any> = {};
const sessionValue = getFormSessionValue(session, pattern.id);
//const sessionError = getFormSessionError(session, pattern.id);
if (options.validate) {
const isValidResult = validatePattern(
checkboxConfig,
Expand Down
64 changes: 0 additions & 64 deletions packages/forms/src/patterns/input.ts

This file was deleted.

20 changes: 20 additions & 0 deletions packages/forms/src/patterns/input/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { z } from 'zod';

import { en as message } from '@atj/common/src/locales/en/app';

import { ParsePatternConfigData, Pattern } from '../../pattern';
import { safeZodParseFormErrors } from '../../util/zod';

const configSchema = z.object({
label: z.string().min(1, message.patterns.input.fieldLabelRequired),
initial: z.string().optional(),
required: z.boolean(),
maxLength: z.coerce.number(),
});
export type InputConfigSchema = z.infer<typeof configSchema>;

export const parseConfigData: ParsePatternConfigData<
InputConfigSchema
> = obj => {
return safeZodParseFormErrors(configSchema, obj);
};
25 changes: 25 additions & 0 deletions packages/forms/src/patterns/input/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { en as message } from '@atj/common/src/locales/en/app';

import { Pattern, type PatternConfig } from '../../pattern';

import { parseConfigData, type InputConfigSchema } from './config';
import { createPrompt } from './prompt';
import { type InputPatternOutput, parseUserInput } from './response';

export type InputPattern = Pattern<InputConfigSchema>;

export const inputConfig: PatternConfig<InputPattern, InputPatternOutput> = {
displayName: message.patterns.input.displayName,
initial: {
label: message.patterns.input.fieldLabel,
initial: '',
required: true,
maxLength: 128,
},
parseUserInput,
parseConfigData,
getChildren() {
return [];
},
createPrompt,
};
35 changes: 35 additions & 0 deletions packages/forms/src/patterns/input/prompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { type InputPattern, inputConfig } from '.';
import {
type CreatePrompt,
type TextInputProps,
getFormSessionValue,
validatePattern,
} from '../..';

export const createPrompt: CreatePrompt<InputPattern> = (
_,
session,
pattern,
options
) => {
const extraAttributes: Record<string, any> = {};
const sessionValue = getFormSessionValue(session, pattern.id);
if (options.validate) {
const isValidResult = validatePattern(inputConfig, pattern, sessionValue);
if (!isValidResult.success) {
extraAttributes['error'] = isValidResult.error;
}
}
return {
props: {
_patternId: pattern.id,
type: 'input',
inputId: pattern.id,
value: sessionValue,
label: pattern.data.label,
required: pattern.data.required,
...extraAttributes,
} as TextInputProps,
children: [],
};
};
23 changes: 23 additions & 0 deletions packages/forms/src/patterns/input/response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { z } from 'zod';

import { ParseUserInput } from '../../pattern';
import { safeZodParseToFormError } from '../../util/zod';

import { type InputPattern } from '.';

const createSchema = (data: InputPattern['data']) => {
const schema = z.string().max(data.maxLength);
if (!data.required) {
return schema;
}
return schema.min(1, { message: 'This field is required' });
};

export type InputPatternOutput = z.infer<ReturnType<typeof createSchema>>;

export const parseUserInput: ParseUserInput<
InputPattern,
InputPatternOutput
> = (pattern, obj) => {
return safeZodParseToFormError(createSchema(pattern['data']), obj);
};
2 changes: 1 addition & 1 deletion packages/forms/src/patterns/page-set/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,5 @@ const getRouteParamSchema = (pattern: PageSetPattern) => {

const parseRouteData = (pattern: PageSetPattern, routeParams?: RouteData) => {
const schema = getRouteParamSchema(pattern);
return safeZodParseFormErrors(schema, routeParams);
return safeZodParseFormErrors(schema, routeParams || {});
};
2 changes: 1 addition & 1 deletion packages/forms/src/patterns/page/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
getPattern,
} from '../..';

import { PagePattern } from './config';
import { type PagePattern } from './config';

export const createPrompt: CreatePrompt<PagePattern> = (
config,
Expand Down
7 changes: 7 additions & 0 deletions packages/forms/src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ export const getFormSessionValue = (
return session.data.values[patternId];
};

export const getFormSessionError = (
session: FormSession,
patternId: PatternId
) => {
return session.data.errors[patternId];
};

export const updateSessionValue = (
session: FormSession,
id: PatternId,
Expand Down
2 changes: 1 addition & 1 deletion packages/forms/src/util/zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as z from 'zod';

import * as r from '@atj/common';

import { FormError, FormErrorMap, type FormErrors, type Pattern } from '..';
import { type FormError, type FormErrors, type Pattern } from '..';

export const safeZodParse = <T extends Pattern>(
schema: z.Schema,
Expand Down

0 comments on commit 765a74c

Please sign in to comment.