diff --git a/azure-pipeline-bff.yaml b/azure-pipeline-bff.yaml index a3d50b74f3..26e6afd1a9 100644 --- a/azure-pipeline-bff.yaml +++ b/azure-pipeline-bff.yaml @@ -2,8 +2,9 @@ trigger: batch: true branches: include: + - ontwikkelen - testen - - az-acceptance + - main paths: include: - src/server @@ -50,6 +51,9 @@ parameters: default: false variables: + - ${{ if eq(variables['Build.SourceBranchName'], 'ontwikkelen') }}: + - name: dtapName + value: o - ${{ if or(eq(variables['Build.SourceBranchName'], 'testen'), eq(variables['Build.Reason'], 'PullRequest')) }}: - name: dtapName value: t @@ -76,4 +80,3 @@ jobs: btdAppBFF: true btdAppUI: false updateAppSettings: ${{ parameters.updateAppSettings }} - aquaScan: ${{ eq(variables['Build.Reason'], 'PullRequest') }} diff --git a/azure-pipeline-ui.yaml b/azure-pipeline-ui.yaml index b59e6845e1..5316f0197b 100644 --- a/azure-pipeline-ui.yaml +++ b/azure-pipeline-ui.yaml @@ -2,8 +2,9 @@ trigger: batch: true branches: include: + - ontwikkelen - testen - - az-acceptance + - main paths: include: - src/client @@ -80,4 +81,3 @@ jobs: btdAppBFF: false btdAppUI: true updateAppSettings: ${{ parameters.updateAppSettings }} - aquaScan: ${{ eq(variables['Build.Reason'], 'PullRequest') }} diff --git a/src/client/AppState.ts b/src/client/AppState.ts index 0b6e8afc47..aff0c0d9f4 100644 --- a/src/client/AppState.ts +++ b/src/client/AppState.ts @@ -60,6 +60,7 @@ export const PRISTINE_APPSTATE: AppState = { profileTypes: ['private'], }), MILIEUZONE: apiPristineResult({ isKnown: false }), + OVERTREDINGEN: apiPristineResult({ isKnown: false }), TOERISTISCHE_VERHUUR: apiPristineResult({ vergunningen: [], registraties: [], diff --git a/src/client/assets/icons/Overtredingen.svg b/src/client/assets/icons/Overtredingen.svg new file mode 100644 index 0000000000..aae1c9ca49 --- /dev/null +++ b/src/client/assets/icons/Overtredingen.svg @@ -0,0 +1,20 @@ + + + Overtredingen voertuigen icoon + + + + + + + + + + + \ No newline at end of file diff --git a/src/client/assets/icons/index.tsx b/src/client/assets/icons/index.tsx index ce7b3c31af..73864f9342 100644 --- a/src/client/assets/icons/index.tsx +++ b/src/client/assets/icons/index.tsx @@ -30,11 +30,11 @@ export { default as IconInkomenSVWI } from './inkomen-svwi.svg?react'; export { default as IconKlachten } from './IconKlachten.svg?react'; export { default as IconKrefia } from './krefia.svg?react'; export { default as IconLogout } from './Logout.svg?react'; -// Map icons export { default as MapIconHomeCommercial } from './map/homeCommercial__primary-red.svg?react'; export { default as IconMarker } from './Marker.svg?react'; export { default as IconMijnGegevens } from './MijnGegevens.svg?react'; export { default as IconMilieuzone } from './milieuzone.svg?react'; +export { default as IconOvertredingen } from './Overtredingen.svg?react'; export { default as IconIndeterminate, default as IconMin, diff --git a/src/client/components/MainNavSubmenu/MainNavSubmenu.module.scss b/src/client/components/MainNavSubmenu/MainNavSubmenu.module.scss index 4a762a4abe..9d70a78dc5 100644 --- a/src/client/components/MainNavSubmenu/MainNavSubmenu.module.scss +++ b/src/client/components/MainNavSubmenu/MainNavSubmenu.module.scss @@ -117,7 +117,7 @@ border-bottom: solid 0.2rem transparent; @include mq-tablet() { - line-height: 2.5rem; + line-height: 1; } } diff --git a/src/client/components/MyChaptersPanel/MyChaptersPanel.tsx b/src/client/components/MyChaptersPanel/MyChaptersPanel.tsx index 467c5d3251..eafa4adaa5 100644 --- a/src/client/components/MyChaptersPanel/MyChaptersPanel.tsx +++ b/src/client/components/MyChaptersPanel/MyChaptersPanel.tsx @@ -1,6 +1,5 @@ -import { AppRoutes } from '../../../universal/config'; +import { AppRoutes, ChapterMenuItem } from '../../../universal/config'; import { ChapterIcons } from '../../config/chapterIcons'; -import { ChapterMenuItem } from '../../config/menuItems'; import { Heading } from '@amsterdam/design-system-react'; import { IconInfo } from '../../assets/icons'; import Linkd from '../Button/Button'; diff --git a/src/client/config/api.ts b/src/client/config/api.ts index 1cdd1140dc..115cf8288b 100644 --- a/src/client/config/api.ts +++ b/src/client/config/api.ts @@ -56,6 +56,7 @@ export const ErrorNames: Record = { BUURT: 'Mijn buurt / Mijn bedrijfsomgeving', BELASTINGEN: 'Actuele updates over uw belastingen', MILIEUZONE: 'Milieuzone', + OVERTREDINGEN: 'Overtredingen voertuigen', MY_LOCATION: 'Uw locatie op de kaart', VERGUNNINGEN: 'Vergunningen', ALL: 'Alle gegevens', // indien data helemaal niet opgehaald kan worden diff --git a/src/client/config/chapterIcons.tsx b/src/client/config/chapterIcons.tsx index d361e03ba0..07574850c8 100644 --- a/src/client/config/chapterIcons.tsx +++ b/src/client/config/chapterIcons.tsx @@ -5,29 +5,29 @@ import { IconAVG, IconBelastingen, IconBezwaren, + IconBodem, IconBurgerZaken, IconErfpacht, IconGarbage, IconHomeCommercial, + IconHoreca, IconInkomen, IconInkomenSVWI, + IconKlachten, + IconKrefia, IconMijnGegevens, IconMilieuzone, IconMyNotifications, + IconOvertredingen, + IconParkeren, + IconSearch, IconSiaMeldingen, IconStadspas, - IconTips, - IconVergunningen, - IconZorg, - IconToeristischeVerhuur, - IconKrefia, IconSubsidie, - IconSearch, + IconToeristischeVerhuur, + IconVergunningen, IconWior, - IconParkeren, - IconKlachten, - IconHoreca, - IconBodem, + IconZorg, } from '../assets/icons'; export const ChapterIcons: Record = { @@ -42,6 +42,7 @@ export const ChapterIcons: Record = { [Chapters.STADSPAS]: IconStadspas, [Chapters.BRP]: IconMijnGegevens, [Chapters.MILIEUZONE]: IconMilieuzone, + [Chapters.OVERTREDINGEN]: IconOvertredingen, [Chapters.SIA]: IconSiaMeldingen, [Chapters.NOTIFICATIONS]: IconMyNotifications, [Chapters.ROOT]: IconBurgerZaken, diff --git a/src/server/config.ts b/src/server/config.ts index beeae40541..5080d9cb68 100644 --- a/src/server/config.ts +++ b/src/server/config.ts @@ -177,7 +177,7 @@ export const ApiConfig: ApiDataRequestConfig = { }, CLEOPATRA: { url: `${process.env.BFF_CLEOPATRA_API_ENDPOINT}`, - postponeFetch: !FeatureToggle.milieuzoneApiActive, + postponeFetch: !FeatureToggle.cleopatraApiActive, method: 'POST', httpsAgent: new https.Agent({ cert: getCert('BFF_SERVER_CLIENT_CERT'), diff --git a/src/server/mock-data/index.ts b/src/server/mock-data/index.ts index 720e3fd05c..4a40f5a10c 100644 --- a/src/server/mock-data/index.ts +++ b/src/server/mock-data/index.ts @@ -18,7 +18,7 @@ import KVK1 from './json/kvk-handelsregister.json'; import KVK2 from './json/kvk-handelsregister2.json'; import LOODMETING_RAPPORT from './json/loodmeting_rapport.json'; import LOODMETINGEN from './json/loodmetingen.json'; -import MILIEUZONE from './json/milieuzone.json'; +import CLEOPATRA from './json/cleopatra.json'; import TOERISTISCHE_VERHUUR_REGISTRATIES_BSN from './json/registraties-toeristische-verhuur-bsn.json'; import TOERISTISCHE_VERHUUR_REGISTRATIE from './json/registraties-toeristische-verhuur.json'; import SIA_HISTORY from './json/sia-history.json'; @@ -226,7 +226,7 @@ export const mockDataConfig: MockDataConfig = { method: 'post', status: (config: any) => (isCommercialUser(config) ? 200 : 200), responseData: async (config: any) => { - return await loadMockApiResponseJson(MILIEUZONE); + return await loadMockApiResponseJson(CLEOPATRA); }, }, [String(ApiUrls.VERGUNNINGEN)]: { diff --git a/src/server/mock-data/json/cleopatra.json b/src/server/mock-data/json/cleopatra.json new file mode 100644 index 0000000000..77005aabfb --- /dev/null +++ b/src/server/mock-data/json/cleopatra.json @@ -0,0 +1,102 @@ +[ + { + "thema": "Milieuzone", + "categorie": "M1", + "prioriteit": 0, + "datum": "2019-12-13", + "titel": "Uw aanvraag ontheffing milieuzone Brom- en snorfietsen", + "omschrijving": "Uw moet uw aanvraag voor ontheffing milieuzone Brom- en snorfietsen nog betalen", + "url": "https://ontheffingen-acc.amsterdam.nl/publiek/aanvraag/56B31535-1404-4AC0-8658-CD77C32C12EE", + "urlNaam": "Betaal direct" + }, + { + "thema": "Milieuzone", + "categorie": "F3", + "prioriteit": 0, + "datum": "2020-12-21", + "titel": "Uw aanvraag ontheffing milieuzone Vracht", + "omschrijving": "Uw aanvraag voor ontheffing milieuzone Vracht is toegekend", + "url": "https://ontheffingen-acc.amsterdam.nl/publiek/aanvraag/2a5d9103-42bb-4a88-9bf9-5cd58580e209", + "urlNaam": "Meer informatie" + }, + { + "thema": "Milieuzone", + "categorie": "F3", + "prioriteit": 0, + "datum": "2019-12-13", + "titel": "Uw aanvraag ontheffing milieuzone Brom- en snorfietsen", + "omschrijving": "Uw aanvraag voor ontheffing milieuzone Brom- en snorfietsen is toegekend", + "url": "https://ontheffingen-acc.amsterdam.nl/publiek/aanvraag/7B46A24E-BC21-44F9-ADB8-B85A75FAFB90", + "urlNaam": "Meer informatie" + }, + { + "thema": "Milieuzone", + "categorie": "F2", + "prioriteit": 50, + "datum": "2023-10-12", + "titel": "Ontheffing milieuzone", + "omschrijving": "", + "url": "https://ontheffingen-acc.amsterdam.nl/publiek/aanvragen", + "urlNaam": "Milieuzone" + }, + { + "thema": "Milieuzone", + "categorie": "M1", + "prioriteit": 0, + "datum": "2019-12-13", + "titel": "Uw aanvraag ontheffing milieuzone Brom- en snorfietsen", + "omschrijving": "Uw moet uw aanvraag voor ontheffing milieuzone Brom- en snorfietsen nog betalen", + "url": "https://ontheffingen-acc.amsterdam.nl/publiek/aanvraag/88D12139-6BFF-45BF-9FA5-4BFCC01F284F", + "urlNaam": "Betaal direct" + }, + { + "thema": "Milieuzone", + "categorie": "F3", + "prioriteit": 0, + "datum": "2019-12-13", + "titel": "Uw aanvraag ontheffing milieuzone Brom- en snorfietsen", + "omschrijving": "Uw aanvraag voor ontheffing milieuzone Brom- en snorfietsen is toegekend", + "url": "https://ontheffingen-acc.amsterdam.nl/publiek/aanvraag/3A36D276-244D-4B12-8A43-97573502728E", + "urlNaam": "Meer informatie" + }, + { + "thema": "Overtredingen", + "categorie": "F2", + "prioriteit": 50, + "datum": "2023-10-12", + "titel": "Overtredingen", + "omschrijving": "", + "url": "https://ontheffingen-acc.amsterdam.nl/publiek/aanvragen", + "urlNaam": "Overtredingen" + }, + { + "thema": "Milieuzone", + "categorie": "F3", + "prioriteit": 0, + "datum": "2019-12-13", + "titel": "Uw aanvraag ontheffing milieuzone Brom- en snorfietsen", + "omschrijving": "Uw aanvraag voor ontheffing milieuzone Brom- en snorfietsen is afgewezen", + "url": "https://ontheffingen-acc.amsterdam.nl/publiek/aanvraag/5A158FD9-B07D-4A4C-B780-8359A9A14906", + "urlNaam": "Meer informatie" + }, + { + "thema": "Milieuzone", + "categorie": "F3", + "prioriteit": 0, + "datum": "2019-12-13", + "titel": "Uw aanvraag ontheffing milieuzone Brom- en snorfietsen", + "omschrijving": "Uw aanvraag voor ontheffing milieuzone Brom- en snorfietsen is afgewezen", + "url": "https://ontheffingen-acc.amsterdam.nl/publiek/aanvraag/2A3B4C05-700C-4C1E-A399-4E171F5B5420", + "urlNaam": "Meer informatie" + }, + { + "thema": "Milieuzone", + "categorie": "M1", + "prioriteit": 0, + "datum": "2021-10-18", + "titel": "Uw aanvraag ontheffing milieuzone Vracht", + "omschrijving": "Uw moet uw aanvraag voor ontheffing milieuzone Vracht nog betalen", + "url": "https://ontheffingen-acc.amsterdam.nl/publiek/aanvraag/e9d0d30b-2de4-4c7a-9340-4e784d51f1d6", + "urlNaam": "Betaal direct" + } +] \ No newline at end of file diff --git a/src/server/mock-data/json/milieuzone.json b/src/server/mock-data/json/milieuzone.json deleted file mode 100644 index 68d43e32a5..0000000000 --- a/src/server/mock-data/json/milieuzone.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "thema": "Milieuzone", - "categorie": "M1", - "prioriteit": 0, - "datum": "2019-03-13", - "titel": "Uw aanvraag ontheffing milieuzone Brom- en snorfietsen", - "omschrijving": "Uw moet uw aanvraag voor ontheffing milieuzone Brom- en snorfietsen nog betalen", - "url": "https://ontheffingen-acc.amsterdam.nl/publiek/aanvraag/1", - "urlNaam": "Betaal direct" - }, - { - "thema": "Milieuzone", - "categorie": "M1", - "prioriteit": 0, - "datum": "2019-03-13", - "titel": "Uw aanvraag ontheffing milieuzone Brom- en snorfietsen", - "omschrijving": "Uw moet uw aanvraag voor ontheffing milieuzone Brom- en snorfietsen nog betalen", - "url": "https://ontheffingen-acc.amsterdam.nl/publiek/aanvraag/2", - "urlNaam": "Betaal direct" - }, - { - "thema": "Milieuzone", - "categorie": "F2", - "prioriteit": 50, - "datum": "2022-06-07", - "titel": "milieuzone.f2.aanvragen_en_ontheffingen.titel", - "omschrijving": "milieuzone.f2.aanvragen_en_ontheffingen.omschrijving", - "url": "https://ontheffingen-acc.amsterdam.nl/publiek/aanvragen", - "urlNaam": "milieuzone.f2.aanvragen_en_ontheffingen.button" - } -] \ No newline at end of file diff --git a/src/server/openapi.yml b/src/server/openapi.yml index 107a96f85a..5bf25855a9 100644 --- a/src/server/openapi.yml +++ b/src/server/openapi.yml @@ -265,6 +265,8 @@ components: $ref: '#/components/schemas/ApiSearchConfigResult' MILIEUZONE: $ref: '#/components/schemas/ApiSearchConfigResult' + OVERTREDINGEN: + $ref: '#/components/schemas/ApiSearchConfigResult' TOERISTISCHE_VERHUUR: $ref: '#/components/schemas/ApiSearchConfigResult' ERFPACHT: @@ -325,6 +327,8 @@ components: type: object MILIEUZONE: type: object + OVERTREDINGEN: + type: object TOERISTISCHE_VERHUUR: type: object SUBSIDIE: diff --git a/src/server/services/afval/afvalpunten.ts b/src/server/services/afval/afvalpunten.ts index 5dbd034786..933e249ba2 100644 --- a/src/server/services/afval/afvalpunten.ts +++ b/src/server/services/afval/afvalpunten.ts @@ -2,7 +2,7 @@ import { LatLngLiteral } from 'leaflet'; import { apiSuccessResult, getApproximateDistance, - sortAlpha, + sortByNumber, } from '../../../universal/helpers'; import type { AfvalPuntenData, @@ -22,7 +22,7 @@ function addApproximateDistance( : 0, }); }) - .sort(sortAlpha('distance')); + .sort(sortByNumber('distance', 'asc')); } export function fetchAfvalpunten(latlng: LatLngLiteral | null) { diff --git a/src/server/services/controller.ts b/src/server/services/controller.ts index 97ca4169db..92ef64d917 100644 --- a/src/server/services/controller.ts +++ b/src/server/services/controller.ts @@ -31,8 +31,10 @@ import { fetchSignals } from './sia'; import { fetchBelasting, fetchMilieuzone, + fetchOvertredingen, fetchSubsidie, } from './simple-connect'; +import { fetchErfpacht, fetchErfpachtV2 } from './simple-connect/erfpacht'; import { fetchTipsAndNotifications, sortNotifications, @@ -52,7 +54,6 @@ import { fetchTonk, fetchTozo, } from './wpi'; -import { fetchErfpacht, fetchErfpachtV2 } from './simple-connect/erfpacht'; // Default service call just passing requestID and request headers as arguments function callService(fetchService: (...args: any) => Promise) { @@ -125,6 +126,7 @@ const AFVALPUNTEN = async (requestID: requestID, req: Request) => // Architectural pattern C. TODO: Make generic services for pattern C. const BELASTINGEN = callService(fetchBelasting); const MILIEUZONE = callService(fetchMilieuzone); +const OVERTREDINGEN = callService(fetchOvertredingen); const ERFPACHT = callService(fetchErfpacht); const ERFPACHTv2 = callService(fetchErfpachtV2); const SUBSIDIE = callService(fetchSubsidie); @@ -177,6 +179,7 @@ const SERVICES_INDEX = { AFVALPUNTEN, BELASTINGEN, MILIEUZONE, + OVERTREDINGEN, TOERISTISCHE_VERHUUR, ERFPACHT, ERFPACHTv2, @@ -219,6 +222,7 @@ type CommercialServices = Pick< | 'MY_LOCATION' | 'KVK' | 'MILIEUZONE' + | 'OVERTREDINGEN' | 'VERGUNNINGEN' | 'TOERISTISCHE_VERHUUR' | 'HORECA' @@ -252,6 +256,7 @@ export const servicesByProfileType: ServicesByProfileType = { MY_LOCATION, KVK, MILIEUZONE, + OVERTREDINGEN, TOERISTISCHE_VERHUUR, SUBSIDIE, VERGUNNINGEN, @@ -281,6 +286,7 @@ export const servicesByProfileType: ServicesByProfileType = { MY_LOCATION, KVK, MILIEUZONE, + OVERTREDINGEN, TOERISTISCHE_VERHUUR, SUBSIDIE, VERGUNNINGEN, diff --git a/src/server/services/simple-connect/cleopatra.test.ts b/src/server/services/simple-connect/cleopatra.test.ts index 7d96eb873d..83eb9edb8e 100644 --- a/src/server/services/simple-connect/cleopatra.test.ts +++ b/src/server/services/simple-connect/cleopatra.test.ts @@ -1,9 +1,10 @@ import { describe, expect, test } from 'vitest'; -import { ApiConfig } from '../../config'; import { AuthProfileAndToken } from '../../helpers/app'; import { fetchMilieuzone, fetchMilieuzoneNotifications, + fetchOvertredingen, + fetchOvertredingenNotifications, getJSONRequestPayload, } from './cleopatra'; import { remoteApi } from '../../../test-utils'; @@ -59,6 +60,7 @@ describe('simple-connect/cleopatra', () => { content: [ { categorie: 'M1', + thema: 'Milieuzone', datum: '2019-03-13', titel: 'Uw aanvraag ontheffing milieuzone Brom- en snorfietsen', omschrijving: @@ -68,6 +70,7 @@ describe('simple-connect/cleopatra', () => { }, { categorie: 'M1', + thema: 'Milieuzone', datum: '2019-03-13', titel: 'Uw aanvraag ontheffing milieuzone Brom- en snorfietsen', omschrijving: @@ -77,6 +80,7 @@ describe('simple-connect/cleopatra', () => { }, { categorie: 'F2', + thema: 'Milieuzone', }, ], status: 'OK', @@ -109,7 +113,7 @@ describe('simple-connect/cleopatra', () => { "chapter": "MILIEUZONE", "datePublished": "2019-03-13", "description": "Uw moet uw aanvraag voor ontheffing milieuzone Brom- en snorfietsen nog betalen", - "id": "milieuzone-M1", + "id": "MILIEUZONE-M1", "link": { "title": "Betaal direct", "to": "https://ontheffingen-acc.amsterdam.nl/publiek/aanvraag/1", @@ -120,7 +124,7 @@ describe('simple-connect/cleopatra', () => { "chapter": "MILIEUZONE", "datePublished": "2019-03-13", "description": "Uw moet uw aanvraag voor ontheffing milieuzone Brom- en snorfietsen nog betalen", - "id": "milieuzone-M1", + "id": "MILIEUZONE-M1", "link": { "title": "Betaal direct", "to": "https://ontheffingen-acc.amsterdam.nl/publiek/aanvraag/2", @@ -133,4 +137,68 @@ describe('simple-connect/cleopatra', () => { } `); }); + + test('fetchOvertredingen content', async () => { + remoteApi + .post('/cleopatra') + .times(2) + .reply(200, { + content: [ + { + categorie: 'M1', + thema: 'Overtredingen', + datum: '2019-03-13', + titel: 'Overtreding betalen', + omschrijving: 'Uw moet uw overtreding nog betalen', + url: 'https://ontheffingen-acc.amsterdam.nl/publiek/aanvraag/1', + urlNaam: 'Betaal direct', + }, + { + categorie: 'F2', + thema: 'Overtredingen', + }, + ], + status: 'OK', + }); + + const responseContent = await fetchOvertredingen( + REQUEST_ID, + authProfileAndToken + ); + + expect(responseContent).toMatchInlineSnapshot(` + { + "content": { + "isKnown": true, + }, + "status": "OK", + } + `); + + const notificationsResponse = await fetchOvertredingenNotifications( + REQUEST_ID, + authProfileAndToken + ); + + expect(notificationsResponse).toMatchInlineSnapshot(` + { + "content": { + "notifications": [ + { + "chapter": "OVERTREDINGEN", + "datePublished": "2019-03-13", + "description": "Uw moet uw overtreding nog betalen", + "id": "OVERTREDINGEN-M1", + "link": { + "title": "Betaal direct", + "to": "https://ontheffingen-acc.amsterdam.nl/publiek/aanvraag/1", + }, + "title": "Overtreding betalen", + }, + ], + }, + "status": "OK", + } + `); + }); }); diff --git a/src/server/services/simple-connect/cleopatra.ts b/src/server/services/simple-connect/cleopatra.ts index 111ba77e28..9e5f37ad80 100644 --- a/src/server/services/simple-connect/cleopatra.ts +++ b/src/server/services/simple-connect/cleopatra.ts @@ -4,7 +4,12 @@ import { Chapters, IS_TAP } from '../../../universal/config'; import { MyNotification } from '../../../universal/types'; import { DataRequestConfig, getApiConfig } from '../../config'; import { AuthProfileAndToken } from '../../helpers/app'; -import { fetchService, fetchTipsAndNotifications } from './api-service'; +import { + ApiPatternResponseA, + fetchService, + fetchTipsAndNotifications, +} from './api-service'; +import { apiSuccessResult } from '../../../universal/helpers'; const DEV_KEY = { kty: 'RSA', @@ -35,7 +40,7 @@ const pemPubKey = export function getJSONRequestPayload( profile: AuthProfileAndToken['profile'] ): string { - const payload: MilieuzoneRequestPayload = + const payload: CleopatraRequestPayload = profile.profileType === 'commercial' ? { kvk: profile.id!, @@ -46,7 +51,7 @@ export function getJSONRequestPayload( return JSON.stringify(payload); } -export async function encryptPayload(payload: MilieuzoneRequestPayloadString) { +export async function encryptPayload(payload: CleopatraRequestPayloadString) { const key = await pemPubKey; return jose.JWE.createEncrypt( @@ -65,8 +70,8 @@ export async function encryptPayload(payload: MilieuzoneRequestPayloadString) { .final(); } -interface MilieuzoneMessage { - thema: 'Milieuzone'; +interface CleopatraMessage { + thema: 'Milieuzone' | 'Overtredingen'; categorie: 'F2' | 'M1' | 'F3'; nummer: number; prioriteit: number; @@ -78,40 +83,56 @@ interface MilieuzoneMessage { informatie: string; } -type MilieuzoneRequestPayload = { kvk: string } | { bsn: string }; -type MilieuzoneRequestPayloadString = string; +type CleopatraRequestPayload = { kvk: string } | { bsn: string }; +type CleopatraRequestPayloadString = string; -function transformMilieuzoneResponse(response: MilieuzoneMessage[]) { +type CleoPatraPatternResponse = ApiPatternResponseA & { + isKnownMilieuzone: boolean; + isKnownOvertredingen: boolean; +}; + +function transformCleopatraResponse(response: CleopatraMessage[]) { const notifications: MyNotification[] = []; - let isKnown: boolean = false; + let isKnownMilieuzone: boolean = false; + let isKnownOvertredingen: boolean = false; if (Array.isArray(response)) { for (const message of response) { - switch (message.categorie) { - case 'F2': - isKnown = true; + switch (true) { + case message.categorie === 'F2' && message.thema === 'Overtredingen': + isKnownOvertredingen = true; + break; + case message.categorie === 'F2' && message.thema === 'Milieuzone': + isKnownMilieuzone = true; break; // Melding / Notification - case 'M1': - case 'F3': - notifications.push({ - id: `milieuzone-${message.categorie}`, - chapter: Chapters.MILIEUZONE, - title: message.titel, - datePublished: message.datum, - description: message.omschrijving, - link: { - title: message.urlNaam, - to: message.url, - }, - }); + case message.categorie === 'M1' || message.categorie === 'F3': + { + const chapter = + message.thema === 'Milieuzone' + ? Chapters.MILIEUZONE + : Chapters.OVERTREDINGEN; + + notifications.push({ + id: `${chapter}-${message.categorie}`, + chapter, + title: message.titel, + datePublished: message.datum, + description: message.omschrijving, + link: { + title: message.urlNaam, + to: message.url, + }, + }); + } break; } } } return { - isKnown, + isKnownOvertredingen, + isKnownMilieuzone, notifications, }; } @@ -125,32 +146,86 @@ async function getConfig( ); return getApiConfig('CLEOPATRA', { - transformResponse: transformMilieuzoneResponse, + transformResponse: transformCleopatraResponse, cacheKey: `cleopatra-${requestID}`, data: postData, }); } -export async function fetchMilieuzone( +async function fetchCleopatra( requestID: requestID, authProfileAndToken: AuthProfileAndToken ) { - return fetchService( + const INCLUDE_TIPS_AND_NOTIFICATIONS = true; + return fetchService( requestID, await getConfig(authProfileAndToken, requestID), - false + INCLUDE_TIPS_AND_NOTIFICATIONS ); } +export async function fetchMilieuzone( + requestID: requestID, + authProfileAndToken: AuthProfileAndToken +) { + const response = await fetchCleopatra(requestID, authProfileAndToken); + + if (response.status === 'OK') { + return apiSuccessResult({ + isKnown: response.content?.isKnownMilieuzone ?? false, + }); + } + + return response; +} + +export async function fetchOvertredingen( + requestID: requestID, + authProfileAndToken: AuthProfileAndToken +) { + const response = await fetchCleopatra(requestID, authProfileAndToken); + + if (response.status === 'OK') { + return apiSuccessResult({ + isKnown: response.content?.isKnownOvertredingen ?? false, + }); + } + + return response; +} + export async function fetchMilieuzoneNotifications( requestID: requestID, authProfileAndToken: AuthProfileAndToken ) { - const response = await fetchTipsAndNotifications( - requestID, - await getConfig(authProfileAndToken, requestID), - Chapters.MILIEUZONE - ); + const response = await fetchCleopatra(requestID, authProfileAndToken); + + if (response.status === 'OK') { + return apiSuccessResult({ + notifications: + response.content?.notifications?.filter( + (notifiction) => notifiction.chapter === Chapters.MILIEUZONE + ) ?? [], + }); + } + + return response; +} + +export async function fetchOvertredingenNotifications( + requestID: requestID, + authProfileAndToken: AuthProfileAndToken +) { + const response = await fetchCleopatra(requestID, authProfileAndToken); + + if (response.status === 'OK') { + return apiSuccessResult({ + notifications: + response.content?.notifications?.filter( + (notifiction) => notifiction.chapter === Chapters.OVERTREDINGEN + ) ?? [], + }); + } return response; } diff --git a/src/server/services/simple-connect/index.ts b/src/server/services/simple-connect/index.ts index 71281d6cee..afe05f3ab1 100644 --- a/src/server/services/simple-connect/index.ts +++ b/src/server/services/simple-connect/index.ts @@ -1,4 +1,9 @@ export { fetchBelasting, fetchBelastingNotifications } from './belasting'; export { fetchSubsidie, fetchSubsidieNotifications } from './subsidie'; -export { fetchMilieuzone, fetchMilieuzoneNotifications } from './cleopatra'; +export { + fetchOvertredingen, + fetchOvertredingenNotifications, + fetchMilieuzone, + fetchMilieuzoneNotifications, +} from './cleopatra'; export { fetchErfpacht, fetchErfpachtNotifications } from './erfpacht'; diff --git a/src/server/services/tips-and-notifications.ts b/src/server/services/tips-and-notifications.ts index cc0c841551..9330699068 100644 --- a/src/server/services/tips-and-notifications.ts +++ b/src/server/services/tips-and-notifications.ts @@ -18,6 +18,7 @@ import { fetchBelastingNotifications, fetchErfpachtNotifications, fetchMilieuzoneNotifications, + fetchOvertredingenNotifications, fetchSubsidieNotifications, } from './simple-connect'; import { convertTipToNotication } from './tips/tips-service'; @@ -104,6 +105,7 @@ type NotificationServices = Record< const notificationServices: NotificationServices = { commercial: { milieuzone: fetchMilieuzoneNotifications, + overtredingen: fetchOvertredingenNotifications, vergunningen: fetchVergunningenNotifications, horeca: fetchHorecaNotifications, erfpacht: fetchErfpachtNotifications, @@ -128,6 +130,7 @@ const notificationServices: NotificationServices = { brp: fetchBrpNotifications, belasting: fetchBelastingNotifications, milieuzone: fetchMilieuzoneNotifications, + overtredingen: fetchOvertredingenNotifications, vergunningen: fetchVergunningenNotifications, erfpacht: fetchErfpachtNotifications, subsidie: fetchSubsidieNotifications, diff --git a/src/universal/config/app.ts b/src/universal/config/app.ts index 7693ffd7eb..ec8309232d 100644 --- a/src/universal/config/app.ts +++ b/src/universal/config/app.ts @@ -4,7 +4,7 @@ import { IS_PRODUCTION } from './env'; export const FeatureToggle = { garbageInformationPage: true, belastingApiActive: true, - milieuzoneApiActive: true, + cleopatraApiActive: true, identiteitsbewijzenActive: true, eherkenningActive: true, vergunningenActive: true, diff --git a/src/universal/config/chapter.ts b/src/universal/config/chapter.ts index afebae9e90..297ec352bf 100644 --- a/src/universal/config/chapter.ts +++ b/src/universal/config/chapter.ts @@ -15,6 +15,7 @@ export type Chapter = | 'STADSPAS' | 'BRP' | 'MILIEUZONE' + | 'OVERTREDINGEN' | 'NOTIFICATIONS' | 'ROOT' | 'ERFPACHT' @@ -44,6 +45,7 @@ export const Chapters: Record = { STADSPAS: 'STADSPAS', BRP: 'BRP', MILIEUZONE: 'MILIEUZONE', + OVERTREDINGEN: 'OVERTREDINGEN', NOTIFICATIONS: 'NOTIFICATIONS', ROOT: 'ROOT', ERFPACHT: 'ERFPACHT', @@ -74,6 +76,7 @@ export const ChapterTitles: { [chapter in Chapter]: string } = { STADSPAS: 'Stadspas', BRP: 'Mijn gegevens', MILIEUZONE: 'Milieuzone', + OVERTREDINGEN: 'Overtredingen voertuigen', NOTIFICATIONS: 'Actueel', ROOT: 'Home', ERFPACHT: 'Erfpacht', @@ -302,6 +305,13 @@ export const myChaptersMenuItems: ChapterMenuItem[] = [ rel: 'external', profileTypes: ['private', 'commercial'], }, + { + title: ChapterTitles.OVERTREDINGEN, + id: Chapters.OVERTREDINGEN, + to: ExternalUrls.SSO_MILIEUZONE || '', // TODO: In de toekomst wordt dit een andere link + rel: 'external', + profileTypes: ['private', 'commercial'], + }, { title: ChapterTitles.SIA, id: Chapters.SIA, diff --git a/src/universal/helpers/chapters.ts b/src/universal/helpers/chapters.ts index 8ae8827efb..3a7cf959cf 100644 --- a/src/universal/helpers/chapters.ts +++ b/src/universal/helpers/chapters.ts @@ -16,6 +16,7 @@ export function isChapterActive(item: ChapterMenuItem, appState: AppState) { BRP, BELASTINGEN, MILIEUZONE, + OVERTREDINGEN, VERGUNNINGEN, SIA, TOERISTISCHE_VERHUUR, @@ -83,8 +84,14 @@ export function isChapterActive(item: ChapterMenuItem, appState: AppState) { case Chapters.MILIEUZONE: return ( !isLoading(MILIEUZONE) && - (FeatureToggle.milieuzoneApiActive - ? MILIEUZONE.content?.isKnown + (FeatureToggle.cleopatraApiActive ? MILIEUZONE.content?.isKnown : false) + ); + + case Chapters.OVERTREDINGEN: + return ( + !isLoading(OVERTREDINGEN) && + (FeatureToggle.cleopatraApiActive + ? OVERTREDINGEN.content?.isKnown : false) ); diff --git a/src/universal/helpers/utils.ts b/src/universal/helpers/utils.ts index 41934e01f6..1fcd375140 100644 --- a/src/universal/helpers/utils.ts +++ b/src/universal/helpers/utils.ts @@ -106,6 +106,16 @@ export function sortAlpha( }; } +export function sortByNumber(key: string, direction: 'asc' | 'desc' = 'asc') { + return (a: Record, b: Record) => { + const sortASC = direction === 'asc'; + let aValue = a[key]; + let bValue = b[key]; + + return sortASC ? aValue - bValue : bValue - aValue; + }; +} + // https://github.com/darkskyapp/string-hash export function hash(str: string) { var hash = 5381,