@gov-cy/govcy-express-services 1.11.4 → 1.12.1

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/README.md CHANGED
@@ -156,17 +156,17 @@ The CY Login settings are configured in the `secrets/.env` file.
156
156
 
157
157
  Each service can specify which types of authenticated CY Login profiles are allowed to access it using the `site.cyLoginPolicies` property in its site configuration.
158
158
 
159
- ```json
160
- "cyLoginPolicies": ["naturalPerson", "legalPerson", "eidasNaturalPerson"]
161
- ```
159
+ ```json
160
+ "cyLoginPolicies": ["naturalPerson", "legalPerson", "eidasNaturalPerson"]
161
+ ```
162
162
 
163
163
  ##### Supported Policies
164
164
 
165
- | Policy name | Description | Typical use |
166
- | --------------- | ------------------------------------------------------------ | ---------------------------------------------------- |
167
- | `naturalPerson` | Allows individual users (Cypriot citizens or foreign residents) who have a verified profile in the Civil Registry. Identified by `profile_type: "Individual"` and a 10-digit identifier starting with `00` (citizen) or `05` (foreigner). | Citizen-facing services, personal applications, etc. |
168
- | `legalPerson` | Allows legal entities (companies, partnerships, organisations) with verified profiles in the Registrar of Companies. Identified by `profile_type: "Organisation"` and a `legal_unique_identifier`. | Business-facing services, company submissions, etc. |
169
- | `eidasNaturalPerson` | Allows eIDAS natural persons identified by `profile_type: "Individual"` and `unique_identifier` in the `CC/CC/<identifier>` format. | Cross-border eIDAS individual services. |
165
+ | Policy name | Description | Typical use |
166
+ | --------------- | ------------------------------------------------------------ | ---------------------------------------------------- |
167
+ | `naturalPerson` | Allows individual users (Cypriot citizens or foreign residents) who have a verified profile in the Civil Registry. Identified by `profile_type: "Individual"` and a 10-digit identifier starting with `00` (citizen) or `05` (foreigner). | Citizen-facing services, personal applications, etc. |
168
+ | `legalPerson` | Allows legal entities (companies, partnerships, organisations) with verified profiles in the Registrar of Companies. Identified by `profile_type: "Organisation"` and a `legal_unique_identifier`. | Business-facing services, company submissions, etc. |
169
+ | `eidasNaturalPerson` | Allows eIDAS natural persons identified by `profile_type: "Individual"` and `unique_identifier` in the `CC/CC/<identifier>` format. | Cross-border eIDAS individual services. |
170
170
 
171
171
  ##### How it works
172
172
 
@@ -185,21 +185,21 @@ This maintains backward compatibility with existing services that only supported
185
185
 
186
186
  ##### Example
187
187
 
188
- Allow both natural and legal persons:
189
-
190
- ```json
191
- "site": {
192
- "cyLoginPolicies": ["naturalPerson", "legalPerson"]
193
- }
194
- ```
195
-
196
- Allow Cypriot and eIDAS natural persons:
197
-
198
- ```json
199
- "site": {
200
- "cyLoginPolicies": ["naturalPerson", "eidasNaturalPerson"]
201
- }
202
- ```
188
+ Allow both natural and legal persons:
189
+
190
+ ```json
191
+ "site": {
192
+ "cyLoginPolicies": ["naturalPerson", "legalPerson"]
193
+ }
194
+ ```
195
+
196
+ Allow Cypriot and eIDAS natural persons:
197
+
198
+ ```json
199
+ "site": {
200
+ "cyLoginPolicies": ["naturalPerson", "eidasNaturalPerson"]
201
+ }
202
+ ```
203
203
 
204
204
  Restrict access to natural persons only:
205
205
 
@@ -320,6 +320,35 @@ Here is an example JSON config:
320
320
  "en": "Menu",
321
321
  "tr": "Menu"
322
322
  },
323
+ "navigation": { //<-- Navigation menu items
324
+ "items": [
325
+ {
326
+ "label": {
327
+ "el": "Αρχική",
328
+ "en": "Home",
329
+ "tr": "Home"
330
+ },
331
+ "href": {
332
+ "el": "/test/",
333
+ "en": "/test/",
334
+ "tr": "/test/"
335
+ }
336
+ },
337
+ {
338
+ "label": {
339
+ "el": "Αίτηση",
340
+ "en": "The application",
341
+ "tr": "The application"
342
+ },
343
+ "href": {
344
+ "el": "/test/task-list",
345
+ "en": "/test/task-list",
346
+ "tr": "/test/task-list"
347
+ }
348
+ }
349
+ ]
350
+ },
351
+ "menuHideLabelVisibility": true,
323
352
  "title": { //<-- Service title (meta)
324
353
  "el": "Υπηρεσία τεστ",
325
354
  "en": "Test service",
@@ -789,6 +818,37 @@ Here are some details explaining the JSON structure:
789
818
  - `submissionDataVersion` : The submission data version,
790
819
  - `rendererVersion` : The govcy-frontend-renderer version,
791
820
  - `designSystemsVersion` : The govcy-design-system version,
821
+ - <span id="site-navigation"></span>`navigation`: Optional menu navigation items. Example:
822
+ ```json
823
+ "navigation": {
824
+ "items": [
825
+ {
826
+ "label": {
827
+ "el": "Αρχική",
828
+ "en": "Home",
829
+ "tr": "Home"
830
+ },
831
+ "href": {
832
+ "el": "/test/",
833
+ "en": "/test/",
834
+ "tr": "/test/"
835
+ }
836
+ },
837
+ {
838
+ "label": {
839
+ "el": "Αίτηση",
840
+ "en": "The application",
841
+ "tr": "The application"
842
+ },
843
+ "href": {
844
+ "el": "/test/task-list",
845
+ "en": "/test/task-list",
846
+ "tr": "/test/task-list"
847
+ }
848
+ }
849
+ ]
850
+ }
851
+ ```
792
852
  - `homeRedirectPage`: An object mapping language codes to URLs. When a user visits the root route (e.g., `https://whatever-your-service-is.service.gov.cy/`), the system redirects to the URL for the user's language. If the user's language is not found, it falls back to `"el"` or the first available URL. If not provided, a list of available sites is shown. Example:
793
853
  ```json
794
854
  "homeRedirectPage": {
@@ -1905,10 +1965,10 @@ The validation rules for each element are defined in the `"validations` array fo
1905
1965
  - `noSpecialChars`: Consists only of letters, numbers and some other characters
1906
1966
  - `noSpecialCharsEl`: Consists only of Greek letters, numbers and some other characters
1907
1967
  - `textWide_EL`: Consists of Greek Letters and a wider range of characters
1908
- - `textWide_EL_Latin`: Consists of Greek Letters, Latin letters, numbers and a wider range of characters
1909
- - `textWide_EL_Latin_TR`: Consists of Greek Letters, Latin letters, Turkish letters, numbers and a wider range of characters
1910
- - `textWide_EL_Latn`: Deprecated alias of `textWide_EL_Latin` (still supported for backward compatibility)
1911
- - `textWide_EL_Latn_TR`: Deprecated alias of `textWide_EL_Latin_TR` (still supported for backward compatibility)
1968
+ - `textWide_EL_Latin`: Consists of Greek Letters, Latin letters, numbers and a wider range of characters
1969
+ - `textWide_EL_Latin_TR`: Consists of Greek Letters, Latin letters, Turkish letters, numbers and a wider range of characters
1970
+ - `textWide_EL_Latn`: Deprecated alias of `textWide_EL_Latin` (still supported for backward compatibility)
1971
+ - `textWide_EL_Latn_TR`: Deprecated alias of `textWide_EL_Latin_TR` (still supported for backward compatibility)
1912
1972
  - `textWide_UTF`: Consists of any letters, numbers and a wider range of characters
1913
1973
  - `numeric`: Numeric input
1914
1974
  - `numDecimal`: Numeric decimal input
@@ -1926,7 +1986,10 @@ The validation rules for each element are defined in the `"validations` array fo
1926
1986
  - `date`: Date input (DD/MM/YYYY)
1927
1987
  - `dateISO`: ISO date input `YYYY-M-D`
1928
1988
  - `dateDMY`: European/Common Format date input `D/M/YYYY`
1929
- - `maxCurrentYear`: Maximum current year input
1989
+ - `maxCurrentYear`: Maximum current year input. Use it for year-only inputs e.g. `2026` or formats that start with year e.g. `YYYY-M-D` (which works well with `dateInput` component). Do not use it with `D/M/YYYY` values (e.g. datePicker values); use date-based checks instead (`minCurrentDate`, `maxCurrentDate`, `withinDaysBeforeToday`, `withinDaysAfterToday`).
1990
+ - `minCurrentYear`: Minimum current year input. Use it for year-only inputs e.g. `2026` or formats that start with year e.g. `YYYY-M-D` (which works well with `dateInput` component). Do not use it with `D/M/YYYY` values (e.g. datePicker values); use date-based checks instead (`minCurrentDate`, `maxCurrentDate`, `withinDaysBeforeToday`, `withinDaysAfterToday`).
1991
+ - `minCurrentDate`: Checks if the value is greater than or equal to the current date.
1992
+ - `maxCurrentDate`: Checks if the value is less than or equal to the current date.
1930
1993
  - `required`: Checks if the value is not null, undefined, or an empty string (after trimming).
1931
1994
  - `length`: Checks if the value has a maximum length passed in the `checkValue` parameter.
1932
1995
  - `regCheck`: Checks if the value matches the specified regular expression passed in the `checkValue` parameter.
@@ -1934,8 +1997,12 @@ The validation rules for each element are defined in the `"validations` array fo
1934
1997
  - `maxValue`: Checks if the value is less than or equal to the specified maximum value passed in the `checkValue` parameter.
1935
1998
  - `minValueDate`: Checks if the value is greater than or equal to the specified minimum date passed in the `checkValue` parameter.
1936
1999
  - `maxValueDate`: Checks if the value is less than or equal to the specified maximum date passed in the `checkValue` parameter.
2000
+ - `withinDaysBeforeToday`: Checks if the value is within the last `N` days (between `today - N` and `today`), where `N` is passed in `checkValue`.
2001
+ - `withinDaysAfterToday`: Checks if the value is within the next `N` days (between `today` and `today + N`), where `N` is passed in `checkValue`.
1937
2002
  - `minLength`: Checks if the value has a minimum length passed in the `checkValue` parameter.
1938
2003
 
2004
+ For `withinDaysBeforeToday` and `withinDaysAfterToday`, validation compares calendar dates only (local server timezone), not time-of-day. Supported input date formats are `YYYY-M-D` / `YYYY-MM-DD` and `D/M/YYYY` / `DD/MM/YYYY`.
2005
+
1939
2006
  Example:
1940
2007
 
1941
2008
  ```json
@@ -2066,16 +2133,16 @@ To use data layer values, use the special `dataLayer[]` array. For example `data
2066
2133
  - `formData` is a reserved word for the form data (already inputed data by the user) for that page
2067
2134
  - `showExtra`refers to a input component with that name
2068
2135
 
2069
- The `dataLayer` typically contains keys such as:
2070
- - `inputData`: **All data submitted by the user through forms**
2071
- - `eligibilityResults`: **Cached results from service eligibility API checks**
2072
- - `user.profile_type`: **Authenticated user profile type from CY Login context**
2073
- - `user.policy`: **The matched CY Login policy name for the current session**
2136
+ The `dataLayer` typically contains keys such as:
2137
+ - `inputData`: **All data submitted by the user through forms**
2138
+ - `eligibilityResults`: **Cached results from service eligibility API checks**
2139
+ - `user.profile_type`: **Authenticated user profile type from CY Login context**
2140
+ - `user.policy`: **The matched CY Login policy name for the current session**
2074
2141
 
2075
2142
  Example structure for a service with ID `my-service`:
2076
2143
 
2077
- ```js
2078
- dataLayer = {
2144
+ ```js
2145
+ dataLayer = {
2079
2146
  'my-service.inputData.index.formData.fullName': 'John Smith',
2080
2147
  'my-service.inputData.index.formData.age': '34',
2081
2148
  'my-service.inputData.contact-details.formData.telephone': '+35712345678',
@@ -2085,13 +2152,13 @@ dataLayer = {
2085
2152
  "permanent_residence"
2086
2153
  ],
2087
2154
  'my-service.inputData.want-to-apply.formData.option-radio': 'yes',
2088
- 'my-service.eligibilityResults.check1.succeeded': true,
2089
- 'my-service.eligibilityResults.check2.ErrorCode': 0,
2090
- 'user.profile_type': 'Individual',
2091
- 'user.policy': 'naturalPerson'
2092
- }
2093
-
2094
- ```
2155
+ 'my-service.eligibilityResults.check1.succeeded': true,
2156
+ 'my-service.eligibilityResults.check2.ErrorCode': 0,
2157
+ 'user.profile_type': 'Individual',
2158
+ 'user.policy': 'naturalPerson'
2159
+ }
2160
+
2161
+ ```
2095
2162
 
2096
2163
  If any part of the key path is missing (e.g., the page hasn’t been visited yet or a form field was left empty), the expression will safely return `undefined` and **will not throw an error**. This behavior is by design, so that conditional logic expressions can fail silently and fallback gracefully.
2097
2164
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gov-cy/govcy-express-services",
3
- "version": "1.11.4",
3
+ "version": "1.12.1",
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",
@@ -146,16 +146,42 @@ function validateValue(value, rules) {
146
146
  }
147
147
  return valueDate >= min;
148
148
  },
149
- maxValueDate: (val, maxDate) => {
150
- const valueDate = parseDate(val); // Parse the input date
151
- const max = parseDate(maxDate); // Parse the maximum date
152
- if (isNaN(valueDate) || isNaN(max)) {
153
- return false; // Return false if either date is invalid
154
- }
155
- return valueDate <= max;
156
- },
157
- minLength: (val, min) => val.length >= min
158
- };
149
+ maxValueDate: (val, maxDate) => {
150
+ const valueDate = parseDate(val); // Parse the input date
151
+ const max = parseDate(maxDate); // Parse the maximum date
152
+ if (isNaN(valueDate) || isNaN(max)) {
153
+ return false; // Return false if either date is invalid
154
+ }
155
+ return valueDate <= max;
156
+ },
157
+ withinDaysBeforeToday: (val, daysBeforeToday) => {
158
+ const valueDate = parseDate(val);
159
+ const days = Number.parseInt(daysBeforeToday, 10);
160
+ if (isNaN(valueDate) || Number.isNaN(days)) return false;
161
+
162
+ const today = new Date();
163
+ const valueOnly = new Date(valueDate.getFullYear(), valueDate.getMonth(), valueDate.getDate());
164
+ const todayOnly = new Date(today.getFullYear(), today.getMonth(), today.getDate());
165
+ const minAllowedDate = new Date(todayOnly);
166
+ minAllowedDate.setDate(minAllowedDate.getDate() - days);
167
+
168
+ return valueOnly >= minAllowedDate && valueOnly <= todayOnly;
169
+ },
170
+ withinDaysAfterToday: (val, daysAfterToday) => {
171
+ const valueDate = parseDate(val);
172
+ const days = Number.parseInt(daysAfterToday, 10);
173
+ if (isNaN(valueDate) || Number.isNaN(days)) return false;
174
+
175
+ const today = new Date();
176
+ const valueOnly = new Date(valueDate.getFullYear(), valueDate.getMonth(), valueDate.getDate());
177
+ const todayOnly = new Date(today.getFullYear(), today.getMonth(), today.getDate());
178
+ const maxAllowedDate = new Date(todayOnly);
179
+ maxAllowedDate.setDate(maxAllowedDate.getDate() + days);
180
+
181
+ return valueOnly >= todayOnly && valueOnly <= maxAllowedDate;
182
+ },
183
+ minLength: (val, min) => val.length >= min
184
+ };
159
185
 
160
186
  for (const rule of rules) {
161
187
  // Extract rule parameters
@@ -219,13 +245,23 @@ function validateValue(value, rules) {
219
245
  return message;
220
246
  }
221
247
 
222
- // Check for "minLength"
223
- if (check === 'minLength' && !validationRules.minLength(value, checkValue)) {
224
- return message;
225
- }
226
-
227
-
228
- }
248
+ // Check for "minLength"
249
+ if (check === 'minLength' && !validationRules.minLength(value, checkValue)) {
250
+ return message;
251
+ }
252
+
253
+ // Check for "withinDaysBeforeToday"
254
+ if (check === 'withinDaysBeforeToday' && !validationRules.withinDaysBeforeToday(value, checkValue)) {
255
+ return message;
256
+ }
257
+
258
+ // Check for "withinDaysAfterToday"
259
+ if (check === 'withinDaysAfterToday' && !validationRules.withinDaysAfterToday(value, checkValue)) {
260
+ return message;
261
+ }
262
+
263
+
264
+ }
229
265
 
230
266
  return null;
231
267
  }