diff --git a/packages/client/src/components/form/FormFieldGenerator.tsx b/packages/client/src/components/form/FormFieldGenerator.tsx index 822ff4f112b..e555dc453fa 100644 --- a/packages/client/src/components/form/FormFieldGenerator.tsx +++ b/packages/client/src/components/form/FormFieldGenerator.tsx @@ -922,6 +922,7 @@ class FormSectionComponent extends React.Component { offlineCountryConfig, intl, draftData, + userDetails, setValues, dynamicDispatch } = this.props @@ -947,7 +948,8 @@ class FormSectionComponent extends React.Component { field, { ...draftData?.[sectionName], ...values }, offlineCountryConfig, - draftData + draftData, + userDetails ) if (conditionalActions.includes('hide')) { diff --git a/packages/client/src/forms/handlebarHelpers.ts b/packages/client/src/forms/handlebarHelpers.ts new file mode 100644 index 00000000000..fbe2f5286d8 --- /dev/null +++ b/packages/client/src/forms/handlebarHelpers.ts @@ -0,0 +1,30 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * OpenCRVS is also distributed under the terms of the Civil Registration + * & Healthcare Disclaimer located at http://opencrvs.org/license. + * + * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. + */ + +import { referenceApi } from '@client/utils/referenceApi' +import * as Handlebars from 'handlebars' + +export let handlebarHelpers: Record + +export async function initHandlebarHelpers() { + handlebarHelpers = await referenceApi.importHandlebarHelpers() +} + +export function registerHandlebarHelpers() { + if (handlebarHelpers) { + for (const funcName of Object.keys(handlebarHelpers)) { + const func = handlebarHelpers[funcName] + if (typeof func === 'function') { + Handlebars.registerHelper(funcName, func) + } + } + } +} diff --git a/packages/client/src/forms/utils.ts b/packages/client/src/forms/utils.ts index b2a98b73afa..4f32226db0e 100644 --- a/packages/client/src/forms/utils.ts +++ b/packages/client/src/forms/utils.ts @@ -540,7 +540,8 @@ export const getConditionalActionsForField = ( */ values: IFormSectionData, offlineCountryConfig?: IOfflineData, - draftData?: IFormData + draftData?: IFormData, + userDetails?: UserDetails | null ): string[] => { if (!field.conditionals) { return [] diff --git a/packages/client/src/offline/actions.ts b/packages/client/src/offline/actions.ts index 821f018ba02..73a25e1b6f9 100644 --- a/packages/client/src/offline/actions.ts +++ b/packages/client/src/offline/actions.ts @@ -24,7 +24,8 @@ import { IApplicationConfigAnonymous, LoadFormsResponse, LoadValidatorsResponse, - LoadConditionalsResponse + LoadConditionalsResponse, + LoadHandlebarHelpersResponse } from '@client/utils/referenceApi' import { System } from '@client/utils/gateway' import { UserDetails } from '@client/utils/userUtils' @@ -324,6 +325,16 @@ export const validatorsFailed = (error: Error) => ({ payload: error }) +export const handlebarsLoaded = (payload: LoadHandlebarHelpersResponse) => ({ + type: 'OFFLINE/HANDLEBARS_LOADED' as const, + payload: payload +}) + +export const handlebarsFailed = (error: Error) => ({ + type: 'OFFLINE/HANDLEBARS_FAILED' as const, + payload: error +}) + export const conditionalsLoaded = (payload: LoadConditionalsResponse) => ({ type: 'OFFLINE/CONDITIONALS_LOADED' as const, payload: payload @@ -367,3 +378,5 @@ export type Action = | ReturnType | ReturnType | ReturnType + | ReturnType + | ReturnType diff --git a/packages/client/src/offline/reducer.ts b/packages/client/src/offline/reducer.ts index aca26ff9288..f1f532f7c01 100644 --- a/packages/client/src/offline/reducer.ts +++ b/packages/client/src/offline/reducer.ts @@ -46,6 +46,7 @@ import { Action as NotificationAction, configurationErrorNotification } from '@client/notification/actions' +import { initHandlebarHelpers } from '@client/forms/handlebarHelpers' export const OFFLINE_LOCATIONS_KEY = 'locations' export const OFFLINE_FACILITIES_KEY = 'facilities' @@ -236,6 +237,11 @@ const VALIDATORS_CMD = Cmd.run(() => initValidators(), { failActionCreator: actions.validatorsFailed }) +const HANDLEBARS_CMD = Cmd.run(() => initHandlebarHelpers(), { + successActionCreator: actions.handlebarsLoaded, + failActionCreator: actions.handlebarsFailed +}) + const RETRY_TIMEOUT = 5000 function delay(cmd: RunCmd, time: number) { @@ -252,6 +258,7 @@ function getDataLoadingCommands() { CONFIG_CMD, CONDITIONALS_CMD, VALIDATORS_CMD, + HANDLEBARS_CMD, FORMS_CMD, CONTENT_CMD ]) diff --git a/packages/client/src/setupTests.ts b/packages/client/src/setupTests.ts index 6721cad969d..7dd999b23e8 100644 --- a/packages/client/src/setupTests.ts +++ b/packages/client/src/setupTests.ts @@ -111,6 +111,17 @@ vi.mock('@client/forms/validators', async () => { } }) +vi.mock('@client/forms/handlebarHelpers', async () => { + const actual = (await vi.importActual( + '@client/forms/handlebarHelpers' + )) as any + return { + ...actual, + handlebarHelpers: {}, + initHandlebarHelpers: () => Promise.resolve() + } +}) + /* * Initialize mocks */ @@ -181,7 +192,8 @@ vi.doMock( loadConfigAnonymousUser: () => Promise.resolve(mockConfigResponse), loadForms: () => Promise.resolve(mockOfflineData.forms.forms), importConditionals: () => Promise.resolve({}), - importValidators: () => Promise.resolve({}) + importValidators: () => Promise.resolve({}), + importHandlebarHelpers: () => Promise.resolve({}) } }) ) diff --git a/packages/client/src/utils/referenceApi.ts b/packages/client/src/utils/referenceApi.ts index 765d582ab39..dd0340589c5 100644 --- a/packages/client/src/utils/referenceApi.ts +++ b/packages/client/src/utils/referenceApi.ts @@ -203,6 +203,19 @@ export async function importConditionals(): Promise { return conditionals } +export type LoadHandlebarHelpersResponse = Record< + string, + Handlebars.HelperDelegate +> +async function importHandlebarHelpers(): Promise { + // https://github.com/rollup/plugins/tree/master/packages/dynamic-import-vars#limitations + const handlebars = await import( + /* @vite-ignore */ `${window.config.COUNTRY_CONFIG_URL}/handlebars.js` + ) + + return handlebars +} + async function loadContent(): Promise { const url = `${window.config.COUNTRY_CONFIG_URL}/content/client` @@ -345,5 +358,6 @@ export const referenceApi = { loadForms, importValidators, importConditionals, + importHandlebarHelpers, loadConfigAnonymousUser } diff --git a/packages/client/src/views/PrintCertificate/PDFUtils.ts b/packages/client/src/views/PrintCertificate/PDFUtils.ts index 2394ec2fb18..052a4f0b29e 100644 --- a/packages/client/src/views/PrintCertificate/PDFUtils.ts +++ b/packages/client/src/views/PrintCertificate/PDFUtils.ts @@ -31,6 +31,7 @@ import { fetchImageAsBase64 } from '@client/utils/imageUtils' import { getOfflineData } from '@client/offline/selectors' import isValid from 'date-fns/isValid' import format from 'date-fns/format' +import { registerHandlebarHelpers } from '@client/forms/handlebarHelpers' type TemplateDataType = string | MessageDescriptor | Array function isMessageDescriptor( @@ -97,6 +98,7 @@ export function executeHandlebarsTemplate( }, cache ) + registerHandlebarHelpers() Handlebars.registerHelper( 'intl', diff --git a/packages/client/src/views/RegisterForm/RegisterForm.tsx b/packages/client/src/views/RegisterForm/RegisterForm.tsx index c7215c8da5c..619fcad7c60 100644 --- a/packages/client/src/views/RegisterForm/RegisterForm.tsx +++ b/packages/client/src/views/RegisterForm/RegisterForm.tsx @@ -680,6 +680,8 @@ class RegisterFormView extends React.Component { informant = 'mother' } else if (declaration?.data?.informant?.informantType === 'FATHER') { informant = 'father' + } else if (declaration?.data?.informant?.informantType === 'SPOUSE') { + informant = 'spouse' } modifiedDeclaration = { diff --git a/packages/client/src/views/RegisterForm/review/ReviewSection.tsx b/packages/client/src/views/RegisterForm/review/ReviewSection.tsx index ff6b9236ce5..34a8ff0a938 100644 --- a/packages/client/src/views/RegisterForm/review/ReviewSection.tsx +++ b/packages/client/src/views/RegisterForm/review/ReviewSection.tsx @@ -857,7 +857,7 @@ class ReviewSectionComp extends React.Component { } isVisibleField(field: IFormField, section: IFormSection) { - const { draft, offlineCountryConfiguration } = this.props + const { draft, offlineCountryConfiguration, userDetails } = this.props if (field.type === HIDDEN) { return false @@ -867,7 +867,8 @@ class ReviewSectionComp extends React.Component { field, draft.data[section.id] || {}, offlineCountryConfiguration, - draft.data + draft.data, + userDetails ) return ( !conditionalActions.includes('hide') && @@ -1358,6 +1359,7 @@ class ReviewSectionComp extends React.Component { getOverriddenFieldsListForPreview( formSections: IFormSection[] ): IFormField[] { + const { userDetails } = this.props const overriddenFields = formSections .map((section) => { return section.groups @@ -1377,7 +1379,8 @@ class ReviewSectionComp extends React.Component { tempField, draft.data[residingSection] || {}, offlineCountryConfiguration, - draft.data + draft.data, + userDetails ).includes('hide') return isVisible ? field : ({} as IFormField) @@ -1528,7 +1531,7 @@ class ReviewSectionComp extends React.Component { offlineCountryConfiguration: IOfflineData, declaration: IDeclaration ) => { - const { intl, draft } = this.props + const { intl, draft, userDetails } = this.props const overriddenFields = this.getOverriddenFieldsListForPreview(formSections) let tempItem: any @@ -1553,7 +1556,8 @@ class ReviewSectionComp extends React.Component { field, draft.data[section.id] || {}, offlineCountryConfiguration, - draft.data + draft.data, + userDetails ) tempItem = field.previewGroup diff --git a/packages/config/src/handlers/forms/validation.ts b/packages/config/src/handlers/forms/validation.ts index 89e1ba727db..982eca6df7c 100644 --- a/packages/config/src/handlers/forms/validation.ts +++ b/packages/config/src/handlers/forms/validation.ts @@ -182,14 +182,12 @@ const REQUIRED_FIELDS_IN_SECTION: Record = { ], deathEvent: [ 'deathDate', - 'placeOfDeathTitle', 'placeOfDeath', 'deathLocation', ...REQUIRED_EVENT_ADDRESS_FIELDS.map((field) => `${field}Placeofdeath`) ], marriageEvent: [ 'marriageDate', - 'placeOfMarriageTitle', ...REQUIRED_EVENT_ADDRESS_FIELDS.map((field) => `${field}Placeofmarriage`) ], groom: [ @@ -285,10 +283,12 @@ const OPTIONAL_FIELDS_IN_SECTION: Record = { 'causeOfDeathEstablished', 'causeOfDeathMethod', 'deathDescription', + 'placeOfDeathTitle', ...OPTIONAL_EVENT_ADDRESS_FIELDS.map((field) => `${field}Placeofdeath`) ], marriageEvent: [ 'typeOfMarriage', + 'placeOfMarriageTitle', ...OPTIONAL_EVENT_ADDRESS_FIELDS.map((field) => `${field}Placeofmarriage`) ], groom: [ diff --git a/packages/login/src/common/LoginBackgroundWrapper.tsx b/packages/login/src/common/LoginBackgroundWrapper.tsx index 43b77811dcf..1ab81e0c0c2 100644 --- a/packages/login/src/common/LoginBackgroundWrapper.tsx +++ b/packages/login/src/common/LoginBackgroundWrapper.tsx @@ -10,7 +10,7 @@ */ import * as React from 'react' import styled, { css, useTheme } from 'styled-components' - +import { storage } from '@login/storage' import { selectCountryBackground, selectCountryLogo @@ -57,29 +57,36 @@ export interface IProps { export function usePersistentCountryBackground() { const theme = useTheme() - const countryBackground: NonNullable< - ReturnType - > = JSON.parse( - localStorage.getItem('country-background') ?? - `{"backgroundColor" : "${(theme as ITheme).colors.backgroundPrimary}"}` - ) + const [countryBackground, setCountryBackground] = React.useState({ + backgroundColor: `${(theme as ITheme).colors.backgroundPrimary}`, + backgroundImage: '', + imageFit: '' + }) const [offlineBackground, setOfflineBackground] = React.useState(countryBackground) + React.useEffect(() => { + storage.getItem('country-background').then((res) => { + res ?? setCountryBackground(JSON.parse(res)) + }) + }, []) const background = useSelector(selectCountryBackground) if (background && !isEqual(background, offlineBackground)) { setOfflineBackground(background) - localStorage.setItem('country-background', JSON.stringify(background)) + storage.setItem('country-background', JSON.stringify(background)) } return offlineBackground } export function usePersistentCountryLogo() { - const [offlineLogo, setOfflineLogo] = React.useState( - localStorage.getItem('country-logo') ?? '' - ) + const [offlineLogo, setOfflineLogo] = React.useState('') + React.useEffect(() => { + storage.getItem('country-logo').then((res) => { + res ?? setOfflineLogo(res) + }) + }, []) const logo = useSelector(selectCountryLogo) if (logo && logo !== offlineLogo) { setOfflineLogo(logo) diff --git a/packages/login/src/login/selectors.ts b/packages/login/src/login/selectors.ts index 33e8e35ff25..cc850b3050c 100644 --- a/packages/login/src/login/selectors.ts +++ b/packages/login/src/login/selectors.ts @@ -10,6 +10,7 @@ */ import { LoginState } from '@login/login/reducer' import { IStoreState } from '@login/store' +import { ILoginBackground } from '@login/utils/authApi' import * as React from 'react' import { useSelector } from 'react-redux' const getPartialState = (store: IStoreState): LoginState => store.login @@ -36,16 +37,25 @@ export function selectCountryLogo(store: IStoreState) { return getKey(store, 'config').COUNTRY_LOGO?.file } -export function selectCountryBackground(store: IStoreState) { +export function selectCountryBackground(store: IStoreState): ILoginBackground { const countryBackground = getKey(store, 'config').LOGIN_BACKGROUND if (countryBackground?.backgroundImage) { return { + backgroundColor: '', backgroundImage: countryBackground.backgroundImage, imageFit: countryBackground.imageFit } } else if (countryBackground?.backgroundColor) { return { - backgroundColor: countryBackground?.backgroundColor + backgroundColor: countryBackground?.backgroundColor, + backgroundImage: '', + imageFit: '' + } + } else { + return { + backgroundColor: '', + backgroundImage: '', + imageFit: '' } } }