diff --git a/libs/application/core/src/lib/fieldBuilders.ts b/libs/application/core/src/lib/fieldBuilders.ts index b8f46194e70f..3b48a68fb268 100644 --- a/libs/application/core/src/lib/fieldBuilders.ts +++ b/libs/application/core/src/lib/fieldBuilders.ts @@ -21,7 +21,6 @@ import { FormTextArray, KeyValueField, LinkField, - MaybeWithApplicationAndField, MessageWithLinkButtonField, Option, PaymentChargeOverviewField, @@ -586,6 +585,7 @@ export const buildExpandableDescriptionField = ( component: FieldComponents.EXPANDABLE_DESCRIPTION, } } + export const buildAlertMessageField = ( data: Omit, ): AlertMessageField => { @@ -842,7 +842,6 @@ export const buildFieldsRepeaterField = ( ): FieldsRepeaterField => { const { fields, - table, title, titleVariant, formTitle, @@ -861,7 +860,6 @@ export const buildFieldsRepeaterField = ( type: FieldTypes.FIELDS_REPEATER, component: FieldComponents.FIELDS_REPEATER, fields, - table, title, titleVariant, formTitle, @@ -923,9 +921,12 @@ export const buildStaticTableField = ( } export const buildSliderField = ( - data: Omit, + data: Omit, ): SliderField => { const { + id, + title, + titleVariant = 'h2', condition, min = 0, max = 10, @@ -933,28 +934,29 @@ export const buildSliderField = ( snap = true, trackStyle, calculateCellStyle, - showLabel = false, - showMinMaxLabels = false, showRemainderOverlay = true, showProgressOverlay = true, showToolTip = false, label, + showLabel = false, + showMinMaxLabels = false, rangeDates, currentIndex, onChange, onChangeEnd, labelMultiplier = 1, - id, saveAsString, marginTop, marginBottom, } = data return { - title: '', + component: FieldComponents.SLIDER, id, + title, + titleVariant, + condition, children: undefined, type: FieldTypes.SLIDER, - component: FieldComponents.SLIDER, min, max, step, @@ -972,7 +974,6 @@ export const buildSliderField = ( onChange, onChangeEnd, labelMultiplier, - condition, saveAsString, marginTop, marginBottom, diff --git a/libs/application/templates/funding-government-projects/src/forms/FundingGovernmentProjectsForm.ts b/libs/application/templates/funding-government-projects/src/forms/FundingGovernmentProjectsForm.ts index 2b5ffa9d68f6..126b4f812e12 100644 --- a/libs/application/templates/funding-government-projects/src/forms/FundingGovernmentProjectsForm.ts +++ b/libs/application/templates/funding-government-projects/src/forms/FundingGovernmentProjectsForm.ts @@ -123,6 +123,7 @@ export const FundingGovernmentProjectsForm: Form = buildForm({ }), buildSliderField({ id: 'project.refundableYears', + title: '', label: { singular: shared.yearSingular, plural: shared.yearPlural, diff --git a/libs/application/templates/parental-leave/src/forms/ParentalLeaveForm.ts b/libs/application/templates/parental-leave/src/forms/ParentalLeaveForm.ts index 6272df76192c..1ea7f6cdf6ec 100644 --- a/libs/application/templates/parental-leave/src/forms/ParentalLeaveForm.ts +++ b/libs/application/templates/parental-leave/src/forms/ParentalLeaveForm.ts @@ -1223,6 +1223,7 @@ export const ParentalLeaveForm: Form = buildForm({ children: [ buildSliderField({ id: 'multipleBirthsRequestDays', + title: '', label: { singular: parentalLeaveFormMessages.shared.day, plural: parentalLeaveFormMessages.shared.days, @@ -1317,6 +1318,7 @@ export const ParentalLeaveForm: Form = buildForm({ children: [ buildSliderField({ id: 'requestRights.requestDays', + title: '', label: { singular: parentalLeaveFormMessages.shared.day, plural: parentalLeaveFormMessages.shared.days, @@ -1365,6 +1367,7 @@ export const ParentalLeaveForm: Form = buildForm({ children: [ buildSliderField({ id: 'giveRights.giveDays', + title: '', label: { singular: parentalLeaveFormMessages.shared.day, plural: parentalLeaveFormMessages.shared.days, diff --git a/libs/application/templates/reference-template/README.md b/libs/application/templates/reference-template/README.md index c4af4abb3e17..85b75745395d 100644 --- a/libs/application/templates/reference-template/README.md +++ b/libs/application/templates/reference-template/README.md @@ -66,3 +66,51 @@ To access the list of national ids for applicantActors that have come in contact ```ts const applicantActors = application.applicantActors ``` + +## Coding guidelines + +The aim is to have all applications to be coded in a similar way, so that every developer that is familiar with the application system can open any application and everything is consistent and feels familiar. + +- Reduce the amount of custom components to a minimum. +- Reduce the use of the "as" and "any" keywords as much as possible. Both of those keywords are tricking the linter to accept code that would otherwise throw errors and might cause hard to trace bugs. +- Try to use the `getValueViaPath` function to access the answers of the application. It makes accessing nested values easier and the code more readable. Note that this function is generic and a type can be provided to make sure the type of the value is correct E.g: + +`getValueViaPath(application.answers, 'some.nested.value', 'optional fallback')` + +- Don't use fake steps, stepper should only be showing steps of the current form. On the first step in the main form, there shouldn't be a back button or the illusion that you can go back to the prerequsites step. + +## Folder structure + +|-- assets/--------------------------------# optional folder for assets like images, icons, etc. +| +|-- components/----------------------------# optional folder for React components that are used by custom components. +| +|-- dataProviders/-------------------------# folder for data providers. +| +|-- fields/--------------------------------# optional folder for custom components if the application needs any. +|-- |-- index.ts---------------------------# Exports all fields from the folder. +|-- |-- myCustomComponent/-----------------# Folder for a custom component, camelCase. +|-- |-- |-- MyCustomComponent.tsx----------# React component file, PascalCase. +|-- |-- |-- MyCustomComponent.css.ts-------# CSS file, PascalCase. +| +|-- forms/---------------------------------# folder for forms. More about form folder structure in the form folder README. +|-- |-- prerequisitesForm/ +|-- |-- mainForm/ +|-- |-- conclusionForm/--------------------# More forms if needed +| +|-- graphql/-------------------------------# optional folder for graphql queries and mutations. +| +|-- lib/-----------------------------------# folder for data schema, messages, and the main template file. +|-- |-- dataScema.ts-----------------------# Validation for the application. +|-- |-- mainTemplate.ts--------------------# Main template file. State machine for the application, mapUsersToRole and more +|-- |-- messages.ts------------------------# File for all text that appears on the screen, synced with Contentful. +|-- |-- messages/--------------------------# optional folder for messages if there is a need to have the messages more organized. +| +|-- shared/--------------------------------# optional folder for code that might be needed in the template-api-modules or +|------------------------------------------# other places outside the template. +| +|-- utils/---------------------------------# folder for utility functions, constants, enums and types. +|-- |-- constants.ts-----------------------# Constants for the application. +|-- |-- enums.ts---------------------------# Enums for the application. +|-- |-- types.ts---------------------------# Types for the application. +|-- |-- helperFunctions.ts-----------------# Helper functions for the application, this can be many files. diff --git a/libs/application/templates/reference-template/src/assets/README.md b/libs/application/templates/reference-template/src/assets/README.md new file mode 100644 index 000000000000..2355070ab130 --- /dev/null +++ b/libs/application/templates/reference-template/src/assets/README.md @@ -0,0 +1,4 @@ +# Assets + +This folder is optional and can be used to store assets like images, icons, etc. +Organization within this folder id up to the developer. diff --git a/libs/application/templates/reference-template/src/assets/akureyri.svg b/libs/application/templates/reference-template/src/assets/akureyri.svg new file mode 100644 index 000000000000..1ad88a2af433 --- /dev/null +++ b/libs/application/templates/reference-template/src/assets/akureyri.svg @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libs/application/templates/reference-template/src/assets/isafjardarbaer.svg b/libs/application/templates/reference-template/src/assets/isafjardarbaer.svg new file mode 100644 index 000000000000..5ec71102e333 --- /dev/null +++ b/libs/application/templates/reference-template/src/assets/isafjardarbaer.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/libs/application/templates/reference-template/src/assets/plate-110-510.tsx b/libs/application/templates/reference-template/src/assets/plate-110-510.tsx new file mode 100644 index 000000000000..f27f9a43737a --- /dev/null +++ b/libs/application/templates/reference-template/src/assets/plate-110-510.tsx @@ -0,0 +1,40 @@ +export const plate110 = () => ( + + + + + + + +) diff --git a/libs/application/templates/reference-template/src/assets/plate-155-305.tsx b/libs/application/templates/reference-template/src/assets/plate-155-305.tsx new file mode 100644 index 000000000000..0cd3f1607825 --- /dev/null +++ b/libs/application/templates/reference-template/src/assets/plate-155-305.tsx @@ -0,0 +1,41 @@ +export const plate155 = () => ( + + + + + + + +) diff --git a/libs/application/templates/reference-template/src/assets/plate-200-280.tsx b/libs/application/templates/reference-template/src/assets/plate-200-280.tsx new file mode 100644 index 000000000000..623c96946c55 --- /dev/null +++ b/libs/application/templates/reference-template/src/assets/plate-200-280.tsx @@ -0,0 +1,40 @@ +export const plate200 = () => ( + + + + + + + +) diff --git a/libs/application/templates/reference-template/src/assets/sambandid.svg b/libs/application/templates/reference-template/src/assets/sambandid.svg new file mode 100644 index 000000000000..a88407920cb4 --- /dev/null +++ b/libs/application/templates/reference-template/src/assets/sambandid.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/libs/application/templates/reference-template/src/components/Logo/Logo.tsx b/libs/application/templates/reference-template/src/components/Logo/Logo.tsx new file mode 100644 index 000000000000..375ce16ce50f --- /dev/null +++ b/libs/application/templates/reference-template/src/components/Logo/Logo.tsx @@ -0,0 +1,31 @@ +import { useEffect, useState } from 'react' +import { Application } from '@island.is/application/types' + +type Props = { + application: Application +} + +// In the prereq when there is no applicant, the logo is the default one. +// When there is an applicant, the logo is dynamic based on the applicant's identity number. +// Gervimaður Færeyjar shows the logo of Ísafjarðarbær and all others show the logo of Akureyri. +export const Logo = ({ application }: Props) => { + const [logo, setLogo] = useState() + + useEffect(() => { + const getLogo = async () => { + const applicant = application.applicant + + const town = !applicant + ? 'sambandid' + : applicant === '0101302399' + ? 'isafjardarbaer' + : 'akureyri' + + const svgLogo = await import(`../../assets/${town}.svg`) + setLogo(svgLogo.default) + } + getLogo() + }, []) + + return Municipality logo +} diff --git a/libs/application/templates/reference-template/src/components/README.md b/libs/application/templates/reference-template/src/components/README.md new file mode 100644 index 000000000000..6b3d80449453 --- /dev/null +++ b/libs/application/templates/reference-template/src/components/README.md @@ -0,0 +1,3 @@ +# The components folder + +This folder contains all React components that are not custom components on their own but are used by custom components or a component for the form logo if it is dynamic. diff --git a/libs/application/templates/reference-template/src/dataProviders/README.md b/libs/application/templates/reference-template/src/dataProviders/README.md new file mode 100644 index 000000000000..3f47dfa8bfa2 --- /dev/null +++ b/libs/application/templates/reference-template/src/dataProviders/README.md @@ -0,0 +1,3 @@ +# Data Providers + +This folder is for all the data providers that are run in the prerequisites form. diff --git a/libs/application/templates/reference-template/src/dataProviders/index.ts b/libs/application/templates/reference-template/src/dataProviders/index.ts index 29a2b6528729..e60ea65b2a39 100644 --- a/libs/application/templates/reference-template/src/dataProviders/index.ts +++ b/libs/application/templates/reference-template/src/dataProviders/index.ts @@ -1,9 +1,6 @@ import { defineTemplateApi } from '@island.is/application/types' import { MockProviderApi } from '@island.is/application/types' -import { - NationalRegistryUserApi, - UserProfileApi, -} from '@island.is/application/types' +import { NationalRegistryUserApi } from '@island.is/application/types' export interface MyParameterType { id: number } diff --git a/libs/application/templates/reference-template/src/fields/Overview/Overview.tsx b/libs/application/templates/reference-template/src/fields/Overview/Overview.tsx new file mode 100644 index 000000000000..dc6e84162996 --- /dev/null +++ b/libs/application/templates/reference-template/src/fields/Overview/Overview.tsx @@ -0,0 +1,67 @@ +import { FieldBaseProps, StaticText } from '@island.is/application/types' +import { Box, GridColumn, GridRow, Text } from '@island.is/island-ui/core' +import { formatText, getValueViaPath } from '@island.is/application/core' +import { useLocale } from '@island.is/localization' +import { ReviewGroup } from '@island.is/application/ui-components' +import { States } from '../../utils/constants' +import { m } from '../../lib/messages' + +type TableRepeaterAnswers = { + fullName: string + nationalId: string + relation: string +} + +const KeyValue = ({ label, value }: { label: StaticText; value: string }) => { + const { formatMessage } = useLocale() + return ( + + + {formatMessage(label)} + + {value} + + ) +} + +export const Overview = ({ application, goToScreen }: FieldBaseProps) => { + const { formatMessage } = useLocale() + + const changeScreens = (screen: string) => { + if (goToScreen) goToScreen(screen) + } + + const tableRepeaterAnswers = getValueViaPath>( + application.answers, + 'tableRepeater', + ) + + return ( + + + changeScreens('tableRepeater')} + isEditable={application.state === States.DRAFT} + > + + + + Values from the table repeater + + {tableRepeaterAnswers && + tableRepeaterAnswers.map((ans, i) => { + return ( + + ) + })} + + + + {/* More review groups as needed... */} + + ) +} diff --git a/libs/application/templates/reference-template/src/fields/README.md b/libs/application/templates/reference-template/src/fields/README.md new file mode 100644 index 000000000000..2bb8efcb7efd --- /dev/null +++ b/libs/application/templates/reference-template/src/fields/README.md @@ -0,0 +1,21 @@ +# The fields folder + +This folder contains all custom components that are used by the application. + +## Organisation + +- All components should be in a folder that holds all files for that component. This includes the .tsx file, possibly a .css.ts file and maybe others. + The folders should be named like the component, but with the first letter in lowercase (camelCase), and then the .tsx and .css.ts files should be capitalized (PascalCase). +- The folder should have an index.ts file that re-exports all the components. +- The index.ts file in the /src folder should then re-export the components in the /fields folder for the template loader. + +## Useage of custom components + +Before creating a custom component, you should: + +1. Try to use the shared components, `buildTextField`, `buildCheckboxField`, `buildSelectField`, `buildFileUploadField` and so on. This is most preferable to make the look and feel of the application more consistent and uniform. +2. If the shared components almost fullfill your needs but you need something more, consider consulting with the designer of the application and try to adjust the design to the built in components. +3. If the design can not be adjusted to the built in components, then consult Norda if a shared component can possibly be adjusted or expanded to fulfill your needs. +4. Is there another application that has made a similar custom component before? If so, then it should be a shared component. +5. If you still need a new component, ask yourself if this is a component that another application might also need in the future. If so make the new component shared. +6. Make a custom component if none of the above apply. diff --git a/libs/application/templates/reference-template/src/fields/exampleCustomComponent/ExampleCustomComponent.css.ts b/libs/application/templates/reference-template/src/fields/exampleCustomComponent/ExampleCustomComponent.css.ts new file mode 100644 index 000000000000..de8bdda26696 --- /dev/null +++ b/libs/application/templates/reference-template/src/fields/exampleCustomComponent/ExampleCustomComponent.css.ts @@ -0,0 +1,10 @@ +import { theme } from '@island.is/island-ui/theme' +import { style } from '@vanilla-extract/css' + +export const boldNames = style({ + fontWeight: 'bold', +}) + +export const bottomBorderRadius = style({ + borderRadius: `0 0 ${theme.border.radius.large} ${theme.border.radius.large}`, +}) diff --git a/libs/application/templates/reference-template/src/fields/exampleCustomComponent/ExampleCustomComponent.tsx b/libs/application/templates/reference-template/src/fields/exampleCustomComponent/ExampleCustomComponent.tsx new file mode 100644 index 000000000000..cdb92ea3e1fc --- /dev/null +++ b/libs/application/templates/reference-template/src/fields/exampleCustomComponent/ExampleCustomComponent.tsx @@ -0,0 +1,37 @@ +import { FieldBaseProps } from '@island.is/application/types' +import { useLocale } from '@island.is/localization' +import * as styles from './ExampleCustomComponent.css' +import { Box, Text } from '@island.is/island-ui/core' +import { m } from '../../lib/messages' + +interface Props { + field: { + props: { + someData: Array + } + } +} + +export const ExampleCustomComponent = ({ field }: Props & FieldBaseProps) => { + const { formatMessage } = useLocale() + const { someData } = field.props + if (!someData) return null + + return ( + + + {formatMessage(m.customComponentAbout)} + + + {someData.map((item) => ( +

{item}

+ ))} +
+
+ ) +} diff --git a/libs/application/templates/reference-template/src/fields/index.ts b/libs/application/templates/reference-template/src/fields/index.ts index c953dd689bb2..03cac91d34b8 100644 --- a/libs/application/templates/reference-template/src/fields/index.ts +++ b/libs/application/templates/reference-template/src/fields/index.ts @@ -1,2 +1,4 @@ export { default as ExampleCountryField } from './ExampleCountryField' export { default as CustomRepeater } from './CustomRepeater' +export { ExampleCustomComponent } from './exampleCustomComponent/ExampleCustomComponent' +export { Overview } from './Overview/Overview' diff --git a/libs/application/templates/reference-template/src/forms/ExampleForm.ts b/libs/application/templates/reference-template/src/forms/ExampleForm.ts deleted file mode 100644 index c402a020c9fd..000000000000 --- a/libs/application/templates/reference-template/src/forms/ExampleForm.ts +++ /dev/null @@ -1,310 +0,0 @@ -import { - buildCheckboxField, - buildForm, - buildDescriptionField, - buildMultiField, - buildRadioField, - buildSection, - buildSubmitField, - buildSubSection, - buildTextField, - buildFileUploadField, - buildRedirectToServicePortalField, - buildSelectField, - buildPhoneField, - buildHiddenInput, - buildHiddenInputWithWatchedValue, - buildTableRepeaterField, -} from '@island.is/application/core' -import { - Comparators, - Form, - FormModes, - FormValue, -} from '@island.is/application/types' -import { ApiActions } from '../shared' -import { m } from '../lib/messages' - -export const ExampleForm: Form = buildForm({ - id: 'ExampleFormDraft', - title: 'Atvinnuleysisbætur', - mode: FormModes.DRAFT, - children: [ - buildSection({ - id: 'conditions', - title: m.conditionsSection, - children: [], - }), - buildSection({ - id: 'tableRepeaterWithPhone', - title: 'Table repeater', - children: [ - buildTableRepeaterField({ - id: 'rentalHousingLandlordInfoTable', - title: '', - marginTop: 1, - fields: { - name: { - component: 'input', - label: 'test 1', - width: 'half', - }, - nationalId: { - component: 'input', - label: 'test 2', - format: '######-####', - width: 'half', - }, - phone: { - component: 'phone', - label: 'test 3', - format: '###-####', - width: 'half', - }, - email: { - component: 'input', - label: 'test 4', - type: 'email', - width: 'half', - }, - isRepresentative: { - component: 'checkbox', - large: true, - displayInTable: false, - label: 'test 5', - options: [ - { - label: 'test 6', - value: 'YES', - }, - ], - }, - }, - table: { - header: [ - 'nameInputLabel', - 'nationalIdHeaderLabel', - 'phoneInputLabel', - 'emailInputLabel', - ], - }, - }), - ], - }), - buildSection({ - id: 'intro', - title: m.introSection, - children: [ - buildDescriptionField({ - id: 'field', - title: m.introField, - description: (application) => ({ - ...m.introIntroduction, - values: { name: application.answers.name }, - }), - }), - buildMultiField({ - id: 'about', - title: m.about.defaultMessage, - children: [ - buildTextField({ - id: 'person.name', - title: m.personName, - }), - buildHiddenInput({ - id: 'person.someHiddenInputRequired', - defaultValue: () => { - return 'validAnswer' - }, - }), - buildHiddenInputWithWatchedValue({ - id: 'person.someHiddenInputWatchedRequired', - watchValue: 'person.name', - valueModifier: (watchedValue: any) => { - return watchedValue + 'Valid' - }, - }), - buildTextField({ - id: 'person.nationalId', - title: m.nationalId, - width: 'half', - }), - buildTextField({ - id: 'person.age', - title: m.age, - width: 'half', - }), - buildTextField({ - id: 'person.email', - title: m.email, - width: 'half', - }), - buildPhoneField({ - id: 'person.phoneNumber', - title: m.phoneNumber, - width: 'half', - condition: { - questionId: 'person.age', - isMultiCheck: false, - comparator: Comparators.GTE, - value: '18', - }, - }), - ], - }), - buildFileUploadField({ - id: 'attachments', - title: (application, locale) => { - if (locale === 'is') { - return 'Viðhengi' - } - return 'Attachments' - }, - introduction: 'Hér getur þú bætt við viðhengjum við umsóknina þína.', - uploadMultiple: true, - }), - ], - }), - buildSection({ - id: 'career', - title: m.career, - children: [ - buildSubSection({ - id: 'history', - title: m.history, - children: [ - buildSelectField({ - id: 'careerIndustry', - title: m.careerIndustry, - description: m.careerIndustryDescription, - required: true, - options: (options, application, locale) => { - if (locale === 'is') { - return [ - { label: locale, value: locale }, - { label: 'Hugbúnaður', value: 'software' }, - { label: 'Fjármál', value: 'finance' }, - { label: 'Efnahagsráðgjöf', value: 'consulting' }, - { label: 'Önnur', value: 'other' }, - ] - } - return [ - { label: locale, value: locale }, - { label: 'Software', value: 'software' }, - { label: 'Finance', value: 'finance' }, - { label: 'Consulting', value: 'consulting' }, - { label: 'Other', value: 'other' }, - ] - }, - }), - buildRadioField({ - id: 'careerHistory', - title: m.careerHistory, - options: [ - { value: 'yes', label: m.yesOptionLabel }, - { value: 'no', label: m.noOptionLabel }, - ], - condition: (formValue: FormValue) => { - return ( - (formValue as { person: { age: string } })?.person?.age >= - '18' - ) - }, - }), - buildMultiField({ - id: 'careerHistoryDetails', - title: '', - children: [ - buildCheckboxField({ - id: 'careerHistoryDetails.careerHistoryCompanies', - title: m.careerHistoryCompanies, - options: [ - { value: 'government', label: m.governmentOptionLabel }, - { value: 'aranja', label: 'Aranja' }, - { value: 'advania', label: 'Advania' }, - { value: 'other', label: 'Annað' }, - ], - }), - buildTextField({ - id: 'careerHistoryDetails.careerHistoryOther', - title: m.careerHistoryOther, - }), - ], - }), - ], - }), - buildSubSection({ - id: 'future', - title: m.future, - children: [ - buildTextField({ - id: 'dreamJob', - title: m.dreamJob, - }), - ], - }), - buildSubSection({ - id: 'assignee', - title: m.assigneeTitle, - children: [ - buildTextField({ - id: 'assigneeEmail', - title: m.assignee, - }), - ], - }), - ], - }), - buildSection({ - id: 'confirmation', - title: 'Staðfesta', - children: [ - buildMultiField({ - title: '', - children: [ - buildSubmitField({ - id: 'submit', - placement: 'footer', - title: 'Senda inn umsókn', - actions: [ - { event: 'SUBMIT', name: 'Senda inn umsókn', type: 'primary' }, - ], - }), - buildDescriptionField({ - id: 'overview', - title: 'Takk fyrir að sækja um', - description: - 'Með því að smella á "Senda" hér að neðan, þá sendist umsóknin inn til úrvinnslu. Við látum þig vita þegar hún er samþykkt eða henni er hafnað.', - }), - ], - }), - buildRedirectToServicePortalField({ - id: 'redirect', - title: '', - }), - buildDescriptionField({ - id: 'final', - title: 'Takk', - description: (application) => { - const sendApplicationActionResult = - application.externalData[ApiActions.createApplication] - - let id = 'unknown' - if (sendApplicationActionResult) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - id = sendApplicationActionResult.data.id - } - - return { - ...m.outroMessage, - values: { - id, - }, - } - }, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/reference-template/src/forms/Prerequisites.ts b/libs/application/templates/reference-template/src/forms/Prerequisites.ts deleted file mode 100644 index 27de62ca28ef..000000000000 --- a/libs/application/templates/reference-template/src/forms/Prerequisites.ts +++ /dev/null @@ -1,118 +0,0 @@ -import get from 'lodash/get' -import { - buildForm, - buildDescriptionField, - buildMultiField, - buildSection, - buildSubmitField, - buildExternalDataProvider, - buildDataProviderItem, -} from '@island.is/application/core' -import { Application, Form, FormModes } from '@island.is/application/types' -import { m } from '../lib/messages' - -import { UserProfileApi } from '@island.is/application/types' -import { - ReferenceDataApi, - MyMockProvider, - NationalRegistryApi, -} from '../dataProviders' - -export const Prerequisites: Form = buildForm({ - id: 'PrerequisitesDraft', - title: 'Skilyrði', - mode: FormModes.DRAFT, - children: [ - buildSection({ - id: 'conditions', - title: m.conditionsSection, - children: [ - buildExternalDataProvider({ - id: 'approveExternalData', - title: 'Utanaðkomandi gögn', - dataProviders: [ - buildDataProviderItem({ - provider: UserProfileApi, - title: 'User profile', - subTitle: 'User profile', - }), - buildDataProviderItem({ - provider: ReferenceDataApi, - title: 'getReferenceData', - subTitle: 'Reference data', - }), - buildDataProviderItem({ - provider: NationalRegistryApi, - title: 'Þjóðskrá', - subTitle: 'Upplýsingar um þig í Þjóðskrá.', - }), - buildDataProviderItem({ - provider: MyMockProvider, - title: 'Mock Data', - subTitle: 'Returns data for mocking', - }), - ], - }), - buildMultiField({ - id: 'externalDataSuccess', - title: 'Tókst að sækja gögn', - children: [ - buildDescriptionField({ - id: 'externalDataSuccessDescription', - title: '', - description: (application: Application) => - `Gildið frá data provider: ${get( - application.externalData, - 'getReferenceData.data.referenceData.numbers', - 'fannst ekki', - )}`, - }), - buildDescriptionField({ - id: 'externalDataSuccessDescription.mock', - title: '', - description: (application: Application) => - `Gildið frá mock data provider: ${get( - application.externalData, - 'referenceMock.data.mockObject.mockString', - 'fannst ekki', - )}`, - }), - buildSubmitField({ - id: 'toDraft', - placement: 'footer', - title: 'Hefja umsókn', - refetchApplicationAfterSubmit: true, - actions: [ - { - event: 'SUBMIT', - name: 'Hefja umsókn', - type: 'primary', - }, - ], - }), - ], - }), - buildDescriptionField({ - id: 'neverDisplayed', - title: '', - description: '', - }), - ], - }), - buildSection({ - id: 'intro', - title: m.introSection, - children: [], - }), - buildSection({ - id: 'career', - title: m.career, - children: [], - }), - buildSection({ - id: 'confirmation', - title: 'Staðfesta', - children: [], - }), - ], -}) diff --git a/libs/application/templates/reference-template/src/forms/README.md b/libs/application/templates/reference-template/src/forms/README.md new file mode 100644 index 000000000000..27ec67b4d251 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/README.md @@ -0,0 +1,48 @@ +# The forms folder + +This folder contains all the forms that are used by the application. + +At minimum there should be a prerequisites form, application form and a confirmation form to reflect the possible states of an application. +For more complicated applications you could have different forms depending on the user type and you could have more states like approved, rejected, waiting to assign... + +## Organization + +All forms should be in a folder with the same name as the form. The folder and files in it should follow camelCase, PascalCase should be reserved for React components. +A simple form with one screen, like the prerequisites form can be just a single file. +Form with more than one section and possibly subsections should be broken down into one file per screen. + +Example folder structure: + +| /prerequisitesForm +| |-- prerequisitesForm.tsx + +| /applicationForm +| |-- index.ts (This file has a buildForm function) +| |-- /section1 +| | |-- index.ts (This file has a buildSection function and imports the subsection childs) +| | |-- subsection1.ts (This file has a buildSubSection function) +| | |-- subsection2.ts (This file has a buildSubSection function) +| | |-- subsection3.ts (This file has a buildSubSection function) +| |-- /section2 +| | |-- index.ts (This file has a buildSection function) +| |-- /section3 +| | |-- index.ts (This file has a buildSection function) + +| /confirmationForm +| |-- confirmationForm.ts + +## BuildForm, buildSection, buildSubSection and buildMultiField + +Those are the four building blocks of each form. + +The root of each form is a buildForm function and that is the only thing that can be imported into the state machine in the main template file. + +The buildForm function has an array of children. Those children should be buildSection functions. + +BuildSection will be displayed in the stepper as top level section. The children array of a buildSection can be buildSubSection, buildMultiField or a regular buildField function. + +BuildSubSection is a subsection of a buildSection and will be displayed in the stepper as a subsection of the buildSection. The children array of a buildSubSection can be buildMultiField or buildField functions. + +BuildMultiField is a wrapper around many buildField functions. if a buildSection or buildSubSection doesn't have a buildMultiField, it will only display one field at a time and stepping through the screens will be strange in regards to the stepper. + +In most cases you want to use one buildMultiField as the child of a buildSection or buildSubSection since in most cases you want to display multiple fields at a time on the screen. diff --git a/libs/application/templates/reference-template/src/forms/Approved.ts b/libs/application/templates/reference-template/src/forms/approvedForm/approvedForm.ts similarity index 100% rename from libs/application/templates/reference-template/src/forms/Approved.ts rename to libs/application/templates/reference-template/src/forms/approvedForm/approvedForm.ts diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/commonActionsSection/conditions2Subsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/commonActionsSection/conditions2Subsection.ts new file mode 100644 index 000000000000..1ecda91a53d1 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/commonActionsSection/conditions2Subsection.ts @@ -0,0 +1,27 @@ +import { + buildDescriptionField, + buildSubSection, + getValueViaPath, + YES, +} from '@island.is/application/core' + +export const conditions2Subsection = buildSubSection({ + condition: (answers) => { + const checkbox2Value = getValueViaPath>( + answers, + 'conditions2Checkbox', + ) + + return checkbox2Value ? checkbox2Value[0] === YES : false + }, + id: 'condition2Subsection', + title: 'This section is conditional', + children: [ + buildDescriptionField({ + id: 'condition2Description', + title: 'This screens visibility is based in previous answers', + description: + 'With this functionality, the application can branch and collect different data for different users', + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/commonActionsSection/conditionsSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/commonActionsSection/conditionsSubsection.ts new file mode 100644 index 000000000000..7456d5b66dfd --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/commonActionsSection/conditionsSubsection.ts @@ -0,0 +1,92 @@ +import { + buildCheckboxField, + buildDescriptionField, + buildHiddenInput, + buildMultiField, + buildSubSection, + buildTextField, + getValueViaPath, + YES, +} from '@island.is/application/core' +import { m } from '../../../lib/messages' + +export const conditionsSubsection = buildSubSection({ + id: 'conditionsSubsection', + title: 'Conditions', + children: [ + buildMultiField({ + id: 'conditionsMultiField', + title: 'Conditions', + children: [ + buildDescriptionField({ + id: 'conditionsDescription', + title: '', + description: + 'It is possible to condition both single fields and text or an entire section/subsection', + marginBottom: 2, + }), + buildDescriptionField({ + id: 'conditionsDescription2', + title: '', + description: m.conditionsDescription2, + marginBottom: 2, + }), + buildDescriptionField({ + id: 'conditionsDescription3', + title: '', + description: + 'The visibility of everything can be dependent on the users answers in the application or data that has been fetched and placed in externalData.', + marginBottom: 2, + }), + buildCheckboxField({ + id: 'conditionsCheckbox', + title: 'Skilyrði fyrir staka reiti', + options: [ + { + label: 'Check this box to see an extra field appear', + value: YES, + }, + ], + }), + buildTextField({ + condition: (answers) => { + const checkboxValue = getValueViaPath>( + answers, + 'conditionsCheckbox', + ) + + return checkboxValue ? checkboxValue[0] === YES : false + }, + id: 'conditionsTextField', + variant: 'textarea', + rows: 8, + title: 'This field is only available if the box above is checked', + }), + buildCheckboxField({ + id: 'conditions2Checkbox', + title: 'Skilyrði fyrir section/subsection', + options: [ + { + label: + 'Check this box to see a new subsection appear in the stepper ------>', + value: YES, + }, + ], + }), + buildHiddenInput({ + // This is a bit of a hack, but in order for the stepper to update and show the conditionally added step, there + // has to be a field on the current step with a matching condition. + condition: (answers) => { + const checkboxValue = getValueViaPath>( + answers, + 'conditions2Checkbox', + ) + + return checkboxValue ? checkboxValue[0] === YES : false + }, + id: 'conditionsTextField', + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/commonActionsSection/getDataFromExternalDataSection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/commonActionsSection/getDataFromExternalDataSection.ts new file mode 100644 index 000000000000..b922ca75751a --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/commonActionsSection/getDataFromExternalDataSection.ts @@ -0,0 +1,63 @@ +import { + buildDescriptionField, + buildMultiField, + buildSubSection, + getValueViaPath, +} from '@island.is/application/core' +import { Application } from '@island.is/application/types' + +export const getDataFromExternalDataSubsection = buildSubSection({ + id: 'getDataFromExternalData', + title: 'External data', + children: [ + buildMultiField({ + id: 'externalDataSuccess', + title: '', + children: [ + buildDescriptionField({ + id: 'externalDataSuccessTitle', + title: 'Example of data being fetched from external data', + marginBottom: [4], + }), + buildDescriptionField({ + id: 'externalDataSuccessDescription', + title: 'Value from data provider', + titleVariant: 'h4', + description: (application: Application) => { + const value = getValueViaPath( + application.externalData, + 'getReferenceData.data.referenceData.applicantName', + ) + + return value ?? 'Not found' + }, + }), + buildDescriptionField({ + id: 'externalDataSuccessDescription2', + title: '', + description: (application: Application) => { + const value = getValueViaPath( + application.externalData, + 'getReferenceData.data.referenceData.numbers', + ) + + return value ? `${value}` : 'Not found' + }, + marginBottom: [2], + }), + buildDescriptionField({ + id: 'externalDataSuccessDescriptionMock', + title: 'Value from mock data provider', + titleVariant: 'h4', + description: (application: Application) => { + const value = getValueViaPath( + application.externalData, + 'referenceMock.data.mockObject.mockString', + ) + return value ?? 'Not found' + }, + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/commonActionsSection/index.ts b/libs/application/templates/reference-template/src/forms/exampleForm/commonActionsSection/index.ts new file mode 100644 index 000000000000..f58a19aa98d8 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/commonActionsSection/index.ts @@ -0,0 +1,16 @@ +import { buildSection } from '@island.is/application/core' +import { conditionsSubsection } from './conditionsSubsection' +import { conditions2Subsection } from './conditions2Subsection' +import { getDataFromExternalDataSubsection } from './getDataFromExternalDataSection' +import { validationSubsection } from './validadionSubsection' + +export const commonActionsSection = buildSection({ + id: 'commonActions', + title: 'Common actions', + children: [ + getDataFromExternalDataSubsection, + validationSubsection, + conditionsSubsection, + conditions2Subsection, + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/commonActionsSection/validadionSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/commonActionsSection/validadionSubsection.ts new file mode 100644 index 000000000000..c40b13340bef --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/commonActionsSection/validadionSubsection.ts @@ -0,0 +1,54 @@ +import { + buildDescriptionField, + buildMultiField, + buildRadioField, + buildSubSection, + buildTextField, +} from '@island.is/application/core' +import { m } from '../../../lib/messages' +import { radioValidationExampleEnum } from '../../../utils/types' + +export const validationSubsection = buildSubSection({ + id: 'validationSubsection', + title: 'Validation', + children: [ + buildMultiField({ + id: 'validationMultiField', + title: 'Validation', + children: [ + buildDescriptionField({ + id: 'validationDescriptionField', + title: '', + description: m.validationDescription, + marginBottom: 2, + }), + buildDescriptionField({ + id: 'validationDescriptionField2', + title: '', + description: + 'All fields on this page have validation that must be filled out to continue', + }), + buildTextField({ + id: 'validation.validationTextField', + title: 'Must be 3 characters or more', + required: true, // Adds the red star to the field + }), + buildDescriptionField({ + id: 'validation.validationDescriptionField3', + title: '', + description: m.validationDescription3, + marginTop: 4, + }), + buildRadioField({ + id: 'validation.validationRadioField', + title: '', + options: [ + { label: 'Option 1', value: radioValidationExampleEnum.OPTION_1 }, + { label: 'Option 2', value: radioValidationExampleEnum.OPTION_2 }, + { label: 'Option 3', value: radioValidationExampleEnum.OPTION_3 }, + ], + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/compositeFieldsSection/applicantInfoSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/compositeFieldsSection/applicantInfoSubsection.ts new file mode 100644 index 000000000000..7f157f065962 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/compositeFieldsSection/applicantInfoSubsection.ts @@ -0,0 +1,9 @@ +import { buildSubSection } from '@island.is/application/core' +import { applicantInformationMultiField } from '@island.is/application/ui-forms' + +export const applicantInfoSubsection = buildSubSection({ + id: 'aplicantInfoSubsection', + title: 'Aplicant Info Subsection', + // Common form, fills automatically with applicant information + children: [applicantInformationMultiField()], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/compositeFieldsSection/bankAccountSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/compositeFieldsSection/bankAccountSubsection.ts new file mode 100644 index 000000000000..8351750821f1 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/compositeFieldsSection/bankAccountSubsection.ts @@ -0,0 +1,15 @@ +import { + buildBankAccountField, + buildSubSection, +} from '@island.is/application/core' + +export const bankAccountSubsection = buildSubSection({ + id: 'bankAccountSubSection', + title: 'Bank account', + children: [ + buildBankAccountField({ + id: 'bankAccountfield', + title: 'Bank account field', + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/compositeFieldsSection/index.ts b/libs/application/templates/reference-template/src/forms/exampleForm/compositeFieldsSection/index.ts new file mode 100644 index 000000000000..a4ffc6c2e115 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/compositeFieldsSection/index.ts @@ -0,0 +1,14 @@ +import { buildSection } from '@island.is/application/core' +import { nationalIdWithNameSubsection } from './nationalIdWithNameSubsection' +import { applicantInfoSubsection } from './applicantInfoSubsection' +import { bankAccountSubsection } from './bankAccountSubsection' + +export const compositeFieldsSection = buildSection({ + id: 'compositeFieldsSection', + title: 'Composite fields', + children: [ + nationalIdWithNameSubsection, + applicantInfoSubsection, + bankAccountSubsection, + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/compositeFieldsSection/nationalIdWithNameSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/compositeFieldsSection/nationalIdWithNameSubsection.ts new file mode 100644 index 000000000000..3e86de1f4078 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/compositeFieldsSection/nationalIdWithNameSubsection.ts @@ -0,0 +1,15 @@ +import { + buildNationalIdWithNameField, + buildSubSection, +} from '@island.is/application/core' + +export const nationalIdWithNameSubsection = buildSubSection({ + id: 'nationalIdWithNameSubsection', + title: 'National ID with name subsection', + children: [ + buildNationalIdWithNameField({ + id: 'pickRole.electPerson', + title: 'Lookup name by national ID', + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/customSection/customSection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/customSection/customSection.ts new file mode 100644 index 000000000000..bbf89d7f9240 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/customSection/customSection.ts @@ -0,0 +1,42 @@ +import { + buildCustomField, + buildDescriptionField, + buildMultiField, + buildSection, +} from '@island.is/application/core' +import { m } from '../../../lib/messages' + +export const customSection = buildSection({ + id: 'customSection', + title: 'Custom section', + children: [ + buildMultiField({ + id: 'customMultiField', + title: '', + children: [ + buildDescriptionField({ + id: 'customDescription', + title: 'Custom Components', + description: m.customComponentDescription, + marginBottom: [2], + }), + buildDescriptionField({ + id: 'customDescription2', + title: '', + description: m.customComponentNumberedList, + marginBottom: [2], + }), + buildCustomField( + { + id: 'customComponent', + title: 'The custom component', + component: 'ExampleCustomComponent', + }, + { + someData: ['foo', 'bar', 'baz'], + }, + ), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/index.ts b/libs/application/templates/reference-template/src/forms/exampleForm/index.ts new file mode 100644 index 000000000000..3ae9601aad80 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/index.ts @@ -0,0 +1,94 @@ +import { createElement } from 'react' +import { + buildForm, + buildDescriptionField, + buildMultiField, + buildSection, + buildSubmitField, + buildRedirectToServicePortalField, +} from '@island.is/application/core' +import { Application, Form, FormModes } from '@island.is/application/types' +import { ApiActions } from '../../shared' +import { introSection } from './introSection/introSection' +import { simpleInputsSection } from './simpleInputsSection' +import { compositeFieldsSection } from './compositeFieldsSection' +import { commonActionsSection } from './commonActionsSection' +import { customSection } from './customSection/customSection' +import { overviewSection } from './overviewSection/overviewSection' +import { noInputFieldsSection } from './noInputFieldsSection' +import { Logo } from '../../components/Logo/Logo' +import { tablesAndRepeatersSection } from './tablesAndRepeatersSection' +import { m } from '../../lib/messages' + +export const ExampleForm: Form = buildForm({ + id: 'ExampleFormDraft', + title: 'Main form', + mode: FormModes.DRAFT, + // The logo prop can either take in a React component or a function that returns a React component. + // Dynamic logo can be based on answers or external data + logo: (application: Application) => { + const logo = createElement(Logo, { application }) + return () => logo + }, + children: [ + introSection, + commonActionsSection, + noInputFieldsSection, + simpleInputsSection, + compositeFieldsSection, + tablesAndRepeatersSection, + customSection, + overviewSection, + buildSection({ + id: 'confirmation', + title: 'Staðfesta', + children: [ + buildMultiField({ + title: '', + children: [ + buildSubmitField({ + id: 'submit', + placement: 'footer', + title: 'Senda inn umsókn', + actions: [ + { event: 'SUBMIT', name: 'Senda inn umsókn', type: 'primary' }, + ], + }), + buildDescriptionField({ + id: 'overview', + title: 'Thank you for applying', + description: + 'By clicking "Submit" below, the application will be sent for processing. We will let you know when it is accepted or rejected.', + }), + ], + }), + buildRedirectToServicePortalField({ + id: 'redirect', + title: '', + }), + buildDescriptionField({ + id: 'final', + title: 'Takk', + description: (application) => { + const sendApplicationActionResult = + application.externalData[ApiActions.createApplication] + + let id = 'unknown' + if (sendApplicationActionResult) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + id = sendApplicationActionResult.data.id + } + + return { + ...m.outroMessage, + values: { + id, + }, + } + }, + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/introSection/introSection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/introSection/introSection.ts new file mode 100644 index 000000000000..a840bc32ffde --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/introSection/introSection.ts @@ -0,0 +1,30 @@ +import { + buildDescriptionField, + buildMultiField, + buildSection, +} from '@island.is/application/core' +import { m } from '../../../lib/messages' + +export const introSection = buildSection({ + id: 'introSection', + title: 'Inngangur', + children: [ + buildMultiField({ + id: 'intro', + title: m.introTitle, + children: [ + buildDescriptionField({ + id: 'introDescription', + title: '', + description: m.introDescription, + marginBottom: 2, + }), + buildDescriptionField({ + id: 'introDescription2', + title: '', + description: m.introDescription2, + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/accordionSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/accordionSubsection.ts new file mode 100644 index 000000000000..d21002cfaa9b --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/accordionSubsection.ts @@ -0,0 +1,27 @@ +import { + buildAccordionField, + buildSubSection, +} from '@island.is/application/core' + +export const accordionSubsection = buildSubSection({ + id: 'accordionSubsection', + title: 'Accordion', + children: [ + buildAccordionField({ + id: 'accordion', + title: 'Accordion', + accordionItems: [ + { + itemTitle: 'Item 1', + itemContent: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.', + }, + { + itemTitle: 'Item 2', + itemContent: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.', + }, + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/actionCardSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/actionCardSubsection.ts new file mode 100644 index 000000000000..9c8716473b04 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/actionCardSubsection.ts @@ -0,0 +1,64 @@ +import { + buildActionCardListField, + buildSubSection, +} from '@island.is/application/core' + +export const actionCardSubsection = buildSubSection({ + id: 'actionCardSubsection', + title: 'Action card', + children: [ + buildActionCardListField({ + id: 'actionCardList', + title: 'Action cards with buttons', + items: (application, lang) => { + return [ + { + eyebrow: 'Card eyebrow', + heading: 'Card heading, all the options', + headingVariant: 'h3', + text: 'Card text, lorem ipsum dolor sit amet dolor sit amet lorem ipsum dolor sit amet', + cta: { + label: 'Card cta', + variant: 'primary', + external: true, + to: 'https://www.google.com', + }, + tag: { + label: 'Outlined label', + outlined: true, + variant: 'blue', + }, + }, + { + heading: 'Card heading', + headingVariant: 'h4', + tag: { + label: 'Not outlined label', + outlined: false, + variant: 'blue', + }, + cta: { + label: 'Card cta', + variant: 'primary', + external: true, + to: 'https://www.google.com', + }, + unavailable: { + label: 'Unavailable label', + message: 'Unavailable message', + }, + }, + { + heading: 'Card heading', + headingVariant: 'h4', + tag: { + label: 'White label', + outlined: true, + variant: 'white', + }, + }, + ] + }, + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/descriptionSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/descriptionSubsection.ts new file mode 100644 index 000000000000..43dab73b1328 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/descriptionSubsection.ts @@ -0,0 +1,84 @@ +import { + buildDescriptionField, + buildMultiField, + buildSubSection, +} from '@island.is/application/core' +import { m } from '../../../lib/messages' + +export const descriptionSubsection = buildSubSection({ + id: 'descriptionSubsection', + title: 'Description', + children: [ + buildMultiField({ + id: 'descriptionMultiField', + title: 'Textar með buildDescriptionField', + children: [ + buildDescriptionField({ + id: 'description1', + title: '', + description: m.descriptionFieldDescription, + marginBottom: [2], + }), + buildDescriptionField({ + id: 'description2', + title: '', + description: m.descriptionFieldDescription2, + marginBottom: [2], + }), + + buildDescriptionField({ + id: 'description3', + title: 'Default title size ', + description: 'Description inserted as a regular string', + marginBottom: [2], + tooltip: 'Tooltip text', + titleTooltip: 'Title tooltip text', + }), + buildDescriptionField({ + id: 'description4', + title: 'h1 title size', + titleVariant: 'h1', + description: m.regularTextExample, + marginBottom: [2], + }), + buildDescriptionField({ + id: 'description5', + title: 'h2 title size (same as default)', + titleVariant: 'h2', + description: m.markdownHeadingExample, + marginBottom: [2], + }), + buildDescriptionField({ + id: 'description6', + title: 'h3 title size', + titleVariant: 'h3', + description: m.markdownBulletListExample, + marginBottom: [2], + }), + buildDescriptionField({ + id: 'description7', + title: 'h4 title size', + titleVariant: 'h4', + description: m.markdownNumberedListExample, + marginBottom: [2], + }), + buildDescriptionField({ + id: 'description8', + title: 'h5 title size', + titleVariant: 'h5', + description: { + ...m.markdownMiscExample, + values: { value1: 'MY VALUE' }, + }, + marginBottom: [2], + }), + buildDescriptionField({ + id: 'description9', + title: '', + description: m.markdownCodeExample, + marginBottom: [2], + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/dividerSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/dividerSubsection.ts new file mode 100644 index 000000000000..b591c9c3509a --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/dividerSubsection.ts @@ -0,0 +1,25 @@ +import { + buildDividerField, + buildMultiField, + buildSubSection, +} from '@island.is/application/core' + +export const dividerSubsection = buildSubSection({ + id: 'dividerSubsection', + title: 'Divider', + children: [ + buildMultiField({ + id: 'dividerMultiField', + title: 'Divider', + children: [ + buildDividerField({}), + buildDividerField({ + title: 'Divider with title', + }), + buildDividerField({ + color: 'red600', + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/index.ts b/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/index.ts new file mode 100644 index 000000000000..08fc70f9ad47 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/index.ts @@ -0,0 +1,17 @@ +import { buildSection } from '@island.is/application/core' +import { descriptionSubsection } from './descriptionSubsection' +import { dividerSubsection } from './dividerSubsection' +import { accordionSubsection } from './accordionSubsection' +import { actionCardSubsection } from './actionCardSubsection' +import { keyValueSubsection } from './keyValueSubsection' +export const noInputFieldsSection = buildSection({ + id: 'noInputFieldsSection', + title: 'Fields without inputs', + children: [ + descriptionSubsection, + dividerSubsection, + accordionSubsection, + actionCardSubsection, + keyValueSubsection, + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/keyValueSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/keyValueSubsection.ts new file mode 100644 index 000000000000..3e07e42070ec --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/noInputFieldsSection/keyValueSubsection.ts @@ -0,0 +1,46 @@ +import { + buildKeyValueField, + buildMultiField, + buildSubSection, +} from '@island.is/application/core' + +export const keyValueSubsection = buildSubSection({ + id: 'keyValueSubsection', + title: 'Key-Value', + children: [ + buildMultiField({ + id: 'keyValueMultiField', + title: 'Key-Value', + children: [ + buildKeyValueField({ + label: 'Key value label', + value: 'One value', + }), + buildKeyValueField({ + label: 'Key value with divider', + value: ['Many values', 'Value 2', 'Value 3'], + divider: true, + }), + buildKeyValueField({ + label: 'Key value displax flex', + value: ['Value', 'Value 2', 'Value 3'], + divider: true, + display: 'flex', + }), + buildKeyValueField({ + label: 'Key value half width', + value: 'One value', + divider: true, + width: 'half', + }), + buildKeyValueField({ + label: 'Key value half width', + value: 'Custom colspan', + divider: true, + width: 'half', + colSpan: '5/12', + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/overviewSection/overviewSection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/overviewSection/overviewSection.ts new file mode 100644 index 000000000000..6bf72bd620d8 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/overviewSection/overviewSection.ts @@ -0,0 +1,43 @@ +import { + buildSection, + buildMultiField, + buildDescriptionField, + buildCustomField, + buildSubmitField, +} from '@island.is/application/core' +import { m } from '../../../lib/messages' +import { DefaultEvents } from '@island.is/application/types' + +export const overviewSection = buildSection({ + id: 'overview', + title: 'Overview', + children: [ + buildMultiField({ + id: 'overviewMultiField', + title: '', + children: [ + buildDescriptionField({ + id: 'overview', + title: 'Overview', + description: m.overviewDescription, + }), + buildCustomField({ + id: 'customComponent', + title: '', + component: 'Overview', + }), + buildSubmitField({ + id: 'submitApplication', + title: '', + actions: [ + { + event: DefaultEvents.SUBMIT, + name: m.overviewSubmit, + type: 'primary', + }, + ], + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/asyncSelectSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/asyncSelectSubsection.ts new file mode 100644 index 000000000000..8ebd875bb2c4 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/asyncSelectSubsection.ts @@ -0,0 +1,77 @@ +import { + buildAsyncSelectField, + buildMultiField, + buildSubSection, +} from '@island.is/application/core' +import { FriggSchoolsByMunicipality } from '../../../utils/types' +import { friggSchoolsByMunicipalityQuery } from '../../../graphql/sampleQuery' + +export const asyncSelectSubsection = buildSubSection({ + id: 'asyncSelectSubsection', + title: 'Async Select Subsection', + children: [ + buildMultiField({ + id: 'asyncSelectMultiField', + title: 'Async Select', + children: [ + buildAsyncSelectField({ + id: 'asyncSelect', + title: 'Async Select', + placeholder: 'Placeholder text', + loadingError: 'Loading error', + loadOptions: async ({ apolloClient }) => { + const { data } = + await apolloClient.query({ + query: friggSchoolsByMunicipalityQuery, + }) + + return ( + data?.friggSchoolsByMunicipality?.map((municipality) => ({ + value: municipality.name, + label: municipality.name, + })) ?? [] + ) + }, + }), + buildAsyncSelectField({ + id: 'asyncSelectSearchable', + title: 'Async Select Searchable', + isSearchable: true, + loadingError: 'Loading error', + loadOptions: async ({ apolloClient }) => { + const { data } = + await apolloClient.query({ + query: friggSchoolsByMunicipalityQuery, + }) + + return ( + data?.friggSchoolsByMunicipality?.map((municipality) => ({ + value: municipality.name, + label: municipality.name, + })) ?? [] + ) + }, + }), + buildAsyncSelectField({ + id: 'asyncSelectMulti', + title: 'Async Select Multi select', + isMulti: true, + loadingError: 'Loading error', + loadOptions: async ({ apolloClient }) => { + const { data } = + await apolloClient.query({ + query: friggSchoolsByMunicipalityQuery, + }) + + return ( + data?.friggSchoolsByMunicipality?.map((municipality) => ({ + value: municipality.name, + label: municipality.name, + })) ?? [] + ) + }, + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/checkboxSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/checkboxSubsection.ts new file mode 100644 index 000000000000..e6c7230dbd83 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/checkboxSubsection.ts @@ -0,0 +1,37 @@ +import { + buildCheckboxField, + buildMultiField, + buildSubSection, +} from '@island.is/application/core' +import { checkboxOptions } from '../../../utils/options' + +export const checkboxSubsection = buildSubSection({ + id: 'checkbox', + title: 'Checkboxes', + children: [ + buildMultiField({ + id: 'checkboxMultiField', + title: 'Checkboxes', + children: [ + buildCheckboxField({ + id: 'checkbox', + title: 'Full width checkboxes', + options: checkboxOptions, // Importing options from utils makes the template much more readable + }), + buildCheckboxField({ + id: 'checkboxHalf', + title: 'Half width checkboxes', + width: 'half', + options: checkboxOptions, + }), + buildCheckboxField({ + id: 'checkboxHalfStrong', + title: 'Half width strong checkboxes', + width: 'half', + strong: true, + options: checkboxOptions, + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/companySearchSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/companySearchSubsection.ts new file mode 100644 index 000000000000..8376800ce7f6 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/companySearchSubsection.ts @@ -0,0 +1,33 @@ +import { + buildCompanySearchField, + buildMultiField, + buildSubSection, +} from '@island.is/application/core' + +export const companySearchSubsection = buildSubSection({ + id: 'companySearchSubsection', + title: 'Company Search Subsection', + children: [ + buildMultiField({ + id: 'companySearchMultiField', + title: 'Company Search MultiField', + children: [ + buildCompanySearchField({ + id: 'companySearch', + title: 'Company Search', + placeholder: 'Search for a company', + }), + buildCompanySearchField({ + id: 'companySearchShouldIncludeIsatNumber', + title: 'Company Search Should include ISAT number', + shouldIncludeIsatNumber: true, + }), + buildCompanySearchField({ + id: 'companySearchCheckIfEmployerIsOnForbiddenList', + title: 'Company Search Check if employer is on forbidden list', + checkIfEmployerIsOnForbiddenList: true, + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/dateSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/dateSubsection.ts new file mode 100644 index 000000000000..d113c21aabda --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/dateSubsection.ts @@ -0,0 +1,54 @@ +import { + buildDateField, + buildMultiField, + buildSubSection, +} from '@island.is/application/core' +import { minDate, maxDate } from '../../../utils/dateUtils' + +export const dateSubsection = buildSubSection({ + id: 'date', + title: 'Date', + children: [ + buildMultiField({ + id: 'dateMultiField', + title: 'Date fields', + children: [ + buildDateField({ + id: 'date', + title: 'Regular datepicker', + }), + buildDateField({ + id: 'halfDate', + title: 'Half datepicker', + width: 'half', + }), + buildDateField({ + id: 'minAndMaxDate', + title: 'Min and max dates datepicker', + width: 'half', + minDate: minDate, + maxDate: maxDate, + }), + buildDateField({ + id: 'whiteDate', + title: 'White datepicker (try to use blue if possible)', + width: 'half', + backgroundColor: 'white', + }), + buildDateField({ + id: 'placeholderDate', + title: 'Placeholder datepicker', + placeholder: 'Select a date', + width: 'half', + }), + buildDateField({ + id: 'readOnlyDate', + title: 'Readonly datepicker', + width: 'half', + readOnly: true, + defaultValue: '2024-01-01', + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/displayFieldSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/displayFieldSubsection.ts new file mode 100644 index 000000000000..13bce1204a8e --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/displayFieldSubsection.ts @@ -0,0 +1,104 @@ +import { + buildSubSection, + buildDisplayField, + buildMultiField, + buildDescriptionField, + buildTextField, + getValueViaPath, + buildRadioField, +} from '@island.is/application/core' + +export const displayFieldSubsection = buildSubSection({ + id: 'displayFieldSubsection', + title: 'Display Field', + children: [ + buildMultiField({ + id: 'displayField', + title: 'Display Field', + children: [ + buildDescriptionField({ + id: 'displayFieldDescription', + title: '', + description: + 'Display field is just a read only input field behind the scenes. What is special about the display field is that the value takes a function that listens to changes in answers and updates the value accordingly. This is useful for displaying sums, multiples or anything else that is calculated from other fields.', + }), + buildTextField({ + id: 'input1', + title: 'Input 1', + variant: 'currency', + width: 'half', + rightAlign: true, + }), + buildTextField({ + id: 'input2', + title: 'Input 2', + variant: 'currency', + width: 'half', + rightAlign: true, + }), + buildTextField({ + id: 'input3', + title: 'Input 3', + variant: 'currency', + width: 'half', + rightAlign: true, + }), + buildDisplayField({ + id: 'displayField', + title: 'Display Field', + variant: 'currency', + label: 'Sum of inputs 1, 2 and 3', + rightAlign: true, + value: (answers) => { + const value1 = Number(getValueViaPath(answers, 'input1')) + const value2 = Number(getValueViaPath(answers, 'input2')) + const value3 = Number(getValueViaPath(answers, 'input3')) + return `${value1 + value2 + value3}` + }, + }), + + buildTextField({ + id: 'input4', + title: 'Upphæð leigu', + variant: 'currency', + width: 'half', + rightAlign: true, + }), + buildRadioField({ + id: 'radioFieldForDisplayField', + title: 'Trygging fyrir íbúð', + width: 'half', + options: [ + { label: 'Einföld leiga', value: '1' }, + { label: 'Tvöföld leiga', value: '2' }, + { label: 'Þreföld leiga', value: '3' }, + { label: 'Önnur upphæð', value: 'other' }, + ], + }), + buildDisplayField({ + id: 'displayField2', + title: 'Upphæð leigu', + variant: 'currency', + rightAlign: true, + value: (answers) => { + const value4 = Number(getValueViaPath(answers, 'input4')) + const value5 = getValueViaPath( + answers, + 'radioFieldForDisplayField', + ) + + if (!value4 || !value5) { + return '' + } + + if (value5 === 'other') { + return 'Önnur upphæð' + } + + return `${value4 * Number(value5)}` + }, + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/fileUploadSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/fileUploadSubsection.ts new file mode 100644 index 000000000000..434a89b55c66 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/fileUploadSubsection.ts @@ -0,0 +1,15 @@ +import { + buildFileUploadField, + buildSubSection, +} from '@island.is/application/core' + +export const fileUploadSubsection = buildSubSection({ + id: 'fileUpload', + title: 'File Upload', + children: [ + buildFileUploadField({ + id: 'fileUpload', + title: 'File Upload', + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/index.ts b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/index.ts new file mode 100644 index 000000000000..d4f165e3fb9f --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/index.ts @@ -0,0 +1,30 @@ +import { buildSection } from '@island.is/application/core' +import { textInputSubsection } from './textInputSubsection' +import { checkboxSubsection } from './checkboxSubsection' +import { radioSubsection } from './radioSubsection' +import { selectSubsection } from './selectSubsection' +import { phoneSubsection } from './phoneSubsection' +import { dateSubsection } from './dateSubsection' +import { fileUploadSubsection } from './fileUploadSubsection' +import { sliderSubsection } from './sliderSubsection' +import { companySearchSubsection } from './companySearchSubsection' +import { asyncSelectSubsection } from './asyncSelectSubsection' +import { displayFieldSubsection } from './displayFieldSubsection' + +export const simpleInputsSection = buildSection({ + id: 'simpleInputsSection', + title: 'Simple inputs', + children: [ + textInputSubsection, + displayFieldSubsection, + checkboxSubsection, + radioSubsection, + selectSubsection, + asyncSelectSubsection, + companySearchSubsection, + phoneSubsection, + dateSubsection, + fileUploadSubsection, + sliderSubsection, + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/phoneSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/phoneSubsection.ts new file mode 100644 index 000000000000..d3946b41a517 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/phoneSubsection.ts @@ -0,0 +1,48 @@ +import { + buildMultiField, + buildPhoneField, + buildSubSection, +} from '@island.is/application/core' + +export const phoneSubsection = buildSubSection({ + id: 'phone', + title: 'Phone', + children: [ + buildMultiField({ + id: 'phoneMultiField', + title: 'Phone fields', + children: [ + buildPhoneField({ + id: 'phone', + title: 'Regular', + }), + buildPhoneField({ + id: 'halfPhone', + title: 'Half', + width: 'half', + }), + buildPhoneField({ + id: 'whitePhone', + title: 'White (try to use blue if possible)', + backgroundColor: 'white', + }), + buildPhoneField({ + id: 'placeholderPhone', + title: 'Placeholder', + placeholder: 'Enter your phone number', + }), + buildPhoneField({ + id: 'countrySelectPhone', + title: 'Country select', + enableCountrySelector: true, + }), + buildPhoneField({ + id: 'countrySelectOnlyAllowedPhone', + title: 'Limited country select', + enableCountrySelector: true, + allowedCountryCodes: ['IS', 'IM', 'VC', 'AI', 'AW'], + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/radioSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/radioSubsection.ts new file mode 100644 index 000000000000..9a64349466b6 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/radioSubsection.ts @@ -0,0 +1,59 @@ +import { + buildMultiField, + buildRadioField, + buildSubSection, +} from '@island.is/application/core' +import { + defaultRadioOption, + radioIllustrationOptions, + radioOptions, +} from '../../../utils/options' + +export const radioSubsection = buildSubSection({ + id: 'radio', + title: 'Radio', + children: [ + buildMultiField({ + id: 'radioMultiField', + title: 'Radio fields', + children: [ + buildRadioField({ + id: 'radio', + title: 'Full width radio', + options: radioOptions, + }), + buildRadioField({ + id: 'halfRadio', + title: 'Half width radio', + width: 'half', + options: radioOptions, + }), + buildRadioField({ + id: 'smallButtonsRadio', + title: 'Small radio buttons', + options: radioOptions, + largeButtons: false, + }), + buildRadioField({ + id: 'radioIllustrations', + title: 'Radio with illustrations', + options: radioIllustrationOptions, + widthWithIllustration: '1/3', + hasIllustration: true, + }), + buildRadioField({ + id: 'defaultRadio', + title: 'Radio with a default value', + options: radioOptions, + defaultValue: defaultRadioOption, + }), + buildRadioField({ + id: 'WhiteBackgroundRadio', + title: 'White background (try to use blue if possible)', + options: radioOptions, + backgroundColor: 'white', + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/selectSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/selectSubsection.ts new file mode 100644 index 000000000000..b781ab57a7e7 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/selectSubsection.ts @@ -0,0 +1,51 @@ +import { + buildMultiField, + buildSelectField, + buildSubSection, +} from '@island.is/application/core' +import { selectOptions } from '../../../utils/options' +import { title } from 'process' + +export const selectSubsection = buildSubSection({ + id: 'select', + title: 'Select', + children: [ + buildMultiField({ + id: 'selectMultiField', + title: 'Select fields', + children: [ + buildSelectField({ + id: 'select', + title: 'Regular select', + options: selectOptions, + }), + buildSelectField({ + id: 'halfSelect', + title: 'Half select', + options: selectOptions, + width: 'half', + }), + buildSelectField({ + id: 'whiteSelect', + title: 'White (try to use blue if possible)', + options: selectOptions, + width: 'half', + backgroundColor: 'white', + }), + buildSelectField({ + id: 'placeholderSelect', + title: 'Placeholder select', + options: selectOptions, + placeholder: 'Select an option', + width: 'half', + }), + buildSelectField({ + id: 'multiSelect', + title: 'Multi select', + options: selectOptions, + isMulti: true, + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/sliderSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/sliderSubsection.ts new file mode 100644 index 000000000000..80cf17ab9c6c --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/sliderSubsection.ts @@ -0,0 +1,134 @@ +import { + buildMultiField, + buildSliderField, + buildSubSection, +} from '@island.is/application/core' + +export const sliderSubsection = buildSubSection({ + id: 'sliderSubsection', + title: 'Slider Field', + children: [ + buildMultiField({ + id: 'slider', + title: '', + children: [ + buildSliderField({ + id: 'basicSlider', + title: 'Basic slider', + min: 1, + max: 10, + trackStyle: { gridTemplateRows: 5 }, + calculateCellStyle: () => { + return { + background: 'ccccd8', + } + }, + label: { + singular: 'day', + plural: 'days', + }, + }), + buildSliderField({ + id: 'basicSliderWithLabels', + title: 'Slider with labels', + min: 1, + max: 10, + trackStyle: { gridTemplateRows: 5 }, + calculateCellStyle: () => { + return { + background: 'ccccd8', + } + }, + label: { + singular: 'day', + plural: 'days', + }, + showMinMaxLabels: true, + showLabel: true, + showToolTip: true, + }), + buildSliderField({ + id: 'basicSliderWithProgressOverlay', + title: 'Slider with progress overlay', + min: 1, + max: 10, + trackStyle: { gridTemplateRows: 5 }, + calculateCellStyle: () => { + return { + background: 'ccccd8', + } + }, + label: { + singular: 'day', + plural: 'days', + }, + showRemainderOverlay: true, + showProgressOverlay: true, + }), + buildSliderField({ + id: 'basicSliderRangeDates', + title: 'Slider with range dates', + min: 1, + max: 10, + trackStyle: { gridTemplateRows: 5 }, + calculateCellStyle: () => { + return { + background: 'ccccd8', + } + }, + label: { + singular: 'day', + plural: 'days', + }, + rangeDates: { + start: { + date: '2024-01-01', + message: 'Start date', + }, + end: { + date: '2024-01-10', + message: 'End date', + }, + }, + showToolTip: true, + }), + buildSliderField({ + id: 'basicSliderWithSteps', + title: 'Slider with steps', + min: 1, + max: 10, + step: 2, + trackStyle: { gridTemplateRows: 5 }, + calculateCellStyle: () => { + return { + background: 'ccccd8', + } + }, + label: { + singular: 'day', + plural: 'days', + }, + }), + + buildSliderField({ + id: 'thickSlider', + title: 'Thick slider', + min: 0, + max: 100, + step: 1, + label: { + singular: 'day', + plural: 'days', + }, + defaultValue: 5, + trackStyle: { gridTemplateRows: 200 }, + calculateCellStyle: () => { + return { + background: 'ccccd8', + } + }, + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/textInputSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/textInputSubsection.ts new file mode 100644 index 000000000000..6068f5f7fb17 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/simpleInputsSection/textInputSubsection.ts @@ -0,0 +1,119 @@ +import { + buildMultiField, + buildSubSection, + buildTextField, +} from '@island.is/application/core' + +export const textInputSubsection = buildSubSection({ + id: 'textInput', + title: 'Text Input', + children: [ + buildMultiField({ + id: 'textInput', + title: 'Text input', + children: [ + buildTextField({ + id: 'textInput', + title: '', + placeholder: 'The most basic text input', + }), + buildTextField({ + id: 'halfTextInput', + title: '', + placeholder: 'Half width text input', + width: 'half', + }), + buildTextField({ + id: 'rightAlignedTextInput', + title: '', + placeholder: 'Right aligned', + width: 'half', + rightAlign: true, + }), + buildTextField({ + id: 'readOnlyTextInput', + title: '', + defaultValue: 'Read only', + width: 'half', + readOnly: true, + }), + buildTextField({ + id: 'maxLengthTextInput', + title: '', + placeholder: 'Max length 3', + width: 'half', + maxLength: 3, + }), + buildTextField({ + id: 'formatTextInput', + title: '', + placeholder: 'Special format (kennitala)', + format: '######-####', + }), + buildTextField({ + id: 'whiteBackgroundTextInput', + title: '', + placeholder: 'White background (try to use blue if possible)', + backgroundColor: 'white', + }), + buildTextField({ + id: 'textTextInput', + title: '', + placeholder: 'Variant text (same as default)', + variant: 'text', + width: 'half', + }), + buildTextField({ + id: 'emailTextInput', + title: '', + placeholder: 'Variant email', + variant: 'email', + width: 'half', + }), + buildTextField({ + id: 'numberTextInput', + title: '', + placeholder: 'Variant number', + variant: 'number', + width: 'half', + }), + buildTextField({ + id: 'currencyTextInput', + title: '', + placeholder: 'Variant currency', + variant: 'currency', + width: 'half', + }), + buildTextField({ + id: 'currencyTextInput2', + title: '', + placeholder: 'Variant currency ($)', + variant: 'currency', + width: 'half', + suffix: ' $', + }), + buildTextField({ + id: 'currencyTextInput3', + title: '', + placeholder: 'Variant currency (€)', + variant: 'currency', + width: 'half', + suffix: ' €', + }), + buildTextField({ + id: 'telTextInput', + title: '', + placeholder: 'Variant tel (try to use buildPhoneField)', + variant: 'tel', + }), + buildTextField({ + id: 'textAreaTextInput', + title: '', + placeholder: 'Textarea', + variant: 'textarea', + rows: 10, + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/tablesAndRepeatersSection/StaticTableSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/tablesAndRepeatersSection/StaticTableSubsection.ts new file mode 100644 index 000000000000..2f74e254c30e --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/tablesAndRepeatersSection/StaticTableSubsection.ts @@ -0,0 +1,48 @@ +import { + buildSubSection, + buildStaticTableField, +} from '@island.is/application/core' + +export const staticTableSubsection = buildSubSection({ + id: 'staticTableSubsection', + title: 'Static table', + children: [ + buildStaticTableField({ + title: 'Static table', + // Header, rows and summary can also be functions that access external data or answers + header: [ + 'Table heading 1', + 'Table heading 2', + 'Table heading 3', + 'Table heading 4', + ], + rows: [ + [ + 'Row 1, Column 1', + 'Row 1, Column 2', + 'Row 1, Column 3', + 'Row 1, Column 4', + ], + [ + 'Row 2, Column 1', + 'Row 2, Column 2', + 'Row 2, Column 3', + 'Row 2, Column 4', + ], + [ + 'Row 3, Column 1', + 'Row 3, Column 2', + 'Row 3, Column 3', + 'Row 3, Column 4', + ], + ], + // Summary is optional + summary: [ + { + label: 'Summary label', + value: 'Summary value', + }, + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/tablesAndRepeatersSection/fieldsRepeaterSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/tablesAndRepeatersSection/fieldsRepeaterSubsection.ts new file mode 100644 index 000000000000..a38927bf858a --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/tablesAndRepeatersSection/fieldsRepeaterSubsection.ts @@ -0,0 +1,79 @@ +import { + buildDescriptionField, + buildFieldsRepeaterField, + buildMultiField, + buildSubSection, +} from '@island.is/application/core' + +export const fieldsRepeaterSubsection = buildSubSection({ + id: 'fieldsRepeaterSubsection', + title: 'Fields Repeater Field', + children: [ + buildMultiField({ + id: 'fieldsRepeater', + title: 'Fields Repeater', + children: [ + buildDescriptionField({ + id: 'fieldsRepeaterDescription', + title: '', + description: + 'FieldsRepeater works similarly to tableRepeater, in that it contains a set of fields to fill out and this set can be repeated as many times as needed. The difference is that in tableRepeater, the values go into a table, while in fieldsRepeater, all fields created are always visible.', + }), + buildFieldsRepeaterField({ + id: 'fieldsRepeater', + title: 'Fields Repeater', + formTitle: 'Title for each form', + width: 'half', + fields: { + input: { + component: 'input', + label: 'Regular input', + width: 'half', + type: 'text', + format: '######-####', + }, + select: { + component: 'select', + label: 'Select', + width: 'half', + options: [ + { label: 'Option 1', value: 'option1' }, + { label: 'Option 2', value: 'option2' }, + ], + }, + radio: { + component: 'radio', + width: 'half', + options: [ + { label: 'Option 1', value: 'option1' }, + { label: 'Option 2', value: 'option2' }, + { label: 'Option 3', value: 'option3' }, + ], + }, + checkbox: { + component: 'checkbox', + options: [ + { label: 'Option 1', value: 'option1' }, + { label: 'Option 2', value: 'option2' }, + ], + }, + date: { + component: 'date', + label: 'Date', + width: 'half', + }, + nationalIdWithName: { + component: 'nationalIdWithName', + label: 'National ID with name', + }, + phone: { + component: 'phone', + label: 'Phone', + width: 'half', + }, + }, + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/tablesAndRepeatersSection/index.ts b/libs/application/templates/reference-template/src/forms/exampleForm/tablesAndRepeatersSection/index.ts new file mode 100644 index 000000000000..3345edd8491e --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/tablesAndRepeatersSection/index.ts @@ -0,0 +1,14 @@ +import { buildSection } from '@island.is/application/core' +import { tableRepeaterSubsection } from './tableRepeaterSubsection' +import { staticTableSubsection } from './StaticTableSubsection' +import { fieldsRepeaterSubsection } from './fieldsRepeaterSubsection' + +export const tablesAndRepeatersSection = buildSection({ + id: 'tablesAndRepeatersSection', + title: 'Tables and repeaters', + children: [ + staticTableSubsection, + tableRepeaterSubsection, + fieldsRepeaterSubsection, + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/exampleForm/tablesAndRepeatersSection/tableRepeaterSubsection.ts b/libs/application/templates/reference-template/src/forms/exampleForm/tablesAndRepeatersSection/tableRepeaterSubsection.ts new file mode 100644 index 000000000000..06c95a4d4e12 --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/exampleForm/tablesAndRepeatersSection/tableRepeaterSubsection.ts @@ -0,0 +1,138 @@ +import { + buildDescriptionField, + buildMultiField, + buildSubSection, + buildTableRepeaterField, +} from '@island.is/application/core' + +export const tableRepeaterSubsection = buildSubSection({ + id: 'repeater', + title: 'Table Repeater', + children: [ + buildMultiField({ + id: 'tableRepeater', + title: 'Table Repeater Field', + children: [ + buildDescriptionField({ + id: 'tableRepeaterDescription', + title: '', + description: + 'In the table repeater, you create a small form that the user fills out and the answers are then sorted into a table. Only one instance of this form is visible at a time. In the table, you can delete and edit rows, and you can disable this functionality. You can also insert data into the table from answers or external data, similar to staticTable.', + marginBottom: 2, + }), + buildDescriptionField({ + id: 'tableRepeaterDescription2', + title: '', + description: + 'In the table repeater, you can use input, select, radio, checkbox, date, nationalIdWithName and phone.', + }), + buildTableRepeaterField({ + id: 'tableRepeater', + title: 'Table Repeater Field', + formTitle: 'Table Repeater Form Title', // Todo: doesn't work + addItemButtonText: 'Custom Add item text', + saveItemButtonText: 'Custom Save item text', + removeButtonTooltipText: 'Custom Remove item text', + editButtonTooltipText: 'Custom Edit item text', + editField: true, + maxRows: 10, + getStaticTableData: (_application) => { + // Possibility to populate the table with data from the answers or external data + // Populated data will not be editable or deletable + return [ + { + input: 'John Doe', + select: 'option 1', + radio: 'option 2', + checkbox: 'option 3', + date: '2024-01-01', + name: 'Test Name', + nationalId: '000000-0000', + phone: '6666666', + }, + { + input: 'Jane Doe', + select: 'option 1', + radio: 'option 2', + checkbox: 'option 3', + date: '2024-01-01', + name: 'Test Name 2', + nationalId: '100000-0000', + phone: '6666666', + }, + ] + }, + // Possible fields: input, select, radio, checkbox, date, nationalIdWithName + fields: { + input: { + component: 'input', + label: 'Regular input', + width: 'half', + required: true, + type: 'text', + }, + select: { + component: 'select', + label: 'Select', + width: 'half', + options: [ + { label: 'Option 1', value: 'option1' }, + { label: 'Option 2', value: 'option2' }, + ], + }, + radio: { + component: 'radio', + width: 'half', + options: [ + { label: 'Option 1', value: 'option1' }, + { label: 'Option 2', value: 'option2' }, + { label: 'Option 3', value: 'option3' }, + ], + }, + checkbox: { + component: 'checkbox', + options: [ + { label: 'Option 1', value: 'option1' }, + { label: 'Option 2', value: 'option2' }, + ], + }, + date: { + component: 'date', + label: 'Date', + width: 'half', + }, + nationalIdWithName: { + component: 'nationalIdWithName', + label: 'National ID with name', + }, + phone: { + component: 'phone', + label: 'Phone', + width: 'half', + }, + }, + table: { + // Format values for display in the table + format: { + input: (value) => `${value} - custom format`, + nationalIdWithName: (value) => { + return `${value} - custom format` + }, + }, + // Overwrite header for the table. If not provided, the labels from the fields will be used + header: [ + 'Input', + 'Select', + 'Radio', + 'Checkbox', + 'Date', + 'Name', + 'NationalId', + 'Phone', + ], + }, + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/PendingReview.ts b/libs/application/templates/reference-template/src/forms/pendingReviewForm/pendingReview.ts similarity index 78% rename from libs/application/templates/reference-template/src/forms/PendingReview.ts rename to libs/application/templates/reference-template/src/forms/pendingReviewForm/pendingReview.ts index 4ae331750e1e..23830ee5fd0e 100644 --- a/libs/application/templates/reference-template/src/forms/PendingReview.ts +++ b/libs/application/templates/reference-template/src/forms/pendingReviewForm/pendingReview.ts @@ -8,8 +8,8 @@ export const PendingReview: Form = buildForm({ children: [ buildDescriptionField({ id: 'inReview', - title: 'Í vinnslu', - description: 'Umsókn þín um ökunám er nú í vinnslu. ', + title: 'In review', + description: 'Your application is now in review. ', }), ], }) diff --git a/libs/application/templates/reference-template/src/forms/prerequisitesForm/prerequisitesForm.ts b/libs/application/templates/reference-template/src/forms/prerequisitesForm/prerequisitesForm.ts new file mode 100644 index 000000000000..7918bc4f5f8d --- /dev/null +++ b/libs/application/templates/reference-template/src/forms/prerequisitesForm/prerequisitesForm.ts @@ -0,0 +1,84 @@ +import { + buildForm, + buildSection, + buildExternalDataProvider, + buildDataProviderItem, + buildSubmitField, + coreMessages, +} from '@island.is/application/core' +import { + Application, + DefaultEvents, + Form, + FormModes, +} from '@island.is/application/types' +import { UserProfileApi } from '@island.is/application/types' +import { + ReferenceDataApi, + MyMockProvider, + NationalRegistryApi, +} from '../../dataProviders' +import { createElement } from 'react' +import { Logo } from '../../components/Logo/Logo' + +export const Prerequisites: Form = buildForm({ + id: 'PrerequisitesDraft', + title: 'Forkröfur', + mode: FormModes.NOT_STARTED, + // The logo prop can either take in a React component or a function that returns a React component. + // Dynamic logo can be based on answers or external data + logo: (application: Application) => { + const logo = createElement(Logo, { application }) + return () => logo + }, + renderLastScreenButton: true, + children: [ + buildSection({ + id: 'conditions', + title: '', // If this is empty, we will not have a visible stepper on the right side of the screen. + tabTitle: 'Forkröfur', // If there is no stepper, add tabTitle to have a title on the browser tab. + children: [ + buildExternalDataProvider({ + id: 'approveExternalData', + title: 'External data', + dataProviders: [ + buildDataProviderItem({ + provider: UserProfileApi, + title: 'User profile', + subTitle: 'User profile', + }), + buildDataProviderItem({ + provider: ReferenceDataApi, + title: 'getReferenceData', + subTitle: 'Reference data', + }), + buildDataProviderItem({ + provider: NationalRegistryApi, + title: 'National Registry', + subTitle: 'Information about you in the National Registry.', + }), + buildDataProviderItem({ + provider: MyMockProvider, + title: 'Mock Data', + subTitle: 'Returns data for mocking', + }), + ], + // Button to trigger the submit event to move the application from the NOT_STARTED state to the DRAFT state. + submitField: buildSubmitField({ + id: 'submit', + placement: 'footer', + title: '', + refetchApplicationAfterSubmit: true, + actions: [ + { + event: DefaultEvents.SUBMIT, + name: coreMessages.buttonNext, + type: 'primary', + }, + ], + }), + }), + ], + }), + ], +}) diff --git a/libs/application/templates/reference-template/src/forms/Rejected.ts b/libs/application/templates/reference-template/src/forms/rejectedForm/rejectedForm.ts similarity index 74% rename from libs/application/templates/reference-template/src/forms/Rejected.ts rename to libs/application/templates/reference-template/src/forms/rejectedForm/rejectedForm.ts index 9e0a4b189554..c3149752b103 100644 --- a/libs/application/templates/reference-template/src/forms/Rejected.ts +++ b/libs/application/templates/reference-template/src/forms/rejectedForm/rejectedForm.ts @@ -8,8 +8,8 @@ export const Rejected: Form = buildForm({ children: [ buildDescriptionField({ id: 'rejected', - title: 'Því miður...', - description: 'Umsókn þinni verið hafnað! Það er frekar leiðinlegt.', + title: 'Sorry...', + description: 'Your application has been rejected! It is pretty sad.', }), ], }) diff --git a/libs/application/templates/reference-template/src/forms/ReviewApplication.ts b/libs/application/templates/reference-template/src/forms/reviewApplicationForm/reviewApplication.ts similarity index 90% rename from libs/application/templates/reference-template/src/forms/ReviewApplication.ts rename to libs/application/templates/reference-template/src/forms/reviewApplicationForm/reviewApplication.ts index bd1b2815fafe..57bc24e5e6af 100644 --- a/libs/application/templates/reference-template/src/forms/ReviewApplication.ts +++ b/libs/application/templates/reference-template/src/forms/reviewApplicationForm/reviewApplication.ts @@ -10,7 +10,7 @@ import { buildTextField, } from '@island.is/application/core' import { Form, FormModes } from '@island.is/application/types' -import { m } from '../lib/messages' +import { m } from '../../lib/messages' export const ReviewApplication: Form = buildForm({ id: 'ExampleInReview', @@ -58,7 +58,7 @@ export const ReviewApplication: Form = buildForm({ buildDividerField({ title: 'Atvinna' }), buildRadioField({ id: 'careerHistory', - title: m.careerHistory, + title: 'def', width: 'half', disabled: true, options: [ @@ -68,7 +68,7 @@ export const ReviewApplication: Form = buildForm({ }), buildCheckboxField({ id: 'careerHistoryDetails.careerHistoryCompanies', - title: m.careerHistoryCompanies, + title: 'abc', disabled: true, width: 'half', options: [ @@ -81,17 +81,17 @@ export const ReviewApplication: Form = buildForm({ buildTextField({ id: 'careerHistoryDetails.careerHistoryOther', disabled: true, - title: m.careerHistoryOther, + title: 'ghi', }), buildTextField({ id: 'dreamJob', - title: m.dreamJob, + title: 'jkl', disabled: true, }), buildSubmitField({ id: 'approvedByReviewer', placement: 'screen', - title: 'Samþykkirðu þessa umsókn?', + title: 'Do yo uapprove this application?', actions: [ { event: 'APPROVE', name: 'Samþykkja', type: 'primary' }, { event: 'REJECT', name: 'Hafna', type: 'reject' }, @@ -103,7 +103,7 @@ export const ReviewApplication: Form = buildForm({ id: 'final', title: 'Takk fyrir', description: - 'Úrvinnslu þinni er lokið. Umsókn er komin áfram í ferlinu.', + 'Your processing is complete. The application has been forwarded to the next step.', }), ], }), diff --git a/libs/application/templates/reference-template/src/forms/WaitingToAssign.ts b/libs/application/templates/reference-template/src/forms/waitingToAssignForm/waitingToAssignForm.ts similarity index 76% rename from libs/application/templates/reference-template/src/forms/WaitingToAssign.ts rename to libs/application/templates/reference-template/src/forms/waitingToAssignForm/waitingToAssignForm.ts index e92b2e0a9cb3..06c29afc238b 100644 --- a/libs/application/templates/reference-template/src/forms/WaitingToAssign.ts +++ b/libs/application/templates/reference-template/src/forms/waitingToAssignForm/waitingToAssignForm.ts @@ -8,7 +8,7 @@ import { Form, FormModes } from '@island.is/application/types' export const PendingReview: Form = buildForm({ id: 'ExamplePending', - title: 'Í vinnslu', + title: 'In review', mode: FormModes.IN_PROGRESS, children: [ buildMultiField({ @@ -16,15 +16,15 @@ export const PendingReview: Form = buildForm({ children: [ buildDescriptionField({ id: 'waitingToAssign', - title: 'Í bið', - description: 'Beðið eftir umsjón.', + title: 'In review', + description: 'Waiting for review.', }), buildSubmitField({ id: 'submitWaiting', placement: 'footer', - title: 'Halda áfram', + title: 'Continue', refetchApplicationAfterSubmit: true, - actions: [{ event: 'SUBMIT', name: 'Halda áfram', type: 'primary' }], + actions: [{ event: 'SUBMIT', name: 'Continue', type: 'primary' }], }), ], }), diff --git a/libs/application/templates/reference-template/src/graphql/README.md b/libs/application/templates/reference-template/src/graphql/README.md new file mode 100644 index 000000000000..a9bcbd3f5a95 --- /dev/null +++ b/libs/application/templates/reference-template/src/graphql/README.md @@ -0,0 +1,3 @@ +# Graphql + +This folder is optional and can be used for all the graphql queries and mutations that are used by the application. diff --git a/libs/application/templates/reference-template/src/graphql/sampleQuery.ts b/libs/application/templates/reference-template/src/graphql/sampleQuery.ts new file mode 100644 index 000000000000..ff815976888a --- /dev/null +++ b/libs/application/templates/reference-template/src/graphql/sampleQuery.ts @@ -0,0 +1,18 @@ +import gql from 'graphql-tag' +export const friggSchoolsByMunicipalityQuery = gql` + query FriggSchoolsByMunicipality { + friggSchoolsByMunicipality { + id + nationalId + name + type + children { + id + nationalId + name + type + gradeLevels + } + } + } +` diff --git a/libs/application/templates/reference-template/src/lib/README.md b/libs/application/templates/reference-template/src/lib/README.md new file mode 100644 index 000000000000..538841b09b22 --- /dev/null +++ b/libs/application/templates/reference-template/src/lib/README.md @@ -0,0 +1,17 @@ +# The lib folder + +This folder contains all the data schema, messages, and the main template file. + +## Data schema + +The data schema defines what the answer object should look like. All validation is done with zod. + +## Messages + +Messages can either be one single file with all messages needed for the application. If more organization is needed, /messages can also be a folder with the messages split up into multiple files. + +## Main template file + +The main template file holds the state machine for the application. It defines how the application should flow from one state to the next and which form to load in each state. Here you can also leverage the `mapUserToRole` function to define which role the user has in the application. This allows you to display different forms to different users even if the application is in the same state. This can be usefull for applications that can both be done as an individual and as a procurer. + +The state machine also holds the actions that are run when the application is in a certain state. diff --git a/libs/application/templates/reference-template/src/lib/ReferenceApplicationTemplate.ts b/libs/application/templates/reference-template/src/lib/ReferenceApplicationTemplate.ts index 579378424e97..c4cd3c8b8fd3 100644 --- a/libs/application/templates/reference-template/src/lib/ReferenceApplicationTemplate.ts +++ b/libs/application/templates/reference-template/src/lib/ReferenceApplicationTemplate.ts @@ -1,3 +1,14 @@ +/* + *** + *** The state machine is for this template is as follows: + *** + *** /-> Approved + *** Prerequisites -> Draft -> Waiting to assign -> In review -- + *** Λ | \-> Rejected + *** |_____________| + *** + */ + import { DefaultStateLifeCycle, getValueViaPath, @@ -26,16 +37,8 @@ import { MyMockProvider, NationalRegistryApi, } from '../dataProviders' -import { ExampleSchema } from './dataSchema' - -const States = { - prerequisites: 'prerequisites', - draft: 'draft', - inReview: 'inReview', - approved: 'approved', - rejected: 'rejected', - waitingToAssign: 'waitingToAssign', -} +import { dataSchema } from './dataSchema' +import { States } from '../utils/constants' type ReferenceTemplateEvent = | { type: DefaultEvents.APPROVE } @@ -50,19 +53,19 @@ enum Roles { } const determineMessageFromApplicationAnswers = (application: Application) => { - const careerHistory = getValueViaPath( + const careerHistory = getValueViaPath( application.answers, 'careerHistory', undefined, - ) as string | undefined - const careerIndustry = getValueViaPath( + ) + const careerIndustry = getValueViaPath( application.answers, 'careerIndustry', undefined, - ) as string | undefined + ) if (careerHistory === 'no') { - return m.nameApplicationNeverWorkedBefore + return 'abcdef' } if (careerIndustry) { return { @@ -72,6 +75,7 @@ const determineMessageFromApplicationAnswers = (application: Application) => { } return m.name } + const ReferenceApplicationTemplate: ApplicationTemplate< ApplicationContext, ApplicationStateSchema, @@ -81,14 +85,14 @@ const ReferenceApplicationTemplate: ApplicationTemplate< name: determineMessageFromApplicationAnswers, institution: m.institutionName, translationNamespaces: [ApplicationConfigurations.ExampleForm.translation], - dataSchema: ExampleSchema, + dataSchema: dataSchema, featureFlag: Features.exampleApplication, allowMultipleApplicationsInDraft: true, stateMachineConfig: { - initial: States.prerequisites, + initial: States.PREREQUISITES, states: { - [States.prerequisites]: { + [States.PREREQUISITES]: { meta: { name: 'Skilyrði', progress: 0, @@ -103,8 +107,8 @@ const ReferenceApplicationTemplate: ApplicationTemplate< { id: Roles.APPLICANT, formLoader: () => - import('../forms/Prerequisites').then((module) => - Promise.resolve(module.Prerequisites), + import('../forms/prerequisitesForm/prerequisitesForm').then( + (module) => Promise.resolve(module.Prerequisites), ), actions: [ { event: 'SUBMIT', name: 'Staðfesta', type: 'primary' }, @@ -131,15 +135,14 @@ const ReferenceApplicationTemplate: ApplicationTemplate< ], }, on: { - SUBMIT: { - target: States.draft, + [DefaultEvents.SUBMIT]: { + target: States.DRAFT, }, }, }, - [States.draft]: { + [States.DRAFT]: { meta: { - name: 'Umsókn um ökunám', - + name: 'Dæmi um umsókn', actionCard: { description: m.draftDescription, historyLogs: { @@ -154,7 +157,7 @@ const ReferenceApplicationTemplate: ApplicationTemplate< { id: Roles.APPLICANT, formLoader: () => - import('../forms/ExampleForm').then((module) => + import('../forms/exampleForm').then((module) => Promise.resolve(module.ExampleForm), ), actions: [ @@ -166,14 +169,14 @@ const ReferenceApplicationTemplate: ApplicationTemplate< ], }, on: { - SUBMIT: [ + [DefaultEvents.SUBMIT]: [ { - target: States.waitingToAssign, + target: States.WAITINGTOASSIGN, }, ], }, }, - [States.waitingToAssign]: { + [States.WAITINGTOASSIGN]: { meta: { name: 'Waiting to assign', progress: 0.75, @@ -207,8 +210,8 @@ const ReferenceApplicationTemplate: ApplicationTemplate< { id: Roles.APPLICANT, formLoader: () => - import('../forms/WaitingToAssign').then((val) => - Promise.resolve(val.PendingReview), + import('../forms/waitingToAssignForm/waitingToAssignForm').then( + (val) => Promise.resolve(val.PendingReview), ), read: 'all', write: 'all', @@ -217,20 +220,20 @@ const ReferenceApplicationTemplate: ApplicationTemplate< { id: Roles.ASSIGNEE, formLoader: () => - import('../forms/WaitingToAssign').then((val) => - Promise.resolve(val.PendingReview), + import('../forms/waitingToAssignForm/waitingToAssignForm').then( + (val) => Promise.resolve(val.PendingReview), ), read: 'all', }, ], }, on: { - SUBMIT: { target: States.inReview }, - ASSIGN: { target: States.inReview }, - EDIT: { target: States.draft }, + [DefaultEvents.SUBMIT]: { target: States.INREVIEW }, + [DefaultEvents.ASSIGN]: { target: States.INREVIEW }, + [DefaultEvents.EDIT]: { target: States.DRAFT }, }, }, - [States.inReview]: { + [States.INREVIEW]: { meta: { name: 'In Review', progress: 0.75, @@ -263,8 +266,8 @@ const ReferenceApplicationTemplate: ApplicationTemplate< { id: Roles.ASSIGNEE, formLoader: () => - import('../forms/ReviewApplication').then((val) => - Promise.resolve(val.ReviewApplication), + import('../forms/reviewApplicationForm/reviewApplication').then( + (val) => Promise.resolve(val.ReviewApplication), ), actions: [ { event: 'APPROVE', name: 'Samþykkja', type: 'primary' }, @@ -279,7 +282,7 @@ const ReferenceApplicationTemplate: ApplicationTemplate< { id: Roles.APPLICANT, formLoader: () => - import('../forms/PendingReview').then((val) => + import('../forms/pendingReviewForm/pendingReview').then((val) => Promise.resolve(val.PendingReview), ), read: 'all', @@ -287,11 +290,11 @@ const ReferenceApplicationTemplate: ApplicationTemplate< ], }, on: { - APPROVE: { target: States.approved }, - REJECT: { target: States.rejected }, + [DefaultEvents.APPROVE]: { target: States.APPROVED }, + [DefaultEvents.REJECT]: { target: States.REJECTED }, }, }, - [States.approved]: { + [States.APPROVED]: { meta: { name: 'Approved', progress: 1, @@ -301,7 +304,7 @@ const ReferenceApplicationTemplate: ApplicationTemplate< { id: Roles.APPLICANT, formLoader: () => - import('../forms/Approved').then((val) => + import('../forms/approvedForm/approvedForm').then((val) => Promise.resolve(val.Approved), ), read: 'all', @@ -309,18 +312,17 @@ const ReferenceApplicationTemplate: ApplicationTemplate< ], }, }, - [States.rejected]: { + [States.REJECTED]: { meta: { name: 'Rejected', progress: 1, status: 'rejected', lifecycle: DefaultStateLifeCycle, - roles: [ { id: Roles.APPLICANT, formLoader: () => - import('../forms/Rejected').then((val) => + import('../forms/rejectedForm/rejectedForm').then((val) => Promise.resolve(val.Rejected), ), }, diff --git a/libs/application/templates/reference-template/src/lib/dataSchema.ts b/libs/application/templates/reference-template/src/lib/dataSchema.ts index e4945ef27981..88387124a8c7 100644 --- a/libs/application/templates/reference-template/src/lib/dataSchema.ts +++ b/libs/application/templates/reference-template/src/lib/dataSchema.ts @@ -1,7 +1,21 @@ +/* + * DataSchema uses Zod to validate the answers object and can be used to refine values, provide + * error messages, and more. + * + * When checking if a value is of an enum type, use z.nativeEnum instead of z.enum. This eliminates the need to list up all possible values in the enum. + */ + import { z } from 'zod' import * as kennitala from 'kennitala' import { isValidNumber } from 'libphonenumber-js' import { m } from './messages' +import { + ApprovedByReviewerEnum, + CareerHistoryEnum, + CareerIndustryEnum, + YesNoEnum, +} from '../utils/constants' +import { radioValidationExampleEnum } from '../utils/types' const careerHistoryCompaniesValidation = (data: any) => { // Applicant selected other but didnt supply the reason so we dont allow it @@ -13,6 +27,18 @@ const careerHistoryCompaniesValidation = (data: any) => { } return true } + +const personSchema = z.object({ + name: z.string().min(1).max(256), + age: z.string().refine((x) => { + const asNumber = parseInt(x) + if (isNaN(asNumber)) { + return false + } + return asNumber > 15 + }), +}) + export const ExampleSchema = z.object({ approveExternalData: z.boolean().refine((v) => v), tableRepeaterField: z.array( @@ -58,32 +84,37 @@ export const ExampleSchema = z.object({ // .string() // .refine((x) => x.includes('Valid')), }), - careerHistory: z.enum(['yes', 'no']).optional(), - careerIndustry: z.enum(['software', 'finance', 'consulting', 'other']), - careerHistoryDetails: z - .object({ - careerHistoryCompanies: z - .array(z.enum(['government', 'aranja', 'advania', 'other'])) - .nonempty(), - careerHistoryOther: z.string(), - }) - .partial() - .refine((data) => careerHistoryCompaniesValidation(data), { - params: m.careerHistoryOtherError, - path: ['careerHistoryOther'], - }), - deepNestedValues: z.object({ - something: z.object({ - very: z.object({ - deep: z.object({ + nationalId: z.string().refine((n) => n && !kennitala.isValid(n), { + params: m.dataSchemeNationalId, + }), + phoneNumber: z + .string() + .refine(isValidNumber, { params: m.dataSchemePhoneNumber }), + email: z.string().email(), +}) + +const careerHistoryDetailsSchema = z + .object({ + careerHistoryCompanies: z.array(z.nativeEnum(CareerHistoryEnum)).nonempty(), + careerHistoryOther: z.string(), + }) + .partial() + .refine((data) => careerHistoryCompaniesValidation(data), { + params: m.regularTextExample, + path: ['careerHistoryOther'], + }) + +const deepNestedSchema = z.object({ + something: z.object({ + very: z.object({ + deep: z.object({ + so: z.object({ so: z.object({ - so: z.object({ + very: z.object({ very: z.object({ - very: z.object({ - deep: z.object({ - nested: z.object({ - value: z.string(), - }), + deep: z.object({ + nested: z.object({ + value: z.string(), }), }), }), @@ -93,7 +124,27 @@ export const ExampleSchema = z.object({ }), }), }), +}) + +const validationSchema = z.object({ + validationTextField: z.string().min(3, { + message: 'Custom validation message', + }), + validationRadioField: z.nativeEnum(radioValidationExampleEnum), +}) + +// The exported dataSchema should be as flat and easy to read as possible. +export const dataSchema = z.object({ + approveExternalData: z.boolean().refine((v) => v), + person: personSchema, + careerHistory: z.nativeEnum(YesNoEnum).optional(), + careerIndustry: z.nativeEnum(CareerIndustryEnum), + careerHistoryDetails: careerHistoryDetailsSchema, + deepNestedValues: deepNestedSchema, dreamJob: z.string().optional(), assigneeEmail: z.string().email(), - approvedByReviewer: z.enum(['APPROVE', 'REJECT']), + approvedByReviewer: z.nativeEnum(ApprovedByReviewerEnum), + validation: validationSchema, }) + +export type AnswersSchema = z.infer diff --git a/libs/application/templates/reference-template/src/lib/messages.ts b/libs/application/templates/reference-template/src/lib/messages.ts index c12509c9901e..9b332c340cf1 100644 --- a/libs/application/templates/reference-template/src/lib/messages.ts +++ b/libs/application/templates/reference-template/src/lib/messages.ts @@ -3,7 +3,7 @@ import { defineMessages } from 'react-intl' export const m = defineMessages({ conditionsSection: { id: 'example.application:conditions.section', - defaultMessage: 'Skilyrði', + defaultMessage: 'Conditions', description: 'Some description', }, institutionName: { @@ -13,118 +13,79 @@ export const m = defineMessages({ }, name: { id: 'example.application:name', - defaultMessage: 'Umsókn', - description: `Application's name`, - }, - nameApplicationNeverWorkedBefore: { - id: 'example.application:name.application.never.worked.before', - defaultMessage: 'Umsókn - Aldrei unnið áður', + defaultMessage: 'Application', description: `Application's name`, }, nameApplicationWithValue: { id: 'example.application:name.application.with.value', - defaultMessage: 'Umsókn {value}', + defaultMessage: 'Application {value}', description: `Application's name with value`, }, draftTitle: { id: 'example.application:draft.title', - defaultMessage: 'Drög', + defaultMessage: 'Draft', description: 'First state title', }, draftDescription: { id: 'example.application:draft.description', - defaultMessage: 'Notendur hafa ekkert að gera á þessu stigi', + defaultMessage: 'Users have nothing to do at this stage', description: 'Description of the state', }, introSection: { id: 'example.application:intro.section', - defaultMessage: 'Upplýsingar', + defaultMessage: 'Information', description: 'Some description', }, - introField: { - id: 'example.application:intro.field', - defaultMessage: 'Velkomin(n)', + introTitle: { + id: 'example.application:intro.title', + defaultMessage: 'Welcome', description: 'Some description', }, - introIntroduction: { - id: 'example.application:intro.introduction', + introDescription: { + id: 'example.application:intro.description', defaultMessage: - '*Hello*, **{name}**! [This is a link to Google!](http://google.com)', + 'This application shows how to build an application and what components are available. Each application is split into a few different forms that are loaded depending on the application state and/or the user role. Right now the application is showing the "exampleForm" and the application state is "draft". This means that you got through the first form "prerequisitesForm" where the state was "prerequisites".', + description: 'Some description', + }, + introDescription2: { + id: 'example.application:intro.description2', + defaultMessage: + 'This form covers all possible components that the application system offers and they are shown with different settings that results in the components appearing or behaving in different ways.', description: 'Some description', }, about: { id: 'example.application:about', - defaultMessage: 'Um þig', + defaultMessage: 'About you', description: 'Some description', }, personName: { id: 'example.application:person.name', - defaultMessage: 'Nafn', + defaultMessage: 'Name', description: 'Some description', }, nationalId: { id: 'example.application:person.nationalId', - defaultMessage: 'Kennitala', + defaultMessage: 'National ID', description: 'Some description', }, age: { id: 'example.application:person.age', - defaultMessage: 'Aldur', + defaultMessage: 'Age', description: 'Some description', }, email: { id: 'example.application:person.email', - defaultMessage: 'Netfang', + defaultMessage: 'Email', description: 'Some description', }, phoneNumber: { id: 'example.application:person.phoneNumber', - defaultMessage: 'Símanúmer', - description: 'Some description', - }, - career: { - id: 'example.application:career', - defaultMessage: 'Starfsferill', - description: 'Some description', - }, - history: { - id: 'example.application:history', - defaultMessage: 'Hvar hefur þú unnið áður?', - description: 'Some description', - }, - careerIndustry: { - id: 'example.application:career.industry', - defaultMessage: 'Starfsgeiri', - description: 'Some description', - }, - careerIndustryDescription: { - id: 'example.application:career.industryDescription', - defaultMessage: 'Í hvaða geira hefur þú unnið?', - description: 'Some description', - }, - careerHistory: { - id: 'example.application:careerHistory', - defaultMessage: 'Hefurðu unnið yfir höfuð einhvern tímann áður?', - description: 'Some description', - }, - careerHistoryCompanies: { - id: 'example.application:careerHistoryCompanies', - defaultMessage: 'Hefurðu unnið fyrir eftirfarandi aðila?', - description: 'Some description', - }, - future: { - id: 'example.application:future', - defaultMessage: 'Hvar langar þig að vinna?', - description: 'Some description', - }, - dreamJob: { - id: 'example.application:dreamJob', - defaultMessage: 'Einhver draumavinnustaður?', + defaultMessage: 'Phone number', description: 'Some description', }, assigneeTitle: { id: 'example.application:assigneeTitle', - defaultMessage: 'Hver á að fara yfir?', + defaultMessage: 'Who should review?', description: 'Some description', }, assignee: { @@ -134,12 +95,12 @@ export const m = defineMessages({ }, yesOptionLabel: { id: 'example.application:yes.option.label', - defaultMessage: 'Já', + defaultMessage: 'Yes', description: 'Some description', }, noOptionLabel: { id: 'example.application:no.option.label', - defaultMessage: 'Nei', + defaultMessage: 'No', description: 'Some description', }, governmentOptionLabel: { @@ -155,29 +116,118 @@ export const m = defineMessages({ }, dataSchemePhoneNumber: { id: 'example.application:dataSchema.phoneNumber', - defaultMessage: 'Símanúmerið þarf að vera gilt.', + defaultMessage: 'Phone number must be valid.', description: 'Error message when phone number is invalid.', }, dataSchemeNationalId: { id: 'example.application:dataSchema.national.id', - defaultMessage: 'Kennitala þarf að vera gild.', + defaultMessage: 'National ID must be valid.', description: 'Error message when the kennitala is invalid.', }, - careerHistoryOther: { - id: 'example.application:careerHistory.other', - defaultMessage: 'Hvern hefur þú unnið fyrir áður?', + approvedByReviewerError: { + id: 'example.application:approvedByReviewerError', + defaultMessage: + 'Please indicate whether the application is approved or not', description: 'Some description', }, - careerHistoryOtherError: { - id: 'example.application:careerHistory.othertError', + regularTextExample: { + id: 'example.application:regularTextExample', defaultMessage: - 'Vinsamlegast tilgreindu fyrir hvern þú hefur unnið fyrir áður?', - description: 'Some description', + 'Regular text coming from a message file, this can be overwritten in contentful by webmaster', + description: 'Example use of messages', }, - approvedByReviewerError: { - id: 'example.application:approvedByReviewerError', + markdownHeadingExample: { + id: 'example.application:markdownHeadingExample#markdown', defaultMessage: - 'Vinsamlegast tilgreindu hvort umsóknin sé samþykkt eða ekki', - description: 'Some description', + '# Markdown heading 1\n ## Markdown heading 2\n ### Markdown heading 3\n #### Markdown heading 4\n Regular markdown text', + description: 'Example use of markdown', + }, + markdownBulletListExample: { + id: 'example.application:markdownBulletListExample#markdown', + defaultMessage: + 'Markdown bullet list\n * bullet 1\n * bullet 2\n * bullet 3', + description: 'Example use of markdown', + }, + markdownNumberedListExample: { + id: 'example.application:markdownNumberedListExample#markdown', + defaultMessage: + 'Markdown numbered list\n 1. number 1\n 2. number 2\n 3. number 3', + description: 'Example use of markdown', + }, + markdownMiscExample: { + id: 'example.application:markdownMiscExample#markdown', + defaultMessage: + 'A markdown link will open in a new tab: [This is a link to Google!](http://google.com)\n\n Markdown value inserted {value1}\n\n **Bold text**\n\n *Italic text*\n\n ***Bold and italic text***', + description: 'Example use of markdown link', + }, + markdownCodeExample: { + id: 'example.application:markdownCodeExample#markdown', + defaultMessage: + 'Markdown code inline `const x = 123` with text\n\n Code block\n\n ```\n const x = 123\n if (x < 100) {\n return true\n }\n```', + description: 'Example use of markdown code', + }, + customComponentDescription: { + id: 'example.application:customComponentDescription', + defaultMessage: + 'Before you make a custom component, go through this list to determine if you really need a custom component. A custom component should be the last option you go for when building an application.', + description: 'Rules for custom components', + }, + customComponentNumberedList: { + id: 'example.application:customComponentDescription#markdown', + defaultMessage: + '1. Try to use the shared components, such as `buildTextField`, `buildCheckboxField`, `buildSelectField`, `buildFileUploadField`, and others. This approach ensures a more consistent and uniform look and feel for the application.\n- If the shared components almost fulfill your needs but require slight adjustments, consult with the designer of the application to explore adapting the design to the built-in components.\n- If the design cannot be adjusted to the built-in components, consult Norda to determine if the shared components can be modified or expanded to meet your requirements.\n- Check if another application has created a similar custom component before. If so, it should be made into a shared component.\n- If you still need a new component, evaluate whether it is something that other applications might need in the future. If so, make the new component shared.\n- Create a custom component only if none of the above conditions apply.', + description: 'Rules for custom components', + }, + customComponentAbout: { + id: 'example.application:customComponentAbout', + defaultMessage: + 'Custom components are just regular React components. They can take in some data you specify in the template and they have access to the application object. They can also be styled with vanilla-extract.', + description: 'About custom components', + }, + overviewTitle: { + id: 'example.application:overviewTitle', + defaultMessage: 'Overview', + description: 'Overview title', + }, + overviewDescription: { + id: 'example.application:overviewTitle', + defaultMessage: + 'At the moment the form overview is a custom component. The plan is to make this a shared component in the near future.', + description: 'Overview title', + }, + overviewSubmit: { + id: 'example.application:overviewSubmit', + defaultMessage: 'Submit', + description: 'Overview title', + }, + validationDescription: { + id: 'example.application:validation.description#markdown', + defaultMessage: + 'Any field can have the value `required: true`, which uses the built-in functionality of `html` and can be disabled by inspecting the DOM for the page.\n\n Generally, it is best to put everything that needs to be filled out or needs to be filled out in a certain way in `/lib/dataSchema.ts`. For validation, use *zod*.', + description: 'Validation description', + }, + validationDescription3: { + id: 'example.application:validation.description3#markdown', + defaultMessage: + 'If the options in a field are all in one `enum`, then use `z.nativeEnum()` in zod validation to skip listing all the options in the enum as is required when using `z.enum()`.', + description: 'Validation description', + }, + conditionsDescription2: { + id: 'example.application:conditions.description2#markdown', + defaultMessage: + 'This is done in the same way in both cases. Everything should be able to take in `condition` as a parameter and condition takes in a function `(answers, externalData) => { ... }` that returns `true` or `false`.', + description: 'Validation description', + }, + descriptionFieldDescription: { + id: 'example.application:descriptionFieldDescription', + defaultMessage: + 'All text that appears in applications should come from `lib/messages.ts`. This text is then loaded into contentful by running `yarn nx run :extract-strings`, where `` is the name of the application as it is written in `project.json` in the relevant template. For this application it is `yarn nx run application-templates-reference-template:extract-strings`. In contentful, the content manager or administrator of the organization is responsible for adding English translations and updating the text from what the developer puts in `defaultMessage`.', + description: 'Description field description', + }, + descriptionFieldDescription2: { + id: 'example.application:descriptionFieldDescription2', + defaultMessage: + 'Here is a list of all the options for text from buildDescriptionField. Most of them are related to adding `#markdown` to the id to be able to use markdown in the text. It is also possible to put variables into the text and control title font sizes.', + description: 'Description field description', }, }) diff --git a/libs/application/templates/reference-template/src/utils/README.md b/libs/application/templates/reference-template/src/utils/README.md new file mode 100644 index 000000000000..1de70d5a8989 --- /dev/null +++ b/libs/application/templates/reference-template/src/utils/README.md @@ -0,0 +1,5 @@ +# Utils + +This folder is for all the utility functions, constants, enums and types. + +The organization of this folder is up to the developer. Try to aim for a happy medium between small focused files and too many files that bloat the folder. The aim is that a developer can enter this folder and understand what the utils are for and have a nice overview of the utils. diff --git a/libs/application/templates/reference-template/src/utils/constants.ts b/libs/application/templates/reference-template/src/utils/constants.ts new file mode 100644 index 000000000000..b44f3134b11a --- /dev/null +++ b/libs/application/templates/reference-template/src/utils/constants.ts @@ -0,0 +1,32 @@ +export enum States { + PREREQUISITES = 'prerequisites', + DRAFT = 'draft', + INREVIEW = 'inReview', + APPROVED = 'approved', + REJECTED = 'rejected', + WAITINGTOASSIGN = 'waitingToAssign', +} + +export enum YesNoEnum { + YES = 'yes', + NO = 'no', +} + +export enum CareerHistoryEnum { + GOVERNMENT = 'government', + ARANJA = 'aranja', + ADVANIA = 'advania', + OTHER = 'other', +} + +export enum CareerIndustryEnum { + SOFTWARE = 'software', + FINANCE = 'finance', + CONSULTING = 'consulting', + OTHER = 'other', +} + +export enum ApprovedByReviewerEnum { + APPROVE = 'APPROVE', + REJECT = 'REJECT', +} diff --git a/libs/application/templates/reference-template/src/utils/dateUtils.ts b/libs/application/templates/reference-template/src/utils/dateUtils.ts new file mode 100644 index 000000000000..5b2eee38976b --- /dev/null +++ b/libs/application/templates/reference-template/src/utils/dateUtils.ts @@ -0,0 +1,3 @@ +export const oneDayInMs = 24 * 60 * 60 * 1000 +export const minDate = new Date(Date.now() - 3 * oneDayInMs) // Three days ago +export const maxDate = new Date(Date.now() + 3 * oneDayInMs) // Three days from now diff --git a/libs/application/templates/reference-template/src/utils/options.ts b/libs/application/templates/reference-template/src/utils/options.ts new file mode 100644 index 000000000000..cdb78dfdd452 --- /dev/null +++ b/libs/application/templates/reference-template/src/utils/options.ts @@ -0,0 +1,68 @@ +import { plate110 } from '../assets/plate-110-510' +import { plate200 } from '../assets/plate-200-280' +import { plate155 } from '../assets/plate-155-305' + +export const checkboxOptions = [ + { + label: 'Checkbox 1', + value: 'checkbox1', + }, + { + label: 'Checkbox 2', + value: 'checkbox2', + }, + { + label: 'Checkbox 3', + value: 'checkbox3', + }, +] + +export const radioOptions = [ + { + label: 'Radio 1', + value: 'radio1', + }, + { + label: 'Radio 2', + value: 'radio2', + }, + { + label: 'Radio 3', + value: 'radio3', + }, +] + +export const defaultRadioOption = 'radio2' + +export const radioIllustrationOptions = [ + { + label: 'Radio 1', + value: 'radio1', + illustration: plate110, + }, + { + label: 'Radio 2', + value: 'radio2', + illustration: plate200, + }, + { + label: 'Radio 3', + value: 'radio3', + illustration: plate155, + }, +] + +export const selectOptions = [ + { + label: 'Select 1', + value: 'select1', + }, + { + label: 'Select 2', + value: 'select2', + }, + { + label: 'Select 3', + value: 'select3', + }, +] diff --git a/libs/application/templates/reference-template/src/utils/types.ts b/libs/application/templates/reference-template/src/utils/types.ts new file mode 100644 index 000000000000..23f160994302 --- /dev/null +++ b/libs/application/templates/reference-template/src/utils/types.ts @@ -0,0 +1,36 @@ +export enum OrganizationModelTypeEnum { + Municipality = 'Municipality', + National = 'National', + School = 'School', +} + +export type FriggSchoolsByMunicipality = { + __typename?: 'Query' + friggSchoolsByMunicipality?: Array<{ + __typename?: 'EducationFriggOrganizationModel' + id: string + nationalId: string + name: string + type: OrganizationModelTypeEnum + children?: Array<{ + __typename?: 'EducationFriggOrganizationModel' + id: string + nationalId: string + name: string + type: OrganizationModelTypeEnum + gradeLevels?: Array | null + }> | null + }> | null +} + +export type TableRepeaterAnswers = { + fullName: string + nationalId: string + relation: string +} + +export enum radioValidationExampleEnum { + OPTION_1 = 'option1', + OPTION_2 = 'option2', + OPTION_3 = 'option3', +} diff --git a/libs/application/types/src/lib/Fields.ts b/libs/application/types/src/lib/Fields.ts index e161ed324e09..5788d6ccac29 100644 --- a/libs/application/types/src/lib/Fields.ts +++ b/libs/application/types/src/lib/Fields.ts @@ -654,19 +654,6 @@ export type FieldsRepeaterField = BaseField & { */ minRows?: number maxRows?: number - table?: { - /** - * List of strings to render, - * if not provided it will be auto generated from the fields - */ - header?: StaticText[] - /** - * List of field id's to render, - * if not provided it will be auto generated from the fields - */ - rows?: string[] - format?: Record string | StaticText> - } } export type AccordionItem = { @@ -752,6 +739,7 @@ export interface SliderField extends BaseField { readonly type: FieldTypes.SLIDER readonly color?: Colors component: FieldComponents.SLIDER + titleVariant?: TitleVariants min: number max: MaybeWithApplicationAndField step?: number diff --git a/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterFormField.tsx b/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterFormField.tsx index 9edc6dd1890e..7504599709ac 100644 --- a/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterFormField.tsx +++ b/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterFormField.tsx @@ -148,7 +148,7 @@ export const FieldsRepeaterFormField = ({ {Array.from({ length: numberOfItems }).map((_i, i) => ( {(formTitleNumbering !== 'none' || formTitle) && ( - + {formTitleNumbering === 'prefix' ? `${i + 1}. ` : ''} {formTitle && diff --git a/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterItem.tsx b/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterItem.tsx index 4fd041e97550..df4f221ff91e 100644 --- a/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterItem.tsx +++ b/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterItem.tsx @@ -172,17 +172,17 @@ export const Item = ({ } if (component === 'radio') { DefaultValue = - (getValueViaPath(application.answers, id) as string[]) ?? + getValueViaPath>(application.answers, id) ?? getDefaultValue(item, application, activeValues) } if (component === 'checkbox') { DefaultValue = - (getValueViaPath(application.answers, id) as string[]) ?? + getValueViaPath>(application.answers, id) ?? getDefaultValue(item, application, activeValues) } if (component === 'date') { DefaultValue = - (getValueViaPath(application.answers, id) as string) ?? + getValueViaPath(application.answers, id) ?? getDefaultValue(item, application, activeValues) } @@ -216,6 +216,7 @@ export const Item = ({ }} application={application} defaultValue={DefaultValue} + large={true} {...props} /> diff --git a/libs/application/ui-fields/src/lib/SelectFormField/SelectFormField.tsx b/libs/application/ui-fields/src/lib/SelectFormField/SelectFormField.tsx index d71c3f42a390..204b3ba45fd7 100644 --- a/libs/application/ui-fields/src/lib/SelectFormField/SelectFormField.tsx +++ b/libs/application/ui-fields/src/lib/SelectFormField/SelectFormField.tsx @@ -1,5 +1,4 @@ import React, { FC, useMemo } from 'react' - import { formatText, buildFieldOptions, diff --git a/libs/application/ui-fields/src/lib/SliderFormField/SliderFormField.stories.mdx b/libs/application/ui-fields/src/lib/SliderFormField/SliderFormField.stories.mdx index eed93c43088e..6a909d1bb270 100644 --- a/libs/application/ui-fields/src/lib/SliderFormField/SliderFormField.stories.mdx +++ b/libs/application/ui-fields/src/lib/SliderFormField/SliderFormField.stories.mdx @@ -36,6 +36,7 @@ You can create a SliderFormField using the following function `buildSliderField` code={dedent(` buildSliderField({ id: 'field.id', + title: 'Slider title', label: { singular: 'day', plural: 'days', @@ -62,6 +63,7 @@ The previous configuration object will result in the following component: application={createMockApplication()} field={{ id: 'field.id', + title: 'Slider title', label: { singular: 'day', plural: 'days', diff --git a/libs/application/ui-fields/src/lib/SliderFormField/SliderFormField.tsx b/libs/application/ui-fields/src/lib/SliderFormField/SliderFormField.tsx index c0209d71e0c5..06d6431bf173 100644 --- a/libs/application/ui-fields/src/lib/SliderFormField/SliderFormField.tsx +++ b/libs/application/ui-fields/src/lib/SliderFormField/SliderFormField.tsx @@ -9,7 +9,7 @@ import { Slider } from '@island.is/application/ui-components' import { getValueViaPath } from '@island.is/application/core' import { useLocale } from '@island.is/localization' import { formatText } from '@island.is/application/core' -import { Box } from '@island.is/island-ui/core' +import { Text, Box } from '@island.is/island-ui/core' import { getDefaultValue } from '../../getDefaultValue' type SliderFormFieldProps = { @@ -20,6 +20,27 @@ type SliderFormFieldProps = { export const SliderFormField: FC< React.PropsWithChildren > = ({ application, field }) => { + const { + id, + min, + max, + trackStyle, + calculateCellStyle, + showLabel, + showMinMaxLabels, + showRemainderOverlay, + showProgressOverlay, + showToolTip, + label, + rangeDates, + onChangeEnd, + labelMultiplier, + snap, + step, + saveAsString, + marginTop, + marginBottom, + } = field const { clearErrors, setValue } = useFormContext() const { formatMessage } = useLocale() const computeMax = ( @@ -36,57 +57,49 @@ export const SliderFormField: FC< const finalMax = useMemo( () => computeMax( - field.max as MaybeWithApplicationAndField, + max as MaybeWithApplicationAndField, application, field, ), - [field, application], + [field, max, application], ) return ( - + ( { - clearErrors(field.id) - const value = field.saveAsString ? String(val) : val + clearErrors(id) + const value = saveAsString ? String(val) : val onChange(value) - setValue(field.id, value) + setValue(id, value) }} - onChangeEnd={field.onChangeEnd} - labelMultiplier={field.labelMultiplier} + onChangeEnd={onChangeEnd} + labelMultiplier={labelMultiplier} /> )} /> diff --git a/libs/application/ui-fields/src/lib/TableRepeaterFormField/TableRepeaterItem.tsx b/libs/application/ui-fields/src/lib/TableRepeaterFormField/TableRepeaterItem.tsx index 5388065f176f..a65e25164f5b 100644 --- a/libs/application/ui-fields/src/lib/TableRepeaterFormField/TableRepeaterItem.tsx +++ b/libs/application/ui-fields/src/lib/TableRepeaterFormField/TableRepeaterItem.tsx @@ -216,6 +216,7 @@ export const Item = ({ }} application={application} defaultValue={DefaultValue} + large={true} {...props} />