diff --git a/src/services/ui/src/components/RHF/types.ts b/src/packages/shared-types/forms.ts similarity index 92% rename from src/services/ui/src/components/RHF/types.ts rename to src/packages/shared-types/forms.ts index ad310e8680..b9d3de993f 100644 --- a/src/services/ui/src/components/RHF/types.ts +++ b/src/packages/shared-types/forms.ts @@ -4,22 +4,26 @@ import { FieldValues, RegisterOptions, } from "react-hook-form"; -import type { ReactElement } from "react"; import { + CalendarProps, InputProps, + RadioProps, + SelectProps, SwitchProps, TextareaProps, - SelectProps, - RadioProps, - CalendarProps, -} from "../Inputs"; +} from "shared-types"; + +export interface FormSchema { + header: string; + sections: Section[]; +} export type RHFSlotProps = { name: string; - label?: ReactElement | string; + label?: string; labelStyling?: string; groupNamePrefix?: string; - description?: ReactElement | string; + description?: string; dependency?: DependencyRule; rules?: RegisterOptions; } & { @@ -43,8 +47,8 @@ export type RHFOption = { export type RHFComponentMap = { Input: InputProps & { - label?: ReactElement | string; - description?: ReactElement | string; + label?: string; + description?: string; }; Textarea: TextareaProps; Switch: SwitchProps; diff --git a/src/packages/shared-types/index.ts b/src/packages/shared-types/index.ts index a0e34a527d..a7d890447f 100644 --- a/src/packages/shared-types/index.ts +++ b/src/packages/shared-types/index.ts @@ -7,3 +7,5 @@ export * from "./opensearch"; export * from "./uploads"; export * from "./actions"; export * from "./action-types/withdraw-record"; +export * from "./forms"; +export * from "./inputs"; diff --git a/src/packages/shared-types/inputs.ts b/src/packages/shared-types/inputs.ts new file mode 100644 index 0000000000..022a9da035 --- /dev/null +++ b/src/packages/shared-types/inputs.ts @@ -0,0 +1,45 @@ +import { DayPicker } from "react-day-picker"; +import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"; +import * as SwitchPrimitives from "@radix-ui/react-switch"; +import * as SelectPrimitive from "@radix-ui/react-select"; + +export type CalendarProps = React.ComponentProps & { + className?: string; + classNames?: any; + showOutsideDays?: boolean; +}; + +export type DatePickerProps = { + date: Date | undefined; + onChange: (date: Date | undefined) => void; +}; + +export interface InputProps + extends React.InputHTMLAttributes { + icon?: string; +} + +export type RadioProps = React.ComponentPropsWithoutRef< + typeof RadioGroupPrimitive.Root +> & { + className?: string; +}; + +export type SelectProps = React.ComponentPropsWithoutRef< + typeof SelectPrimitive.Root +> & { + options: { label: string; value: any }[]; + className?: string; +}; + +export type SwitchProps = React.ComponentPropsWithoutRef< + typeof SwitchPrimitives.Root +> & { + className?: string; +}; + +export interface TextareaProps + extends React.TextareaHTMLAttributes { + charcount?: "simple" | "limited"; + charcountstyling?: string; +} diff --git a/src/services/api/handlers/forms.ts b/src/services/api/handlers/forms.ts index 9e1bee72fa..0e6bf3de9b 100644 --- a/src/services/api/handlers/forms.ts +++ b/src/services/api/handlers/forms.ts @@ -1,12 +1,11 @@ import { response } from "../libs/handler"; - import * as fs from "fs"; import { APIGatewayEvent } from "aws-lambda"; export const forms = async (event: APIGatewayEvent) => { try { - const formId = event.queryStringParameters.formId; - const formVersion = event.queryStringParameters.formVersion; + const formId = event.queryStringParameters?.formId?.toLocaleUpperCase(); + let formVersion = event.queryStringParameters?.formVersion; if (!formId) { return response({ @@ -16,25 +15,53 @@ export const forms = async (event: APIGatewayEvent) => { } const filePath = getFilepathForIdAndVersion(formId, formVersion); + + if (!filePath) { + return response({ + statusCode: 404, + body: JSON.stringify({ + error: "No file was found with provided formId and formVersion", + }), + }); + } + const jsonData = await fs.promises.readFile(filePath, "utf-8"); if (!jsonData) { return response({ statusCode: 404, body: JSON.stringify({ - error: "No file was found with provided formId and formVersion", + error: `File found for ${formId}, but it's empty`, + }), + }); + } + + if (!formVersion) formVersion = getMaxVersion(formId); + + try { + const formObj = await import(`/opt/${formId}/v${formVersion}.js`); + + if (formObj?.form) { + return response({ + statusCode: 200, + body: formObj.form, + }); + } + } catch (importError) { + console.error("Error importing module:", importError); + return response({ + statusCode: 500, + body: JSON.stringify({ + error: importError.message + ? importError.message + : "Internal server error", }), }); } - console.log(jsonData); - return response({ - statusCode: 200, - body: jsonData, - }); } catch (error) { console.error("Error:", error); return response({ - statusCode: 500, + statusCode: 502, body: JSON.stringify({ error: error.message ? error.message : "Internal server error", }), @@ -42,14 +69,7 @@ export const forms = async (event: APIGatewayEvent) => { } }; -export function getFilepathForIdAndVersion( - formId: string, - formVersion: string | undefined -): string | undefined { - if (formId && formVersion) { - return `/opt/${formId}/v${formVersion}.json`; - } - +export function getMaxVersion(formId: string) { const files = fs.readdirSync(`/opt/${formId}`); if (!files) return undefined; const versionNumbers = files?.map((fileName: string) => { @@ -59,11 +79,21 @@ export function getFilepathForIdAndVersion( } return 1; }); - const maxVersion = Math.max(...versionNumbers); + return Math.max(...versionNumbers).toString(); +} - if (!maxVersion) return undefined; +export function getFilepathForIdAndVersion( + formId: string, + formVersion: string | undefined +): string | undefined { + if (formId && formVersion) { + return `/opt/${formId}/v${formVersion}.js`; + } - return `/opt/${formId}/v${maxVersion}.json`; + const maxVersion = getMaxVersion(formId); + + if (!maxVersion) return undefined; + return `/opt/${formId}/v${maxVersion}.js`; } export const handler = forms; diff --git a/src/services/api/handlers/tests/forms.test.ts b/src/services/api/handlers/tests/forms.test.ts index 77bf3f6f3c..d4ea9ab2f3 100644 --- a/src/services/api/handlers/tests/forms.test.ts +++ b/src/services/api/handlers/tests/forms.test.ts @@ -10,8 +10,8 @@ describe("Forms Lambda Tests", () => { } as APIGatewayProxyEvent; const result = await forms(event); - expect(result.statusCode).toBe(400); - expect(JSON.parse(result.body)).toEqual({ + expect(result?.statusCode).toBe(400); + expect(JSON.parse(result?.body as string)).toEqual({ error: "File ID was not provided", }); }); @@ -22,9 +22,9 @@ describe("Forms Lambda Tests", () => { } as APIGatewayProxyEvent; const result = await forms(event); - expect(result.statusCode).toBe(500); - expect(JSON.parse(result.body)).toEqual({ - error: "ENOENT: no such file or directory, open '/opt/test/v1.json'", + expect(result?.statusCode).toBe(500); + expect(JSON.parse(result?.body as string)).toEqual({ + error: "ENOENT: no such file or directory, open '/opt/test/v1.js'", }); }); @@ -38,8 +38,8 @@ describe("Forms Lambda Tests", () => { } as APIGatewayProxyEvent; const result = await forms(event); - expect(result.statusCode).toBe(200); - expect(result.headers["Content-Type"]).toBe("application/json"); + expect(result?.statusCode).toBe(200); + expect(result?.headers["Content-Type"]).toBe("application/json"); }); it("should return 500 with a custom error message for other internal errors", async () => { @@ -53,8 +53,8 @@ describe("Forms Lambda Tests", () => { const result = await forms(event); - expect(result.statusCode).toBe(500); - expect(JSON.parse(result.body)).toEqual({ + expect(result?.statusCode).toBe(500); + expect(JSON.parse(result?.body as string)).toEqual({ error: "Internal Server Error Message", }); }); @@ -62,10 +62,10 @@ describe("Forms Lambda Tests", () => { it("should return the correct JSON data for different file versions", async () => { vi.spyOn(fs.promises, "readFile").mockImplementation(async (filePath) => { const filePathString = filePath.toString(); - if (filePathString.includes("/opt/testform/v1.json")) { - return JSON.stringify({ version: "1", data: "v1 data" }); - } else if (filePathString.includes("/opt/testform/v2.json")) { - return JSON.stringify({ version: "2", data: "v2 data" }); + if (filePathString.includes("/opt/testform/v1.js")) { + return Buffer.from(JSON.stringify({ version: "1", data: "v1 data" })); + } else { + return Buffer.from(JSON.stringify({ version: "2", data: "v2 data" })); } }); @@ -79,14 +79,14 @@ describe("Forms Lambda Tests", () => { const resultV1 = await forms(eventV1); const resultV2 = await forms(eventV2); - expect(resultV1.statusCode).toBe(200); - expect(resultV2.statusCode).toBe(200); + expect(resultV1?.statusCode).toBe(200); + expect(resultV2?.statusCode).toBe(200); - expect(JSON.parse(resultV1.body)).toEqual({ + expect(JSON.parse(resultV1?.body as string)).toEqual({ version: "1", data: "v1 data", }); - expect(JSON.parse(resultV2.body)).toEqual({ + expect(JSON.parse(resultV2?.body as string)).toEqual({ version: "2", data: "v2 data", }); diff --git a/src/services/ui/src/pages/form/proto.tsx b/src/services/api/layers/ABP1/v1.ts similarity index 99% rename from src/services/ui/src/pages/form/proto.tsx rename to src/services/api/layers/ABP1/v1.ts index f2b1c29635..59637fd088 100644 --- a/src/services/ui/src/pages/form/proto.tsx +++ b/src/services/api/layers/ABP1/v1.ts @@ -1,6 +1,6 @@ -import { Document } from "@/components/RHF/types"; +import { FormSchema } from "shared-types"; -export const ABP1: Document = { +const ABP1: FormSchema = { header: "ABP 1: Alternative Benefit Plan populations", sections: [ { @@ -1319,3 +1319,5 @@ export const ABP1: Document = { // }, ], }; + +export const form = ABP1; diff --git a/src/services/api/layers/testform/v1.json b/src/services/api/layers/testform/v1.json deleted file mode 100644 index b1f7a5e452..0000000000 --- a/src/services/api/layers/testform/v1.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "formId": "sampleForm", - "formVersion": "1.0", - "formTitle": "Sample Form", - "formFields": [ - { - "name": "fullName", - "label": "Full Name", - "type": "text", - "required": true - }, - { - "name": "email", - "label": "Email", - "type": "email", - "required": true - }, - { - "name": "phoneNumber", - "label": "Phone Number", - "type": "tel", - "required": false - }, - { - "name": "message", - "label": "Message", - "type": "textarea", - "required": true - } - ] - } - \ No newline at end of file diff --git a/src/services/api/layers/testform/v2.json b/src/services/api/layers/testform/v2.json deleted file mode 100644 index 99a81f196b..0000000000 --- a/src/services/api/layers/testform/v2.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "formId": "sampleForm", - "formVersion": "2.0", - "formTitle": "Sample Form", - "formFields": [ - { - "name": "fullName", - "label": "Full Name", - "type": "text", - "required": true - }, - { - "name": "email", - "label": "Email", - "type": "email", - "required": true - }, - { - "name": "phoneNumber", - "label": "Phone Number", - "type": "tel", - "required": false - }, - { - "name": "message", - "label": "Message", - "type": "textarea", - "required": true - } - ] - } - \ No newline at end of file diff --git a/src/services/api/package.json b/src/services/api/package.json index 94a6529cda..61b7c2770b 100644 --- a/src/services/api/package.json +++ b/src/services/api/package.json @@ -28,6 +28,7 @@ }, "version": "0.0.0", "scripts": { + "build": "tsc", "lint": "eslint '**/*.{ts,js}'" } } diff --git a/src/services/api/serverless.yml b/src/services/api/serverless.yml index 9ec228d761..aee04474bf 100644 --- a/src/services/api/serverless.yml +++ b/src/services/api/serverless.yml @@ -6,6 +6,8 @@ plugins: - "@stratiformdigital/serverless-iam-helper" - "@stratiformdigital/serverless-s3-security-helper" - serverless-scriptable-plugin + - serverless-plugin-scripts + provider: name: aws runtime: nodejs18.x @@ -64,6 +66,10 @@ custom: scriptable: hooks: package:compileEvents: ./handlers/repack.js + scripts: + hooks: + package:initialize: | + yarn build params: master: formsProvisionedConcurrency: 2 @@ -247,7 +253,6 @@ layers: forms: path: layers description: Lambda Layer for forms function - resources: Resources: ApiGateway400ErrorCount: diff --git a/src/services/api/tsconfig.json b/src/services/api/tsconfig.json index 3884eb2003..3e4f76053c 100644 --- a/src/services/api/tsconfig.json +++ b/src/services/api/tsconfig.json @@ -1,6 +1,10 @@ { "compilerOptions": { "target": "ES2016", - "moduleResolution": "node" - } + "moduleResolution": "node", + "module": "commonjs", + "allowSyntheticDefaultImports": true + }, + "include": ["./layers/**/*.ts"], + "exclude": ["node_modules"] } diff --git a/src/services/ui/e2e/tests/home/index.spec.ts b/src/services/ui/e2e/tests/home/index.spec.ts index 12ef20ac1f..e4e7616ff1 100644 --- a/src/services/ui/e2e/tests/home/index.spec.ts +++ b/src/services/ui/e2e/tests/home/index.spec.ts @@ -17,12 +17,15 @@ test("has title", async ({ page }) => { await expect(page).toHaveTitle(/CMS MAKO/); }); -test("see frequently asked questions header when in faq page", async ({ page }) => { +test("see frequently asked questions header when in faq page", async ({ + page, +}) => { await page.goto("/"); const popup = page.waitForEvent("popup"); await page.getByRole("link", { name: "FAQ", exact: true }).click(); const foundFaqHeading = await popup; - await foundFaqHeading.getByRole("heading", { name: "Frequently Asked Questions" }) + await foundFaqHeading + .getByRole("heading", { name: "Frequently Asked Questions" }) .isVisible(); expect(foundFaqHeading).toBeTruthy(); }); @@ -32,8 +35,8 @@ test("see dashboard link when log in", async ({ page }) => { await page.getByRole("button", { name: "Sign In" }).click(); await page .getByRole("textbox", { name: "name@host.com" }) - .type(testUsers.state); - await page.getByRole("textbox", { name: "Password" }).type(password); + .fill(testUsers.state); + await page.getByRole("textbox", { name: "Password" }).fill(password); await page.getByRole("button", { name: "submit" }).click(); await page.getByRole("link", { name: "Dashboard" }).click(); @@ -46,8 +49,8 @@ test("see dashboard link when log in", async ({ page }) => { test("failed incorrect login username", async ({ page }) => { await page.goto("/"); await page.getByRole("button", { name: "Sign In" }).click(); - await page.getByRole("textbox", { name: "name@host.com" }).type("."); - await page.getByRole("textbox", { name: "Password" }).type(password); + await page.getByRole("textbox", { name: "name@host.com" }).fill("."); + await page.getByRole("textbox", { name: "Password" }).fill(password); await page.getByRole("button", { name: "submit" }).click(); await page.locator("#loginErrorMessage").first().isVisible(); }); diff --git a/src/services/ui/src/api/useGetForm.ts b/src/services/ui/src/api/useGetForm.ts index 5cb2f9eeca..7f4fd08793 100644 --- a/src/services/ui/src/api/useGetForm.ts +++ b/src/services/ui/src/api/useGetForm.ts @@ -1,12 +1,12 @@ import { useQuery } from "@tanstack/react-query"; import { API } from "aws-amplify"; import { ReactQueryApiError } from "shared-types"; +import { FormSchema } from "shared-types"; -// TODO: Use the Document type here once it is in a shared location. export const getForm = async ( formId: string, formVersion?: string -): Promise => { +): Promise => { const form = await API.get("os", "/forms", { queryStringParameters: { formId, formVersion }, }); @@ -15,7 +15,7 @@ export const getForm = async ( }; export const useGetForm = (formId: string, formVersion?: string) => { - return useQuery([formId], () => + return useQuery([formId, formVersion], () => getForm(formId, formVersion) ); }; diff --git a/src/services/ui/src/components/Inputs/calendar.tsx b/src/services/ui/src/components/Inputs/calendar.tsx index 0a626733a8..080244a827 100644 --- a/src/services/ui/src/components/Inputs/calendar.tsx +++ b/src/services/ui/src/components/Inputs/calendar.tsx @@ -1,15 +1,9 @@ import * as React from "react"; +import { CalendarProps } from "shared-types"; import { DayPicker } from "react-day-picker"; - import { cn } from "@/lib/utils"; import { buttonVariants } from "./button"; -export type CalendarProps = React.ComponentProps & { - className?: string; - classNames?: any; - showOutsideDays?: boolean; -}; - function Calendar({ className, classNames, diff --git a/src/services/ui/src/components/Inputs/date-picker.tsx b/src/services/ui/src/components/Inputs/date-picker.tsx index f85c207a99..0f4661cf39 100644 --- a/src/services/ui/src/components/Inputs/date-picker.tsx +++ b/src/services/ui/src/components/Inputs/date-picker.tsx @@ -3,17 +3,12 @@ import * as React from "react"; import { format } from "date-fns"; import { Calendar as CalendarIcon } from "lucide-react"; - +import { DatePickerProps } from "shared-types"; import { cn } from "@/lib/utils"; import { Button } from "@/components/Inputs/button"; import { Calendar } from "@/components/Inputs/calendar"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/Popover"; -type DatePickerProps = { - date: Date | undefined; - onChange: (date: Date | undefined) => void; -}; - export const DatePicker = ({ date, onChange }: DatePickerProps) => { const [isCalendarOpen, setIsCalendarOpen] = React.useState(false); diff --git a/src/services/ui/src/components/Inputs/input.tsx b/src/services/ui/src/components/Inputs/input.tsx index b674ae4695..c7e1cd6696 100644 --- a/src/services/ui/src/components/Inputs/input.tsx +++ b/src/services/ui/src/components/Inputs/input.tsx @@ -1,10 +1,6 @@ import * as React from "react"; import { cn } from "@/lib/utils"; - -export interface InputProps - extends React.InputHTMLAttributes { - icon?: string; -} +import { InputProps } from "shared-types"; const Input = React.forwardRef( ({ className, icon, ...props }, ref) => { diff --git a/src/services/ui/src/components/Inputs/radio-group.tsx b/src/services/ui/src/components/Inputs/radio-group.tsx index 3485da7fe7..d456a6598d 100644 --- a/src/services/ui/src/components/Inputs/radio-group.tsx +++ b/src/services/ui/src/components/Inputs/radio-group.tsx @@ -18,11 +18,6 @@ const RadioGroup = React.forwardRef< ); }); RadioGroup.displayName = RadioGroupPrimitive.Root.displayName; -export type RadioProps = React.ComponentPropsWithoutRef< - typeof RadioGroupPrimitive.Root -> & { - className?: string; -}; const RadioGroupItem = React.forwardRef< React.ElementRef & { className?: string }, diff --git a/src/services/ui/src/components/Inputs/select.tsx b/src/services/ui/src/components/Inputs/select.tsx index 7adc3db81a..853cc0fa40 100644 --- a/src/services/ui/src/components/Inputs/select.tsx +++ b/src/services/ui/src/components/Inputs/select.tsx @@ -5,15 +5,7 @@ import * as SelectPrimitive from "@radix-ui/react-select"; import { cn } from "@/lib/utils"; const Select = SelectPrimitive.Root; -export type SelectProps = React.ComponentPropsWithoutRef< - typeof SelectPrimitive.Root -> & { - options: { label: string; value: any }[]; - className?: string; -}; - const SelectGroup = SelectPrimitive.Group; - const SelectValue = SelectPrimitive.Value; const SelectTrigger = React.forwardRef< diff --git a/src/services/ui/src/components/Inputs/switch.tsx b/src/services/ui/src/components/Inputs/switch.tsx index 7082d4ccce..15f3c0b9b7 100644 --- a/src/services/ui/src/components/Inputs/switch.tsx +++ b/src/services/ui/src/components/Inputs/switch.tsx @@ -1,14 +1,8 @@ /* eslint-disable react/prop-types */ import * as React from "react"; import * as SwitchPrimitives from "@radix-ui/react-switch"; - import { cn } from "@/lib/utils"; - -export type SwitchProps = React.ComponentPropsWithoutRef< - typeof SwitchPrimitives.Root -> & { - className?: string; -}; +import { SwitchProps } from "shared-types"; const Switch = React.forwardRef< React.ElementRef & { className?: string }, diff --git a/src/services/ui/src/components/Inputs/textarea.tsx b/src/services/ui/src/components/Inputs/textarea.tsx index 2c262e4b5e..2a59cecb74 100644 --- a/src/services/ui/src/components/Inputs/textarea.tsx +++ b/src/services/ui/src/components/Inputs/textarea.tsx @@ -1,12 +1,6 @@ import * as React from "react"; - import { cn } from "@/lib/utils"; - -export interface TextareaProps - extends React.TextareaHTMLAttributes { - charcount?: "simple" | "limited"; - charcountstyling?: string; -} +import { TextareaProps } from "shared-types"; const Textarea = React.forwardRef( ({ className, ...props }, ref) => { diff --git a/src/services/ui/src/components/Layout/index.tsx b/src/services/ui/src/components/Layout/index.tsx index 194a632a2a..d72c7f358c 100644 --- a/src/services/ui/src/components/Layout/index.tsx +++ b/src/services/ui/src/components/Layout/index.tsx @@ -32,7 +32,7 @@ const getLinks = (isAuthenticated: boolean, role?: boolean) => { { name: "Webforms", link: "/webforms", - condition: !isProd, + condition: isAuthenticated && !isProd, }, ].filter((l) => l.condition); }; diff --git a/src/services/ui/src/components/RHF/Document.tsx b/src/services/ui/src/components/RHF/Document.tsx index accefd047f..8f9d05460a 100644 --- a/src/services/ui/src/components/RHF/Document.tsx +++ b/src/services/ui/src/components/RHF/Document.tsx @@ -2,10 +2,10 @@ import { Control, FieldValues } from "react-hook-form"; import { FormLabel } from "../Inputs"; import { RHFSection } from "./Section"; -import type { Document } from "./types"; +import { FormSchema } from "shared-types"; export const RHFDocument = (props: { - document: Document; + document: FormSchema; control: Control; }) => { return ( diff --git a/src/services/ui/src/components/RHF/FieldArray.tsx b/src/services/ui/src/components/RHF/FieldArray.tsx index 0ded5f818e..61a594d7d8 100644 --- a/src/services/ui/src/components/RHF/FieldArray.tsx +++ b/src/services/ui/src/components/RHF/FieldArray.tsx @@ -3,7 +3,7 @@ import { Plus, Trash2 } from "lucide-react"; import { RHFSlot } from "./Slot"; import { Button, FormField } from "../Inputs"; -import { FieldArrayProps } from "./types"; +import { FieldArrayProps } from "shared-types"; import { slotInitializer } from "./utils"; import { useEffect } from "react"; diff --git a/src/services/ui/src/components/RHF/FieldGroup.tsx b/src/services/ui/src/components/RHF/FieldGroup.tsx index 2c66018832..fcebaea3dd 100644 --- a/src/services/ui/src/components/RHF/FieldGroup.tsx +++ b/src/services/ui/src/components/RHF/FieldGroup.tsx @@ -3,7 +3,7 @@ import { Plus } from "lucide-react"; import { RHFSlot } from "./Slot"; import { Button, FormField } from "../Inputs"; -import { FieldGroupProps } from "./types"; +import { FieldGroupProps } from "shared-types"; import { slotInitializer } from "./utils"; import { useEffect } from "react"; diff --git a/src/services/ui/src/components/RHF/FormGroup.tsx b/src/services/ui/src/components/RHF/FormGroup.tsx index d8dd32cb28..10efe5ea9b 100644 --- a/src/services/ui/src/components/RHF/FormGroup.tsx +++ b/src/services/ui/src/components/RHF/FormGroup.tsx @@ -2,7 +2,7 @@ import { Control, FieldValues } from "react-hook-form"; import { FormLabel, FormField } from "../Inputs"; import { DependencyWrapper } from "./dependencyWrapper"; import { RHFSlot } from "./Slot"; -import * as TRhf from "./types"; +import * as TRhf from "shared-types"; export const RHFFormGroup = (props: { form: TRhf.FormGroup; diff --git a/src/services/ui/src/components/RHF/Section.tsx b/src/services/ui/src/components/RHF/Section.tsx index a4b20acb09..4adf2be47a 100644 --- a/src/services/ui/src/components/RHF/Section.tsx +++ b/src/services/ui/src/components/RHF/Section.tsx @@ -2,7 +2,7 @@ import { Control, FieldValues } from "react-hook-form"; import { FormLabel } from "../Inputs"; import { DependencyWrapper } from "./dependencyWrapper"; -import { Section } from "./types"; +import { Section } from "shared-types"; import { RHFFormGroup } from "./FormGroup"; export const RHFSection = (props: { diff --git a/src/services/ui/src/components/RHF/Slot.tsx b/src/services/ui/src/components/RHF/Slot.tsx index 15fca2676c..d69d2c7655 100644 --- a/src/services/ui/src/components/RHF/Slot.tsx +++ b/src/services/ui/src/components/RHF/Slot.tsx @@ -28,7 +28,7 @@ import { cn } from "@/lib"; import { format } from "date-fns"; import { RHFFieldArray } from "./FieldArray"; import { FieldGroup } from "./FieldGroup"; -import type { RHFSlotProps, RHFComponentMap, FormGroup } from "./types"; +import type { RHFSlotProps, RHFComponentMap, FormGroup } from "shared-types"; import { useEffect, useMemo } from "react"; export const RHFSlot = < diff --git a/src/services/ui/src/components/RHF/dependencyWrapper.tsx b/src/services/ui/src/components/RHF/dependencyWrapper.tsx index 27c3b01a08..2c134adfa2 100644 --- a/src/services/ui/src/components/RHF/dependencyWrapper.tsx +++ b/src/services/ui/src/components/RHF/dependencyWrapper.tsx @@ -1,6 +1,6 @@ import { PropsWithChildren, useEffect, useState } from "react"; import { useFormContext } from "react-hook-form"; -import { DependencyRule, DependencyWrapperProps } from "./types"; +import { DependencyRule, DependencyWrapperProps } from "shared-types"; const checkTriggeringValue = ( dependentValue: unknown[], diff --git a/src/services/ui/src/components/RHF/utils/initializer.ts b/src/services/ui/src/components/RHF/utils/initializer.ts index 424c797907..444bfcee3e 100644 --- a/src/services/ui/src/components/RHF/utils/initializer.ts +++ b/src/services/ui/src/components/RHF/utils/initializer.ts @@ -1,4 +1,5 @@ -import * as T from "@/components/RHF/types"; +import * as T from "shared-types"; +import { FormSchema } from "shared-types"; type GL = Record; @@ -49,7 +50,7 @@ export const slotInitializer = (ACC: GL, SLOT: T.RHFSlotProps): GL => { return ACC; }; -export const documentInitializer = (document: T.Document) => { +export const documentInitializer = (document: FormSchema) => { return document.sections.reduce((ACC, SEC) => { SEC.form.reduce(formGroupInitializer, ACC); return ACC; diff --git a/src/services/ui/src/components/RHF/utils/validator.ts b/src/services/ui/src/components/RHF/utils/validator.ts index 6ea390e63f..3d80b3f20e 100644 --- a/src/services/ui/src/components/RHF/utils/validator.ts +++ b/src/services/ui/src/components/RHF/utils/validator.ts @@ -1,5 +1,6 @@ -import * as T from "@/components/RHF/types"; +import * as T from "shared-types"; import { RegisterOptions } from "react-hook-form"; +import { FormSchema } from "shared-types"; import { isNullOrUndefined, @@ -247,7 +248,7 @@ export const slotValidator = return ACC; }; -export const documentValidator = (document: T.Document) => (data: any) => { +export const documentValidator = (document: FormSchema) => (data: any) => { return document.sections.reduce((ACC, SEC) => { SEC.form.reduce(formGroupValidator(data), ACC); return ACC; diff --git a/src/services/ui/src/components/Webform/index.tsx b/src/services/ui/src/components/Webform/index.tsx new file mode 100644 index 0000000000..647b5387ba --- /dev/null +++ b/src/services/ui/src/components/Webform/index.tsx @@ -0,0 +1,79 @@ +import { useForm } from "react-hook-form"; +import { Button, Form } from "@/components/Inputs"; +import { RHFDocument } from "@/components/RHF"; +import { Link, useParams } from "react-router-dom"; +import { SubNavHeader } from "@/components"; +import { documentInitializer } from "@/components/RHF/utils"; +import { useGetForm } from "@/api"; +import { LoadingSpinner } from "@/components"; +export const Webforms = () => { + return ( + <> + +

Webforms

+
+
+
+ ABP1 +
+
+ + ); +}; + +export function Webform() { + const { id, version } = useParams<{ + id: string; + version: string; + }>(); + const { data, isLoading, error } = useGetForm(id as string, version); + + const defaultValues = data ? documentInitializer(data) : {}; + const savedData = localStorage.getItem(`${id}v${version}`); + const form = useForm({ + defaultValues: savedData ? JSON.parse(savedData) : defaultValues, + }); + + const onSave = () => { + const values = form.getValues(); + localStorage.setItem(`${id}v${version}`, JSON.stringify(values)); + alert("Saved"); + }; + + const onSubmit = form.handleSubmit( + (data) => { + console.log({ data }); + // const validate = documentValidator(ABP1); + // const isValid = validate(data); + // console.log({ isValid }); + }, + (err) => { + console.log({ err }); + } + ); + + if (isLoading) return ; + if (error || !data) { + return ( +
+ {`There was an error loading ${id}`} +
+ ); + } + + return ( +
+
+ + +
+ + +
+ + +
+ ); +} diff --git a/src/services/ui/src/components/index.tsx b/src/services/ui/src/components/index.tsx index e1e79751d3..edad3ab571 100644 --- a/src/services/ui/src/components/index.tsx +++ b/src/services/ui/src/components/index.tsx @@ -17,3 +17,4 @@ export * from "./SearchForm"; export * from "./SubmissionInfo"; export * from "./Modal"; export * from "./Dialog"; +export * from "./Webform"; diff --git a/src/services/ui/src/pages/form/index.tsx b/src/services/ui/src/pages/form/index.tsx index b7a71ca9aa..139fe75f36 100644 --- a/src/services/ui/src/pages/form/index.tsx +++ b/src/services/ui/src/pages/form/index.tsx @@ -1,128 +1 @@ -import { useForm } from "react-hook-form"; -import { Button, Form } from "@/components/Inputs"; -import { RHFDocument } from "@/components/RHF"; -import { ABP1 } from "./proto"; -import { documentInitializer, documentValidator } from "@/components/RHF/utils"; -import { Link, useParams } from "react-router-dom"; -import { SubNavHeader } from "@/components"; -import { Footer } from "./footer"; - -export const Webforms = () => { - return ( - <> - -

Webforms

-
-
-
- ABP1 1.0 -
-
- - ); -}; - -export function Webform() { - const { id, version } = useParams<{ - id: string; - version: string; - }>(); - console.log({ id, version }); - - const defaultValues = documentInitializer(ABP1); - - const form = useForm({ - defaultValues, - }); - - const onSubmit = form.handleSubmit( - (data) => { - const validate = documentValidator(ABP1); - const isValid = validate({ - alt_benefit_plan_population_name: - "agadgasdfg2f2fdsfascvcvaeqfwf22fasdfasdfsd", - is_enrollment_available: "no", - income_target: "income_target_below", - federal_poverty_level_percentage: "", - ssi_federal_benefit_percentage: "", - other_percentage: "", - other_describe: "", - income_definition_percentage: "other", - is_incremental_amount: false, - doller_incremental_amount: "", - income_definition_region_statewide_group: [ - { - income_definition_region_statewide_arr: [ - { - household_size: "", - standard: "", - }, - ], - is_incremental_amount: false, - doller_incremental_amount: "", - }, - ], - income_definition_specific: "other_standard", - income_definition: "income_definition_specific", - other_description: "asdfasdf", - health_conditions: ["physical_disability", "brain_injury", "hiv_aids"], - other_targeting_criteria_description: "", - target_criteria: [ - "income_standard", - "health", - "other_targeting_criteria", - ], - is_geographic_area: "no", - specify_counties: "", - specify_regions: "", - specify_cities_towns: "", - specify_other: "", - geographic_variation: "other", - additional_information: "", - eligibility_groups: [ - { - eligibility_group: "parents_caretaker_relatives", - mandatory_voluntary: "voluntary", - }, - ], - income_definition_specific_statewide_group_other: [ - { - name_of_group: "", - group_description: "", - is_incremental_amount: false, - doller_incremental_amount: "", - income_definition_specific_statewide_arr: [ - { - household_size: "", - standard: "", - }, - ], - }, - ], - }); - console.log({ isValid }); - }, - (err) => { - console.log({ err }); - } - ); - return ( -
-
- - -
- -
- {/* */} - -
-
- - -
-
- ); -} - export * from "./medicaid-form"; diff --git a/src/services/ui/src/router.tsx b/src/services/ui/src/router.tsx index 5990fa3d03..877b0459e1 100644 --- a/src/services/ui/src/router.tsx +++ b/src/services/ui/src/router.tsx @@ -67,8 +67,8 @@ export const router = createBrowserRouter([ }, { path: ROUTES.MEDICAID_NEW, element: }, { path: ROUTES.ACTION, element: }, - { path: ROUTES.WEBFORMS, element: }, - { path: ROUTES.WEBFORM, element: }, + { path: ROUTES.WEBFORMS, element: }, + { path: ROUTES.WEBFORM, element: }, ], loader: rootLoader(queryClient), },