@gov-cy/govcy-express-services 1.0.0-alpha.8 → 1.0.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gov-cy/govcy-express-services",
3
- "version": "1.0.0-alpha.8",
3
+ "version": "1.0.0-alpha.9",
4
4
  "description": "An Express-based system that dynamically renders services using @gov-cy/govcy-frontend-renderer and posts data to a submission API.",
5
5
  "author": "DMRID - DSF Team",
6
6
  "license": "MIT",
package/src/index.mjs CHANGED
@@ -25,6 +25,7 @@ import { govcyRoutePageHandler } from './middleware/govcyRoutePageHandler.mjs';
25
25
  import { govcyServiceEligibilityHandler } from './middleware/govcyServiceEligibilityHandler.mjs';
26
26
  import { govcyLoadSubmissionData } from './middleware/govcyLoadSubmissionData.mjs';
27
27
  import { govcyUploadMiddleware } from './middleware/govcyUpload.mjs';
28
+ import { govcyDeleteFilePageHandler, govcyDeleteFilePostHandler } from './middleware/govcyDeleteFileHandler.mjs';
28
29
  import { isProdOrStaging , getEnvVariable, whatsIsMyEnvironment } from './utils/govcyEnvVariables.mjs';
29
30
  import { logger } from "./utils/govcyLogger.mjs";
30
31
 
@@ -150,9 +151,15 @@ export default function initializeGovCyExpressService(){
150
151
  // ✅ -- ROUTE: Add Success Page Route (BEFORE the dynamic route)
151
152
  app.get('/:siteId/success',serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(), govcySuccessPageHandler(), renderGovcyPage());
152
153
 
154
+ // ❌🗃️ -- ROUTE: Delete file (BEFORE the dynamic route)
155
+ app.get('/:siteId/:pageUrl/:elementName/delete-file', serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(), govcyLoadSubmissionData(), govcyDeleteFilePageHandler(), renderGovcyPage());
156
+
153
157
  // 📝 -- ROUTE: Dynamic route to render pages based on siteId and pageUrl, using govcyPageHandler middleware
154
158
  app.get('/:siteId/:pageUrl', serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(true), govcyLoadSubmissionData(), govcyPageHandler(), renderGovcyPage());
155
159
 
160
+ // ❌🗃️📥 -- ROUTE: Handle POST requests for delete file
161
+ app.post('/:siteId/:pageUrl/:elementName/delete-file', serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(true), govcyDeleteFilePostHandler());
162
+
156
163
  // 📥 -- ROUTE: Handle POST requests for review page. The `submit` action
157
164
  app.post('/:siteId/review', serviceConfigDataMiddleware, requireAuth, naturalPersonPolicy, govcyServiceEligibilityHandler(), govcyReviewPostHandler());
158
165
 
@@ -0,0 +1,234 @@
1
+ import * as govcyResources from "../resources/govcyResources.mjs";
2
+ import * as dataLayer from "../utils/govcyDataLayer.mjs";
3
+ import { logger } from "../utils/govcyLogger.mjs";
4
+ import { pageContainsFileInput } from "../utils/govcyHandleFiles.mjs";
5
+ import { whatsIsMyEnvironment } from '../utils/govcyEnvVariables.mjs';
6
+ import { handleMiddlewareError } from "../utils/govcyUtils.mjs";
7
+ import { getPageConfigData } from "../utils/govcyLoadConfigData.mjs";
8
+ import { evaluatePageConditions } from "../utils/govcyExpressions.mjs";
9
+ import { URL } from "url";
10
+
11
+
12
+ /**
13
+ * Middleware to handle the delete file page.
14
+ * This middleware processes the delete file page, populates the question, and shows validation errors.
15
+ */
16
+ export function govcyDeleteFilePageHandler() {
17
+ return (req, res, next) => {
18
+ try {
19
+ const { siteId, pageUrl, elementName } = req.params;
20
+
21
+ // Create a deep copy of the service to avoid modifying the original
22
+ let serviceCopy = req.serviceData;
23
+
24
+ // ⤵️ Find the current page based on the URL
25
+ const page = getPageConfigData(serviceCopy, pageUrl);
26
+
27
+ // deep copy the page template to avoid modifying the original
28
+ const pageTemplateCopy = JSON.parse(JSON.stringify(page.pageTemplate));
29
+
30
+ // ----- Conditional logic comes here
31
+ // ✅ Skip this POST handler if the page's conditions evaluate to true (redirect away)
32
+ const conditionResult = evaluatePageConditions(page, req.session, siteId, req);
33
+ if (conditionResult.result === false) {
34
+ logger.debug("⛔️ Page condition evaluated to true on POST — skipping form save and redirecting:", conditionResult);
35
+ return res.redirect(govcyResources.constructPageUrl(siteId, conditionResult.redirect));
36
+ }
37
+
38
+ // Validate the field: Only allow upload if the page contains a fileInput with the given name
39
+ const fileInputElement = pageContainsFileInput(pageTemplateCopy, elementName);
40
+ if (!fileInputElement) {
41
+ return handleMiddlewareError(`File input [${elementName}] not allowed on this page`, 404, next);
42
+ }
43
+
44
+ // Validate if the file input has a label
45
+ if (!fileInputElement?.params?.label) {
46
+ return handleMiddlewareError(`File input [${elementName}] does not have a label`, 404, next);
47
+ }
48
+
49
+ //get element data
50
+ const elementData = dataLayer.getFormDataValue(req.session, siteId, pageUrl, elementName)
51
+
52
+ // If the element data is not found, return an error response
53
+ if (!elementData) {
54
+ return handleMiddlewareError(`File input [${elementName}] data not found on this page`, 404, next);
55
+ }
56
+
57
+ // Deep copy page title (so we don’t mutate template)
58
+ const pageTitle = JSON.parse(JSON.stringify(govcyResources.staticResources.text.deleteFileTitle));
59
+
60
+ // Replace label placeholders on page title
61
+ for (const lang of Object.keys(pageTitle)) {
62
+ const labelForLang = fileInputElement.params.label[lang] || fileInputElement.params.label.el || "";
63
+ pageTitle[lang] = pageTitle[lang].replace("{{file}}", labelForLang);
64
+ }
65
+
66
+
67
+ // Deep copy renderer pageData from
68
+ let pageData = JSON.parse(JSON.stringify(govcyResources.staticResources.rendererPageData));
69
+
70
+ // Handle isTesting
71
+ pageData.site.isTesting = (whatsIsMyEnvironment() === "staging");
72
+
73
+ // Base page template structure
74
+ let pageTemplate = {
75
+ sections: [
76
+ {
77
+ name: "beforeMain",
78
+ elements: [govcyResources.staticResources.elements.backLink]
79
+ }
80
+ ]
81
+ };
82
+
83
+ // Construct page title
84
+ const pageRadios = {
85
+ element: "radios",
86
+ params: {
87
+ id: "deleteFile",
88
+ name: "deleteFile",
89
+ legend: pageTitle,
90
+ isPageHeading: true,
91
+ classes: "govcy-mb-6",
92
+ items:[
93
+ {
94
+ value: "yes",
95
+ text: govcyResources.staticResources.text.deleteYesOption
96
+ },
97
+ {
98
+ value: "no",
99
+ text: govcyResources.staticResources.text.deleteNoOption
100
+ }
101
+ ]
102
+
103
+ }
104
+ };
105
+ //-------------
106
+
107
+ // Construct submit button
108
+ const formElement = {
109
+ element: "form",
110
+ params: {
111
+ action: govcyResources.constructPageUrl(siteId, `${pageUrl}/${elementName}/delete-file`,(req.query.route === "review" ? "review" : "")),
112
+ method: "POST",
113
+ elements: [
114
+ pageRadios,
115
+ {
116
+ element: "button",
117
+ params: {
118
+ type: "submit",
119
+ text: govcyResources.staticResources.text.continue
120
+ }
121
+ },
122
+ govcyResources.csrfTokenInput(req.csrfToken())
123
+ ]
124
+ }
125
+ };
126
+
127
+ // --------- Handle Validation Errors ---------
128
+ // Check if validation errors exist in the request
129
+ const validationErrors = [];
130
+ let mainElements = [];
131
+ if (req?.query?.hasError ) {
132
+ validationErrors.push({
133
+ link: '#deleteFile-option-1',
134
+ text: govcyResources.staticResources.text.deleteFileValidationError
135
+ });
136
+ mainElements.push(govcyResources.errorSummary(validationErrors));
137
+ formElement.params.elements[0].params.error = govcyResources.staticResources.text.deleteFileValidationError;
138
+ }
139
+ //--------- End Handle Validation Errors ---------
140
+
141
+ // Add elements to the main section, the H1, summary list, the submit button and the JS
142
+ mainElements.push(formElement, govcyResources.staticResources.elements["govcyFormsJs"]);
143
+ // Append generated summary list to the page template
144
+ pageTemplate.sections.push({ name: "main", elements: mainElements });
145
+
146
+ //if user is logged in add he user bane section in the page template
147
+ if (dataLayer.getUser(req.session)) {
148
+ pageTemplate.sections.push(govcyResources.userNameSection(dataLayer.getUser(req.session).name)); // Add user name section
149
+ }
150
+
151
+ //prepare pageData
152
+ pageData.site = serviceCopy.site;
153
+ pageData.pageData.title = pageTitle;
154
+
155
+ // Attach processed page data to the request
156
+ req.processedPage = {
157
+ pageData: pageData,
158
+ pageTemplate: pageTemplate
159
+ };
160
+ logger.debug("Processed delete file page data:", req.processedPage);
161
+ next();
162
+ } catch (error) {
163
+ return next(error); // Pass error to govcyHttpErrorHandler
164
+ }
165
+ };
166
+ }
167
+
168
+
169
+ /**
170
+ * Middleware to handle delete file post form processing
171
+ * This middleware processes the post, validates the form and handles the file data layer
172
+ */
173
+ export function govcyDeleteFilePostHandler() {
174
+ return (req, res, next) => {
175
+ try {
176
+ // Extract siteId and pageUrl from request
177
+ let { siteId, pageUrl, elementName } = req.params;
178
+
179
+ // get service data
180
+ let serviceCopy = req.serviceData;
181
+
182
+ // 🔍 Find the page by pageUrl
183
+ const page = getPageConfigData(serviceCopy, pageUrl);
184
+
185
+ // Deep copy pageTemplate to avoid modifying the original
186
+ const pageTemplateCopy = JSON.parse(JSON.stringify(page.pageTemplate));
187
+
188
+ // ----- Conditional logic comes here
189
+ // Check if the page has conditions and apply logic
190
+ const conditionResult = evaluatePageConditions(page, req.session, siteId, req);
191
+ if (conditionResult.result === false) {
192
+ logger.debug("⛔️ Page condition evaluated to true on POST — skipping form save and redirecting:", conditionResult);
193
+ return res.redirect(govcyResources.constructPageUrl(siteId, conditionResult.redirect));
194
+ }
195
+
196
+
197
+ // the page base return url
198
+ const pageBaseReturnUrl = `http://localhost:3000/${siteId}/${pageUrl}`;
199
+
200
+ //check if input `deleteFile` has a value
201
+ if (!req?.body?.deleteFile ||
202
+ (req.body.deleteFile !== "yes" && req.body.deleteFile !== "no")) {
203
+ logger.debug("⛔️ No deleteFile value provided on POST — skipping form save and redirecting:", req.body);
204
+ //construct the page url with error
205
+ let myUrl = new URL(pageBaseReturnUrl+`/${elementName}/delete-file`);
206
+ //check if the route is review
207
+ if (req.query.route === "review") {
208
+ myUrl.searchParams.set("route", "review");
209
+ }
210
+ //set the error flag
211
+ myUrl.searchParams.set("hasError", "1");
212
+
213
+ //redirect to the same page with error summary (relative path)
214
+ return res.redirect(govcyResources.constructErrorSummaryUrl(myUrl.pathname + myUrl.search));
215
+ }
216
+
217
+ //if no validation errors
218
+ if (req.body.deleteFile === "yes") {
219
+ dataLayer.storePageDataElement(req.session, siteId, pageUrl, elementName, "");
220
+ }
221
+ // construct the page url
222
+ let myUrl = new URL(pageBaseReturnUrl);
223
+ //check if the route is review
224
+ if (req.query.route === "review") {
225
+ myUrl.searchParams.set("route", "review");
226
+ }
227
+
228
+ // redirect to the page (relative path)
229
+ res.redirect(myUrl.pathname + myUrl.search);
230
+ } catch (error) {
231
+ return next(error); // Pass error to govcyHttpErrorHandler
232
+ }
233
+ };
234
+ }
@@ -91,7 +91,7 @@ export function govcyPageHandler() {
91
91
  }
92
92
  //--------- End of Handle Validation Errors ---------
93
93
 
94
- populateFormData(element.params.elements, theData,validationErrors, req.session, siteId, pageUrl, req.globalLang);
94
+ populateFormData(element.params.elements, theData,validationErrors, req.session, siteId, pageUrl, req.globalLang, null, req.query.route);
95
95
  // if there are validation errors, add an error summary
96
96
  if (validationErrors?.errorSummary?.length > 0) {
97
97
  element.params.elements.unshift(govcyResources.errorSummary(validationErrors.errorSummary));
@@ -120,3 +120,4 @@ export function govcyPageHandler() {
120
120
  }
121
121
  };
122
122
  }
123
+
@@ -9,94 +9,106 @@ fileInputs.forEach(function(input) {
9
9
 
10
10
  /**
11
11
  * Handles the upload of a file event
12
- *
12
+ *
13
13
  * @param {object} event The event
14
14
  */
15
- async function _uploadFileEventHandler(event) {
15
+ function _uploadFileEventHandler(event) {
16
16
  var input = event.target;
17
17
  var messages = {
18
- "uploadSuccesful": {
19
- "el": "Το αρχείο ανεβαστηκε",
20
- "en": "File uploaded successfully",
21
- "tr": "File uploaded successfully"
22
- },
23
- "uploadFailed": {
24
- "el": "Αποτυχια ανεβασης",
25
- "en": "File upload failed",
26
- "tr": "File upload failed"
27
- },
28
- "uploadFailed406": {
29
- "el": "Το επιλεγμένο αρχείο είναι κενό",
30
- "en": "The selected file is empty",
31
- "tr": "The selected file is empty"
32
- },
33
- "uploadFailed407": {
34
- "el": "Το επιλεγμένο αρχείο πρέπει να είναι JPG, JPEG, PNG ή PDF",
35
- "en": "The selected file must be a JPG, JPEG, PNG or PDF",
36
- "tr": "The selected file must be a JPG, JPEG, PNG or PDF"
37
- },
38
- "uploadFailed408": {
39
- "el": "Το επιλεγμένο αρχείο πρέπει να είναι JPG, JPEG, PNG ή PDF",
40
- "en": "The selected file must be a JPG, JPEG, PNG or PDF",
41
- "tr": "The selected file must be a JPG, JPEG, PNG or PDF"
42
- },
43
- "uploadFailed409": {
44
- "el": "Το επιλεγμένο αρχείο πρέπει να είναι μικρότερο από 5MB",
45
- "en": "The selected file must be smaller than 5MB",
46
- "tr": "The selected file must be smaller than 5MB"
18
+ "uploadSuccesful": {
19
+ "el": "Το αρχείο ανεβαστηκε",
20
+ "en": "File uploaded successfully",
21
+ "tr": "File uploaded successfully"
22
+ },
23
+ "uploadFailed": {
24
+ "el": "Αποτυχια ανεβασης",
25
+ "en": "File upload failed",
26
+ "tr": "File upload failed"
27
+ },
28
+ "uploadFailed406": {
29
+ "el": "Το επιλεγμένο αρχείο είναι κενό",
30
+ "en": "The selected file is empty",
31
+ "tr": "The selected file is empty"
32
+ },
33
+ "uploadFailed407": {
34
+ "el": "Το επιλεγμένο αρχείο πρέπει να είναι JPG, JPEG, PNG ή PDF",
35
+ "en": "The selected file must be a JPG, JPEG, PNG or PDF",
36
+ "tr": "The selected file must be a JPG, JPEG, PNG or PDF"
37
+ },
38
+ "uploadFailed408": {
39
+ "el": "Το επιλεγμένο αρχείο πρέπει να είναι JPG, JPEG, PNG ή PDF",
40
+ "en": "The selected file must be a JPG, JPEG, PNG or PDF",
41
+ "tr": "The selected file must be a JPG, JPEG, PNG or PDF"
42
+ },
43
+ "uploadFailed409": {
44
+ "el": "Το επιλεγμένο αρχείο πρέπει να είναι μικρότερο από 5MB",
45
+ "en": "The selected file must be smaller than 5MB",
46
+ "tr": "The selected file must be smaller than 5MB"
47
+ }
48
+ };
49
+
50
+ // 🔐 Get the CSRF token from a hidden input field (generated by your backend)
51
+ var csrfEl = document.querySelector('input[type="hidden"][name="_csrf"]');
52
+ var csrfToken = csrfEl ? csrfEl.value : '';
53
+
54
+ // 🔧 Define siteId and pageUrl (you can dynamically extract these later)
55
+ var siteId = window._govcySiteId || "";
56
+ var pageUrl = window._govcyPageUrl || "";
57
+ var lang = window._govcyLang || "el";
58
+
59
+ // 📦 Grab the selected file
60
+ var file = event.target.files[0];
61
+ var elementName = input.name; // Form field's `name` attribute
62
+ var elementId = input.id; // Form field's `id` attribute
63
+
64
+ if (!file) return; // Exit if no file was selected
65
+
66
+ // 🧵 Prepare form-data payload for the API
67
+ var formData = new FormData();
68
+ formData.append('file', file); // Attach the actual file
69
+ formData.append('elementName', elementName); // Attach the field name for backend lookup
70
+
71
+ // 🚀 CHANGED: using fetch instead of axios.post
72
+ fetch(`/apis/${siteId}/${pageUrl}/upload`, {
73
+ method: "POST",
74
+ headers: {
75
+ "X-CSRF-Token": csrfToken // 🔐 Pass CSRF token in custom header
76
+ },
77
+ body: formData
78
+ })
79
+ .then(function(response) {
80
+ // 🚀 CHANGED: fetch does not auto-throw on error codes → check manually
81
+ if (!response.ok) {
82
+ return response.json().then(function(errData) {
83
+ throw { response: { data: errData } };
84
+ });
47
85
  }
48
- };
49
- // 🔐 Get the CSRF token from a hidden input field (generated by your backend)
50
- var csrfEl = document.querySelector('input[type="hidden"][name="_csrf"]');
51
- var csrfToken = csrfEl ? csrfEl.value : '';
52
-
53
- // 🔧 Define siteId and pageUrl (you can dynamically extract these later)
54
- var siteId = window._govcySiteId || "";
55
- var pageUrl = window._govcyPageUrl || "";
56
- var lang = window._govcyLang || "el";
57
- // 📦 Grab the selected file
58
- var file = event.target.files[0];
59
- var elementName = input.name; // Form field's `name` attribute
60
- var elementId = input.id; // Form field's `id` attribute
61
-
62
- if (!file) return; // Exit if no file was selected
63
-
64
- // 🧵 Prepare form-data payload for the API
65
- var formData = new FormData();
66
- formData.append('file', file); // Attach the actual file
67
- formData.append('elementName', elementName); // Attach the field name for backend lookup
68
-
69
- try {
70
- // 🚀 Send file to the backend upload API
71
- var response = await axios.post(`/apis/${siteId}/${pageUrl}/upload`, formData, {
72
- headers: {
73
- 'X-CSRF-Token': csrfToken // 🔐 Pass CSRF token in custom header
74
- }
75
- });
76
-
77
- var sha256 = response.data.Data.sha256;
78
- var fileId = response.data.Data.fileId;
79
-
80
- // 📝 Store returned metadata in hidden fields for submission with the form
86
+ return response.json();
87
+ })
88
+ .then(function(data) {
89
+ // Success response
90
+ var sha256 = data.Data.sha256;
91
+ var fileId = data.Data.fileId;
92
+
93
+ // 📝 Store returned metadata in hidden fields if needed
81
94
  // document.querySelector('[name="' + elementName + 'Attachment[fileId]"]').value = fileId;
82
95
  // document.querySelector('[name="' + elementName + 'Attachment[sha256]"]').value = sha256;
83
96
 
84
97
  // Render the file view
85
98
  _renderFileElement("fileView", elementId, elementName, fileId, sha256, null);
86
-
99
+
87
100
  // Accessibility: Update ARIA live region with success message
88
101
  var statusRegion = document.getElementById('_govcy-upload-status');
89
102
  if (statusRegion) {
90
103
  setTimeout(function() {
91
104
  statusRegion.textContent = messages.uploadSuccesful[lang];
92
- }, 200)
105
+ }, 200);
93
106
  setTimeout(function() {
94
107
  statusRegion.textContent = '';
95
- }, 5000);
108
+ }, 5000);
96
109
  }
97
- // alert('✅ File uploaded successfully');
98
-
99
- } catch (err) {
110
+ })
111
+ .catch(function(err) {
100
112
  // ⚠️ Show an error message if upload fails
101
113
  var errorMessage = messages.uploadFailed;
102
114
  var errorCode = err && err.response && err.response.data && err.response.data.ErrorCode;
@@ -106,8 +118,8 @@ async function _uploadFileEventHandler(event) {
106
118
  }
107
119
 
108
120
  // Render the file input with error
109
- _renderFileElement("fileInput", elementId, elementName, "","", errorMessage);
110
-
121
+ _renderFileElement("fileInput", elementId, elementName, "", "", errorMessage);
122
+
111
123
  // Re-bind the file input's change handler
112
124
  var newInput = document.getElementById(elementId);
113
125
  if (newInput) {
@@ -116,10 +128,10 @@ async function _uploadFileEventHandler(event) {
116
128
 
117
129
  // Accessibility: Focus on the form field
118
130
  document.getElementById(elementId)?.focus();
119
-
120
- }
131
+ });
121
132
  }
122
133
 
134
+
123
135
  /**
124
136
  * Renders a file element in the DOM
125
137
  *
@@ -131,6 +143,14 @@ async function _uploadFileEventHandler(event) {
131
143
  * @param {object} errorMessage The error message in all supported languages
132
144
  */
133
145
  function _renderFileElement(elementState, elementId, elementName, fileId, sha256, errorMessage) {
146
+
147
+ // Grab the query string part (?foo=bar&route=something)
148
+ var queryString = window.location.search;
149
+ // Parse it
150
+ var params = new URLSearchParams(queryString);
151
+ // Get the "route" value (null if not present)
152
+ var route = params.get("route");
153
+
134
154
  // Create an instance of GovcyFrontendRendererBrowser
135
155
  var renderer = new GovcyFrontendRendererBrowser();
136
156
  var lang = window._govcyLang || "el";
@@ -141,7 +161,7 @@ function _renderFileElement(elementState, elementId, elementName, fileId, sha256
141
161
  "lang": lang
142
162
  }
143
163
  };
144
- var fileInputMap = window._govcyFileInputs || {};
164
+ var fileInputMap = JSON.parse(JSON.stringify(window._govcyFileInputs));
145
165
  var fileElement = fileInputMap[elementName];
146
166
  fileElement.element = elementState;
147
167
  if (errorMessage != null) fileElement.params.error = errorMessage;
@@ -151,7 +171,8 @@ function _renderFileElement(elementState, elementId, elementName, fileId, sha256
151
171
  fileElement.params.visuallyHiddenText = fileElement.params.label;
152
172
  // TODO: Also need to set the `view` and `download` URLs
153
173
  fileElement.params.viewHref = "#viewHref";
154
- fileElement.params.deleteHref = "#deleteHref";
174
+ fileElement.params.deleteHref = "/" + window._govcySiteId + "/" + window._govcyPageUrl + "/" + elementName + "/delete-file"
175
+ + (route !== null ? "?route=" + encodeURIComponent(route) : "");
155
176
  }
156
177
  // Construct the JSONTemplate
157
178
  var JSONTemplate = {
@@ -1,20 +1,31 @@
1
1
  document.addEventListener("DOMContentLoaded", function () {
2
2
  // --- Show conditionals for checked radios ---
3
- document.querySelectorAll('.govcy-radio-input[data-aria-controls]:checked').forEach(radio => {
4
- const targetId = radio.getAttribute('data-aria-controls');
5
- const targetElement = document.getElementById(targetId);
3
+ // CHANGED: NodeList.prototype.forEach is not in IE11 → use Array.prototype.forEach.call
4
+ // CHANGED: Arrow function → function
5
+ Array.prototype.forEach.call(
6
+ document.querySelectorAll('.govcy-radio-input[data-aria-controls]:checked'),
7
+ function (radio) { // CHANGED: arrow → function
8
+ // CHANGED: const → var (ES5)
9
+ var targetId = radio.getAttribute('data-aria-controls');
10
+ var targetElement = document.getElementById(targetId);
6
11
 
7
- if (targetElement) {
8
- targetElement.classList.remove('govcy-radio__conditional--hidden');
12
+ if (targetElement) {
13
+ targetElement.classList.remove('govcy-radio__conditional--hidden');
14
+ }
9
15
  }
10
- });
16
+ );
17
+
11
18
  // --- Disable submit button after form submission ---
12
- document.querySelectorAll('form').forEach(form => {
19
+ // CHANGED: NodeList.forEach Array.prototype.forEach.call
20
+ Array.prototype.forEach.call(document.querySelectorAll('form'), function (form) { // CHANGED
13
21
  form.addEventListener('submit', function (e) {
14
- const submitButton = form.querySelector('[type="submit"]');
22
+ // CHANGED: const var (ES5)
23
+ var submitButton = form.querySelector('[type="submit"]');
15
24
  if (submitButton) {
16
25
  submitButton.disabled = true;
17
26
  submitButton.setAttribute('aria-disabled', 'true');
27
+ // (Optional) announce busy state for AT:
28
+ // submitButton.setAttribute('aria-busy', 'true'); // CHANGED: optional a11y improvement
18
29
  }
19
30
  });
20
31
  });
@@ -6,6 +6,11 @@ export const staticResources = {
6
6
  el: "Υποβολή",
7
7
  tr: "Gönder"
8
8
  },
9
+ continue: {
10
+ en: "Continue",
11
+ el: "Συνέχεια",
12
+ tr: "Continue"
13
+ },
9
14
  cancel: {
10
15
  en: "Cancel",
11
16
  el: "Ακύρωση",
@@ -102,14 +107,39 @@ export const staticResources = {
102
107
  tr: "We have received your request. "
103
108
  },
104
109
  fileUploaded : {
105
- en: "File has been uploaded. ",
106
- el: "Το αρχείο ανεβάστηκε. ",
107
- tr: "File has been uploaded. "
110
+ en: "File uploaded",
111
+ el: "Το αρχείο ανεβάστηκε",
112
+ tr: "File uploaded"
108
113
  },
109
114
  fileNotUploaded : {
110
115
  en: "File has not been uploaded. ",
111
116
  el: "Το αρχείο δεν ανεβάστηκε. ",
112
117
  tr: "File has not been uploaded. "
118
+ },
119
+ fileYouHaveUploaded : {
120
+ en: "You have uploaded the file for \"{{file}}\"",
121
+ el: "Έχετε ανεβάσει το αρχείο \"{{file}}\"",
122
+ tr: "You have uploaded the file for \"{{file}}\""
123
+ },
124
+ deleteFileTitle : {
125
+ en: "Are you sure you want to delete the file \"{{file}}\"? ",
126
+ el: "Είστε σίγουροι ότι θέλετε να διαγράψετε το αρχείο \"{{file}}\";",
127
+ tr: "Are you sure you want to delete the file \"{{file}}\"? "
128
+ },
129
+ deleteYesOption: {
130
+ el:"Ναι, θέλω να διαγράψω το αρχείο",
131
+ en:"Yes, I want to delete this file",
132
+ tr:"Yes, I want to delete this file"
133
+ },
134
+ deleteNoOption: {
135
+ el:"Όχι, δεν θέλω να διαγράψω το αρχείο",
136
+ en:"No, I don't want to delete this file",
137
+ tr:"No, I don't want to delete this file"
138
+ },
139
+ deleteFileValidationError: {
140
+ en: "Select if you want to delete the file",
141
+ el: "Επιλέξτε αν θέλετε να διαγράψετε το αρχείο",
142
+ tr: "Select if you want to delete the filez"
113
143
  }
114
144
  },
115
145
  //remderer sections
@@ -123,9 +153,9 @@ export const staticResources = {
123
153
  element: "htmlElement",
124
154
  params: {
125
155
  text: {
126
- en: `<script src="https://cdn.jsdelivr.net/npm/axios@1.6.2/dist/axios.min.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyCompiledTemplates.browser.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyFrontendRenderer.browser.js"></script><script src="/js/govcyForms.js"></script><script src="/js/govcyFiles.js"></script>`,
127
- el: `<script src="https://cdn.jsdelivr.net/npm/axios@1.6.2/dist/axios.min.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyCompiledTemplates.browser.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyFrontendRenderer.browser.js"></script><script src="/js/govcyForms.js"></script><script src="/js/govcyFiles.js"></script>`,
128
- tr: `<script src="https://cdn.jsdelivr.net/npm/axios@1.6.2/dist/axios.min.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyCompiledTemplates.browser.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyFrontendRenderer.browser.js"></script><script src="/js/govcyForms.js"></script><script src="/js/govcyFiles.js"></script>`
156
+ en: `<script src="https://cdn.jsdelivr.net/npm/axios@1.6.2/dist/axios.min.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyCompiledTemplates.browser.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyFrontendRenderer.browser.js"></script><script type="module" src="/js/govcyForms.js"></script><script type="module" src="/js/govcyFiles.js"></script>`,
157
+ el: `<script src="https://cdn.jsdelivr.net/npm/axios@1.6.2/dist/axios.min.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyCompiledTemplates.browser.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyFrontendRenderer.browser.js"></script><script type="module" src="/js/govcyForms.js"></script><script type="module" src="/js/govcyFiles.js"></script>`,
158
+ tr: `<script src="https://cdn.jsdelivr.net/npm/axios@1.6.2/dist/axios.min.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyCompiledTemplates.browser.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1/dist/govcyFrontendRenderer.browser.js"></script><script type="module" src="/js/govcyForms.js"></script><script type="module" src="/js/govcyFiles.js"></script>`
129
159
  }
130
160
  }
131
161
  },
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { ALLOWED_FORM_ELEMENTS } from "./govcyConstants.mjs";
8
8
  import * as dataLayer from "./govcyDataLayer.mjs";
9
+ import * as govcyResources from "../resources/govcyResources.mjs";
9
10
 
10
11
 
11
12
  /**
@@ -17,8 +18,10 @@ import * as dataLayer from "./govcyDataLayer.mjs";
17
18
  * @param {string} siteId The site ID
18
19
  * @param {string} pageUrl The page URL
19
20
  * @param {string} lang The language
21
+ * @param {Object} fileInputElements The file input elements
22
+ * @param {string} routeParam The route parameter
20
23
  */
21
- export function populateFormData(formElements, theData, validationErrors, store = {}, siteId = "", pageUrl = "", lang = "el", fileInputElements = null) {
24
+ export function populateFormData(formElements, theData, validationErrors, store = {}, siteId = "", pageUrl = "", lang = "el", fileInputElements = null, routeParam = "") {
22
25
  const inputElements = ALLOWED_FORM_ELEMENTS;
23
26
  const isRootCall = !fileInputElements;
24
27
  if (isRootCall) {
@@ -76,7 +79,7 @@ export function populateFormData(formElements, theData, validationErrors, store
76
79
  element.params.visuallyHiddenText = element.params.label;
77
80
  // TODO: Also need to set the `view` and `download` URLs
78
81
  element.params.viewHref = "#viewHref";
79
- element.params.deleteHref = "#deleteHref";
82
+ element.params.deleteHref = `/${siteId}/${pageUrl}/${fieldName}/delete-file${(routeParam) ? `?route=${routeParam}` : ''}`;
80
83
  } else {
81
84
  // TODO: Ask Andreas how to handle empty file inputs
82
85
  element.params.value = "";
@@ -107,7 +110,7 @@ export function populateFormData(formElements, theData, validationErrors, store
107
110
  if (element.element === "radios" && element.params.items) {
108
111
  element.params.items.forEach(item => {
109
112
  if (item.conditionalElements) {
110
- populateFormData(item.conditionalElements, theData, validationErrors,store, siteId , pageUrl, lang, fileInputElements);
113
+ populateFormData(item.conditionalElements, theData, validationErrors,store, siteId , pageUrl, lang, fileInputElements, routeParam);
111
114
 
112
115
  // Check if any conditional element has an error and add to the parent "conditionalHasErrors": true
113
116
  if (item.conditionalElements.some(condEl => condEl.params?.error)) {
@@ -234,11 +234,12 @@ function containsFileInput(elements = [], targetName) {
234
234
  for (const el of elements) {
235
235
  // ✅ Direct file input match
236
236
  if (el.element === 'fileInput' && el.params?.name === targetName) {
237
- return true;
237
+ return el;
238
238
  }
239
239
  // 🔁 Recurse into nested elements (e.g. groups, conditionals)
240
240
  if (Array.isArray(el?.params?.elements)) {
241
- if (containsFileInput(el.params.elements, targetName)) return true;
241
+ const nestedMatch = containsFileInput(el.params.elements, targetName);
242
+ if (nestedMatch) return nestedMatch; // ← propagate the found element
242
243
  }
243
244
 
244
245
  // 🎯 Special case: conditional radios/checkboxes
@@ -248,7 +249,8 @@ function containsFileInput(elements = [], targetName) {
248
249
  ) {
249
250
  for (const item of el.params.items) {
250
251
  if (Array.isArray(item?.conditionalElements)) {
251
- if (containsFileInput(item.conditionalElements, targetName)) return true;
252
+ const match = containsFileInput(item.conditionalElements, targetName);
253
+ if (match) return match; // ← propagate the found element
252
254
  }
253
255
  }
254
256
  }
@@ -264,16 +266,24 @@ function containsFileInput(elements = [], targetName) {
264
266
  * @param {string} elementName The name of the element to check
265
267
  * @return {boolean} True if a fileInput exists, false otherwise
266
268
  */
267
- function pageContainsFileInput(pageTemplate, elementName) {
269
+ export function pageContainsFileInput(pageTemplate, elementName) {
268
270
  const sections = pageTemplate?.sections || [];
269
- return sections.some(section =>
270
- section?.elements?.some(el =>
271
- el.element === 'form' &&
272
- containsFileInput(el.params?.elements, elementName)
273
- )
274
- );
271
+
272
+ for (const section of sections) {
273
+ for (const el of section?.elements || []) {
274
+ if (el.element === 'form') {
275
+ const match = containsFileInput(el.params?.elements, elementName);
276
+ if (match) {
277
+ return match; // ← return the actual element
278
+ }
279
+ }
280
+ }
281
+ }
282
+
283
+ return null; // no match found
275
284
  }
276
285
 
286
+
277
287
  /**
278
288
  * Validates magic bytes against expected mimetype
279
289
  * @param {Buffer} buffer