22500 Phase 1: extract pure helpers from elements index
This commit is contained in:
@@ -0,0 +1,110 @@
|
|||||||
|
export const getThumbnailIconByMimeType = (fileType) => {
|
||||||
|
switch (fileType) {
|
||||||
|
case "text/html":
|
||||||
|
return "/assets/images/documenttypes/html.png";
|
||||||
|
case "text/plain":
|
||||||
|
return "/assets/images/documenttypes/txt.png";
|
||||||
|
case "application/msword":
|
||||||
|
return "/assets/images/documenttypes/doc.png";
|
||||||
|
case "application/pdf":
|
||||||
|
return "/assets/images/documenttypes/pdf.png";
|
||||||
|
case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
||||||
|
return "/assets/images/documenttypes/docx.png";
|
||||||
|
case "text/csv":
|
||||||
|
return "/assets/images/documenttypes/csv.png";
|
||||||
|
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
|
||||||
|
return "/assets/images/documenttypes/xlsx.png";
|
||||||
|
case "image/jpeg":
|
||||||
|
return "/assets/images/documenttypes/jpg.png";
|
||||||
|
case "image/png":
|
||||||
|
return "/assets/images/documenttypes/png.png";
|
||||||
|
default:
|
||||||
|
return "/assets/images/documenttypes/default.png";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDocumentTypePrefix = (docCode) => {
|
||||||
|
const repDate = new Date();
|
||||||
|
|
||||||
|
let day = repDate.getDate();
|
||||||
|
let month = repDate.getMonth() + 1;
|
||||||
|
let year = repDate.getFullYear();
|
||||||
|
|
||||||
|
const pdfFileNameDateStamp =
|
||||||
|
year + "-" + ("0" + month).slice(-2) + "-" + ("0" + day).slice(-2);
|
||||||
|
|
||||||
|
switch (docCode) {
|
||||||
|
case "000000":
|
||||||
|
return pdfFileNameDateStamp + "_-_Statement_of_Case";
|
||||||
|
case "000001":
|
||||||
|
return pdfFileNameDateStamp + "_-_Application_Form";
|
||||||
|
case "000002":
|
||||||
|
return pdfFileNameDateStamp + "_-_Site_Ownership_Certificate";
|
||||||
|
case "000003":
|
||||||
|
return pdfFileNameDateStamp + "_-_Decision_Notice";
|
||||||
|
case "000004":
|
||||||
|
return pdfFileNameDateStamp + "_-_Site_Location_Plan";
|
||||||
|
case "000005":
|
||||||
|
return pdfFileNameDateStamp + "_-_Plans_Drawing_Documents";
|
||||||
|
case "000006":
|
||||||
|
return (
|
||||||
|
pdfFileNameDateStamp + "_-_Additional_Plans_Drawings_Documents"
|
||||||
|
);
|
||||||
|
case "000007":
|
||||||
|
return pdfFileNameDateStamp + "_-_Design_and_Access_Statement";
|
||||||
|
case "000008":
|
||||||
|
return pdfFileNameDateStamp + "_-_NSB_LPA_Additional_Documents";
|
||||||
|
case "000009":
|
||||||
|
return pdfFileNameDateStamp + "_-_LPA_Correspondence";
|
||||||
|
case "000010":
|
||||||
|
return pdfFileNameDateStamp + "_-_LPA_Original_Permission";
|
||||||
|
case "000011":
|
||||||
|
return pdfFileNameDateStamp + "_-_LPA's_Registration_Letter";
|
||||||
|
case "000012":
|
||||||
|
return pdfFileNameDateStamp + "_-_Environmental_Statement";
|
||||||
|
case "000013":
|
||||||
|
return pdfFileNameDateStamp + "_-_Cost_of_Application";
|
||||||
|
case "000014":
|
||||||
|
return pdfFileNameDateStamp + "_-_Other_Relevant_Material";
|
||||||
|
case "000015":
|
||||||
|
return (
|
||||||
|
pdfFileNameDateStamp +
|
||||||
|
"_-_S106_Agreement_or_Unilateral_Undertaking"
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
return pdfFileNameDateStamp + "_-_000000_-_";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const FILENAME_ALLOWED = /^[A-Za-z0-9 ._\-:()—']+$/;
|
||||||
|
|
||||||
|
export const validateUploadFilename = (
|
||||||
|
file,
|
||||||
|
t,
|
||||||
|
invalidFilenameLabelKey = "newappeal:new-appeal-fileupload-file-error-invalid-filename-label"
|
||||||
|
) => {
|
||||||
|
const name = file.name;
|
||||||
|
|
||||||
|
if (name.includes("#")) {
|
||||||
|
return {
|
||||||
|
code: "filename-invalid-chars",
|
||||||
|
message: `${name} ${t(invalidFilenameLabelKey)}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FILENAME_ALLOWED.test(name)) {
|
||||||
|
return {
|
||||||
|
code: "filename-invalid-chars",
|
||||||
|
message: `${name} ${t(invalidFilenameLabelKey)}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/[<>"/\\|?*]/.test(name)) {
|
||||||
|
return {
|
||||||
|
code: "filename-invalid-chars",
|
||||||
|
message: `${name} ${t(invalidFilenameLabelKey)}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { JSONPath as jsonpath } from "jsonpath-plus";
|
||||||
|
|
||||||
|
export const getFieldTranslation = ({
|
||||||
|
label,
|
||||||
|
locale,
|
||||||
|
appealtypes,
|
||||||
|
fieldLookup
|
||||||
|
}) => {
|
||||||
|
let formObj = jsonpath({
|
||||||
|
path: "$['" + appealtypes + "']",
|
||||||
|
json: fieldLookup,
|
||||||
|
eval: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return locale == "cy"
|
||||||
|
? jsonpath({
|
||||||
|
path: '$..[?(@ && @.value=="' + label + '")].value_cy',
|
||||||
|
json: formObj,
|
||||||
|
eval: true
|
||||||
|
})[0]
|
||||||
|
: label;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPickListTranslation = ({
|
||||||
|
optionValue,
|
||||||
|
locale,
|
||||||
|
pickListLookup
|
||||||
|
}) => {
|
||||||
|
return locale == "cy"
|
||||||
|
? jsonpath({
|
||||||
|
path: '$..[?(@ && @.value=="' + optionValue + '")].value_cy',
|
||||||
|
json: pickListLookup,
|
||||||
|
eval: true
|
||||||
|
})
|
||||||
|
: optionValue;
|
||||||
|
};
|
||||||
+19
-174
@@ -42,6 +42,15 @@ const ReactQuill = dynamic(() => import("react-quill-new"), { ssr: false });
|
|||||||
import { useStore as store, useSelector } from "react-redux";
|
import { useStore as store, useSelector } from "react-redux";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
import { validateField } from "./validationUtils"; // Import the validation function
|
import { validateField } from "./validationUtils"; // Import the validation function
|
||||||
|
import {
|
||||||
|
getDocumentTypePrefix,
|
||||||
|
getThumbnailIconByMimeType,
|
||||||
|
validateUploadFilename
|
||||||
|
} from "./helpers/fileUploadHelpers";
|
||||||
|
import {
|
||||||
|
getFieldTranslation,
|
||||||
|
getPickListTranslation
|
||||||
|
} from "./helpers/translationHelpers";
|
||||||
|
|
||||||
const RenderTextfield = ({
|
const RenderTextfield = ({
|
||||||
id,
|
id,
|
||||||
@@ -1876,41 +1885,20 @@ export const FieldsTranslations = (label) => {
|
|||||||
const { locale } = router;
|
const { locale } = router;
|
||||||
const { appealtypes } = router.query;
|
const { appealtypes } = router.query;
|
||||||
|
|
||||||
let formObj = jsonpath({
|
return getFieldTranslation({
|
||||||
path: "$['" + appealtypes + "']",
|
label,
|
||||||
json: fieldLookup,
|
locale,
|
||||||
eval: true
|
appealtypes,
|
||||||
|
fieldLookup
|
||||||
});
|
});
|
||||||
|
|
||||||
let labelTrans =
|
|
||||||
router.locale == "cy"
|
|
||||||
? jsonpath({
|
|
||||||
path: '$..[?(@ && @.value=="' + label + '")].value_cy',
|
|
||||||
json: formObj,
|
|
||||||
eval: true
|
|
||||||
})[0]
|
|
||||||
: label;
|
|
||||||
|
|
||||||
//labelTrans = labelTrans.length > 1 ? labelTrans[0] : labelTrans;
|
|
||||||
|
|
||||||
return labelTrans;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const PickListTranslations = (optionValue) => {
|
const PickListTranslations = (optionValue) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { locale } = router;
|
const { locale } = router;
|
||||||
const { appealtypes } = router.query;
|
const { appealtypes } = router.query;
|
||||||
//console.log(optionValue);
|
|
||||||
let optionTrans =
|
return getPickListTranslation({ optionValue, locale, pickListLookup });
|
||||||
router.locale == "cy"
|
|
||||||
? jsonpath({
|
|
||||||
path: '$..[?(@ && @.value=="' + optionValue + '")].value_cy',
|
|
||||||
json: pickListLookup,
|
|
||||||
eval: true
|
|
||||||
})
|
|
||||||
: optionValue;
|
|
||||||
//console.log(optionTrans, optionValue);
|
|
||||||
return optionTrans;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function FileUploadField(props) {
|
export function FileUploadField(props) {
|
||||||
@@ -1999,111 +1987,6 @@ const RenderFileUpload = (field) => {
|
|||||||
</div>
|
</div>
|
||||||
));
|
));
|
||||||
|
|
||||||
const getThumbnailIcon = (fileObj, fileType) => {
|
|
||||||
switch (fileType) {
|
|
||||||
case "text/html":
|
|
||||||
return "/assets/images/documenttypes/html.png";
|
|
||||||
break;
|
|
||||||
case "text/plain":
|
|
||||||
return "/assets/images/documenttypes/txt.png";
|
|
||||||
break;
|
|
||||||
case "application/msword":
|
|
||||||
return "/assets/images/documenttypes/doc.png";
|
|
||||||
break;
|
|
||||||
case "application/pdf":
|
|
||||||
return "/assets/images/documenttypes/pdf.png";
|
|
||||||
break;
|
|
||||||
case "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
|
||||||
return "/assets/images/documenttypes/docx.png";
|
|
||||||
break;
|
|
||||||
case "text/csv":
|
|
||||||
return "/assets/images/documenttypes/csv.png";
|
|
||||||
break;
|
|
||||||
case "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
|
|
||||||
return "/assets/images/documenttypes/xlsx.png";
|
|
||||||
break;
|
|
||||||
case "image/jpeg":
|
|
||||||
return "/assets/images/documenttypes/jpg.png";
|
|
||||||
case "image/png":
|
|
||||||
return "/assets/images/documenttypes/png.png";
|
|
||||||
//return URL.createObjectURL(fileObj);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return "/assets/images/documenttypes/default.png";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDocumentType = (docCode) => {
|
|
||||||
const repDate = new Date();
|
|
||||||
|
|
||||||
let day = repDate.getDate();
|
|
||||||
let month = repDate.getMonth() + 1;
|
|
||||||
let year = repDate.getFullYear();
|
|
||||||
|
|
||||||
const pdfFileNameDateStamp =
|
|
||||||
year + "-" + ("0" + month).slice(-2) + "-" + ("0" + day).slice(-2);
|
|
||||||
|
|
||||||
switch (docCode) {
|
|
||||||
case "000000":
|
|
||||||
return pdfFileNameDateStamp + "_-_Statement_of_Case";
|
|
||||||
case "000001":
|
|
||||||
return pdfFileNameDateStamp + "_-_Application_Form";
|
|
||||||
break;
|
|
||||||
case "000002":
|
|
||||||
return pdfFileNameDateStamp + "_-_Site_Ownership_Certificate";
|
|
||||||
break;
|
|
||||||
case "000003":
|
|
||||||
return pdfFileNameDateStamp + "_-_Decision_Notice";
|
|
||||||
break;
|
|
||||||
case "000004":
|
|
||||||
return pdfFileNameDateStamp + "_-_Site_Location_Plan";
|
|
||||||
break;
|
|
||||||
case "000005":
|
|
||||||
return pdfFileNameDateStamp + "_-_Plans_Drawing_Documents";
|
|
||||||
break;
|
|
||||||
case "000006":
|
|
||||||
return (
|
|
||||||
pdfFileNameDateStamp +
|
|
||||||
"_-_Additional_Plans_Drawings_Documents"
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
case "000007":
|
|
||||||
return pdfFileNameDateStamp + "_-_Design_and_Access_Statement";
|
|
||||||
break;
|
|
||||||
case "000008":
|
|
||||||
return pdfFileNameDateStamp + "_-_NSB_LPA_Additional_Documents";
|
|
||||||
break;
|
|
||||||
case "000009":
|
|
||||||
return pdfFileNameDateStamp + "_-_LPA_Correspondence";
|
|
||||||
break;
|
|
||||||
case "000010":
|
|
||||||
return pdfFileNameDateStamp + "_-_LPA_Original_Permission";
|
|
||||||
break;
|
|
||||||
case "000011":
|
|
||||||
return pdfFileNameDateStamp + "_-_LPA's_Registration_Letter";
|
|
||||||
break;
|
|
||||||
case "000012":
|
|
||||||
return pdfFileNameDateStamp + "_-_Environmental_Statement";
|
|
||||||
break;
|
|
||||||
case "000013":
|
|
||||||
return pdfFileNameDateStamp + "_-_Cost_of_Application";
|
|
||||||
break;
|
|
||||||
case "000014":
|
|
||||||
return pdfFileNameDateStamp + "_-_Other_Relevant_Material";
|
|
||||||
break;
|
|
||||||
case "000015":
|
|
||||||
return (
|
|
||||||
pdfFileNameDateStamp +
|
|
||||||
"_-_S106_Agreement_or_Unilateral_Undertaking"
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return pdfFileNameDateStamp + "_-_000000_-_";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const filelistObj = field.fileList || {};
|
const filelistObj = field.fileList || {};
|
||||||
|
|
||||||
const blobList = jsonpath({
|
const blobList = jsonpath({
|
||||||
@@ -2187,43 +2070,6 @@ const RenderFileUpload = (field) => {
|
|||||||
setRejectedFiles([]); // Clear any previously rejected file errorsss
|
setRejectedFiles([]); // Clear any previously rejected file errorsss
|
||||||
};
|
};
|
||||||
|
|
||||||
const FILENAME_ALLOWED = /^[A-Za-z0-9 ._\-:()—']+$/;
|
|
||||||
|
|
||||||
const validateFilename = (file, t) => {
|
|
||||||
const name = file.name;
|
|
||||||
|
|
||||||
// block #
|
|
||||||
if (name.includes("#")) {
|
|
||||||
return {
|
|
||||||
code: "filename-invalid-chars",
|
|
||||||
message: `${name} ${t(
|
|
||||||
"newappeal:new-appeal-fileupload-file-error-invalid-filename-label"
|
|
||||||
)}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!FILENAME_ALLOWED.test(name)) {
|
|
||||||
return {
|
|
||||||
code: "filename-invalid-chars",
|
|
||||||
message: `${name} ${t(
|
|
||||||
"newappeal:new-appeal-fileupload-file-error-invalid-filename-label"
|
|
||||||
)}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// block characters < > " / \ | ? *
|
|
||||||
if (/[<>"/\\|?*]/.test(name)) {
|
|
||||||
return {
|
|
||||||
code: "filename-invalid-chars",
|
|
||||||
message: `${name} ${t(
|
|
||||||
"newappeal:new-appeal-fileupload-file-error-invalid-filename-label"
|
|
||||||
)}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null; // valid
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Dropzone
|
<Dropzone
|
||||||
@@ -2240,7 +2086,7 @@ const RenderFileUpload = (field) => {
|
|||||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet":
|
||||||
[".xlsx"]
|
[".xlsx"]
|
||||||
}}
|
}}
|
||||||
validator={(file) => validateFilename(file, t)}
|
validator={(file) => validateUploadFilename(file, t)}
|
||||||
onDrop={(acceptedFiles, fileRejections, e) => {
|
onDrop={(acceptedFiles, fileRejections, e) => {
|
||||||
// build + display rejected messages (ALWAYS, even if some accepted)
|
// build + display rejected messages (ALWAYS, even if some accepted)
|
||||||
if (fileRejections?.length) {
|
if (fileRejections?.length) {
|
||||||
@@ -2266,7 +2112,7 @@ const RenderFileUpload = (field) => {
|
|||||||
(file) =>
|
(file) =>
|
||||||
new File(
|
new File(
|
||||||
[file],
|
[file],
|
||||||
`${getDocumentType(field.documentTypeCode)}_-_${
|
`${getDocumentTypePrefix(field.documentTypeCode)}_-_${
|
||||||
file.name
|
file.name
|
||||||
}`,
|
}`,
|
||||||
{ type: file.type }
|
{ type: file.type }
|
||||||
@@ -2434,8 +2280,7 @@ const RenderFileUpload = (field) => {
|
|||||||
−
|
−
|
||||||
</span> */}
|
</span> */}
|
||||||
<img
|
<img
|
||||||
src={getThumbnailIcon(
|
src={getThumbnailIconByMimeType(
|
||||||
file,
|
|
||||||
file.type
|
file.type
|
||||||
)}
|
)}
|
||||||
alt={file.name}
|
alt={file.name}
|
||||||
|
|||||||
@@ -18,6 +18,40 @@ Follow-ups:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### CL-00X: 22500 `components/elements/index.js` Phase 1 helper extraction
|
||||||
|
|
||||||
|
date: 2026-04-07
|
||||||
|
author: Cline
|
||||||
|
scope: `components/elements/index.js`, `components/elements/helpers/fileUploadHelpers.js`, `components/elements/helpers/translationHelpers.js`, `memory-bank/refactor-backlog.md`
|
||||||
|
type: change
|
||||||
|
rationale: Execute Phase 1 of the approved `components/elements/index.js` decomposition plan by extracting pure helper logic only, reducing monolith coupling while preserving UI/component behavior.
|
||||||
|
impact: No route/API contract changes; refactor-only extraction of translation/file-upload helper functions with expected behavior parity for EN/CY field labels, file naming, thumbnail icon mapping, and filename validation.
|
||||||
|
status: completed
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
|
||||||
|
- Created work branch from `origin/SIPS-Development`: `22500-elements-index-phase1`.
|
||||||
|
- Added helper modules:
|
||||||
|
- `components/elements/helpers/fileUploadHelpers.js`
|
||||||
|
- `getThumbnailIconByMimeType`
|
||||||
|
- `getDocumentTypePrefix`
|
||||||
|
- `validateUploadFilename`
|
||||||
|
- `components/elements/helpers/translationHelpers.js`
|
||||||
|
- `getFieldTranslation`
|
||||||
|
- `getPickListTranslation`
|
||||||
|
- Updated `components/elements/index.js` to consume these helpers and removed duplicated inline helper implementations.
|
||||||
|
- Kept field renderer/component placement and external prop contracts unchanged (Phase 1 non-goals respected).
|
||||||
|
- Updated `memory-bank/refactor-backlog.md` with a phased Priority 6 track and Phase 1 guardrail-aligned acceptance criteria.
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
|
||||||
|
- `npx eslint components/elements/index.js components/elements/helpers/fileUploadHelpers.js components/elements/helpers/translationHelpers.js` -> pass
|
||||||
|
|
||||||
|
Follow-ups:
|
||||||
|
|
||||||
|
- Phase 2: extract low-risk leaf field renderer components from `components/elements/index.js` in bounded slices.
|
||||||
|
- Perform manual EN/CY + a11y smoke matrix on new appeal/myportal form journeys before merge.
|
||||||
|
|
||||||
### CL-001: TASK22211 endpoint search-document contract consistency slice
|
### CL-001: TASK22211 endpoint search-document contract consistency slice
|
||||||
|
|
||||||
date: 2026-03-23
|
date: 2026-03-23
|
||||||
|
|||||||
@@ -65,6 +65,59 @@ Last updated: 2026-03-12
|
|||||||
3. hash utility behavior
|
3. hash utility behavior
|
||||||
4. locale rewrite mapping sanity checks
|
4. locale rewrite mapping sanity checks
|
||||||
|
|
||||||
|
## Priority 6 — Decompose `components/elements/index.js` monolith (phased)
|
||||||
|
|
||||||
|
- **Problem:** `components/elements/index.js` has grown into a high-coupling UI monolith (~2700+ LOC) combining field primitives, validation/conditional logic, translation helpers, file-upload orchestration, and field-array behavior.
|
||||||
|
- **Why it matters:** Very high regression surface for new appeal/my portal forms, slower change velocity, and poor testability/isolation.
|
||||||
|
- **Target outcome:** `components/elements/index.js` reduced to a thin barrel export with responsibility split into focused modules.
|
||||||
|
- **Context alignment:** Matches architecture direction in `context/architecture.md` (endpoint/component sprawl reduction, bounded slices) and file boundary guidance in `context/coding-conventions.md` (`pages` thin, reusable logic/components split by concern).
|
||||||
|
- **Guardrail constraints:**
|
||||||
|
- Preserve public-service reliability for core flows (`search`, `case`, `myportal`, `newappeal`).
|
||||||
|
- No auth/session/security header behavior changes (out of scope).
|
||||||
|
- Maintain EN/CY parity for user-facing behavior.
|
||||||
|
- Keep accessibility behavior unchanged (labels, focus, keyboard flow, errors).
|
||||||
|
|
||||||
|
### Priority 6 — Phase 1 (start here): extract pure helpers only
|
||||||
|
|
||||||
|
- **Scope (Phase 1 only):**
|
||||||
|
- Move pure/helper logic from `components/elements/index.js` into focused helper modules under `components/elements/` (or `components/elements/helpers/`) without behavior change.
|
||||||
|
- Candidate helper extraction set:
|
||||||
|
- translation helpers (`FieldsTranslations`, picklist translation helper)
|
||||||
|
- file upload helper utilities (icon/doc type naming/pure format helpers)
|
||||||
|
- other deterministic pure functions used by field renderers
|
||||||
|
- Keep all field renderers/components in place for Phase 1.
|
||||||
|
- **Non-goals (Phase 1):**
|
||||||
|
- No JSX component relocation yet.
|
||||||
|
- No upload flow logic rewrites.
|
||||||
|
- No validation rule behavior changes.
|
||||||
|
- No prop contract changes for existing consumers.
|
||||||
|
|
||||||
|
- **Acceptance criteria (Phase 1):**
|
||||||
|
- `components/elements/index.js` imports extracted helpers from new helper modules and behavior remains equivalent.
|
||||||
|
- No route/API changes.
|
||||||
|
- Existing new appeal + myportal form journeys continue to function in EN and CY.
|
||||||
|
- Accessibility smoke unchanged for touched form controls (label association, keyboard reachability, inline error visibility).
|
||||||
|
- Lint passes for touched files.
|
||||||
|
|
||||||
|
- **Validation matrix (minimum):**
|
||||||
|
1. `npm run lint`
|
||||||
|
2. Manual smoke:
|
||||||
|
- new appeal form step rendering + validation messages
|
||||||
|
- myportal representation/new appeal editing flow controls
|
||||||
|
- file upload field icon/name behavior unchanged
|
||||||
|
3. Locale parity checks (EN/CY) for touched user-facing labels/routes.
|
||||||
|
4. A11y smoke checks on touched fields (focus, labels, errors).
|
||||||
|
|
||||||
|
- **Rollback plan (Phase 1):**
|
||||||
|
- Revert helper module extraction commit(s) to restore single-file implementation.
|
||||||
|
- No migration/data rollback required.
|
||||||
|
|
||||||
|
- **Next phases (for tracking):**
|
||||||
|
- **Phase 2:** extract low-risk leaf field renderer components.
|
||||||
|
- **Phase 3:** extract `RenderFileUpload` and upload container.
|
||||||
|
- **Phase 4:** extract field-array/repeater components.
|
||||||
|
- **Phase 5:** finalize `components/elements/index.js` as barrel-only export.
|
||||||
|
|
||||||
## Sequencing recommendation
|
## Sequencing recommendation
|
||||||
|
|
||||||
1. Priorities 2 + 4 (security/integrity foundation)
|
1. Priorities 2 + 4 (security/integrity foundation)
|
||||||
|
|||||||
Reference in New Issue
Block a user