From dda70b62d20c1d5c5daed87e7b713f2d40554431 Mon Sep 17 00:00:00 2001 From: ulysses Date: Fri, 8 Mar 2024 16:55:18 +0000 Subject: [PATCH] northern ireland and rest of uk option page done. --- .gitignore | 1 + package-lock.json | 12 + package.json | 1 + src/server/common/templates/layouts/page.njk | 1 + src/server/index.js | 3 +- src/server/location-id/index.js | 2 +- src/server/locations/controller.js | 243 +++++++++++++++++-- src/server/locations/index.js | 2 +- src/server/search-location/controller.js | 235 ++++-------------- src/server/search-location/index.njk | 76 +++++- 10 files changed, 362 insertions(+), 214 deletions(-) diff --git a/.gitignore b/.gitignore index 09b4482e..0273c3fe 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ npm-debug.log coverage .cache .envrc +.env \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 459740dc..a47b3b5b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "babel-plugin-module-resolver": "5.0.0", "convict": "6.2.4", "date-fns": "3.3.1", + "dotenv": "16.4.5", "govuk-frontend": "5.1.0", "hapi-pino": "12.1.0", "https-proxy-agent": "7.0.2", @@ -6007,6 +6008,17 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", diff --git a/package.json b/package.json index cc9e9d53..ae4bdcdb 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "babel-plugin-module-resolver": "5.0.0", "convict": "6.2.4", "date-fns": "3.3.1", + "dotenv": "16.4.5", "govuk-frontend": "5.1.0", "hapi-pino": "12.1.0", "https-proxy-agent": "7.0.2", diff --git a/src/server/common/templates/layouts/page.njk b/src/server/common/templates/layouts/page.njk index be3967d7..8705f2b1 100644 --- a/src/server/common/templates/layouts/page.njk +++ b/src/server/common/templates/layouts/page.njk @@ -15,6 +15,7 @@ {% from "toggletip/macro.njk" import toggletip %} {% from "aq-levels-table/macro.njk" import aqLevelsTable %} {% from "input/macro.njk" import govukInput -%} +{% from "radios/macro.njk" import govukRadios -%} {% set mainClasses = "app-main-wrapper" %} diff --git a/src/server/index.js b/src/server/index.js index 1262d839..645bc5fe 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -8,7 +8,8 @@ import { requestLogger } from '~/src/server/common/helpers/logging/request-logge import { catchAll } from '~/src/server/common/helpers/errors' import { secureContext } from '~/src/server/common/helpers/secure-context' import hapiCookie from '@hapi/cookie' - +import dotenv from 'dotenv' +dotenv.config() const isProduction = config.get('isProduction') async function createServer() { diff --git a/src/server/location-id/index.js b/src/server/location-id/index.js index 4bfe8fb6..abd93cc8 100644 --- a/src/server/location-id/index.js +++ b/src/server/location-id/index.js @@ -1,4 +1,4 @@ -import { getLocationDetailsController } from '~/src/server/search-location/controller' +import { getLocationDetailsController } from '~/src/server/locations/controller' const locationId = { plugin: { diff --git a/src/server/locations/controller.js b/src/server/locations/controller.js index 753ad54d..f2380e50 100644 --- a/src/server/locations/controller.js +++ b/src/server/locations/controller.js @@ -1,24 +1,229 @@ -const searchLocationController = { +import axios from 'axios' +import { + monitoringSites, + siteTypeDescriptions, + pollutantTypes +} from '../data/monitoring-sites.js' +import * as airQualityData from '../data/air-quality.js' +import { getAirQuality } from '../data/air-quality.js' + +const getLocationDataController = { + handler: async (request, h) => { + const locationType = request?.payload?.locationType + let locationNameOrPostcode = '' + if (locationType === 'uk-location') { + locationNameOrPostcode = request.payload.engScoWal + } else if (locationType === 'ni-location') { + locationNameOrPostcode = request.payload.ni + } + + if (!locationNameOrPostcode && !locationType) { + request.yar.set('errors', { + errors: { + titleText: 'There is a problem', + errorList: [ + { + text: 'Select a location', + href: '#' + } + ] + } + }) + request.yar.set('locationType', '') + + return h.redirect('/aqie-front-end/search-location') + } + try { + let userLocation = locationNameOrPostcode.toUpperCase() // Use 'let' to allow reassignment + // Regex patterns to check for full and partial postcodes + const fullPostcodePattern = /^([A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2})$/ + const partialPostcodePattern = /^([A-Z]{1,2}\d[A-Z\d]?)$/ + + // Insert a space for full postcodes without a space + if ( + fullPostcodePattern.test(userLocation) && + !userLocation.includes(' ') + ) { + const spaceIndex = userLocation.length - 3 + userLocation = `${userLocation.slice(0, spaceIndex)} ${userLocation.slice( + spaceIndex + )}` + } + + if (!userLocation && locationType === 'uk-location') { + request.yar.set('errors', { + errors: { + titleText: 'There is a problem', + errorList: [ + { + text: 'Enter a location or postcode', + href: '#engScoWal' + } + ] + } + }) + request.yar.set('errorMessage', { + errorMessage: { + text: 'Enter a location or postcode' + } + }) + request.yar.set('locationType', 'uk-location') + return h.redirect('/aqie-front-end/search-location') + } + if (!userLocation && locationType === 'ni-location') { + request.yar.set('errors', { + errors: { + titleText: 'There is a problem', + errorList: [ + { + text: 'Enter a postcode', + href: '#ni' + } + ] + } + }) + request.yar.set('errorMessage', { + errorMessage: { + text: 'Enter a postcode' + } + }) + request.yar.set('locationType', 'ni-location') + return h.redirect('/aqie-front-end/search-location') + } + const airQuality = getAirQuality(request.payload.aq) + + if (locationType === 'uk-location') { + const filters = [ + 'LOCAL_TYPE:City', + 'LOCAL_TYPE:Town', + 'LOCAL_TYPE:Village', + 'LOCAL_TYPE:Suburban_Area', + 'LOCAL_TYPE:Postcode', + 'LOCAL_TYPE:Airport' + ].join('+') + + const apiUrl = `${process.env.OS_PLACES_API_URL}${encodeURIComponent( + userLocation + )}&fq=${encodeURIComponent(filters)}&key=${process.env.OS_PLACES_API_KEY}` + + const response = await axios.get(apiUrl) + + const { results } = response.data + + if (!results || results.length === 0) { + return h.view('locations/location-not-found', { + userLocation: locationNameOrPostcode + }) + } + + let matches = results.filter((item) => { + const name = item.GAZETTEER_ENTRY.NAME1.toUpperCase() + return name.includes(userLocation) || userLocation.includes(name) + }) + + // If it's a partial postcode and there are matches, use the first match and adjust the title + if ( + partialPostcodePattern.test(locationNameOrPostcode.toUpperCase()) && + matches.length > 0 && + locationNameOrPostcode.length <= 3 + ) { + matches[0].GAZETTEER_ENTRY.NAME1 = + locationNameOrPostcode.toUpperCase() // Set the name to the partial postcode + matches = [matches[0]] + } + + request.yar.set('locationData', { data: matches }) + + if (matches.length === 1) { + return h.view('locations/location', { + result: matches[0], + airQuality, + airQualityData: airQualityData.commonMessages, + monitoringSites, + siteTypeDescriptions, + pollutantTypes, + locationType: 'single location', + serviceName: 'Check local air quality' + }) + } else if (matches.length > 1 && locationNameOrPostcode.length > 3) { + return h.view('locations/multiple-locations', { + results: matches, + userLocation: locationNameOrPostcode, + airQuality, + airQualityData: airQualityData.commonMessages, + monitoringSites, + siteTypeDescriptions, + pollutantTypes, + serviceName: 'Check local air quality' + }) + } else { + return h.view('locations/location-not-found', { + userLocation: locationNameOrPostcode + }) + } + } else if (locationType === 'ni-location') { + const postcodeApiUrl = `${process.env.NORTHERN_IRELAND_POSTCODE_URL}${encodeURIComponent(userLocation)}` + const response = await axios.get(postcodeApiUrl) + const { result } = response.data + + if (!result || result.length === 0) { + return h.view('locations/location-not-found', { + userLocation: locationNameOrPostcode + }) + } + + const locationData = { + GAZETTEER_ENTRY: { + NAME1: result[0].postcode, + DISTRICT_BOROUGH: result[0].admin_district + } + } + return h.view('locations/location', { + result: locationData, + airQuality, + airQualityData: airQualityData.commonMessages, + monitoringSites, + siteTypeDescriptions, + pollutantTypes + }) + } + } catch (error) { + return h.view('error/index', { + userLocation: locationNameOrPostcode + }) + } + } +} + +const getLocationDetailsController = { handler: (request, h) => { - return h.view('search-location/index', { - pageTitle: 'Check local air quality', - heading: 'Check local air quality', - page: 'location', - serviceName: 'Check local air quality', - searchParams: { - label: { - text: 'Where do you want to check?', - classes: 'govuk-label--l govuk-!-margin-bottom-6', - isPageHeading: true - }, - hint: { - text: 'Enter a location or postcode' - }, - id: 'location', - name: 'location' + try { + const locationId = request.path.split('/')[3] + const locationData = request.yar.get('locationData') || [] + const locationDetails = locationData.data.find( + (item) => item.GAZETTEER_ENTRY.ID === locationId + ) + + if (locationDetails) { + const airQuality = + getAirQuality(/* Retrieved from session or another source */) + return h.view('locations/location', { + result: locationDetails, + airQuality, + airQualityData: airQualityData.commonMessages, + monitoringSites, + siteTypeDescriptions, + pollutantTypes + }) + } else { + return h.view('location-not-found') } - }) + } catch (error) { + return h.status(500).render('error', { + error: 'An error occurred while retrieving location details.' + }) + } } } -export { searchLocationController } +export { getLocationDataController, getLocationDetailsController } diff --git a/src/server/locations/index.js b/src/server/locations/index.js index 91af44fe..c814464a 100644 --- a/src/server/locations/index.js +++ b/src/server/locations/index.js @@ -1,4 +1,4 @@ -import { getLocationDataController } from '~/src/server/search-location/controller' +import { getLocationDataController } from '~/src/server/locations/controller' const locations = { plugin: { diff --git a/src/server/search-location/controller.js b/src/server/search-location/controller.js index 70b1cad5..afe40f8c 100644 --- a/src/server/search-location/controller.js +++ b/src/server/search-location/controller.js @@ -1,198 +1,53 @@ -import axios from 'axios' -import { getAirQuality } from '../data/air-quality.js' -import { - monitoringSites, - siteTypeDescriptions, - pollutantTypes -} from '../data/monitoring-sites.js' -import * as airQualityData from '../data/air-quality.js' - -const apiKey = 'vvR3FiaNjSWCnFzSKBst23TX6efl0oL9' - -const getLocationDataController = { - handler: async (request, h) => { - const originalUserLocation = request.payload.location.trim() - try { - let userLocation = originalUserLocation.toUpperCase() // Use 'let' to allow reassignment - // Regex patterns to check for full and partial postcodes - const fullPostcodePattern = /^([A-Z]{1,2}\d[A-Z\d]? ?\d[A-Z]{2})$/ - const partialPostcodePattern = /^([A-Z]{1,2}\d[A-Z\d]?)$/ - - // Insert a space for full postcodes without a space - if ( - fullPostcodePattern.test(userLocation) && - !userLocation.includes(' ') - ) { - const spaceIndex = userLocation.length - 3 - userLocation = `${userLocation.slice(0, spaceIndex)} ${userLocation.slice( - spaceIndex - )}` - } - - const aqValue = request.payload.aq - const airQuality = getAirQuality(aqValue) - - if (!userLocation) { - return h.view('search-location/index', { - errors: { - titleText: 'There is a problem', - errorList: [ - { - text: 'Enter a location or postcode', - href: 'enter-location.html#location' - } - ] - }, - searchParams: { - label: { - text: 'Where do you want to check?', - classes: 'govuk-label--l govuk-!-margin-bottom-6', - isPageHeading: true - }, - hint: { - text: 'Enter a location or postcode' - }, - id: 'location', - name: 'location', - errorMessage: { - text: 'Enter a location or postcode' - } - } - }) - } - - const filters = [ - 'LOCAL_TYPE:City', - 'LOCAL_TYPE:Town', - 'LOCAL_TYPE:Village', - 'LOCAL_TYPE:Suburban_Area', - 'LOCAL_TYPE:Postcode', - 'LOCAL_TYPE:Airport' - ].join('+') - - const apiUrl = `https://api.os.uk/search/names/v1/find?query=${encodeURIComponent( - userLocation - )}&fq=${encodeURIComponent(filters)}&key=${apiKey}` - - const response = await axios.get(apiUrl) - - const { results } = response.data - - if (!results || results.length === 0) { - return h.view('locations/location-not-found', { - userLocation: originalUserLocation - }) - } - - let matches = results.filter((item) => { - const name = item.GAZETTEER_ENTRY.NAME1.toUpperCase() - return name.includes(userLocation) || userLocation.includes(name) - }) - - // If it's a partial postcode and there are matches, use the first match and adjust the title - if ( - partialPostcodePattern.test(originalUserLocation.toUpperCase()) && - matches.length > 0 && - originalUserLocation.length <= 3 - ) { - matches[0].GAZETTEER_ENTRY.NAME1 = originalUserLocation.toUpperCase() // Set the name to the partial postcode - matches = [matches[0]] - } - - request.yar.set('locationData', { data: matches }) - - if (matches.length === 1) { - return h.view('locations/location', { - result: matches[0], - airQuality, - airQualityData: airQualityData.commonMessages, - monitoringSites, - siteTypeDescriptions, - pollutantTypes, - locationType: 'single location', - serviceName: 'Check local air quality' - }) - } else if (matches.length > 1 && originalUserLocation.length > 3) { - return h.view('locations/multiple-locations', { - results: matches, - userLocation: originalUserLocation, - airQuality, - airQualityData: airQualityData.commonMessages, - monitoringSites, - siteTypeDescriptions, - pollutantTypes, - serviceName: 'Check local air quality' - }) - } else { - return h.view('locations/location-not-found', { - userLocation: originalUserLocation - }) - } - } catch (error) { - return h.view('error/index', { - userLocation: originalUserLocation - }) - } - } -} - const searchLocationController = { handler: (request, h) => { - return h.view('search-location/index', { - pageTitle: 'Check local air quality', - heading: 'Check local air quality', - page: 'search-location', - serviceName: 'Check local air quality', - searchParams: { - label: { - text: 'Where do you want to check?', - classes: 'govuk-label--l govuk-!-margin-bottom-6', - isPageHeading: true - }, - hint: { - text: 'Enter a location or postcode' + const errors = request.yar.get('errors') + const errorMessage = request.yar.get('errorMessage') + const locationType = request.yar.get('locationType') + if (errors) { + request.yar.set('errors', null) + request.yar.set('errorMessage', null) + return h.view('search-location/index', { + pageTitle: 'Check local air quality', + heading: 'Check local air quality', + page: 'search-location', + serviceName: 'Check local air quality', + searchParams: { + label: { + text: 'Where do you want to check?', + classes: 'govuk-label--l govuk-!-margin-bottom-6', + isPageHeading: true + }, + hint: { + text: 'Enter a location or postcode' + }, + id: 'location', + name: 'location' }, - id: 'location', - name: 'location' - } - }) - } -} -const getLocationDetailsController = { - handler: (request, h) => { - try { - const locationId = request.path.split('/')[3] - const locationData = request.yar.get('locationData') || [] - const locationDetails = locationData.data.find( - (item) => item.GAZETTEER_ENTRY.ID === locationId - ) - - if (locationDetails) { - const airQuality = - getAirQuality(/* Retrieved from session or another source */) - return h.view('locations/location', { - result: locationDetails, - airQuality, - airQualityData: airQualityData.commonMessages, - monitoringSites, - siteTypeDescriptions, - pollutantTypes, - locationType: 'single location', - serviceName: 'Check local air quality' - }) - } else { - return h.view('location-not-found') - } - } catch (error) { - return h.status(500).render('error', { - error: 'An error occurred while retrieving location details.' + locationType, + errors: errors.errors, + errorMessage: errorMessage?.errorMessage + }) + } else { + return h.view('search-location/index', { + pageTitle: 'Check local air quality', + heading: 'Check local air quality', + page: 'search-location', + serviceName: 'Check local air quality', + searchParams: { + label: { + text: 'Where do you want to check?', + classes: 'govuk-label--l govuk-!-margin-bottom-6', + isPageHeading: true + }, + hint: { + text: 'Enter a location or postcode' + }, + id: 'location', + name: 'location' + } }) } } } -export { - getLocationDataController, - searchLocationController, - getLocationDetailsController -} +export { searchLocationController } diff --git a/src/server/search-location/index.njk b/src/server/search-location/index.njk index 2e510d33..93a8f938 100644 --- a/src/server/search-location/index.njk +++ b/src/server/search-location/index.njk @@ -18,8 +18,80 @@ {% endif %}
-
- {{ appSearch(searchParams) }} + + + {% if locationType === 'uk-location' %} + {% set ukHtml %} + {{ govukInput({ + id: "engScoWal", + name: "engScoWal", + classes: "govuk-!-width-one-half", + errorMessage:errorMessage + }) }} + {% endset %} + {% else %} + {% set ukHtml %} + {{ govukInput({ + id: "engScoWal", + name: "engScoWal", + classes: "govuk-!-width-one-half", + label: { text: "Enter a location or postcode" } + }) }} + {% endset %} + {% endif %} + + + {% if locationType === 'ni-location' %} + {% set niHtml %} + {{ govukInput({ + id: "ni", + name: "ni", + classes: "govuk-!-width-one-half", + errorMessage:errorMessage + }) }} + {% endset %} + {% else %} + {% set niHtml %} + {{ govukInput({ + id: "ni", + name: "ni", + classes: "govuk-!-width-one-half", + label: { text: "Enter a location or postcode" } + }) }} + {% endset %} + {% endif %} + + + {{ govukRadios({ + name: "locationType", + fieldset: { + legend: { + text: "Where do you want to check?", + isPageHeading: true, + classes: "govuk-fieldset__legend--l govuk-label--l govuk-!-margin-bottom-6" + } + }, + items: [ + { + value: "uk-location", + text: "England, Scotland or Wales", + checked: locationType === 'uk-location', + conditional: { html: ukHtml } + }, + { + value: "ni-location", + text: "Northern Ireland", + checked: locationType === 'ni-location', + conditional: { html: niHtml } + } + ] + }) }} + + + {{ govukButton({ + classes: "app-search__button", + text: "Continue" + }) }}