diff --git a/components/case/representation/representationQuestionnaires/representationLPAQuestionnaire_enforcement.js b/components/case/representation/representationQuestionnaires/representationLPAQuestionnaire_enforcement.js index b0fc7a30..a36b3447 100644 --- a/components/case/representation/representationQuestionnaires/representationLPAQuestionnaire_enforcement.js +++ b/components/case/representation/representationQuestionnaires/representationLPAQuestionnaire_enforcement.js @@ -1228,7 +1228,7 @@ let Enforcement_Questionnaire = (props) => { label={t("myrepresentations:enf-section-question-date")} component={RenderDatePicker} validate={[required]} - errorMsg="Select a date" + errorMsg={t("newappeal:select-a-date-label")} dateStart="0" dateEnd="1-y" /> diff --git a/components/elements/index.js b/components/elements/index.js index 800d187d..b96f491d 100644 --- a/components/elements/index.js +++ b/components/elements/index.js @@ -116,8 +116,6 @@ export function Textfield(props) { maxFieldLength, hint, } = props; - // const required = (value) => - // value ? undefined : t("newappeal:is-required-label"); const required = (value) => { let errors; @@ -239,7 +237,7 @@ export function Textfield(props) { className="govuk-input govuk-input--width-20" validate={eval(validation)} label={props.label} - errorMsg="Is required" + errorMsg={t("newappeal:is-required-label")} maxFieldLength={props.maxFieldLength} /> )} @@ -319,14 +317,14 @@ export function MultiLinefield(props) { // Check for the required field if (!value) { - errors = "This field is required"; + errors = t("newappeal:is-required-label"); } else { // Check for emojis using a regular expression const emojiRegex = /[\u{1F600}-\u{1F64F}\u{1F300}-\u{1F5FF}\u{1F680}-\u{1F6FF}\u{1F700}-\u{1F77F}\u{1F780}-\u{1F7FF}\u{1F800}-\u{1F8FF}\u{1F900}-\u{1F9FF}\u{1FA00}-\u{1FA6F}\u{1FA70}-\u{1FAFF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{2300}-\u{23FF}\u{2B50}\u{1F004}-\u{1F0CF}\u{1F0A0}-\u{1F0A5}\u{1F170}-\u{1F251}]/gu; if (emojiRegex.test(value)) { - errors = "Emojis are not allowed"; + errors = t("newappeal:emojis-not-allowed-label"); } } @@ -492,7 +490,8 @@ export function RichMultiLinefield(props) { validation, maxFieldLength, } = props; - const required = (value) => (value ? undefined : "Is required"); + const required = (value) => + value ? undefined : t("newappeal:is-required-label"); var showIfHasParentShowValue = parentField != false && @@ -702,6 +701,7 @@ export function DateFieldPicker(props) { dateStart, dateEnd, } = props; + let { t } = useTranslation(); const required = (value) => (value ? undefined : "Required"); var showIfHasParentShowValue = @@ -720,7 +720,7 @@ export function DateFieldPicker(props) { label={label} component={RenderDatePicker} validate={eval(validation)} - errorMsg="Select a date" + errorMsg={t("newappeal:select-a-date-label")} dateStart={dateStart} dateEnd={dateEnd} hint={props.hint} @@ -733,7 +733,7 @@ export function DateFieldPicker(props) { label={label} component={RenderDatePicker} validate={eval(validation)} - errorMsg="Select a date" + errorMsg={t("newappeal:select-a-date-label")} dateStart={dateStart} dateEnd={dateEnd} hint={props.hint} @@ -1318,7 +1318,9 @@ const RenderPickList = ({ dropdownObj = dropdownObj[0]; const required = (value) => { - return value || value == 0 ? undefined : "Is required"; + return value || value == 0 + ? undefined + : t("newappeal:is-required-label"); }; return ( @@ -1363,7 +1365,9 @@ export function PickList(props) { const { name, label, datafieldname, hint } = props; let { t } = useTranslation(); const required = (value) => { - return value || value == 0 ? undefined : "Is required"; + return value || value == 0 + ? undefined + : t("newappeal:is-required-label"); }; return ( ); @@ -1391,12 +1395,19 @@ export function NumericField(props) { parentField, maxFieldLength, } = props; + + let { t } = useTranslation(); + const required = (value) => { - return value || value == 0 ? undefined : "Is required"; + return value || value == 0 + ? undefined + : t("newappeal:is-required-label"); }; const isNumber = (value) => { const regex = /^\d*\.?\d{0,1}$/; - return regex.test(value) ? undefined : "Should be a number"; + return regex.test(value) + ? undefined + : t("newappeal:invalid-number-label"); }; const maxLength = (max) => (value) => @@ -1495,11 +1506,17 @@ export function DecimalField(props) { parentField, maxFieldLength, } = props; - const required = (value) => (value ? undefined : "Is required"); + + let { t } = useTranslation(); + + const required = (value) => + value ? undefined : t("newappeal:is-required-label"); const isNumber = (value) => { const regex = /^\d*\.?\d{0,1}$/; - return regex.test(value) ? undefined : "Should be a number or decimal"; + return regex.test(value) + ? undefined + : t("newappeal:invalid-number-label"); }; const maxLength = (max) => (value) => @@ -1521,6 +1538,24 @@ export function DecimalField(props) { //console.log("parentField:", parentField, showIfHasParentShowValue); + const normalizeDecimal = (value) => { + return value ? parseFloat(value) : value; + }; + + const validateDecimal = (value) => { + if (!value) return t("newappeal:is-required-label"); + + // Regex to match whole numbers or decimal numbers (up to 7 characters including decimal) + const regex = /^\d{1,6}(\.\d{1,2})?$/; // Up to 6 digits before the decimal, 2 after + + // Check if the value matches the regex pattern + if (!regex.test(value)) { + return t("newappeal:invalid-decimal-label"); + } + + return undefined; + }; + return ( <> {parentField != false ? ( @@ -1557,7 +1592,7 @@ export function DecimalField(props) { pattern="^\d*\.?\d+$" // inputMode="numeric" // parse={Number} - validate={validateDecimal} + validate={[validateDecimal]} component={RenderDecimalField} label={props.label} maxFieldLength={props.maxFieldLength} @@ -1569,24 +1604,6 @@ export function DecimalField(props) { ); } -const normalizeDecimal = (value) => { - return value ? parseFloat(value) : value; -}; - -const validateDecimal = (value) => { - if (!value) return "This field is required"; - - // Regex to match whole numbers or decimal numbers (up to 7 characters including decimal) - const regex = /^\d{1,6}(\.\d{1,2})?$/; // Up to 6 digits before the decimal, 2 after - - // Check if the value matches the regex pattern - if (!regex.test(value)) { - return "Invalid number (max 7 characters, 2 decimal places)"; - } - - return undefined; -}; - const RenderDecimalField = ({ name, id, @@ -2010,7 +2027,9 @@ const RenderFileUpload = (field) => { )} )} - {blobList.length > 0 &&

Previously added files

} + {blobList.length > 0 && ( +

{t("newappeal:previously-added-files")}

+ )} {blobList.map( (blob, i) => blob.documentType == field.documentTypeCode && ( diff --git a/components/newappeal/buildfield.js b/components/newappeal/buildfield.js index 6b2e6eb8..16c5590a 100644 --- a/components/newappeal/buildfield.js +++ b/components/newappeal/buildfield.js @@ -96,7 +96,8 @@ export default function BuildField(props) { ); break; case "{4273EDBD-AC1D-40d3-9FB2-095C621B552D}": - const required = (value) => (value ? undefined : "Is required"); + const required = (value) => + value ? undefined : t("newappeal:is-required-label"); return ( { diff --git a/components/newappeal/buildsection.js b/components/newappeal/buildsection.js index 57da936f..31fd9f3d 100644 --- a/components/newappeal/buildsection.js +++ b/components/newappeal/buildsection.js @@ -626,12 +626,14 @@ const mapStateToProps = (state, ownProps) => { formData: state.formData, appealType: state.appealType, initialValues: state.appealType.caseReference.caseDetails, + currentView: state.currentView, } : { search: state.search, searchResultsObj: state.searchResultsObj, formData: state.formData, appealType: state.appealType, + currentView: state.currentView, //form: state.form, }; }; @@ -658,22 +660,28 @@ const mapDispatchToProps = (dispatch) => { const selector = formValueSelector("appealForm"); // <-- same as form name -const validate = (values) => { +const validate = (values, props) => { const errors = {}; const strippedHtml = (htmlStr) => { return htmlStr.replace(/(<([^>]+)>)/gi, "").trim(); }; + const locale = props.currentView.locale; + if (values.hasOwnProperty("pinswg_attachsoc")) { if (values.pinswg_attachsoc == "false") { if (values.hasOwnProperty("pinswg_statementofcase")) { if (strippedHtml(values.pinswg_statementofcase).length == 0) errors.pinswg_statementofcase = - "Submit your Statement of Case or select Yes to attach a separate Statement of Case Document"; + locale != "cy" + ? "Submit your Statement of Case or select Yes to attach a separate Statement of Case Document" + : "Cyflwynwch eich Datganiad Achos neu dewiswch Ie i atodi Dogfen Datganiad Achos ar wahân"; } else { errors.pinswg_statementofcase = - "Submit your Statement of Case or select Yes to attach a separate Statement of Case Document"; + locale != "cy" + ? "Submit your Statement of Case or select Yes to attach a separate Statement of Case Document" + : "Cyflwynwch eich Datganiad Achos neu dewiswch Ie i atodi Dogfen Datganiad Achos ar wahân"; } } } @@ -684,10 +692,14 @@ const validate = (values) => { if (values.hasOwnProperty("pinswg_costapplication")) { if (strippedHtml(values.pinswg_costapplication).length == 0) errors.pinswg_costapplication = - "Submit a Costs application or select Yes to attach a separate Costs application"; + locale != "cy" + ? "Submit a Costs application or select Yes to attach a separate Costs application" + : "Cyflwyno cais Costau neu ddewis Ie i atodi cais Costau ar wahân"; } else { errors.pinswg_costapplication = - "Submit a Costs application or select Yes to attach a separate Costs application"; + locale != "cy" + ? "Submit a Costs application or select Yes to attach a separate Costs application" + : "Cyflwyno cais Costau neu ddewis Ie i atodi cais Costau ar wahân"; } } } diff --git a/data/formsxml/planningappeals78.xml b/data/formsxml/planningappeals78.xml index bbc92d95..152b63bf 100644 --- a/data/formsxml/planningappeals78.xml +++ b/data/formsxml/planningappeals78.xml @@ -46,7 +46,7 @@ - + @@ -142,7 +142,7 @@ - + @@ -286,7 +286,7 @@ - + @@ -316,13 +316,13 @@ - + - + diff --git a/locales/cy/newappeal.json b/locales/cy/newappeal.json index dd99e3ac..dee9e91f 100644 --- a/locales/cy/newappeal.json +++ b/locales/cy/newappeal.json @@ -16,6 +16,9 @@ "appeal-confirmation-label": "Cadarnhaf fy mod yn gwneud apêl yn erbyn penderfyniad i wrthod caniatâd cynllunio neu fethiant i benderfynu ar gais cynllunio o fewn 8 wythnos.", "select-to-confirm-label": "Cadarnhewch i barhau", "is-required-label": "Yn ofynnol", + "select-a-date-label": "Dewiswch ddyddiad", + "invalid-decimal-label": "Rhif annilys (uchafswm o 7 nod, 2 le degol)", + "invalid-number-label": "Dylai fod yn rhif", "emojis-not-allowed-label": "Ni chaniateir emojis", "invalid-postcode-label": "Cod post annilys", "hascas-confirmation-label": "Cadarnhewch fod angen apêl HAS/CAS", @@ -99,5 +102,14 @@ "new-appeal-complete-back-link": "Yn ôl i Fy Mhorth", "new-appeal-multiline-pid": "Peidiwch â chynnwys gwybodaeth bersonol nac ariannol", "new-appeal-statement-case-error": "Cyflwynwch eich Datganiad Achos neu dewiswch Ie i atodi Dogfen Datganiad Achos ar wahân", - "new-appeal-costs-application-error": "Cyflwyno cais Costau neu ddewis Ie i atodi cais Costau ar wahân" + "new-appeal-costs-application-error": "Cyflwyno cais Costau neu ddewis Ie i atodi cais Costau ar wahân", + "application-form": "Ffurflen gais", + "description-of-development-change-confirmation": "Disgrifiad o gadarnhad newid datblygiad", + "relevant-ownership-certificate": "Tystysgrif Perchnogaeth Berthnasol", + "statement-of-case": "Datganiad Achos", + "cost-application": "Cais Cost", + "planning-obligation-(s106/unilateral)": "Rhwymedigaeth gynllunio (S106/Unochrog)", + "site-location-plan": "Cynllun Lleoliad Safle", + "registration-of-application-letter": "Llythyr Cofrestru Cais", + "previously-added-files": "Ffeiliau a ychwanegwyd yn flaenorol" } \ No newline at end of file diff --git a/locales/en/newappeal.json b/locales/en/newappeal.json index 68abca98..1c6b73ce 100644 --- a/locales/en/newappeal.json +++ b/locales/en/newappeal.json @@ -1,5 +1,6 @@ { "viewall-link": "View all", + "viewall-link-count": "View all {{count}} cases", "parent-page-title": "My portal", "page-title": "New appeal", "select-lpa-label": "Select your Local Planning Authority (LPA)", @@ -15,6 +16,9 @@ "appeal-confirmation-label": "I confirm that I am making an appeal against a refusal of planning permission or failure to determine a planning application within 8 weeks.", "select-to-confirm-label": "Confirm to continue", "is-required-label": "Is required", + "select-a-date-label": "Select a date", + "invalid-decimal-label": "Invalid number (max 7 characters, 2 decimal places)", + "invalid-number-label": "Should be a number", "emojis-not-allowed-label": "Emojis are not allowed", "invalid-postcode-label": "Invalid postcode", "hascas-confirmation-label": "Confirm HAS/CAS appeal is required", @@ -98,5 +102,14 @@ "new-appeal-complete-back-link": "Back to My Portal", "new-appeal-multiline-pid": "Do not include personal or financial information.", "new-appeal-statement-case-error": "Submit your Statement of Case or select Yes to attach a separate Statement of Case Document", - "new-appeal-costs-application-error": "Submit a Costs application or select Yes to attach a separate Costs application" + "new-appeal-costs-application-error": "Submit a Costs application or select Yes to attach a separate Costs application", + "application-form": "Application form", + "description-of-development-change-confirmation": "Description of development change confirmation", + "relevant-ownership-certificate": "Relevant Ownership certificate", + "statement-of-case": "Statement of Case", + "cost-application": "Cost Application", + "planning-obligation-(s106/unilateral)": "Planning obligation (S106/Unilateral)", + "site-location-plan": "Site Location Plan", + "registration-of-application-letter": "Registration of Application letter", + "previously-added-files": "Previously added files" } \ No newline at end of file diff --git a/pages/myportal/[appealtypes].js b/pages/myportal/[appealtypes].js index 432849b8..c4975bb5 100644 --- a/pages/myportal/[appealtypes].js +++ b/pages/myportal/[appealtypes].js @@ -44,7 +44,7 @@ import { setAwaitingSubmissionDetails, setAwaitingSubmissionFromBlob, } from "../../store/awaitingSubmission/action"; -import { setCurrentView } from "../../store/currentView/action"; +import { setCurrentView, setLocale } from "../../store/currentView/action"; import { setForm } from "../../store/formData/action"; import { wrapper } from "../../store/store"; import ServiceBanner from "../../components/myportal/servicebanner"; @@ -238,6 +238,9 @@ export const getServerSideProps = wrapper.getServerSideProps( }; } + console.log(ctx.locale); + store.dispatch(setLocale(ctx.locale)); + let loggedInUserIdent = thisSession.user.id; let loggedInUserEmail = thisSession.user.email; diff --git a/pages/myportal/index.js b/pages/myportal/index.js index b8eb5d63..03cd5f14 100644 --- a/pages/myportal/index.js +++ b/pages/myportal/index.js @@ -34,7 +34,7 @@ import { setAwaitingSubmissionDetails, setAwaitingSubmissionFromBlob, } from "../../store/awaitingSubmission/action"; -import { setShowReps } from "../../store/currentView/action"; +import { setShowReps, setLocale } from "../../store/currentView/action"; import { setMyCases, setMyCasesDetails } from "../../store/myCases/action"; import { setMyRepresentations, @@ -260,6 +260,8 @@ export const getServerSideProps = wrapper.getServerSideProps( console.log("req locale :", ctx.locale); + store.dispatch(setLocale(ctx.locale)); + const preferredLocale = accountDetails.pinswg_preferredlanguage == "846040000" ? "cy" diff --git a/store/currentView/action.js b/store/currentView/action.js index 95dadefb..4e263ffa 100644 --- a/store/currentView/action.js +++ b/store/currentView/action.js @@ -11,6 +11,7 @@ export const currentViewActionTypes = { SETREPRESENTATIONMESSAGESENT: "SETREPRESENTATIONMESSAGESENT", SETREPRESENTATIONRESET: "SETREPRESENTATIONRESET", SETSHOWLOGIN: "SETSHOWLOGIN", + SETLOCALE: "SETLOCALE", }; export const getCurrentViewObj = () => (dispatch) => { @@ -102,3 +103,10 @@ export const setShowReps = (showReps, showLogin) => (dispatch) => { showReps: showReps, }); }; + +export const setLocale = (locale) => (dispatch) => { + return dispatch({ + type: currentViewActionTypes.SETLOCALE, + locale: locale, + }); +}; diff --git a/store/currentView/reducer.js b/store/currentView/reducer.js index e2bff8ac..41f10da2 100644 --- a/store/currentView/reducer.js +++ b/store/currentView/reducer.js @@ -11,6 +11,7 @@ const currentViewInitialState = { currentPage: 1, showReps: false, showLogin: false, + locale: "", }; export default function reducer(state = currentViewInitialState, action) { @@ -72,6 +73,11 @@ export default function reducer(state = currentViewInitialState, action) { showReps: action.showReps, showLogin: action.showLogin, }; + case currentViewActionTypes.SETLOCALE: + return { + ...state, + locale: action.locale, + }; default: return state; }