@gov-cy/govcy-express-services 1.0.0-alpha.10 → 1.0.0-alpha.12

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.10",
3
+ "version": "1.0.0-alpha.12",
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",
@@ -44,11 +44,13 @@
44
44
  "test:integration": "mocha --recursive tests/integration/**/*.test.mjs",
45
45
  "test:package": "mocha --recursive tests/package/**/*.test.mjs",
46
46
  "test:functional": "mocha --timeout 30000 --recursive tests/functional/**/*.test.mjs",
47
- "test:watch": "mocha --watch --timeout 60000 tests/**/*.test.mjs"
47
+ "test:watch": "mocha --watch --timeout 60000 tests/**/*.test.mjs",
48
+ "coverage": "c8 --reporter=html --reporter=text --reporter=lcov npm test",
49
+ "coverage:report": "c8 report"
48
50
  },
49
51
  "dependencies": {
50
52
  "@gov-cy/dsf-email-templates": "^2.1.0",
51
- "@gov-cy/govcy-frontend-renderer": "^1.21.0",
53
+ "@gov-cy/govcy-frontend-renderer": "^1.22.0",
52
54
  "axios": "^1.9.0",
53
55
  "cookie-parser": "^1.4.7",
54
56
  "dotenv": "^16.3.1",
@@ -60,6 +62,7 @@
60
62
  "puppeteer": "^24.6.0"
61
63
  },
62
64
  "devDependencies": {
65
+ "c8": "^10.1.3",
63
66
  "chai": "^5.2.0",
64
67
  "chai-http": "^5.1.1",
65
68
  "mocha": "^11.1.0",
@@ -139,7 +139,7 @@ export function govcyFileDeletePageHandler() {
139
139
  //--------- End Handle Validation Errors ---------
140
140
 
141
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"]);
142
+ mainElements.push(formElement);
143
143
  // Append generated summary list to the page template
144
144
  pageTemplate.sections.push({ name: "main", elements: mainElements });
145
145
 
@@ -221,6 +221,7 @@ export function govcyFileDeletePostHandler() {
221
221
  //if no validation errors
222
222
  if (req.body.deleteFile === "yes") {
223
223
  dataLayer.storePageDataElement(req.session, siteId, pageUrl, elementName, "");
224
+ logger.info(`File deleted by user`, { siteId, pageUrl, elementName });
224
225
  }
225
226
  // construct the page url
226
227
  let myUrl = new URL(pageBaseReturnUrl);
@@ -53,11 +53,7 @@ export function govcyPageHandler() {
53
53
  element.params.method = "POST";
54
54
  // ➕ Add CSRF token
55
55
  element.params.elements.push(govcyResources.csrfTokenInput(req.csrfToken()));
56
- // // ➕ Add siteId and pageUrl to form data
57
- // element.params.elements.push(govcyResources.siteAndPageInput(siteId, pageUrl, req.globalLang));
58
- // ➕ Add govcyFormsJs script to the form
59
- element.params.elements.push(govcyResources.staticResources.elements["govcyFormsJs"]);
60
-
56
+
61
57
  // 🔍 Find the first button with `prototypeNavigate`
62
58
  const button = element.params.elements.find(subElement =>
63
59
  // subElement.element === "button" && subElement.params.prototypeNavigate
@@ -1,4 +1,5 @@
1
1
  import { govcyFrontendRenderer } from "@gov-cy/govcy-frontend-renderer";
2
+ import * as govcyResources from "../resources/govcyResources.mjs";
2
3
 
3
4
  /**
4
5
  * Middleware function to render pages using the GovCy Frontend Renderer.
@@ -6,8 +7,17 @@ import { govcyFrontendRenderer } from "@gov-cy/govcy-frontend-renderer";
6
7
  */
7
8
  export function renderGovcyPage() {
8
9
  return (req, res) => {
10
+ const afterBody = {
11
+ name: "afterBody",
12
+ elements: [
13
+ govcyResources.staticResources.elements["govcyLoadingOverlay"],
14
+ govcyResources.staticResources.elements["govcyFormsJs"]
15
+ ]
16
+ };
17
+ // Initialize the renderer
9
18
  const renderer = new govcyFrontendRenderer();
10
19
  const { processedPage } = req;
20
+ processedPage.pageTemplate.sections.push(afterBody);
11
21
  const html = renderer.renderFromJSON(processedPage.pageTemplate, processedPage.pageData);
12
22
  res.send(html);
13
23
  };
@@ -79,7 +79,10 @@ export function govcyReviewPageHandler() {
79
79
  //--------- End Handle Validation Errors ---------
80
80
 
81
81
  // Add elements to the main section, the H1, summary list, the submit button and the JS
82
- mainElements.push(pageH1, summaryList, submitButton, govcyResources.staticResources.elements["govcyFormsJs"]);
82
+ mainElements.push(pageH1,
83
+ summaryList,
84
+ submitButton
85
+ );
83
86
  // Append generated summary list to the page template
84
87
  pageTemplate.sections.push({ name: "main", elements: mainElements });
85
88
 
@@ -87,8 +87,7 @@ export function govcySuccessPageHandler(isPDF = false) {
87
87
  weHaveSendYouAnEmail,
88
88
  pdfLink,
89
89
  theDataFromYourRequest,
90
- summaryList,
91
- govcyResources.staticResources.elements["govcyFormsJs"]
90
+ summaryList
92
91
  );
93
92
  // Append generated summary list to the page template
94
93
  pageTemplate.sections.push({ name: "main", elements: mainElements });
@@ -1,11 +1,105 @@
1
1
  // 🔍 Select all file inputs that have the .govcy-file-upload class
2
2
  var fileInputs = document.querySelectorAll('input[type="file"].govcy-file-upload');
3
3
 
4
+ // select overlay and app root elements
5
+ var _govcyOverlay = document.getElementById("govcy--loadingOverlay");
6
+ var _govcyAppRoot = document.getElementById("govcy--body");
7
+
8
+ // Accessibility: Keep track of previously focused element and disabled elements
9
+ var _govcyPrevFocus = null;
10
+ var _govcyDisabledEls = [];
11
+
4
12
  // 🔁 Loop over each file input and attach a change event listener
5
13
  fileInputs.forEach(function(input) {
6
14
  input.addEventListener('change', _uploadFileEventHandler);
7
15
  });
8
16
 
17
+ /**
18
+ * Disables all focusable elements within a given root element
19
+ * @param {*} root The root element whose focusable children will be disabled
20
+ */
21
+ function disableFocusables(root) {
22
+ var sel = 'a[href],area[href],button,input,select,textarea,iframe,summary,[contenteditable="true"],[tabindex]:not([tabindex="-1"])';
23
+ var nodes = root.querySelectorAll(sel);
24
+ _govcyDisabledEls = [];
25
+ for (var i = 0; i < nodes.length; i++) {
26
+ var el = nodes[i];
27
+ if (_govcyOverlay.contains(el)) continue; // don’t disable overlay itself
28
+ var prev = el.getAttribute('tabindex');
29
+ el.setAttribute('data-prev-tabindex', prev === null ? '' : prev);
30
+ el.setAttribute('tabindex', '-1');
31
+ _govcyDisabledEls.push(el);
32
+ }
33
+ root.setAttribute('aria-hidden', 'true'); // hide from AT on fallback
34
+ root.setAttribute('aria-busy', 'true');
35
+ }
36
+
37
+ /**
38
+ * Restores all focusable elements within a given root element
39
+ * @param {*} root The root element whose focusable children will be restored
40
+ */
41
+ function restoreFocusables(root) {
42
+ for (var i = 0; i < _govcyDisabledEls.length; i++) {
43
+ var el = _govcyDisabledEls[i];
44
+ var prev = el.getAttribute('data-prev-tabindex');
45
+ if (prev === '') el.removeAttribute('tabindex'); else el.setAttribute('tabindex', prev);
46
+ el.removeAttribute('data-prev-tabindex');
47
+ }
48
+ _govcyDisabledEls = [];
49
+ root.removeAttribute('aria-hidden');
50
+ root.removeAttribute('aria-busy');
51
+ }
52
+
53
+ /**
54
+ * Traps tab key navigation within the overlay
55
+ * @param {*} e The event
56
+ * @returns
57
+ */
58
+ function trapTab(e) {
59
+ if (e.key !== 'Tab') return;
60
+ var focusables = _govcyOverlay.querySelectorAll('a[href],button,input,select,textarea,[tabindex]:not([tabindex="-1"])');
61
+ if (focusables.length === 0) { e.preventDefault(); _govcyOverlay.focus(); return; }
62
+ var first = focusables[0], last = focusables[focusables.length - 1];
63
+ if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }
64
+ else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
65
+ }
66
+
67
+ /**
68
+ * Shows the loading spinner overlay and traps focus within it
69
+ */
70
+ function showLoadingSpinner() {
71
+ _govcyPrevFocus = document.activeElement;
72
+ _govcyOverlay.setAttribute('aria-hidden', 'false');
73
+ _govcyOverlay.setAttribute('tabindex', '-1');
74
+ _govcyOverlay.style.display = 'flex';
75
+ document.documentElement.style.overflow = 'hidden';
76
+
77
+ if ('inert' in HTMLElement.prototype) { // progressive enhancement
78
+ _govcyAppRoot.inert = true;
79
+ } else {
80
+ disableFocusables(_govcyAppRoot);
81
+ document.addEventListener('keydown', trapTab, true);
82
+ }
83
+ _govcyOverlay.focus();
84
+ }
85
+
86
+ /**
87
+ * Hides the loading spinner overlay and restores focus to the previously focused element
88
+ */
89
+ function hideLoadingSpinner() {
90
+ _govcyOverlay.style.display = 'none';
91
+ _govcyOverlay.setAttribute('aria-hidden', 'true');
92
+ document.documentElement.style.overflow = '';
93
+
94
+ if ('inert' in HTMLElement.prototype) {
95
+ _govcyAppRoot.inert = false;
96
+ } else {
97
+ restoreFocusables(_govcyAppRoot);
98
+ document.removeEventListener('keydown', trapTab, true);
99
+ }
100
+ if (_govcyPrevFocus && _govcyPrevFocus.focus) _govcyPrevFocus.focus();
101
+ }
102
+
9
103
 
10
104
  /**
11
105
  * Handles the upload of a file event
@@ -63,6 +157,9 @@ function _uploadFileEventHandler(event) {
63
157
 
64
158
  if (!file) return; // Exit if no file was selected
65
159
 
160
+ // Show loading spinner
161
+ showLoadingSpinner();
162
+
66
163
  // 🧵 Prepare form-data payload for the API
67
164
  var formData = new FormData();
68
165
  formData.append('file', file); // Attach the actual file
@@ -93,6 +190,9 @@ function _uploadFileEventHandler(event) {
93
190
  // 📝 Store returned metadata in hidden fields if needed
94
191
  // document.querySelector('[name="' + elementName + 'Attachment[fileId]"]').value = fileId;
95
192
  // document.querySelector('[name="' + elementName + 'Attachment[sha256]"]').value = sha256;
193
+
194
+ // Hide loading spinner
195
+ hideLoadingSpinner();
96
196
 
97
197
  // Render the file view
98
198
  _renderFileElement("fileView", elementId, elementName, fileId, sha256, null);
@@ -117,6 +217,8 @@ function _uploadFileEventHandler(event) {
117
217
  errorMessage = messages["uploadFailed" + errorCode];
118
218
  }
119
219
 
220
+ // Hide loading spinner
221
+ hideLoadingSpinner();
120
222
  // Render the file input with error
121
223
  _renderFileElement("fileInput", elementId, elementName, "", "", errorMessage);
122
224
 
@@ -59,7 +59,7 @@ export const staticResources = {
59
59
  errorPage403NaturalOnlyPolicyBody: {
60
60
  el: "<p>Η πρόσβαση επιτρέπεται μόνο σε φυσικά πρόσωπα με επιβεβαιωμένο προφίλ. <a href=\"/logout\">Αποσυνδεθείτε</a> και δοκιμάστε ξανά αργότερα.</p>",
61
61
  en: "<p>Access is only allowed to individuals with a verified profile.<a href=\"/logout\">Sign out</a> and try again later.</p>",
62
- tr: "<p>Access is only allowed to individuals with a confirmed profile.<a href=\"/logout\">Giriş yapmadan</a> sonra tekrar deneyiniz.</p>"
62
+ tr: "<p>Access is only allowed to individuals with a verified profile.<a href=\"/logout\">Giriş yapmadan</a> sonra tekrar deneyiniz.</p>"
63
63
  },
64
64
  errorPage500Title: {
65
65
  el: "Λυπούμαστε, υπάρχει πρόβλημα με την υπηρεσία",
@@ -158,9 +158,19 @@ export const staticResources = {
158
158
  element: "htmlElement",
159
159
  params: {
160
160
  text: {
161
- 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>`,
162
- 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>`,
163
- 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>`
161
+ en: `<script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1.22.0/dist/govcyCompiledTemplates.browser.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1.22.0/dist/govcyFrontendRenderer.browser.js"></script><script type="module" src="/js/govcyForms.js"></script><script type="module" src="/js/govcyFiles.js"></script>`,
162
+ el: `<script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1.22.0/dist/govcyCompiledTemplates.browser.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1.22.0/dist/govcyFrontendRenderer.browser.js"></script><script type="module" src="/js/govcyForms.js"></script><script type="module" src="/js/govcyFiles.js"></script>`,
163
+ tr: `<script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1.22.0/dist/govcyCompiledTemplates.browser.js"></script><script src="https://cdn.jsdelivr.net/gh/gov-cy/govcy-frontend-renderer@v1.22.0/dist/govcyFrontendRenderer.browser.js"></script><script type="module" src="/js/govcyForms.js"></script><script type="module" src="/js/govcyFiles.js"></script>`
164
+ }
165
+ }
166
+ },
167
+ govcyLoadingOverlay: {
168
+ element: "htmlElement",
169
+ params: {
170
+ text: {
171
+ en: `<style>.govcy-loadingOverlay{position:fixed;top:0;right:0;bottom:0;left:0;display:none;justify-content:center;align-items:center;background:rgba(255,255,255,.7);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);z-index:1050}.govcy-loadingOverlay[aria-hidden="false"]{display:flex}</style><div id="govcy--loadingOverlay" class="govcy-loadingOverlay" aria-hidden="true" role="dialog" aria-modal="true" tabindex="-1"><div class="govcy-loadingOverlay__content" role="status" aria-live="polite"><div class="spinner-border govcy-text-primary" role="status"><span class="govcy-visually-hidden">Loading...</span></div></div></div>`,
172
+ el: `<style>.govcy-loadingOverlay{position:fixed;top:0;right:0;bottom:0;left:0;display:none;justify-content:center;align-items:center;background:rgba(255,255,255,.7);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);z-index:1050}.govcy-loadingOverlay[aria-hidden="false"]{display:flex}</style><div id="govcy--loadingOverlay" class="govcy-loadingOverlay" aria-hidden="true" role="dialog" aria-modal="true" tabindex="-1"><div class="govcy-loadingOverlay__content" role="status" aria-live="polite"><div class="spinner-border govcy-text-primary" role="status"><span class="govcy-visually-hidden">Φόρτωση...</span></div></div></div>`,
173
+ tr: `<style>.govcy-loadingOverlay{position:fixed;top:0;right:0;bottom:0;left:0;display:none;justify-content:center;align-items:center;background:rgba(255,255,255,.7);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);z-index:1050}.govcy-loadingOverlay[aria-hidden="false"]{display:flex}</style><div id="govcy--loadingOverlay" class="govcy-loadingOverlay" aria-hidden="true" role="dialog" aria-modal="true" tabindex="-1"><div class="govcy-loadingOverlay__content" role="status" aria-live="polite"><div class="spinner-border govcy-text-primary" role="status"><span class="govcy-visually-hidden">Loading...</span></div></div></div>`
164
174
  }
165
175
  }
166
176
  },