From 1a5958979e5b15e684d839a75e693f0b6c74f7ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Eorkell=20M=C3=A1ni=20=C3=9Eorkelsson?= Date: Wed, 14 Aug 2024 13:06:15 +0000 Subject: [PATCH] feat(license-service): update license service (#15204) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:updat emodels * feat/bunch * feat: updates * feat: add and remove * chore: stash merge * feat: more stuff * fix: license type * feat: license overivew works * chore:revert * chore: revert messages * chore: console * chore: revert * chore: nx format:write update dirty files * fix: passports * chore: start details * chore: nx format:write update dirty files * feat: update details * fix: enum * fix: date format * fix: type import * chore:small fixes * chore:rename-liceinse-service ' ' * chore: remove original license service * chore: add unchanged license service * chore: add mocks back * chore: add to resolver * chore:renmae license * chore: readd licens service * fix: license-v2 * fix: readd ls-v1 * feat: rename * chore: nx format:write update dirty files * fix: passport display * chore: codeowners * chore: move datafields * fix: html semantics * fix:pkpass display * chore:console log * chore: use types * feat: remove v2 and merge in v1 * chore:import * fix: expires * fix:navigation * fix: mapping * chore: import * fix: time format * fix:fix * chore: clean up types * fix: async * fix: messages * fix: update display slightly * fix: mcoks * chore: deprecate model * chore: import clean up * fix: update mapping * chore: import clean up * fix: reimport reaect * chore: use tag map * chore: coderabbit improvements * fix: const issues * chore: concising --------- Co-authored-by: Þorkell Máni Þorkelsson Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../Notifications/NotificationMenu.tsx | 5 +- .../src/components/Sidemenu/Sidemenu.tsx | 6 +- .../acceptance/mocks/licenses.mock.ts | 20 +- libs/api/domains/license-service/project.json | 6 + .../src/lib/dto/CreateBarcodeResult.dto.ts | 4 +- .../src/lib/dto/GenericLicense.dto.ts | 11 +- .../lib/dto/GenericLicenseCollection.dto.ts | 12 + .../lib/dto/GenericLicenseDataField.dto.ts | 13 +- .../src/lib/dto/GenericLicenseError.dto.ts | 25 ++ .../src/lib/dto/GenericLicenseProvider.dto.ts | 12 + .../src/lib/dto/GenericPkPass.dto.ts | 2 +- .../src/lib/dto/GenericPkPassQrCode.dto.ts | 2 +- .../lib/dto/GenericPkPassVerification.dto.ts | 8 +- .../src/lib/dto/GenericUserLicense.dto.ts | 7 +- .../lib/dto/GenericUserLicenseAlert.dto.ts | 18 + .../dto/GenericUserLicenseMetaLinks.dto.ts | 6 +- .../lib/dto/GenericUserLicenseMetaTag.dto.ts | 36 ++ .../lib/dto/GenericUserLicenseMetadata.dto.ts | 51 ++- ...nericUserLicenseMetadataDescription.dto.ts | 17 + .../src/lib/dto/GetGenericLicense.input.ts | 2 +- .../src/lib/dto/Payload.dto.ts | 9 +- .../src/lib/dto/UserLicensesResponse.dto.ts | 5 +- .../src/lib/dto/VerifyLicenseBarcodeInput.ts | 2 +- .../src/lib/dto/VerifyPkPass.input.ts | 2 +- .../src/lib/licenceService.type.ts | 200 +++------- .../src/lib/licenseService.constants.ts | 20 +- .../src/lib/licenseService.module.ts | 26 +- .../src/lib/licenseService.resolver.ts | 159 -------- .../src/lib/licenseService.service.ts | 300 ++++++-------- .../src/lib/mappers/adrLicenseMapper.ts | 95 +++-- .../lib/mappers/disabilityLicenseMapper.ts | 81 ++-- .../src/lib/mappers/drivingLicenseMapper.ts | 119 +++--- .../src/lib/mappers/ehicCardMapper.ts | 203 ++++++---- .../src/lib/mappers/firearmLicenseMapper.ts | 310 ++++++++------- .../src/lib/mappers/huntingLicenseMapper.ts | 122 +++--- .../src/lib/mappers/licenseMapper.module.ts | 5 + .../src/lib/mappers/machineLicenseMapper.ts | 101 +++-- .../src/lib/mappers/pCardMapper.ts | 94 +++-- .../src/lib/mappers/passportMapper.ts | 265 +++++++++++++ .../license-service/src/lib/messages.ts | 372 ++++++++++++++++++ .../lib/providers/licenseMapper.provider.ts | 5 + .../resolvers/licenseCollection.resolver.ts | 71 ++++ .../src/lib/resolvers/pkPass.resolver.ts | 87 ++++ .../src/lib/resolvers/provider.resolver.ts | 54 +++ .../src/lib/resolvers/userLicense.resolver.ts | 63 +++ .../src/lib/utils/capitalize.ts | 17 + .../src/lib/utils/expiryTag.ts | 39 ++ .../src/lib/utils/formatDate.ts | 3 + .../license-service/src/lib/utils/index.ts | 3 + .../src/lib/utils/translations.ts | 147 ------- .../src/domains/license-service/factories.ts | 2 - .../passport-client/passportClient.type.ts | 9 + .../passportsClient.service.ts | 37 +- .../src/lib/licenseClient.module.ts | 6 + .../src/lib/licenseClient.type.ts | 7 +- libs/feature-flags/src/lib/features.ts | 3 + .../screens/VehicleMileage/VehicleMileage.tsx | 32 +- .../src/components/ActionCard/ActionCard.tsx | 16 +- .../components/IntroHeader/IntroHeader.tsx | 8 +- .../components/UserInfoLine/UserInfoLine.tsx | 1 + libs/service-portal/core/src/lib/messages.ts | 9 + libs/service-portal/licenses/codegen.yml | 19 + libs/service-portal/licenses/project.json | 7 + .../ExpandableLine/ExpandableLine.css.ts} | 0 .../ExpandableLine}/ExpandableLine.tsx | 12 +- .../LicenseDataFields/LicenseDataFields.tsx | 209 ++++++++++ .../licenses/src/lib/constants.ts | 11 + .../licenses/src/lib/messages.ts | 9 + .../licenses/src/lib/navigation.ts | 5 + libs/service-portal/licenses/src/lib/paths.ts | 1 + libs/service-portal/licenses/src/module.tsx | 13 +- .../licenses/src/screens/LicensesOverview.tsx | 42 ++ .../v1/LicenseDetail/LicenseDetail.css.ts | 82 ++++ .../{ => v1}/LicenseDetail/LicenseDetail.tsx | 62 +-- .../LicensesOverview/ChildrenLicenses.tsx | 2 +- .../LicensesOverview/UserLicenses.tsx | 8 +- .../{ => v1}/LicensesOverview/index.tsx | 7 +- .../PassportDetail/PassportDetail.css.ts | 0 .../PassportDetail/PassportDetail.tsx | 9 +- .../v2/LicenseDetail/LicenseDetail.graphql | 108 +++++ .../v2/LicenseDetail/LicenseDetail.tsx | 163 ++++++++ .../LicensesOverview/LicensesOverview.graphql | 64 +++ .../v2/LicensesOverview/LicensesOverview.tsx | 154 ++++++++ .../licenses/src/utils/capitalize.ts | 14 +- .../licenses/src/utils/mapPaths.ts | 62 +++ .../OccupationalLicensesDetail.tsx | 1 - 86 files changed, 3154 insertions(+), 1282 deletions(-) create mode 100644 libs/api/domains/license-service/src/lib/dto/GenericLicenseCollection.dto.ts create mode 100644 libs/api/domains/license-service/src/lib/dto/GenericLicenseError.dto.ts create mode 100644 libs/api/domains/license-service/src/lib/dto/GenericUserLicenseAlert.dto.ts create mode 100644 libs/api/domains/license-service/src/lib/dto/GenericUserLicenseMetaTag.dto.ts create mode 100644 libs/api/domains/license-service/src/lib/dto/GenericUserLicenseMetadataDescription.dto.ts delete mode 100644 libs/api/domains/license-service/src/lib/licenseService.resolver.ts create mode 100644 libs/api/domains/license-service/src/lib/mappers/passportMapper.ts create mode 100644 libs/api/domains/license-service/src/lib/messages.ts create mode 100644 libs/api/domains/license-service/src/lib/resolvers/licenseCollection.resolver.ts create mode 100644 libs/api/domains/license-service/src/lib/resolvers/pkPass.resolver.ts create mode 100644 libs/api/domains/license-service/src/lib/resolvers/provider.resolver.ts create mode 100644 libs/api/domains/license-service/src/lib/resolvers/userLicense.resolver.ts create mode 100644 libs/api/domains/license-service/src/lib/utils/capitalize.ts create mode 100644 libs/api/domains/license-service/src/lib/utils/expiryTag.ts create mode 100644 libs/api/domains/license-service/src/lib/utils/formatDate.ts create mode 100644 libs/api/domains/license-service/src/lib/utils/index.ts delete mode 100644 libs/api/domains/license-service/src/lib/utils/translations.ts create mode 100644 libs/clients/license-client/src/lib/clients/passport-client/passportClient.type.ts create mode 100644 libs/service-portal/licenses/codegen.yml rename libs/service-portal/licenses/src/{screens/LicenseDetail/LicenseDetail.css.ts => components/ExpandableLine/ExpandableLine.css.ts} (100%) rename libs/service-portal/licenses/src/{screens/LicenseDetail => components/ExpandableLine}/ExpandableLine.tsx (94%) create mode 100644 libs/service-portal/licenses/src/components/LicenseDataFields/LicenseDataFields.tsx create mode 100644 libs/service-portal/licenses/src/screens/LicensesOverview.tsx create mode 100644 libs/service-portal/licenses/src/screens/v1/LicenseDetail/LicenseDetail.css.ts rename libs/service-portal/licenses/src/screens/{ => v1}/LicenseDetail/LicenseDetail.tsx (86%) rename libs/service-portal/licenses/src/screens/{ => v1}/LicensesOverview/ChildrenLicenses.tsx (94%) rename libs/service-portal/licenses/src/screens/{ => v1}/LicensesOverview/UserLicenses.tsx (94%) rename libs/service-portal/licenses/src/screens/{ => v1}/LicensesOverview/index.tsx (99%) rename libs/service-portal/licenses/src/screens/{ => v1}/PassportDetail/PassportDetail.css.ts (100%) rename libs/service-portal/licenses/src/screens/{ => v1}/PassportDetail/PassportDetail.tsx (97%) create mode 100644 libs/service-portal/licenses/src/screens/v2/LicenseDetail/LicenseDetail.graphql create mode 100644 libs/service-portal/licenses/src/screens/v2/LicenseDetail/LicenseDetail.tsx create mode 100644 libs/service-portal/licenses/src/screens/v2/LicensesOverview/LicensesOverview.graphql create mode 100644 libs/service-portal/licenses/src/screens/v2/LicensesOverview/LicensesOverview.tsx create mode 100644 libs/service-portal/licenses/src/utils/mapPaths.ts diff --git a/apps/service-portal/src/components/Notifications/NotificationMenu.tsx b/apps/service-portal/src/components/Notifications/NotificationMenu.tsx index d320c0aa81f9..35806e33d7d4 100644 --- a/apps/service-portal/src/components/Notifications/NotificationMenu.tsx +++ b/apps/service-portal/src/components/Notifications/NotificationMenu.tsx @@ -152,10 +152,7 @@ const NotificationMenu = ({ isVisible={sideMenuOpen} hideOnClickOutside={true} hideOnEsc={true} - modalLabel={formatMessage({ - id: 'service.portal:notification-button-aria', - defaultMessage: 'Valmynd fyrir tilkynningar', - })} + modalLabel={formatMessage(m.notificationButtonAria)} removeOnClose={true} preventBodyScroll={false} onVisibilityChange={(visibility: boolean) => { diff --git a/apps/service-portal/src/components/Sidemenu/Sidemenu.tsx b/apps/service-portal/src/components/Sidemenu/Sidemenu.tsx index 49e26f50af6f..ae17b9dc87c5 100644 --- a/apps/service-portal/src/components/Sidemenu/Sidemenu.tsx +++ b/apps/service-portal/src/components/Sidemenu/Sidemenu.tsx @@ -124,11 +124,7 @@ const Sidemenu = ({ isVisible={sideMenuOpen} hideOnClickOutside={true} hideOnEsc={true} - modalLabel={formatMessage({ - id: 'service.portal:menu-button-aria', - description: 'Lýsing á notendavalmynd fyrir skjálesara', - defaultMessage: 'Valmynd fyrir yfirlit', - })} + modalLabel={formatMessage(m.menuButtonAria)} removeOnClose={true} preventBodyScroll={false} onVisibilityChange={(visibility: boolean) => { diff --git a/apps/system-e2e/src/tests/islandis/service-portal/acceptance/mocks/licenses.mock.ts b/apps/system-e2e/src/tests/islandis/service-portal/acceptance/mocks/licenses.mock.ts index 96ab517f7c06..ae291c2e4a92 100644 --- a/apps/system-e2e/src/tests/islandis/service-portal/acceptance/mocks/licenses.mock.ts +++ b/apps/system-e2e/src/tests/islandis/service-portal/acceptance/mocks/licenses.mock.ts @@ -455,7 +455,7 @@ export const loadLicensesXroadMocks = async () => { categories: [ { id: 68446262, - nr: '5u84961', + nr: 'A', categoryName: 'Big big trucks', publishDate: new Date('2017-04-22T15:30:23Z'), dateTo: new Date('2030-04-22T15:30:23Z'), @@ -524,7 +524,7 @@ export const loadLicensesXroadMocks = async () => { licenseNumber: '987654321', properties: [ { - category: 'testCatA', + category: 'A', typeOfFirearm: 'cannon', name: 'Howitzer', serialNumber: '1337', @@ -533,7 +533,7 @@ export const loadLicensesXroadMocks = async () => { limitation: 'aint no brakes', }, { - category: 'testCatB', + category: 'B', typeOfFirearm: 'laserrailgun', name: 'Macguffin', serialNumber: '010101', @@ -552,8 +552,18 @@ export const loadLicensesXroadMocks = async () => { apiPath: '/api/FirearmApplication/Categories', response: [ new Response().withJSONBody({ - testCatA: 'bibbiddí boo', - testCatB: 'babbbada', + 'Flokkur A': + '1. Haglabyssum nr. 12 og minni, þó eigi sjálfvirkum eða hálfsjálfvirkum.\n2. Rifflum cal. 22 (long rifle og minni), þ.m.t. loftrifflum, þó eigi sjálfvirkum eða hálfsjálfvirkum.', + 'Flokkur B': + 'Leyfi fyrir rifflum með hlaupvídd allt að cal. 30 og hálfsjálfvirkum haglabyssum skal ekki veitt nema sérstakar ástæður mæli með því, enda hafi umsækjandi haft skotvopnaleyfi í a.m.k. eitt ár.', + 'Flokkur C': + 'Leyfi fyrir skotvopnum sem sérstaklega eru ætluð til minkaveiða eða meindýraeyðingar (t.d. skammbyssur fyrir haglaskot) má aðeins veita að fenginni umsögn veiðistjóra. Áskilið er að umsækjandi hafi haft aukin skotvopnaréttindi (B flokkur) í eitt ár. Slík leyfi vegna þeirra sem stunda minkaveiðar skal ekki veita til að eignast skotvopn heldur einungis til láns eða leigu. Lögreglustjóri skal senda slíkar umsóknir með umsögn sinni ríkislögreglustjóra til ákvörðunar.', + 'Flokkur D': + 'Leyfi sem sérstaklega er veitt einstaklingi eða skotfélagi fyrir skammbyssum vegna íþróttaskotfimi sbr. 11. gr. Lögreglustjóri skal senda slíkar umsóknir með umsögn sinni ríkislögreglustjóra til ákvörðunar.', + 'Flokkur E': + 'Leyfi lögreglustjóra til að hlaða skothylki til eigin nota í þau skotvopn sem viðkomandi hefur leyfi fyrir, enda sé að öðru leyti heimilt að nota slík skotfæri hér á landi.', + 'Flokkur S': + 'Söfnunarleyfi sbr. 20. gr. Reglugerð um skotvopn, skotfæri o.fl.', }), ], }) diff --git a/libs/api/domains/license-service/project.json b/libs/api/domains/license-service/project.json index c884416947a6..7fd144772bbe 100644 --- a/libs/api/domains/license-service/project.json +++ b/libs/api/domains/license-service/project.json @@ -14,6 +14,12 @@ "options": { "jestConfig": "libs/api/domains/license-service/jest.config.ts" } + }, + "extract-strings": { + "executor": "nx:run-commands", + "options": { + "command": "yarn ts-node -P libs/localization/tsconfig.lib.json libs/localization/scripts/extract 'libs/api/domains/license-service/src/lib/messages.ts'" + } } } } diff --git a/libs/api/domains/license-service/src/lib/dto/CreateBarcodeResult.dto.ts b/libs/api/domains/license-service/src/lib/dto/CreateBarcodeResult.dto.ts index dd0d08d4dd27..7aade747eb83 100644 --- a/libs/api/domains/license-service/src/lib/dto/CreateBarcodeResult.dto.ts +++ b/libs/api/domains/license-service/src/lib/dto/CreateBarcodeResult.dto.ts @@ -1,4 +1,4 @@ -import { Field, ObjectType } from '@nestjs/graphql' +import { Field, ObjectType, Int } from '@nestjs/graphql' @ObjectType('CreateBarcodeResult') export class CreateBarcodeResult { @@ -7,7 +7,7 @@ export class CreateBarcodeResult { }) token!: string - @Field(() => Number, { + @Field(() => Int, { description: 'Barcode expire time in seconds', }) expiresIn!: number diff --git a/libs/api/domains/license-service/src/lib/dto/GenericLicense.dto.ts b/libs/api/domains/license-service/src/lib/dto/GenericLicense.dto.ts index beda14a96967..e4af68741204 100644 --- a/libs/api/domains/license-service/src/lib/dto/GenericLicense.dto.ts +++ b/libs/api/domains/license-service/src/lib/dto/GenericLicense.dto.ts @@ -1,4 +1,4 @@ -import { Field, ObjectType, registerEnumType } from '@nestjs/graphql' +import { Field, Int, ObjectType, registerEnumType } from '@nestjs/graphql' import { GenericLicenseType, GenericUserLicensePkPassStatus, @@ -33,20 +33,19 @@ export class GenericLicense { }) provider!: GenericLicenseProvider - @Field({ description: 'Display name of license' }) - name?: string - @Field({ description: 'Does the license support pkpass?' }) pkpass!: boolean @Field({ description: 'Does the license support verification of pkpass?' }) pkpassVerify!: boolean - @Field({ + @Field(() => Int, { description: 'How long the data about the license should be treated as fresh', + nullable: true, + deprecationReason: 'Unclear if this is used, will revert if necessary', }) - timeout!: number + timeout?: number @Field(() => GenericUserLicenseStatus, { description: 'Status of license' }) status!: GenericUserLicenseStatus diff --git a/libs/api/domains/license-service/src/lib/dto/GenericLicenseCollection.dto.ts b/libs/api/domains/license-service/src/lib/dto/GenericLicenseCollection.dto.ts new file mode 100644 index 000000000000..eb4ed0c00db8 --- /dev/null +++ b/libs/api/domains/license-service/src/lib/dto/GenericLicenseCollection.dto.ts @@ -0,0 +1,12 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { GenericLicenseError } from './GenericLicenseError.dto' +import { GenericUserLicense } from './GenericUserLicense.dto' + +@ObjectType('GenericLicenseCollection') +export class LicenseCollection { + @Field(() => [GenericUserLicense], { nullable: true }) + licenses!: Array + + @Field(() => [GenericLicenseError], { nullable: true }) + errors?: Array +} diff --git a/libs/api/domains/license-service/src/lib/dto/GenericLicenseDataField.dto.ts b/libs/api/domains/license-service/src/lib/dto/GenericLicenseDataField.dto.ts index 0621119961e8..5d63c5fdf270 100644 --- a/libs/api/domains/license-service/src/lib/dto/GenericLicenseDataField.dto.ts +++ b/libs/api/domains/license-service/src/lib/dto/GenericLicenseDataField.dto.ts @@ -1,6 +1,7 @@ import { Field, ObjectType, registerEnumType } from '@nestjs/graphql' import { GenericLicenseDataFieldType } from '../licenceService.type' import { GenericUserLicenseMetaLinks } from './GenericUserLicenseMetaLinks.dto' +import { GenericUserLicenseMetaTag } from './GenericUserLicenseMetaTag.dto' registerEnumType(GenericLicenseDataFieldType, { name: 'GenericLicenseDataFieldType', @@ -20,15 +21,19 @@ export class GenericLicenseDataField { @Field({ nullable: true, description: 'Label of data field' }) label?: string - @Field({ nullable: true, description: 'Value of data field' }) - value?: string - @Field({ nullable: true, - description: 'Same as value, used in service portal', + description: 'Display value of data field category', + deprecationReason: 'Only used for cosmetic purposes, can be done better', }) description?: string + @Field({ nullable: true, description: 'Value of data field' }) + value?: string + + @Field(() => GenericUserLicenseMetaTag, { nullable: true }) + tag?: GenericUserLicenseMetaTag + @Field(() => GenericUserLicenseMetaLinks, { nullable: true, description: 'External meta link', diff --git a/libs/api/domains/license-service/src/lib/dto/GenericLicenseError.dto.ts b/libs/api/domains/license-service/src/lib/dto/GenericLicenseError.dto.ts new file mode 100644 index 000000000000..8684bf7b7bec --- /dev/null +++ b/libs/api/domains/license-service/src/lib/dto/GenericLicenseError.dto.ts @@ -0,0 +1,25 @@ +import { Field, Int, ObjectType } from '@nestjs/graphql' +import { GenericLicenseType } from '../licenceService.type' +import { GenericLicenseProvider } from './GenericLicenseProvider.dto' +import { GenericLicenseFetch } from './GenericLicenseFetch.dto' + +@ObjectType('GenericLicenseError') +export class GenericLicenseError { + @Field(() => GenericLicenseType) + type!: GenericLicenseType + + @Field(() => GenericLicenseFetch, { description: 'Info about license fetch' }) + fetch!: GenericLicenseFetch + + @Field(() => GenericLicenseProvider, { nullable: true }) + provider?: GenericLicenseProvider + + @Field(() => Int, { nullable: true }) + code?: number + + @Field({ nullable: true }) + message?: string + + @Field({ nullable: true }) + extraData?: string +} diff --git a/libs/api/domains/license-service/src/lib/dto/GenericLicenseProvider.dto.ts b/libs/api/domains/license-service/src/lib/dto/GenericLicenseProvider.dto.ts index 88e693e357df..78f55598729b 100644 --- a/libs/api/domains/license-service/src/lib/dto/GenericLicenseProvider.dto.ts +++ b/libs/api/domains/license-service/src/lib/dto/GenericLicenseProvider.dto.ts @@ -12,4 +12,16 @@ export class GenericLicenseProvider { description: 'ID of license provider', }) id!: GenericLicenseProviderId + + @Field({ + nullable: true, + description: 'Contentful reference id', + }) + referenceId?: string + + @Field({ nullable: true }) + providerName?: string + + @Field({ nullable: true }) + providerLogo?: string } diff --git a/libs/api/domains/license-service/src/lib/dto/GenericPkPass.dto.ts b/libs/api/domains/license-service/src/lib/dto/GenericPkPass.dto.ts index 2338bd7dabab..22b4f348066e 100644 --- a/libs/api/domains/license-service/src/lib/dto/GenericPkPass.dto.ts +++ b/libs/api/domains/license-service/src/lib/dto/GenericPkPass.dto.ts @@ -2,6 +2,6 @@ import { Field, ObjectType } from '@nestjs/graphql' @ObjectType() export class GenericPkPass { - @Field(() => String) + @Field() pkpassUrl!: string } diff --git a/libs/api/domains/license-service/src/lib/dto/GenericPkPassQrCode.dto.ts b/libs/api/domains/license-service/src/lib/dto/GenericPkPassQrCode.dto.ts index f7d6031dabfd..bca654ce0ee8 100644 --- a/libs/api/domains/license-service/src/lib/dto/GenericPkPassQrCode.dto.ts +++ b/libs/api/domains/license-service/src/lib/dto/GenericPkPassQrCode.dto.ts @@ -2,6 +2,6 @@ import { Field, ObjectType } from '@nestjs/graphql' @ObjectType() export class GenericPkPassQrCode { - @Field(() => String) + @Field() pkpassQRCode!: string } diff --git a/libs/api/domains/license-service/src/lib/dto/GenericPkPassVerification.dto.ts b/libs/api/domains/license-service/src/lib/dto/GenericPkPassVerification.dto.ts index c8f3e7ccf3d6..f9efe522c65a 100644 --- a/libs/api/domains/license-service/src/lib/dto/GenericPkPassVerification.dto.ts +++ b/libs/api/domains/license-service/src/lib/dto/GenericPkPassVerification.dto.ts @@ -2,21 +2,21 @@ import { Field, ObjectType } from '@nestjs/graphql' @ObjectType() export class GenericPkPassVerificationError { - @Field(() => String, { + @Field({ nullable: true, description: 'pkpass verification error code, depandant on origination service, "0" for unknown error', }) status?: string - @Field(() => String, { + @Field({ nullable: true, description: 'pkpass verification error message, depandant on origination service', }) message?: string - @Field(() => String, { + @Field({ nullable: true, description: 'Optional data related to the error', }) @@ -25,7 +25,7 @@ export class GenericPkPassVerificationError { @ObjectType() export class GenericPkPassVerification { - @Field(() => String, { + @Field({ nullable: true, description: 'Optional data related to the pkpass verification', }) diff --git a/libs/api/domains/license-service/src/lib/dto/GenericUserLicense.dto.ts b/libs/api/domains/license-service/src/lib/dto/GenericUserLicense.dto.ts index 36bf49579b19..9ca2a21e1214 100644 --- a/libs/api/domains/license-service/src/lib/dto/GenericUserLicense.dto.ts +++ b/libs/api/domains/license-service/src/lib/dto/GenericUserLicense.dto.ts @@ -7,10 +7,15 @@ import { Payload } from './Payload.dto' export class GenericUserLicense { @Field({ description: 'National ID of license owner', - deprecationReason: 'Moved one level up', }) nationalId!: string + @Field({ + nullable: true, + description: 'Is license owner child of user', + }) + isOwnerChildOfUser?: boolean + @Field(() => GenericLicense, { description: 'License info' }) license!: GenericLicense diff --git a/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseAlert.dto.ts b/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseAlert.dto.ts new file mode 100644 index 000000000000..a10ae06084e9 --- /dev/null +++ b/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseAlert.dto.ts @@ -0,0 +1,18 @@ +import { Field, ObjectType, registerEnumType } from '@nestjs/graphql' +import { AlertType } from '../licenceService.type' + +registerEnumType(AlertType, { + name: 'GenericUserLicenseAlertType', +}) + +@ObjectType('GenericUserLicenseAlert') +export class GenericUserLicenseAlert { + @Field() + title!: string + + @Field(() => AlertType, { defaultValue: AlertType.WARNING }) + type?: AlertType + + @Field({ nullable: true }) + message?: string +} diff --git a/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseMetaLinks.dto.ts b/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseMetaLinks.dto.ts index c90be37f62a6..c0b02de95b45 100644 --- a/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseMetaLinks.dto.ts +++ b/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseMetaLinks.dto.ts @@ -8,13 +8,13 @@ registerEnumType(GenericUserLicenseMetaLinksType, { @ObjectType() export class GenericUserLicenseMetaLinks { - @Field(() => String, { nullable: true }) + @Field({ nullable: true }) label?: string - @Field(() => String, { nullable: true }) + @Field({ nullable: true }) value?: string - @Field(() => String, { nullable: true }) + @Field({ nullable: true }) name?: string @Field(() => GenericUserLicenseMetaLinksType, { nullable: true }) diff --git a/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseMetaTag.dto.ts b/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseMetaTag.dto.ts new file mode 100644 index 000000000000..18d757ded229 --- /dev/null +++ b/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseMetaTag.dto.ts @@ -0,0 +1,36 @@ +import { Field, ObjectType, registerEnumType } from '@nestjs/graphql' +import { + GenericUserLicenseDataFieldTagColor, + GenericUserLicenseDataFieldTagType, +} from '../licenceService.type' + +registerEnumType(GenericUserLicenseDataFieldTagType, { + name: 'GenericUserLicenseDataFieldTagType', + description: 'Exhaustive list of possible tag icons', +}) +registerEnumType(GenericUserLicenseDataFieldTagColor, { + name: 'GenericUserLicenseDataFieldTagColor', + description: 'Exhaustive list of possible tag icon color', +}) + +@ObjectType('GenericUserLicenseMetaTag') +export class GenericUserLicenseMetaTag { + @Field() + text!: string + + @Field({ nullable: true }) + color?: string + + @Field(() => GenericUserLicenseDataFieldTagType, { nullable: true }) + icon?: GenericUserLicenseDataFieldTagType + + @Field(() => GenericUserLicenseDataFieldTagColor, { nullable: true }) + iconColor?: GenericUserLicenseDataFieldTagColor + + @Field({ + nullable: true, + description: + 'Defaults to the text property if icon defined but iconText left undefined', + }) + iconText?: string +} diff --git a/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseMetadata.dto.ts b/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseMetadata.dto.ts index 9d8b3549cd0d..18a03036abf2 100644 --- a/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseMetadata.dto.ts +++ b/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseMetadata.dto.ts @@ -1,29 +1,62 @@ import { Field, ObjectType } from '@nestjs/graphql' import { GenericUserLicenseMetaLinks } from './GenericUserLicenseMetaLinks.dto' +import { GenericUserLicenseMetaTag } from './GenericUserLicenseMetaTag.dto' +import { GenericUserLicenseMetadataDescription } from './GenericUserLicenseMetadataDescription.dto' +import { GenericUserLicenseAlert } from './GenericUserLicenseAlert.dto' @ObjectType() export class GenericUserLicenseMetadata { @Field(() => [GenericUserLicenseMetaLinks], { nullable: true }) links?: Array - @Field(() => String) + @Field({ nullable: true }) licenseNumber?: string - @Field(() => String, { + @Field({ nullable: true, description: 'Unique license identifier', }) licenseId?: string - @Field(() => Boolean, { nullable: true }) - expired?: boolean | null + @Field({ nullable: true }) + expired?: boolean - @Field(() => String, { nullable: true }) - expireDate?: string | null + @Field({ nullable: true }) + expireDate?: string - @Field(() => String) + @Field(() => GenericUserLicenseMetaTag, { nullable: true }) + displayTag?: GenericUserLicenseMetaTag + + @Field({ + nullable: true, + description: 'Display name of license for the overview', + }) + name?: string + + @Field({ nullable: true }) title?: string - @Field(() => String) - logo?: string + @Field({ + nullable: true, + description: 'Display subtitle for detail view', + }) + subtitle?: string + + @Field(() => [GenericUserLicenseMetadataDescription], { + nullable: true, + description: 'Display description for detail view', + }) + description?: Array + + @Field(() => GenericUserLicenseMetaLinks, { + nullable: true, + description: 'CTA link, only use if necessary', + }) + ctaLink?: GenericUserLicenseMetaLinks + + @Field(() => GenericUserLicenseAlert, { + nullable: true, + description: 'Display an alert on the detail view', + }) + alert?: GenericUserLicenseAlert } diff --git a/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseMetadataDescription.dto.ts b/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseMetadataDescription.dto.ts new file mode 100644 index 000000000000..986475b54929 --- /dev/null +++ b/libs/api/domains/license-service/src/lib/dto/GenericUserLicenseMetadataDescription.dto.ts @@ -0,0 +1,17 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { GenericUserLicenseMetaLinksType } from '../licenceService.type' + +@ObjectType('GenericUserLicenseMetadataDescription') +export class GenericUserLicenseMetadataDescription { + @Field() + text!: string + + @Field({ nullable: true, description: 'If defined, changes text to link' }) + linkInText?: string + + @Field(() => GenericUserLicenseMetaLinksType, { + nullable: true, + defaultValue: GenericUserLicenseMetaLinksType.External, + }) + linkIconType?: GenericUserLicenseMetaLinksType +} diff --git a/libs/api/domains/license-service/src/lib/dto/GetGenericLicense.input.ts b/libs/api/domains/license-service/src/lib/dto/GetGenericLicense.input.ts index b49e2109d0dc..7e3d4a18a3e3 100644 --- a/libs/api/domains/license-service/src/lib/dto/GetGenericLicense.input.ts +++ b/libs/api/domains/license-service/src/lib/dto/GetGenericLicense.input.ts @@ -6,6 +6,6 @@ export class GetGenericLicenseInput { @Field(() => String) licenseType!: GenericLicenseType - @Field(() => String, { nullable: true }) + @Field({ nullable: true }) licenseId?: string } diff --git a/libs/api/domains/license-service/src/lib/dto/Payload.dto.ts b/libs/api/domains/license-service/src/lib/dto/Payload.dto.ts index d30a76cb65f9..2a9d462ac0b1 100644 --- a/libs/api/domains/license-service/src/lib/dto/Payload.dto.ts +++ b/libs/api/domains/license-service/src/lib/dto/Payload.dto.ts @@ -1,6 +1,5 @@ import { Field, ObjectType } from '@nestjs/graphql' -import { IsObject } from 'class-validator' -import graphqlTypeJson from 'graphql-type-json' +import { GraphQLJSON } from 'graphql-type-json' import { GenericLicenseDataField } from './GenericLicenseDataField.dto' import { GenericUserLicenseMetadata } from './GenericUserLicenseMetadata.dto' @@ -11,13 +10,11 @@ export class Payload { }) data!: GenericLicenseDataField[] - @Field(() => graphqlTypeJson, { + @Field(() => GraphQLJSON, { nullable: true, description: 'Raw JSON data', }) - @IsObject() - // eslint-disable-next-line @typescript-eslint/ban-types - rawData?: object + rawData?: string @Field(() => GenericUserLicenseMetadata) metadata?: GenericUserLicenseMetadata diff --git a/libs/api/domains/license-service/src/lib/dto/UserLicensesResponse.dto.ts b/libs/api/domains/license-service/src/lib/dto/UserLicensesResponse.dto.ts index 682548cc62a9..34bc36f027b3 100644 --- a/libs/api/domains/license-service/src/lib/dto/UserLicensesResponse.dto.ts +++ b/libs/api/domains/license-service/src/lib/dto/UserLicensesResponse.dto.ts @@ -3,7 +3,10 @@ import { GenericUserLicense } from './GenericUserLicense.dto' @ObjectType() export class UserLicensesResponse { - @Field({ description: 'National ID of licenses owner' }) + @Field({ + description: 'National ID of licenses owner', + deprecationReason: 'Unnecessary', + }) nationalId!: string @Field(() => [GenericUserLicense], { diff --git a/libs/api/domains/license-service/src/lib/dto/VerifyLicenseBarcodeInput.ts b/libs/api/domains/license-service/src/lib/dto/VerifyLicenseBarcodeInput.ts index 8e2d883870e2..9fa86e5777cf 100644 --- a/libs/api/domains/license-service/src/lib/dto/VerifyLicenseBarcodeInput.ts +++ b/libs/api/domains/license-service/src/lib/dto/VerifyLicenseBarcodeInput.ts @@ -2,6 +2,6 @@ import { Field, InputType } from '@nestjs/graphql' @InputType('VerifyLicenseBarcodeInput') export class VerifyLicenseBarcodeInput { - @Field(() => String) + @Field() data!: string } diff --git a/libs/api/domains/license-service/src/lib/dto/VerifyPkPass.input.ts b/libs/api/domains/license-service/src/lib/dto/VerifyPkPass.input.ts index 605adf988478..9b9609772cfb 100644 --- a/libs/api/domains/license-service/src/lib/dto/VerifyPkPass.input.ts +++ b/libs/api/domains/license-service/src/lib/dto/VerifyPkPass.input.ts @@ -2,6 +2,6 @@ import { Field, InputType } from '@nestjs/graphql' @InputType() export class VerifyPkPassInput { - @Field(() => String) + @Field() data!: string } diff --git a/libs/api/domains/license-service/src/lib/licenceService.type.ts b/libs/api/domains/license-service/src/lib/licenceService.type.ts index 57bf8380bf35..3423724deb9a 100644 --- a/libs/api/domains/license-service/src/lib/licenceService.type.ts +++ b/libs/api/domains/license-service/src/lib/licenceService.type.ts @@ -1,5 +1,23 @@ -import { User } from '@island.is/auth-nest-tools' import { Locale } from '@island.is/shared/types' +import { GenericLicenseError } from './dto/GenericLicenseError.dto' +import { Payload } from './dto/Payload.dto' +import { GenericUserLicense as GenericUserLicenseModel } from './dto/GenericUserLicense.dto' + +export interface GenericLicenseMappedPayloadResponse { + licenseName: string + payload: Payload + type: 'user' | 'child' +} + +export type LicenseTypeFetchResponse = + | { + fetchResponseType: 'error' + data: GenericLicenseError + } + | { + fetchResponseType: 'licenses' + data: Array + } export enum GenericLicenseType { DriversLicense = 'DriversLicense', @@ -13,24 +31,6 @@ export enum GenericLicenseType { Passport = 'Passport', } -/** - * Get organization slug from the CMS. - * https://app.contentful.com/spaces/8k0h54kbe6bj/entries?id=kc7049zAeHdJezwG&contentTypeId=organization - * */ -export enum GenericLicenseOrganizationSlug { - DriversLicense = 'rikislogreglustjori', - // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values - FirearmLicense = 'rikislogreglustjori', - HuntingLicense = 'umhverfisstofnun', - AdrLicense = 'vinnueftirlitid', - // eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values - MachineLicense = 'vinnueftirlitid', - DisabilityLicense = 'tryggingastofnun', - PCard = 'syslumenn', - EHIC = 'sjukratryggingar-islands', - Passport = 'thjodskra-islands', -} - export type GenericLicenseTypeType = keyof typeof GenericLicenseType export enum GenericLicenseProviderId { @@ -77,55 +77,38 @@ export enum GenericUserLicenseMetaLinksType { Download = 'Download', } -export type GenericLicenseProvider = { - id: GenericLicenseProviderId -} - -export type GenericLicenseMetadata = { - type: GenericLicenseType - provider: GenericLicenseProvider - pkpass: boolean - pkpassVerify: boolean - timeout: number - orgSlug?: GenericLicenseOrganizationSlug -} - -export type GenericLicenseOrgdata = { - title?: string - logo?: string +export enum GenericUserLicenseValidity { + Unknown = 'Unknown', + Expired = 'Expired', + Valid = 'Valid', } -export type GenericLicenseDataField = { - type: GenericLicenseDataFieldType - name?: string - label?: string - value?: string - description?: string - // if any functionality comes attached to said data field, f.x. renewLicense - link?: GenericUserLicenseMetaLinks - hideFromServicePortal?: boolean - fields?: Array +export enum GenericUserLicenseDataFieldTagType { + 'checkmarkCircle', + 'closeCircle', } -export type GenericUserLicenseMetaLinks = { - label?: string - value?: string - name?: string - type?: GenericUserLicenseMetaLinksType +export enum GenericUserLicenseDataFieldTagColor { + 'green', + 'red', + 'yellow', } -export type GenericUserLicenseMetadata = { - links?: GenericUserLicenseMetaLinks[] - licenseId?: string - licenseNumber: string - expired: boolean | null - expireDate?: string +export enum AlertType { + WARNING, + ERROR, + INFO, } -export type GenericUserLicensePayload = { - data: Array - rawData: unknown - metadata?: GenericUserLicenseMetadata +export type GenericLicenseMetadata = { + type: GenericLicenseType + provider: { + id: GenericLicenseProviderId + referenceId: string + } + pkpass: boolean + pkpassVerify: boolean + timeout: number } export type GenericLicenseUserdata = { @@ -133,48 +116,6 @@ export type GenericLicenseUserdata = { pkpassStatus: GenericUserLicensePkPassStatus } -export type GenericLicenseFetchResult = { - data: Array - fetch: GenericLicenseFetch -} - -// A bit of an awkward type, it contains data from any external API, but we don't know if it's -// too narrow or not until we bring in more licenses -export type GenericLicenseUserdataExternal = { - status: GenericUserLicenseStatus - pkpassStatus: GenericUserLicensePkPassStatus - payload?: GenericUserLicensePayload | null -} - -export type GenericLicenseFetch = { - status: GenericUserLicenseFetchStatus - updated: Date -} - -export type GenericLicenseCached = { - data: GenericLicenseUserdata | null - fetch: GenericLicenseFetch - payload?: GenericUserLicensePayload -} - -export type LicenseLabelsObject = { - [x: string]: string -} - -export type GenericLicenseLabels = { - labels?: LicenseLabelsObject -} - -export type GenericUserLicense = { - nationalId: string - license: GenericLicenseMetadata & - GenericLicenseUserdata & - GenericLicenseOrgdata & - GenericLicenseLabels - fetch: GenericLicenseFetch - payload?: GenericUserLicensePayload -} - export type PkPassVerificationError = { /** * Generic placeholder for a status code, could be the HTTP status code, code @@ -194,66 +135,15 @@ export type PkPassVerificationError = { data?: string } -export type PassTemplateIds = { - firearmLicense: string - adrLicense: string - machineLicense: string - disabilityLicense: string - drivingLicense: string -} - export type PkPassVerification = { valid: boolean data?: string error?: PkPassVerificationError } -export type PkPassVerificationInputData = { - code: string - date: string -} - -/** - * Interface for client services, fetches generic payload and status from a third party API. - * Only one license per client to start with. - */ -export interface GenericLicenseClient { - // This might be cached - getLicense: ( - user: User, - locale: Locale, - labels: GenericLicenseLabels, - ) => Promise - - // This will never be cached - getLicenseDetail: ( - user: User, - locale: Locale, - labels: GenericLicenseLabels, - ) => Promise - - getPkPassUrl: ( - user: User, - data?: LicenseType, - locale?: Locale, - ) => Promise - - getPkPassQRCode: ( - user: User, - data?: LicenseType, - locale?: Locale, - ) => Promise - - verifyPkPass: ( - data: string, - passTemplateId: string, - ) => Promise -} - export interface GenericLicenseMapper { parsePayload: ( payload: Array, - locale?: Locale, - labels?: GenericLicenseLabels, - ) => Array + locale: Locale, + ) => Promise> } diff --git a/libs/api/domains/license-service/src/lib/licenseService.constants.ts b/libs/api/domains/license-service/src/lib/licenseService.constants.ts index dd937fce9a80..080b548aae6a 100644 --- a/libs/api/domains/license-service/src/lib/licenseService.constants.ts +++ b/libs/api/domains/license-service/src/lib/licenseService.constants.ts @@ -1,11 +1,11 @@ import { GenericLicenseMetadata, - GenericLicenseOrganizationSlug, GenericLicenseProviderId, GenericLicenseType, } from './licenceService.type' export const LICENSE_MAPPER_FACTORY = 'license-mapper-factory' +export const LICENSE_NAMESPACE = 'api.license-service' export const DEFAULT_LICENSE_ID = 'default' @@ -14,90 +14,90 @@ export const AVAILABLE_LICENSES: GenericLicenseMetadata[] = [ type: GenericLicenseType.FirearmLicense, provider: { id: GenericLicenseProviderId.NationalPoliceCommissioner, + referenceId: '06303', }, pkpass: true, pkpassVerify: true, timeout: 100, - orgSlug: GenericLicenseOrganizationSlug.FirearmLicense, }, { type: GenericLicenseType.DriversLicense, provider: { id: GenericLicenseProviderId.NationalPoliceCommissioner, + referenceId: '06303', }, pkpass: true, pkpassVerify: true, timeout: 100, - orgSlug: GenericLicenseOrganizationSlug.DriversLicense, }, { type: GenericLicenseType.AdrLicense, provider: { id: GenericLicenseProviderId.AdministrationOfOccupationalSafetyAndHealth, + referenceId: '07331', }, pkpass: true, pkpassVerify: true, timeout: 100, - orgSlug: GenericLicenseOrganizationSlug.AdrLicense, }, { type: GenericLicenseType.MachineLicense, provider: { id: GenericLicenseProviderId.AdministrationOfOccupationalSafetyAndHealth, + referenceId: '07331', }, pkpass: true, pkpassVerify: true, timeout: 100, - orgSlug: GenericLicenseOrganizationSlug.MachineLicense, }, { type: GenericLicenseType.DisabilityLicense, provider: { id: GenericLicenseProviderId.SocialInsuranceAdministration, + referenceId: '07821', }, pkpass: true, pkpassVerify: true, timeout: 100, - orgSlug: GenericLicenseOrganizationSlug.DisabilityLicense, }, { type: GenericLicenseType.HuntingLicense, provider: { id: GenericLicenseProviderId.EnvironmentAgency, + referenceId: '14211', }, pkpass: true, pkpassVerify: true, timeout: 100, - orgSlug: GenericLicenseOrganizationSlug.HuntingLicense, }, { type: GenericLicenseType.PCard, provider: { id: GenericLicenseProviderId.DistrictCommissioners, + referenceId: 'syslumenn', }, pkpass: false, pkpassVerify: false, timeout: 100, - orgSlug: GenericLicenseOrganizationSlug.PCard, }, { type: GenericLicenseType.Ehic, provider: { id: GenericLicenseProviderId.IcelandicHealthInsurance, + referenceId: '08202', }, pkpass: false, pkpassVerify: false, timeout: 100, - orgSlug: GenericLicenseOrganizationSlug.EHIC, }, { type: GenericLicenseType.Passport, provider: { id: GenericLicenseProviderId.RegistersIceland, + referenceId: '10601', }, pkpass: false, pkpassVerify: false, timeout: 100, - orgSlug: GenericLicenseOrganizationSlug.Passport, }, ] diff --git a/libs/api/domains/license-service/src/lib/licenseService.module.ts b/libs/api/domains/license-service/src/lib/licenseService.module.ts index 1784490b2b63..a808a4953ae5 100644 --- a/libs/api/domains/license-service/src/lib/licenseService.module.ts +++ b/libs/api/domains/license-service/src/lib/licenseService.module.ts @@ -1,31 +1,31 @@ import { LicenseClientModule } from '@island.is/clients/license-client' -import { CmsModule } from '@island.is/cms' import { LicenseModule } from '@island.is/services/license' import { Module } from '@nestjs/common' -import { CacheModule } from '@nestjs/cache-manager' - -import { LicenseServiceResolver } from './licenseService.resolver' -import { LicenseServiceService } from './licenseService.service' import { LicenseMapperModule } from './mappers/licenseMapper.module' - +import { FeatureFlagModule } from '@island.is/nest/feature-flags' +import { LicenseCollectionResolver } from './resolvers/licenseCollection.resolver' +import { PkPassResolver } from './resolvers/pkPass.resolver' +import { UserLicenseResolver } from './resolvers/userLicense.resolver' +import { LicenseProviderResolver } from './resolvers/provider.resolver' import { LicenseMapperProvider, LoggerProvider } from './providers' +import { LicenseService } from './licenseService.service' @Module({ imports: [ LicenseClientModule, LicenseMapperModule, - CmsModule, LicenseModule, - CacheModule.register({ - ttl: 60 * 10 * 1000, // 10 minutes - }), + FeatureFlagModule, ], providers: [ - LicenseServiceResolver, - LicenseServiceService, + LicenseService, LoggerProvider, LicenseMapperProvider, + LicenseCollectionResolver, + PkPassResolver, + UserLicenseResolver, + LicenseProviderResolver, ], - exports: [LicenseServiceService], + exports: [LicenseService], }) export class LicenseServiceModule {} diff --git a/libs/api/domains/license-service/src/lib/licenseService.resolver.ts b/libs/api/domains/license-service/src/lib/licenseService.resolver.ts deleted file mode 100644 index 62696266045b..000000000000 --- a/libs/api/domains/license-service/src/lib/licenseService.resolver.ts +++ /dev/null @@ -1,159 +0,0 @@ -import type { User } from '@island.is/auth-nest-tools' -import { - CurrentUser, - IdsUserGuard, - Scopes, - ScopesGuard, -} from '@island.is/auth-nest-tools' -import { ApiScope, LicenseApiScope } from '@island.is/auth/scopes' -import { Audit } from '@island.is/nest/audit' - -import type { Locale } from '@island.is/shared/types' -import { ForbiddenException, UseGuards } from '@nestjs/common' -import { - Args, - Mutation, - Parent, - Query, - ResolveField, - Resolver, -} from '@nestjs/graphql' -import { CreateBarcodeResult } from './dto/CreateBarcodeResult.dto' -import { GeneratePkPassInput } from './dto/GeneratePkPass.input' -import { GenericPkPass } from './dto/GenericPkPass.dto' -import { GenericPkPassQrCode } from './dto/GenericPkPassQrCode.dto' -import { GenericPkPassVerification } from './dto/GenericPkPassVerification.dto' -import { GenericUserLicense } from './dto/GenericUserLicense.dto' -import { GetGenericLicenseInput } from './dto/GetGenericLicense.input' -import { GetGenericLicensesInput } from './dto/GetGenericLicenses.input' -import { UserLicensesResponse } from './dto/UserLicensesResponse.dto' -import { VerifyLicenseBarcodeInput } from './dto/VerifyLicenseBarcodeInput' -import { VerifyLicenseBarcodeResult } from './dto/VerifyLicenseBarcodeResult.dto' -import { VerifyPkPassInput } from './dto/VerifyPkPass.input' -import { LicenseServiceService } from './licenseService.service' - -@UseGuards(IdsUserGuard, ScopesGuard) -@Scopes(ApiScope.internal, ApiScope.licenses) -@Resolver(() => GenericUserLicense) -@Audit({ namespace: '@island.is/api/license-service' }) -export class LicenseServiceResolver { - constructor(private readonly licenseServiceService: LicenseServiceService) {} - - @Query(() => [GenericUserLicense], { - deprecationReason: 'Use genericUserLicenses instead', - }) - @Audit() - async genericLicenses( - @CurrentUser() user: User, - @Args('locale', { type: () => String, nullable: true }) - locale: Locale = 'is', - @Args('input', { nullable: true }) input?: GetGenericLicensesInput, - ) { - return this.licenseServiceService.getAllLicenses(user, locale, { - includedTypes: input?.includedTypes ?? ['DriversLicense'], - excludedTypes: input?.excludedTypes, - force: input?.force, - onlyList: input?.onlyList, - }) - } - - @Query(() => GenericUserLicense, { nullable: true }) - @Audit() - async genericLicense( - @CurrentUser() user: User, - @Args('locale', { type: () => String, nullable: true }) - locale: Locale = 'is', - @Args('input') input: GetGenericLicenseInput, - ) { - return this.licenseServiceService.getLicense( - user, - locale, - input.licenseType, - input.licenseId, - ) - } - - @ResolveField('barcode', () => CreateBarcodeResult, { nullable: true }) - async createBarcode( - @CurrentUser() user: User, - @Parent() genericUserLicense: GenericUserLicense, - ): Promise { - if (!user.scope.includes(LicenseApiScope.licensesBarcode)) { - throw new ForbiddenException( - 'User does not have permission to create barcode', - ) - } - - return this.licenseServiceService.createBarcode(user, genericUserLicense) - } - - @Query(() => UserLicensesResponse) - @Audit() - async genericUserLicenses( - @CurrentUser() user: User, - @Args('locale', { type: () => String, nullable: true }) - locale: Locale = 'is', - @Args('input') input: GetGenericLicensesInput, - ) { - return this.licenseServiceService.getUserLicenses(user, locale, { - ...input, - includedTypes: input?.includedTypes ?? ['DriversLicense'], - }) - } - - @Mutation(() => GenericPkPass) - @Audit() - async generatePkPass( - @CurrentUser() user: User, - @Args('input') input: GeneratePkPassInput, - ): Promise { - const pkpassUrl = await this.licenseServiceService.generatePkPassUrl( - user, - input.licenseType, - ) - - return { - pkpassUrl, - } - } - - @Mutation(() => GenericPkPassQrCode) - @Audit() - async generatePkPassQrCode( - @CurrentUser() user: User, - @Args('input') input: GeneratePkPassInput, - ): Promise { - const pkpassQRCode = await this.licenseServiceService.generatePkPassQRCode( - user, - input.licenseType, - ) - - return { - pkpassQRCode, - } - } - - @Scopes(ApiScope.internal, ApiScope.licensesVerify) - @Mutation(() => GenericPkPassVerification, { - deprecationReason: - 'Should use verifyLicenseBarcode instead of verifyPkPass', - }) - @Audit() - async verifyPkPass( - @Args('input') - input: VerifyPkPassInput, - ): Promise { - return this.licenseServiceService.verifyPkPassDeprecated(input.data) - } - - @Scopes(ApiScope.internal, ApiScope.licensesVerify) - @Mutation(() => VerifyLicenseBarcodeResult, { - name: 'verifyLicenseBarcode', - }) - @Audit() - async verifyLicenseBarcode( - @Args('input') input: VerifyLicenseBarcodeInput, - ): Promise { - return this.licenseServiceService.verifyLicenseBarcode(input.data) - } -} diff --git a/libs/api/domains/license-service/src/lib/licenseService.service.ts b/libs/api/domains/license-service/src/lib/licenseService.service.ts index 72ffb9e4f457..c6cd333d783e 100644 --- a/libs/api/domains/license-service/src/lib/licenseService.service.ts +++ b/libs/api/domains/license-service/src/lib/licenseService.service.ts @@ -1,19 +1,11 @@ import { User } from '@island.is/auth-nest-tools' -import isAfter from 'date-fns/isAfter' import { - LicenseClient, LicenseClientService, LicenseType, LicenseVerifyExtraDataResult, } from '@island.is/clients/license-client' -import { CmsContentfulService } from '@island.is/cms' import type { Logger } from '@island.is/logging' import { LOGGER_PROVIDER } from '@island.is/logging' -import { - BarcodeService, - TOKEN_EXPIRED_ERROR, -} from '@island.is/services/license' - import { Locale } from '@island.is/shared/types' import { BadRequestException, @@ -21,20 +13,15 @@ import { Injectable, InternalServerErrorException, } from '@nestjs/common' -import { isJSON, isJWT } from 'class-validator' -import { Cache } from 'cache-manager' -import { CACHE_MANAGER } from '@nestjs/cache-manager' +// eslint-disable-next-line @typescript-eslint/naming-convention import ShortUniqueId from 'short-unique-id' import { GenericUserLicense } from './dto/GenericUserLicense.dto' -import { UserLicensesResponse } from './dto/UserLicensesResponse.dto' import { VerifyLicenseBarcodeError, VerifyLicenseBarcodeResult, VerifyLicenseBarcodeType, } from './dto/VerifyLicenseBarcodeResult.dto' import { - GenericLicenseFetchResult, - GenericLicenseLabels, GenericLicenseMapper, GenericLicenseType, GenericLicenseTypeType, @@ -42,6 +29,7 @@ import { GenericUserLicenseFetchStatus, GenericUserLicensePkPassStatus, GenericUserLicenseStatus, + LicenseTypeFetchResponse, PkPassVerification, } from './licenceService.type' import { @@ -51,6 +39,14 @@ import { } from './licenseService.constants' import { CreateBarcodeResult } from './dto/CreateBarcodeResult.dto' import { isDefined } from '@island.is/shared/utils' +import { GenericLicenseError as LicenseError } from './dto/GenericLicenseError.dto' +import { LicenseCollection } from './dto/GenericLicenseCollection.dto' +import isAfter from 'date-fns/isAfter' +import { isJSON, isJWT } from 'class-validator' +import { + BarcodeService, + TOKEN_EXPIRED_ERROR, +} from '@island.is/services/license' const LOG_CATEGORY = 'license-service' @@ -68,19 +64,12 @@ const COMMON_VERIFY_ERROR = { error: VerifyLicenseBarcodeError.ERROR, } -type Namespace = { - namespace: string - fields?: string -} - @Injectable() -export class LicenseServiceService { +export class LicenseService { constructor( @Inject(LOGGER_PROVIDER) private logger: Logger, private readonly barcodeService: BarcodeService, private readonly licenseClient: LicenseClientService, - private readonly cmsContentfulService: CmsContentfulService, - @Inject(CACHE_MANAGER) private readonly cacheManager: Cache, @Inject(LICENSE_MAPPER_FACTORY) private readonly licenseMapperFactory: ( type: GenericLicenseType, @@ -103,66 +92,11 @@ export class LicenseServiceService { ? GenericLicenseType.DriversLicense : (type as unknown as GenericLicenseType) - private getLicenseLabels = async ( - locale: Locale, - ): Promise => { - const cacheKey = `namespace-licenses-${locale}` - const namespace = await this.cacheManager.get(cacheKey) - - let licenseNamespace: Namespace | null - if (!namespace) { - const result = await this.cmsContentfulService.getNamespace( - 'Licenses', - locale, - ) - await this.cacheManager.set(cacheKey, result) - licenseNamespace = result - } else { - licenseNamespace = namespace - } - - return { - labels: licenseNamespace?.fields - ? JSON.parse(licenseNamespace.fields) - : undefined, - } - } - - private async fetchLicenses( - user: User, - licenseClient: LicenseClient, - ): Promise { - if (!licenseClient) { - throw new InternalServerErrorException('License service failed') - } - - const licenseRes = await licenseClient.getLicenses(user) - - if (!licenseRes.ok) { - return { - data: [], - fetch: { - status: GenericUserLicenseFetchStatus.Error, - updated: new Date(), - }, - } - } - - return { - data: licenseRes.data, - fetch: { - status: GenericUserLicenseFetchStatus.Fetched, - updated: new Date(), - }, - } - } - - async getUserLicenses( + async getLicenseCollection( user: User, locale: Locale, { includedTypes, excludedTypes, onlyList }: GetGenericLicenseOptions = {}, - ): Promise { - const labels = await this.getLicenseLabels(locale) + ): Promise { const fetchPromises = AVAILABLE_LICENSES.map(async (license) => { if (excludedTypes && excludedTypes.indexOf(license.type) >= 0) { return null @@ -173,156 +107,138 @@ export class LicenseServiceService { } if (!onlyList) { - return this.getLicensesOfType(user, locale, license.type, labels) + return this.getLicensesOfType(user, locale, license.type) } return null }).filter(isDefined) const licenses: Array = [] + const errors: Array = [] + for (const licenseArrayResult of await Promise.allSettled(fetchPromises)) { if ( licenseArrayResult.status === 'fulfilled' && licenseArrayResult.value ) { - licenses.push(...licenseArrayResult.value) + const licenseResult = licenseArrayResult.value + if (licenseResult.fetchResponseType === 'error') { + errors.push(licenseResult.data) + } else { + licenses.push(...licenseResult.data) + } } } return { - nationalId: user.nationalId, - licenses: licenses ?? [], + licenses, + errors, } } - async getAllLicenses( - user: User, - locale: Locale, - { includedTypes, excludedTypes, onlyList }: GetGenericLicenseOptions = {}, - ): Promise { - const licenseLabels = await this.getLicenseLabels(locale) - - const fetchPromises = AVAILABLE_LICENSES.map(async (license) => { - if (excludedTypes && excludedTypes.indexOf(license.type) >= 0) { - return null - } - - if (includedTypes && includedTypes.indexOf(license.type) < 0) { - return null - } - - if (!onlyList) { - return this.getLicensesOfType(user, locale, license.type, licenseLabels) - } - - return null - }).filter(isDefined) - - const licenses: Array = [] - for (const licenseArrayResult of await Promise.allSettled(fetchPromises)) { - if ( - licenseArrayResult.status === 'fulfilled' && - licenseArrayResult.value - ) { - licenses.push(...licenseArrayResult.value) - } - } - - return licenses - } async getLicensesOfType( user: User, locale: Locale, licenseType: GenericLicenseType, - labels?: GenericLicenseLabels, - ): Promise | null> { + ): Promise { const licenseTypeDefinition = AVAILABLE_LICENSES.find( (i) => i.type === licenseType, ) - const mappedLicenseType = this.mapLicenseType(licenseType) - const licenseService = await this.licenseClient.getClientByLicenseType< - typeof mappedLicenseType - >(mappedLicenseType) - - if (!licenseTypeDefinition || !licenseService) { - this.logger.error(`Invalid license type. type: ${licenseType}`, { + if (!licenseTypeDefinition) { + this.logger.warn('Invalid license type supplied', { + licenseType, category: LOG_CATEGORY, }) return null } - const licenseRes = await this.fetchLicenses(user, licenseService) + const mappedLicenseType = this.mapLicenseType(licenseTypeDefinition.type) + const client = await this.getClient(mappedLicenseType) + + const licensesFetchResponse = await client.getLicenses(user) + + if (!licensesFetchResponse.ok) { + return { + fetchResponseType: 'error', + data: { + type: licenseType, + fetch: { + status: GenericUserLicenseFetchStatus.Error, + updated: new Date().getTime().toString(), + }, + code: licensesFetchResponse.error.code, + message: licensesFetchResponse.error.message, + extraData: licensesFetchResponse.error.data, + }, + } + } - const mapper = await this.licenseMapperFactory(licenseType) + const mapper = await this.licenseMapperFactory(licenseTypeDefinition.type) if (!mapper) { this.logger.warn('Service failure. No mapper created', { + licenseType, category: LOG_CATEGORY, }) return null } - const licensesPayload = - licenseRes.fetch.status !== GenericUserLicenseFetchStatus.Error - ? mapper.parsePayload(licenseRes.data, locale, labels) - : [] - - const mappedLicenses = licensesPayload.map((lp) => { - const licenseUserData: GenericLicenseUserdata = { - status: GenericUserLicenseStatus.Unknown, - pkpassStatus: GenericUserLicensePkPassStatus.Unknown, - } + const licensesPayload = await mapper.parsePayload( + licensesFetchResponse.data, + locale, + ) - if (lp) { - licenseUserData.pkpassStatus = licenseService.clientSupportsPkPass - ? (licenseService.licenseIsValidForPkPass?.( - lp.rawData, - user, - ) as unknown as GenericUserLicensePkPassStatus) ?? - GenericUserLicensePkPassStatus.Unknown - : GenericUserLicensePkPassStatus.NotAvailable - licenseUserData.status = GenericUserLicenseStatus.HasLicense - } else { - licenseUserData.status = GenericUserLicenseStatus.NotAvailable - } + const mappedLicenses: Array = await Promise.all( + licensesPayload.map(async (lp) => { + const licenseUserData: GenericLicenseUserdata = { + status: GenericUserLicenseStatus.Unknown, + pkpassStatus: GenericUserLicensePkPassStatus.Unknown, + } - return { - nationalId: user.nationalId, - license: { - ...licenseTypeDefinition, - status: licenseUserData.status, - pkpassStatus: licenseUserData.pkpassStatus, - }, - fetch: { - ...licenseRes.fetch, - updated: licenseRes.fetch.updated.getTime().toString(), - }, - payload: - { - ...lp, - rawData: lp.rawData ?? undefined, - } ?? undefined, - } - }) + if (lp) { + licenseUserData.pkpassStatus = client.clientSupportsPkPass + ? ((await client.licenseIsValidForPkPass?.( + lp.payload.rawData, + user, + )) as unknown as GenericUserLicensePkPassStatus) ?? + GenericUserLicensePkPassStatus.Unknown + : GenericUserLicensePkPassStatus.NotAvailable + licenseUserData.status = GenericUserLicenseStatus.HasLicense + } else { + licenseUserData.status = GenericUserLicenseStatus.NotAvailable + } - return ( - mappedLicenses ?? [ - { + return { nationalId: user.nationalId, + isOwnerChildOfUser: lp.type === 'child', license: { ...licenseTypeDefinition, - status: GenericUserLicenseStatus.Unknown, - pkpassStatus: GenericUserLicenseStatus.Unknown, + status: licenseUserData.status, + pkpassStatus: licenseUserData.pkpassStatus, + name: lp.licenseName, }, fetch: { - ...licenseRes.fetch, - updated: licenseRes.fetch.updated.getTime().toString(), + status: GenericUserLicenseFetchStatus.Fetched, + updated: new Date().getTime().toString(), }, - payload: undefined, - }, - ] + payload: { + ...lp.payload, + metadata: { + ...lp.payload.metadata, + expired: lp.payload.metadata?.expired ?? undefined, + expireDate: lp.payload.metadata?.expireDate ?? undefined, + }, + rawData: lp.payload.rawData ?? undefined, + }, + } + }), ) + + return { + fetchResponseType: 'licenses', + data: mappedLicenses, + } } async getLicense( @@ -330,21 +246,31 @@ export class LicenseServiceService { locale: Locale, licenseType: GenericLicenseType, licenseId?: string, - ): Promise { - const labels = await this.getLicenseLabels(locale) + ): Promise { + const licensesOfType = await this.getLicensesOfType( + user, + locale, + licenseType, + ) - const licensesOfType = - (await this.getLicensesOfType(user, locale, licenseType, labels)) ?? [] + if (!licensesOfType) { + return null + } + + if (licensesOfType.fetchResponseType === 'error') { + return licensesOfType.data + } if (!licenseId || licenseId === DEFAULT_LICENSE_ID) { - return licensesOfType[0] ?? null + return licensesOfType.data[0] ?? null } - return ( - licensesOfType.find( + const license = + licensesOfType.data.find( (l) => l.payload?.metadata?.licenseId === licenseId, ) ?? null - ) + + return license ?? null } async getClient(type: LicenseType) { diff --git a/libs/api/domains/license-service/src/lib/mappers/adrLicenseMapper.ts b/libs/api/domains/license-service/src/lib/mappers/adrLicenseMapper.ts index 67ce1be5c600..93e9efac3c97 100644 --- a/libs/api/domains/license-service/src/lib/mappers/adrLicenseMapper.ts +++ b/libs/api/domains/license-service/src/lib/mappers/adrLicenseMapper.ts @@ -4,84 +4,114 @@ import { FlattenedAdrDto, FlattenedAdrRightsDto, } from '@island.is/clients/license-client' -import { DEFAULT_LICENSE_ID } from '../licenseService.constants' import { - GenericLicenseDataField, + DEFAULT_LICENSE_ID, + LICENSE_NAMESPACE, +} from '../licenseService.constants' +import { Injectable } from '@nestjs/common' +import { IntlService } from '@island.is/cms-translations' +import { m } from '../messages' +import { formatDate, expiryTag } from '../utils' +import { GenericLicenseDataFieldType, - GenericLicenseLabels, + GenericLicenseMappedPayloadResponse, GenericLicenseMapper, - GenericUserLicensePayload, } from '../licenceService.type' -import { getLabel } from '../utils/translations' -import { Injectable } from '@nestjs/common' +import { GenericLicenseDataField } from '../dto/GenericLicenseDataField.dto' @Injectable() export class AdrLicensePayloadMapper implements GenericLicenseMapper { - parsePayload( + constructor(private readonly intlService: IntlService) {} + async parsePayload( payload: Array, locale: Locale = 'is', - labels?: GenericLicenseLabels, - ): Array { - if (!payload) return [] + ): Promise> { + if (!payload) return Promise.resolve([]) const typedPayload = payload as Array - const label = labels?.labels + const { formatMessage } = await this.intlService.useIntl( + [LICENSE_NAMESPACE], + locale, + ) + + const mappedPayload: Array = + typedPayload.map((t) => { + const isExpired: boolean | undefined = t.gildirTil + ? !isAfter(new Date(t.gildirTil), new Date()) + : undefined - const mappedPayload: Array = typedPayload.map( - (t) => { const data: Array = [ { - name: getLabel('basicInfoLicense', locale, label), + name: formatMessage(m.basicInfoLicense), type: GenericLicenseDataFieldType.Value, - label: getLabel('licenseNumber', locale, label), + label: formatMessage(m.licenseNumber), value: t.skirteinisNumer?.toString(), }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('fullName', locale, label), + label: formatMessage(m.fullName), value: t.fulltNafn ?? '', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('publisher', locale, label), + label: formatMessage(m.publisher), value: 'Vinnueftirlitið', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('validTo', locale, label), - value: t.gildirTil ?? '', + label: formatMessage(m.validTo), + value: t.gildirTil ? formatDate(new Date(t.gildirTil)) : '', + tag: expiryTag(formatMessage, isExpired), }, ] const adrRights = (t.adrRettindi ?? []).filter((field) => field.grunn) const tankar = this.parseRights( - getLabel('tanks', locale, label) ?? '', + formatMessage(m.tanks) ?? '', adrRights.filter((field) => field.tankar), ) if (tankar) data.push(tankar) const grunn = this.parseRights( - getLabel('otherThanTanks', locale, label) ?? '', + formatMessage(m.otherThanTanks) ?? '', adrRights, ) if (grunn) data.push(grunn) return { - data, - rawData: JSON.stringify(t), - metadata: { - licenseNumber: t.skirteinisNumer?.toString() ?? '', - licenseId: DEFAULT_LICENSE_ID, - expired: t.gildirTil - ? !isAfter(new Date(t.gildirTil), new Date()) - : null, - expireDate: t.gildirTil ?? undefined, + licenseName: formatMessage(m.adrLicense), + type: 'user', + payload: { + data, + rawData: JSON.stringify(t), + metadata: { + licenseNumber: t.skirteinisNumer ?? '', + subtitle: formatMessage(m.licenseNumberVariant, { + arg: t.skirteinisNumer ?? formatMessage(m.unknown), + }), + licenseId: DEFAULT_LICENSE_ID, + expired: isExpired, + expireDate: t.gildirTil ?? undefined, + displayTag: expiryTag( + formatMessage, + isExpired, + t.gildirTil + ? formatMessage(m.validUntil, { + arg: formatDate(new Date(t.gildirTil)), + }) + : undefined, + ), + name: formatMessage(m.adrLicense), + title: formatMessage(m.yourADRLicense), + description: [ + { text: formatMessage(m.yourAdrLicenseDescription) }, + ], + }, }, } - }, - ) + }) return mappedPayload } @@ -101,7 +131,6 @@ export class AdrLicensePayloadMapper implements GenericLicenseMapper { type: GenericLicenseDataFieldType.Category, name: field.flokkur ?? '', label: field.heiti ?? '', - description: field.heiti ?? '', })), } } diff --git a/libs/api/domains/license-service/src/lib/mappers/disabilityLicenseMapper.ts b/libs/api/domains/license-service/src/lib/mappers/disabilityLicenseMapper.ts index c5cdf10be9de..b4d1b9afc84e 100644 --- a/libs/api/domains/license-service/src/lib/mappers/disabilityLicenseMapper.ts +++ b/libs/api/domains/license-service/src/lib/mappers/disabilityLicenseMapper.ts @@ -1,63 +1,90 @@ import isAfter from 'date-fns/isAfter' import { Locale } from '@island.is/shared/types' import { OrorkuSkirteini } from '@island.is/clients/disability-license' -import { DEFAULT_LICENSE_ID } from '../licenseService.constants' import { - GenericLicenseDataField, + DEFAULT_LICENSE_ID, + LICENSE_NAMESPACE, +} from '../licenseService.constants' +import { GenericLicenseDataFieldType, - GenericLicenseLabels, + GenericLicenseMappedPayloadResponse, GenericLicenseMapper, - GenericUserLicensePayload, } from '../licenceService.type' -import { i18n } from '../utils/translations' import { Injectable } from '@nestjs/common' +import { IntlService } from '@island.is/cms-translations' +import { m } from '../messages' +import { formatDate, expiryTag } from '../utils' +import { GenericLicenseDataField } from '../dto/GenericLicenseDataField.dto' @Injectable() export class DisabilityLicensePayloadMapper implements GenericLicenseMapper { - parsePayload( + constructor(private readonly intlService: IntlService) {} + async parsePayload( payload: Array, locale: Locale = 'is', - labels?: GenericLicenseLabels, - ): Array { - if (!payload) return [] + ): Promise> { + if (!payload) return Promise.resolve([]) const typedPayload = payload as Array - const label = labels?.labels - const mappedPayload: Array = typedPayload.map( - (t) => { + const { formatMessage } = await this.intlService.useIntl( + [LICENSE_NAMESPACE], + locale, + ) + + const mappedPayload: Array = + typedPayload.map((t) => { + const isExpired: boolean | undefined = t.gildirtil + ? !isAfter(new Date(t.gildirtil), new Date()) + : undefined + const data: Array = [ { type: GenericLicenseDataFieldType.Value, - name: 'Grunnupplýsingar örorkuskírteinis', - label: label ? label['fullName'] : i18n.fullName[locale], + name: formatMessage(m.basicInfoDisabilityLicense), + label: formatMessage(m.fullName), value: t.nafn ?? '', }, { type: GenericLicenseDataFieldType.Value, - label: label ? label['publisher'] : i18n.publisher[locale], + label: formatMessage(m.publisher), value: 'Tryggingastofnun', }, { type: GenericLicenseDataFieldType.Value, - label: label ? label['validTo'] : i18n.validTo[locale], - value: t.gildirtil?.toISOString() ?? '', + label: formatMessage(m.validTo), + value: t.gildirtil ? formatDate(t.gildirtil) : '', + tag: + isExpired !== undefined && t.gildirtil + ? expiryTag(formatMessage, isExpired) + : undefined, }, ] return { - data, - rawData: JSON.stringify(t), - metadata: { - licenseNumber: t.kennitala?.toString() ?? '', - licenseId: DEFAULT_LICENSE_ID, - expired: t.gildirtil - ? !isAfter(new Date(t.gildirtil), new Date()) - : null, + licenseName: formatMessage(m.disabilityCard), + type: 'user', + payload: { + data, + rawData: JSON.stringify(t), + metadata: { + licenseNumber: t.kennitala?.toString() ?? '', + subtitle: formatMessage(m.licenseNumberVariant, { + arg: t.kennitala?.toString() ?? formatMessage(m.unknown), + }), + licenseId: DEFAULT_LICENSE_ID, + expired: isExpired, + expireDate: t.gildirtil?.toISOString() ?? undefined, + displayTag: expiryTag(formatMessage, isExpired), + name: formatMessage(m.disabilityCard), + title: formatMessage(m.yourDisabilityLicense), + description: [ + { text: formatMessage(m.yourDisabilityLicenseDescription) }, + ], + }, }, } - }, - ) + }) return mappedPayload } diff --git a/libs/api/domains/license-service/src/lib/mappers/drivingLicenseMapper.ts b/libs/api/domains/license-service/src/lib/mappers/drivingLicenseMapper.ts index ec7c3b962920..761923ec9876 100644 --- a/libs/api/domains/license-service/src/lib/mappers/drivingLicenseMapper.ts +++ b/libs/api/domains/license-service/src/lib/mappers/drivingLicenseMapper.ts @@ -1,69 +1,78 @@ -import { DEFAULT_LICENSE_ID } from '../licenseService.constants' +import { + DEFAULT_LICENSE_ID, + LICENSE_NAMESPACE, +} from '../licenseService.constants' import { GenericLicenseDataFieldType, - GenericLicenseLabels, + GenericLicenseMappedPayloadResponse, GenericLicenseMapper, - GenericUserLicensePayload, + GenericUserLicenseMetaLinksType, } from '../licenceService.type' import isAfter from 'date-fns/isAfter' import { Locale } from '@island.is/shared/types' -import { getLabel } from '../utils/translations' import { Injectable } from '@nestjs/common' import { DriverLicenseDto as DriversLicense } from '@island.is/clients/driving-license' import { isDefined } from '@island.is/shared/utils' +import { IntlService } from '@island.is/cms-translations' +import { m } from '../messages' +import { formatDate, expiryTag } from '../utils' @Injectable() export class DrivingLicensePayloadMapper implements GenericLicenseMapper { - parsePayload( + constructor(private readonly intlService: IntlService) {} + async parsePayload( payload: Array, locale: Locale = 'is', - labels?: GenericLicenseLabels, - ): Array { - if (!payload) return [] + ): Promise> { + if (!payload) return Promise.resolve([]) const typedPayload = payload as Array - const label = labels?.labels + const { formatMessage } = await this.intlService.useIntl( + [LICENSE_NAMESPACE], + locale, + ) // Parse license data into the fields as they're displayed on the physical drivers license // see: https://www.reglugerd.is/reglugerdir/eftir-raduneytum/srn/nr/18033 - const mappedPayload: Array = typedPayload.map( - (t) => { - const expired = t.dateValidTo + const mappedPayload: Array = + typedPayload.map((t) => { + const isExpired = t.dateValidTo ? !isAfter(new Date(t.dateValidTo), new Date()) - : null + : undefined const data = [ { - name: getLabel('basicInfoLicense', locale, label), + name: formatMessage(m.basicInfoLicense), type: GenericLicenseDataFieldType.Value, - label: getLabel('licenseNumber', locale, label), + label: formatMessage(m.licenseNumber), value: (t.id ?? '').toString(), }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('fullName', locale, label), + label: formatMessage(m.fullName), value: t.name ?? '', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('publisher', locale, label), + label: formatMessage(m.publisher), value: 'Ríkislögreglustjóri', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('publishedDate', locale, label), - value: t.publishDate ? new Date(t.publishDate).toISOString() : '', + label: formatMessage(m.publishedDate), + value: t.publishDate ? formatDate(t.publishDate) : '', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('validTo', locale, label), - value: t.dateValidTo ? new Date(t.dateValidTo).toISOString() : '', + label: formatMessage(m.validTo), + value: t.dateValidTo ? formatDate(t.dateValidTo) : '', + tag: expiryTag(formatMessage, isExpired), }, { type: GenericLicenseDataFieldType.Group, - label: getLabel('classesOfRights', locale, label), + label: formatMessage(m.classesOfRights), fields: (t.categories ?? []).map((field) => ({ type: GenericLicenseDataFieldType.Category, name: field.nr ?? '', @@ -71,20 +80,18 @@ export class DrivingLicensePayloadMapper implements GenericLicenseMapper { fields: [ { type: GenericLicenseDataFieldType.Value, - label: getLabel('expiryDate', locale, label), - value: field.dateTo ? field.dateTo.toISOString() : '', + label: formatMessage(m.expiryDate), + value: field.dateTo ? formatDate(field.dateTo) : '', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('publishedDate', locale, label), - value: field.publishDate - ? field.publishDate.toISOString() - : '', + label: formatMessage(m.publishedDate), + value: field.publishDate ? formatDate(field.publishDate) : '', }, field.comment ? { type: GenericLicenseDataFieldType.Value, - label: getLabel('comment', locale, label), + label: formatMessage(m.comments), value: field.comment ?? '', } : undefined, @@ -94,23 +101,47 @@ export class DrivingLicensePayloadMapper implements GenericLicenseMapper { ] return { - data, - rawData: JSON.stringify(t), - metadata: { - licenseNumber: t.id?.toString() ?? '', - licenseId: DEFAULT_LICENSE_ID, - expired, - expireDate: t.dateValidTo?.toISOString() ?? undefined, - links: [ - { - label: getLabel('renewDrivingLicense', locale, label), - value: 'https://island.is/endurnyjun-okuskirteina', - }, - ], + licenseName: formatMessage(m.drivingLicense), + type: 'user', + payload: { + data, + rawData: JSON.stringify(t), + metadata: { + links: [ + { + label: formatMessage(m.renewLicense, { + arg: formatMessage(m.drivingLicense).toLowerCase(), + }), + value: 'https://island.is/endurnyjun-okuskirteina', + type: GenericUserLicenseMetaLinksType.External, + }, + ], + licenseNumber: t.id?.toString() ?? '', + subtitle: formatMessage(m.licenseNumberVariant, { + arg: t.id?.toString() ?? formatMessage(m.unknown), + }), + licenseId: DEFAULT_LICENSE_ID, + expired: isExpired, + expireDate: t.dateValidTo?.toISOString() ?? undefined, + displayTag: + isExpired !== undefined && t.dateValidTo + ? expiryTag( + formatMessage, + isExpired, + formatMessage(m.validUntil, { + arg: formatDate(t.dateValidTo), + }), + ) + : undefined, + name: formatMessage(m.drivingLicense), + title: formatMessage(m.yourDrivingLicense), + description: [ + { text: formatMessage(m.yourDrivingLicenseDescription) }, + ], + }, }, } - }, - ) + }) return mappedPayload } } diff --git a/libs/api/domains/license-service/src/lib/mappers/ehicCardMapper.ts b/libs/api/domains/license-service/src/lib/mappers/ehicCardMapper.ts index ce6177996eaf..cae346f4d4f6 100644 --- a/libs/api/domains/license-service/src/lib/mappers/ehicCardMapper.ts +++ b/libs/api/domains/license-service/src/lib/mappers/ehicCardMapper.ts @@ -1,110 +1,143 @@ import isAfter from 'date-fns/isAfter' import { Locale } from '@island.is/shared/types' import { - GenericLicenseDataField, GenericLicenseDataFieldType, - GenericLicenseLabels, + GenericLicenseMappedPayloadResponse, GenericLicenseMapper, GenericUserLicenseMetaLinksType, - GenericUserLicensePayload, } from '../licenceService.type' -import { getLabel } from '../utils/translations' import { Injectable } from '@nestjs/common' import { getDocument, isDefined } from '@island.is/shared/utils' -import { format } from 'kennitala' +import { format as formatNationalId } from 'kennitala' import { EhicCardResponse } from '@island.is/clients/license-client' +import { IntlService } from '@island.is/cms-translations' +import { LICENSE_NAMESPACE } from '../licenseService.constants' +import { m } from '../messages' +import { expiryTag, formatDate } from '../utils' +import { GenericLicenseDataField } from '../dto/GenericLicenseDataField.dto' @Injectable() export class EHICCardPayloadMapper implements GenericLicenseMapper { - parsePayload( + constructor(private readonly intlService: IntlService) {} + async parsePayload( payload: Array, locale: Locale = 'is', - labels?: GenericLicenseLabels, - ): Array { - if (!payload) return [] + ): Promise> { + if (!payload) return Promise.resolve([]) const typedPayload = payload as Array - const label = labels?.labels - const mappedPayload: Array = typedPayload - .map((t) => { - if (!t || !t.expiryDate) return null + const { formatMessage } = await this.intlService.useIntl( + [LICENSE_NAMESPACE], + locale, + ) - const expired = t.expiryDate - ? !isAfter(new Date(t.expiryDate?.toISOString()), new Date()) - : null + const mappedPayload: Array = + typedPayload + .map((t) => { + if (!t || !t.expiryDate) return null - const data: Array = [ - t.cardHolderName - ? { - type: GenericLicenseDataFieldType.Value, - label: getLabel('fullName', locale, label), - value: t.cardHolderName ?? '', - } - : null, - t.cardHolderNationalId - ? { - type: GenericLicenseDataFieldType.Value, - label: getLabel('nationalId', locale, label), - value: t.cardHolderNationalId - ? format(t.cardHolderNationalId) - : '', - } - : null, - t.cardNumber - ? { - type: GenericLicenseDataFieldType.Value, - label: getLabel('cardNumber', locale, label), - value: t.cardNumber, - } - : null, - t.issued - ? { - type: GenericLicenseDataFieldType.Value, - label: getLabel('publishedDate', locale, label), - value: t.issued.toISOString(), - } - : null, - t.expiryDate - ? { - type: GenericLicenseDataFieldType.Value, - label: getLabel('validTo', locale, label), - value: t.expiryDate.toISOString(), - } - : null, - { - type: GenericLicenseDataFieldType.Value, - label: getLabel('publisher', locale, label), - value: 'Sjúkratryggingar', - }, - ].filter(isDefined) + const isExpired = t.expiryDate + ? !isAfter(new Date(t.expiryDate?.toISOString()), new Date()) + : undefined - return { - data, - rawData: JSON.stringify(t), - metadata: { - licenseNumber: t.cardNumber?.toString() ?? '', - licenseId: t.cardNumber?.toString() ?? 'default', - expired, - expireDate: t.expiryDate.toISOString(), - links: [ - t.hasTempCard && t.tempCardPdf - ? { - label: getLabel('downloadCard', locale, label), - value: getDocument(t.tempCardPdf, 'pdf'), - type: GenericUserLicenseMetaLinksType.Download, - name: `EHIC_${new Date().toISOString().split('T')[0]}.pdf`, - } - : undefined, - { - label: getLabel('applyForNewCard', locale, label), - value: '/umsoknir/evropska-sjukratryggingakortid', + const data: Array = [ + t.cardHolderName + ? { + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.fullName), + value: t.cardHolderName ?? '', + } + : null, + t.cardHolderNationalId + ? { + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.nationalId), + value: t.cardHolderNationalId + ? formatNationalId(t.cardHolderNationalId) + : '', + } + : null, + t.cardNumber + ? { + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.cardNumber), + value: t.cardNumber, + } + : null, + t.issued + ? { + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.publishedDate), + value: t.issued ? formatDate(t.issued) : '', + } + : null, + t.expiryDate + ? { + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.validTo), + value: t.expiryDate ? formatDate(t.expiryDate) : '', + tag: expiryTag(formatMessage, isExpired), + } + : null, + { + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.publisher), + value: 'Sjúkratryggingar', + }, + ].filter(isDefined) + + return { + licenseName: formatMessage(m.ehicCard), + type: 'user' as const, + payload: { + data, + rawData: JSON.stringify(t), + metadata: { + licenseNumber: t.cardNumber?.toString() ?? '', + subtitle: formatMessage(m.licenseNumberVariant, { + arg: t.cardNumber?.toString() ?? formatMessage(m.unknown), + }), + licenseId: t.cardNumber?.toString() ?? 'default', + expired: isExpired, + expireDate: t.expiryDate.toISOString(), + displayTag: expiryTag( + formatMessage, + isExpired, + formatMessage(m.validUntil, { + arg: formatDate(t.expiryDate), + }), + ), + links: [ + t.hasTempCard && t.tempCardPdf + ? { + label: formatMessage(m.downloadCard), + value: getDocument(t.tempCardPdf, 'pdf'), + type: GenericUserLicenseMetaLinksType.Download, + name: `EHIC_${ + new Date().toISOString().split('T')[0] + }.pdf`, + } + : undefined, + { + label: formatMessage(m.applyForNewCard), + value: '/umsoknir/evropska-sjukratryggingakortid', + }, + ].filter(isDefined), + name: formatMessage(m.ehicCard), + title: formatMessage(m.ehicCard), + description: [ + { text: formatMessage(m.ehicDescription) }, + { + text: formatMessage(m.ehicDescription2), + linkInText: formatMessage(m.ehicDescriptionLink), + }, + ], }, - ].filter(isDefined), - }, - } - }) - .filter(isDefined) + }, + } + }) + .filter(isDefined) return mappedPayload } diff --git a/libs/api/domains/license-service/src/lib/mappers/firearmLicenseMapper.ts b/libs/api/domains/license-service/src/lib/mappers/firearmLicenseMapper.ts index 31729b73143f..089afc2143b3 100644 --- a/libs/api/domains/license-service/src/lib/mappers/firearmLicenseMapper.ts +++ b/libs/api/domains/license-service/src/lib/mappers/firearmLicenseMapper.ts @@ -4,208 +4,244 @@ import { } from '@island.is/clients/firearm-license' import isAfter from 'date-fns/isAfter' import { Locale } from '@island.is/shared/types' -import { DEFAULT_LICENSE_ID } from '../licenseService.constants' import { - GenericLicenseDataField, + DEFAULT_LICENSE_ID, + LICENSE_NAMESPACE, +} from '../licenseService.constants' +import { GenericLicenseDataFieldType, - GenericLicenseLabels, + GenericLicenseMappedPayloadResponse, GenericLicenseMapper, - GenericUserLicensePayload, - LicenseLabelsObject, + GenericUserLicenseMetaLinksType, } from '../licenceService.type' -import { getLabel } from '../utils/translations' import { FirearmLicenseDto } from '@island.is/clients/license-client' import { Injectable } from '@nestjs/common' import { isDefined } from '@island.is/shared/utils' +import { FormatMessage, IntlService } from '@island.is/cms-translations' +import { m } from '../messages' +import { expiryTag, formatDate } from '../utils' +import { GenericLicenseDataField } from '../dto/GenericLicenseDataField.dto' + @Injectable() export class FirearmLicensePayloadMapper implements GenericLicenseMapper { - parsePayload( + constructor(private readonly intlService: IntlService) {} + async parsePayload( payload: Array, locale: Locale = 'is', - labels?: GenericLicenseLabels, - ): Array { - if (!payload) return [] + ): Promise> { + if (!payload) return Promise.resolve([]) const typedPayload = payload as Array - const label = labels?.labels - const mappedPayload: Array = typedPayload - .map((t) => { - const { licenseInfo, properties, categories } = t + const { formatMessage } = await this.intlService.useIntl( + [LICENSE_NAMESPACE], + locale, + ) + const mappedPayload: Array = + typedPayload + .map((t) => { + const { licenseInfo, properties, categories } = t - const expired = licenseInfo?.expirationDate - ? !isAfter(new Date(licenseInfo.expirationDate), new Date()) - : null - if (!licenseInfo) return null + if (!licenseInfo) return null - const data: Array = [ - licenseInfo.licenseNumber - ? { - name: getLabel('basicInfoLicense', locale, label), - type: GenericLicenseDataFieldType.Value, - label: getLabel('licenseNumber', locale, label), - value: licenseInfo.licenseNumber, - } - : null, - licenseInfo.name - ? { - type: GenericLicenseDataFieldType.Value, - label: getLabel('fullName', locale, label), - value: licenseInfo.name, - } - : null, - licenseInfo.issueDate - ? { - type: GenericLicenseDataFieldType.Value, - label: getLabel('publishedDate', locale, label), - value: licenseInfo.issueDate ?? '', - } - : null, - licenseInfo.expirationDate - ? { - type: GenericLicenseDataFieldType.Value, - label: getLabel('validTo', locale, label), - value: licenseInfo.expirationDate ?? '', - } - : null, - licenseInfo.collectorLicenseExpirationDate - ? { - type: GenericLicenseDataFieldType.Value, - label: getLabel('collectorLicenseValidTo', locale, label), - value: licenseInfo.collectorLicenseExpirationDate ?? '', - } - : null, + const isExpired = licenseInfo?.expirationDate + ? !isAfter(new Date(licenseInfo.expirationDate), new Date()) + : undefined - licenseInfo.qualifications - ? this.parseQualifications( - licenseInfo.qualifications, - locale, - categories ?? undefined, - label, - ) - : null, - properties - ? { - type: GenericLicenseDataFieldType.Group, - hideFromServicePortal: true, - label: getLabel('firearmProperties', locale, label), - fields: (properties.properties ?? []).map((property) => ({ - type: GenericLicenseDataFieldType.Category, - fields: this.parseProperties( - labels, - property, - locale, - )?.filter(isDefined), - })), - } - : null, - properties - ? { - type: GenericLicenseDataFieldType.Table, - label: getLabel('firearmProperties', locale, label), - fields: (properties.properties ?? []).map((property) => ({ - type: GenericLicenseDataFieldType.Category, - fields: this.parseProperties( - labels, - property, - locale, - )?.filter(isDefined), - })), - } - : null, - ].filter(isDefined) + const data: Array = [ + licenseInfo.licenseNumber + ? { + name: formatMessage(m.basicInfoLicense), + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.licenseNumber), + value: licenseInfo.licenseNumber, + } + : null, + licenseInfo.name + ? { + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.fullName), + value: licenseInfo.name, + } + : null, + licenseInfo.issueDate + ? { + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.publishedDate), + value: formatDate(new Date(licenseInfo.issueDate)) ?? '', + } + : null, + licenseInfo.expirationDate + ? { + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.validTo), + value: formatDate(new Date(licenseInfo.expirationDate)) ?? '', + tag: expiryTag(formatMessage, isExpired), + } + : null, + licenseInfo.collectorLicenseExpirationDate + ? { + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.collectorLicenseValidTo), + value: + formatDate( + new Date(licenseInfo.collectorLicenseExpirationDate), + ) ?? '', + } + : null, - return { - data, - rawData: JSON.stringify(t), - metadata: { - licenseNumber: t.licenseInfo?.licenseNumber?.toString() ?? '', - licenseId: DEFAULT_LICENSE_ID, - expired, - expireDate: t.licenseInfo?.expirationDate ?? undefined, - links: [ - { - label: getLabel('renewFirearmLicense', locale, label), - value: 'https://island.is/skotvopnaleyfi', + licenseInfo.qualifications + ? this.parseQualifications( + licenseInfo.qualifications, + categories ?? undefined, + formatMessage, + ) + : null, + properties + ? { + type: GenericLicenseDataFieldType.Group, + hideFromServicePortal: true, + label: formatMessage(m.firearmProperties), + fields: (properties.properties ?? []).map((property) => ({ + type: GenericLicenseDataFieldType.Category, + fields: this.parseProperties( + property, + formatMessage, + )?.filter(isDefined), + })), + } + : null, + properties + ? { + type: GenericLicenseDataFieldType.Table, + label: formatMessage(m.firearmProperties), + fields: (properties.properties ?? []).map((property) => ({ + type: GenericLicenseDataFieldType.Category, + fields: this.parseProperties( + property, + formatMessage, + )?.filter(isDefined), + })), + } + : null, + ].filter(isDefined) + + return { + licenseName: formatMessage(m.firearmLicense), + type: 'user' as const, + payload: { + data, + rawData: JSON.stringify(t), + metadata: { + licenseNumber: t.licenseInfo?.licenseNumber?.toString() ?? '', + subtitle: formatMessage(m.licenseNumberVariant, { + arg: + t.licenseInfo?.licenseNumber?.toString() ?? + formatMessage(m.unknown), + }), + licenseId: DEFAULT_LICENSE_ID, + expired: isExpired, + expireDate: t.licenseInfo?.expirationDate ?? undefined, + displayTag: + isExpired !== undefined && t.licenseInfo?.expirationDate + ? expiryTag( + formatMessage, + isExpired, + formatMessage(m.validUntil, { + arg: formatDate( + new Date(t.licenseInfo.expirationDate), + ), + }), + ) + : undefined, + links: [ + { + label: formatMessage(m.renewLicense, { + arg: formatMessage(m.firearmLicense).toLowerCase(), + }), + value: 'https://island.is/skotvopnaleyfi', + type: GenericUserLicenseMetaLinksType.External, + }, + ], + name: formatMessage(m.firearmLicense), + title: formatMessage(m.yourFirearmLicense), + description: [ + { text: formatMessage(m.yourFirearmLicenseDescription) }, + ], }, - ], - }, - } - }) - .filter(isDefined) + }, + } + }) + .filter(isDefined) return mappedPayload } private parseQualifications = ( qualifications: string, - locale: Locale = 'is', categories?: FirearmCategories, - labels?: LicenseLabelsObject, + formatMessage?: FormatMessage, ): GenericLicenseDataField | null => { - if (!categories) { + if (!categories || !formatMessage) { return null } return { type: GenericLicenseDataFieldType.Group, - label: getLabel('classesOfRights', locale, labels), - fields: qualifications.split('').map((qualification) => ({ - type: GenericLicenseDataFieldType.Category, - name: qualification, - label: - categories?.[ - `${getLabel('category', locale, labels)} ${qualification}` - ] ?? '', - description: - categories?.[ - `${getLabel('category', locale, labels)} ${qualification}` - ] ?? '', - })), + label: formatMessage(m.classesOfRights), + fields: qualifications.split('').map((qualification) => { + const key: keyof FirearmCategories = `${formatMessage( + m.category, + )} ${qualification}` + + return { + type: GenericLicenseDataFieldType.Category, + name: qualification, + label: categories?.[key] ?? '', + } + }), } } private parseProperties = ( - labels?: GenericLicenseLabels, property?: FirearmProperty, - locale: Locale = 'is', + formatMessage?: FormatMessage, ): Array | null => { - if (!property) return null - const label = labels?.labels + if (!property || !formatMessage) return null const mappedProperty = [ { type: GenericLicenseDataFieldType.Value, - label: getLabel('firearmStatus', locale, label), + label: formatMessage(m.firearmStatus), value: property.category ?? '', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('type', locale, label), + label: formatMessage(m.type), value: property.typeOfFirearm ?? '', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('name', locale, label), + label: formatMessage(m.name), value: property.name ?? '', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('number', locale, label), + label: formatMessage(m.number), value: property.serialNumber ?? '', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('countryNumber', locale, label), + label: formatMessage(m.countryNumber), value: property.landsnumer ?? '', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('caliber', locale, label), + label: formatMessage(m.caliber), value: property.caliber ?? '', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('limitation', locale, label), + label: formatMessage(m.limitations), value: property.limitation ?? '', }, ] diff --git a/libs/api/domains/license-service/src/lib/mappers/huntingLicenseMapper.ts b/libs/api/domains/license-service/src/lib/mappers/huntingLicenseMapper.ts index 01f433162a65..b44cefc3f37b 100644 --- a/libs/api/domains/license-service/src/lib/mappers/huntingLicenseMapper.ts +++ b/libs/api/domains/license-service/src/lib/mappers/huntingLicenseMapper.ts @@ -1,37 +1,45 @@ import { Locale } from '@island.is/shared/types' import { - GenericLicenseDataField, GenericLicenseDataFieldType, - GenericLicenseLabels, + GenericLicenseMappedPayloadResponse, GenericLicenseMapper, - GenericUserLicensePayload, + GenericUserLicenseMetaLinksType, } from '../licenceService.type' import { HuntingLicenseDto } from '@island.is/clients/hunting-license' -import { getLabel } from '../utils/translations' -import { Injectable } from '@nestjs/common' -import { format } from 'kennitala' -import dateFormat from 'date-fns/format' +import { Inject, Injectable } from '@nestjs/common' import { isDefined } from '@island.is/shared/utils' -import capitalize from 'lodash/capitalize' -import { DEFAULT_LICENSE_ID } from '../licenseService.constants' - -const formatDate = (date: Date) => dateFormat(date, 'dd.MM.yyyy') +import { + DEFAULT_LICENSE_ID, + LICENSE_NAMESPACE, +} from '../licenseService.constants' +import { IntlService } from '@island.is/cms-translations' +import { m } from '../messages' +import { format as formatNationalId } from 'kennitala' +import { expiryTag, formatDate, capitalize } from '../utils' +import { GenericLicenseDataField } from '../dto/GenericLicenseDataField.dto' +import { type Logger, LOGGER_PROVIDER } from '@island.is/logging' @Injectable() export class HuntingLicensePayloadMapper implements GenericLicenseMapper { - parsePayload( + constructor( + @Inject(LOGGER_PROVIDER) private readonly logger: Logger, + private readonly intlService: IntlService, + ) {} + async parsePayload( payload: Array, locale: Locale = 'is', - labels?: GenericLicenseLabels, - ): Array { - if (!payload) return [] + ): Promise> { + if (!payload) return Promise.resolve([]) const typedPayload = payload as Array - const label = labels?.labels + const { formatMessage } = await this.intlService.useIntl( + [LICENSE_NAMESPACE], + locale, + ) - const mappedPayload: Array = typedPayload.map( - (t) => { + const mappedPayload: Array = + typedPayload.map((t) => { let address = t.holderAddress if (t.holderCity) { address += `, ${t.holderCity}` @@ -39,51 +47,53 @@ export class HuntingLicensePayloadMapper implements GenericLicenseMapper { const data: Array = [ { - name: getLabel('basicInfoLicense', locale, label), + name: formatMessage(m.basicInfoLicense), type: GenericLicenseDataFieldType.Value, - label: getLabel('personName', locale, label), + label: formatMessage(m.personName), value: t.holderName ?? '', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('nationalId', locale, label), - value: t.holderNationalId ? format(t.holderNationalId) : '', + label: formatMessage(m.nationalId), + value: t.holderNationalId + ? formatNationalId(t.holderNationalId) + : '', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('legalAddress', locale, label), + label: formatMessage(m.legalAddress), value: address, }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('cardNumber', locale, label), + label: formatMessage(m.cardNumber), value: t.number ?? '', }, t.validFrom ? { type: GenericLicenseDataFieldType.Value, - label: getLabel('publishedDate', locale, label), + label: formatMessage(m.publishedDate), value: formatDate(t.validFrom), } : undefined, t.validFrom && t.validTo ? { type: GenericLicenseDataFieldType.Value, - label: getLabel('validDuration', locale, label), + label: formatMessage(m.validDuration), value: `${formatDate(t.validFrom)} - ${formatDate(t.validTo)}`, } : undefined, t.permitFor?.length ? { type: GenericLicenseDataFieldType.Value, - label: getLabel('huntingPermitValidFor', locale, label), + label: formatMessage(m.huntingPermitValidFor), value: t.permitFor?.map((p) => capitalize(p))?.join(', ') ?? '', } : undefined, t.benefits?.length ? { type: GenericLicenseDataFieldType.Value, - label: getLabel('huntingPermitBenefits', locale, label), + label: formatMessage(m.huntingPermitBenefits), value: t.benefits?.map((b) => capitalize(b.land))?.join(', ') ?? '', } @@ -91,25 +101,47 @@ export class HuntingLicensePayloadMapper implements GenericLicenseMapper { ].filter(isDefined) return { - data, - rawData: JSON.stringify(t), - metadata: { - licenseNumber: t.number?.toString() ?? '', - licenseId: DEFAULT_LICENSE_ID, - expired: !t.isValid, - expireDate: t.validTo ? t.validTo.toISOString() : undefined, - links: [ - { - label: getLabel('renewHuntingLicense', locale, label), - value: - t.renewalUrl ?? - 'https://innskraning.island.is/?id=gogn.ust.is', - }, - ], + licenseName: formatMessage(m.huntingCard), + type: 'user', + payload: { + data, + rawData: JSON.stringify(t), + metadata: { + licenseNumber: t.number ?? '', + subtitle: formatMessage(m.licenseNumberVariant, { + arg: t.number ?? formatMessage(m.unknown), + }), + licenseId: DEFAULT_LICENSE_ID, + expired: !t.isValid, + expireDate: t.validTo ? t.validTo.toISOString() : undefined, + displayTag: expiryTag( + formatMessage, + !t.isValid, + t.validTo && + formatMessage(m.validUntil, { + arg: formatDate(t.validTo), + }), + ), + links: [ + { + label: formatMessage(m.renewLicense, { + arg: formatMessage(m.huntingCard).toLowerCase(), + }), + value: + t.renewalUrl ?? + 'https://innskraning.island.is/?id=gogn.ust.is', + type: GenericUserLicenseMetaLinksType.External, + }, + ], + name: formatMessage(m.huntingCard), + title: formatMessage(m.yourHuntingCard), + description: [ + { text: formatMessage(m.huntingLicenseDescription) }, + ], + }, }, } - }, - ) + }) return mappedPayload } diff --git a/libs/api/domains/license-service/src/lib/mappers/licenseMapper.module.ts b/libs/api/domains/license-service/src/lib/mappers/licenseMapper.module.ts index 1c57fda4e0f3..724de9d9d2fd 100644 --- a/libs/api/domains/license-service/src/lib/mappers/licenseMapper.module.ts +++ b/libs/api/domains/license-service/src/lib/mappers/licenseMapper.module.ts @@ -7,8 +7,11 @@ import { DrivingLicensePayloadMapper } from '../mappers/drivingLicenseMapper' import { PCardPayloadMapper } from '../mappers/pCardMapper' import { EHICCardPayloadMapper } from '../mappers/ehicCardMapper' import { HuntingLicensePayloadMapper } from '../mappers/huntingLicenseMapper' +import { PassportMapper } from '../mappers/passportMapper' +import { CmsTranslationsModule } from '@island.is/cms-translations' @Module({ + imports: [CmsTranslationsModule], providers: [ AdrLicensePayloadMapper, FirearmLicensePayloadMapper, @@ -18,6 +21,7 @@ import { HuntingLicensePayloadMapper } from '../mappers/huntingLicenseMapper' HuntingLicensePayloadMapper, PCardPayloadMapper, EHICCardPayloadMapper, + PassportMapper, ], exports: [ AdrLicensePayloadMapper, @@ -28,6 +32,7 @@ import { HuntingLicensePayloadMapper } from '../mappers/huntingLicenseMapper' HuntingLicensePayloadMapper, PCardPayloadMapper, EHICCardPayloadMapper, + PassportMapper, ], }) export class LicenseMapperModule {} diff --git a/libs/api/domains/license-service/src/lib/mappers/machineLicenseMapper.ts b/libs/api/domains/license-service/src/lib/mappers/machineLicenseMapper.ts index ed047747791f..f6708d60fb0f 100644 --- a/libs/api/domains/license-service/src/lib/mappers/machineLicenseMapper.ts +++ b/libs/api/domains/license-service/src/lib/mappers/machineLicenseMapper.ts @@ -6,17 +6,24 @@ import { import isAfter from 'date-fns/isAfter' import { Locale } from '@island.is/shared/types' import { - GenericLicenseLabels, - GenericUserLicensePayload, - GenericLicenseDataField, + DEFAULT_LICENSE_ID, + LICENSE_NAMESPACE, +} from '../licenseService.constants' +import { Injectable } from '@nestjs/common' +import { m } from '../messages' +import { FormatMessage, IntlService } from '@island.is/cms-translations' +import { formatDate, expiryTag } from '../utils' +import { GenericLicenseDataFieldType, + GenericLicenseMappedPayloadResponse, GenericLicenseMapper, } from '../licenceService.type' -import { DEFAULT_LICENSE_ID } from '../licenseService.constants' -import { getLabel } from '../utils/translations' -import { Injectable } from '@nestjs/common' +import { GenericLicenseDataField } from '../dto/GenericLicenseDataField.dto' + @Injectable() export class MachineLicensePayloadMapper implements GenericLicenseMapper { + constructor(private readonly intlService: IntlService) {} + private checkLicenseExpirationDate(license: VinnuvelaDto) { return license.vinnuvelaRettindi ? license.vinnuvelaRettindi @@ -28,102 +35,116 @@ export class MachineLicensePayloadMapper implements GenericLicenseMapper { field.stjorna && !isAfter(new Date(field.stjorna), new Date()), ) - : null + : undefined } - parsePayload( + async parsePayload( payload: Array, locale: Locale = 'is', - labels?: GenericLicenseLabels, - ): Array { - if (!payload) return [] + ): Promise> { + if (!payload) return Promise.resolve([]) const typedPayload = payload as Array - const label = labels?.labels - const mappedPayload: Array = typedPayload.map( - (t) => { - const expired: boolean | null = this.checkLicenseExpirationDate(t) + const { formatMessage } = await this.intlService.useIntl( + [LICENSE_NAMESPACE], + locale, + ) + const mappedPayload: Array = + typedPayload.map((t) => { const data: Array = [ { - name: getLabel('basicInfoLicense', locale, label), + name: formatMessage(m.basicInfoLicense), type: GenericLicenseDataFieldType.Value, - label: getLabel('licenseNumber', locale, label), + label: formatMessage(m.licenseNumber), value: t.skirteinisNumer?.toString(), }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('fullName', locale, label), + label: formatMessage(m.fullName), value: t?.fulltNafn ?? '', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('placeOfIssue', locale, label), + label: formatMessage(m.placeOfIssue), value: t.utgafuStadur ?? '', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('firstPublishedDate', locale, label), - value: t.fyrstiUtgafuDagur?.toString(), + label: formatMessage(m.firstPublishedDate), + value: t.fyrstiUtgafuDagur + ? formatDate(new Date(t.fyrstiUtgafuDagur)) + : '', }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('validTo', locale, label), - value: getLabel('seeRights', locale, label), + label: formatMessage(m.validTo), + value: formatMessage(m.seeRights), }, { type: GenericLicenseDataFieldType.Value, - label: getLabel('drivingLicenseNumber', locale, label), + label: formatMessage(m.drivingLicenseNumber), value: t.okuskirteinisNumer ?? '', }, { type: GenericLicenseDataFieldType.Group, - label: getLabel('classesOfRights', locale, label), + label: formatMessage(m.classesOfRights), fields: (t.vinnuvelaRettindi ?? []) .filter((field) => field.kenna || field.stjorna) .map((field) => ({ type: GenericLicenseDataFieldType.Category, name: field.flokkur ?? '', label: field.fulltHeiti ?? field.stuttHeiti ?? '', - description: field.fulltHeiti ?? field.stuttHeiti ?? '', - fields: this.parseVvrRights(field, locale, labels), + fields: this.parseVvrRights(field, formatMessage), })), }, ] + const isExpired = this.checkLicenseExpirationDate(t) + return { - data, - rawData: JSON.stringify(t), - metadata: { - licenseNumber: t.skirteinisNumer?.toString() ?? '', - licenseId: DEFAULT_LICENSE_ID, - expired: expired, + licenseName: formatMessage(m.heavyMachineryLicense), + type: 'user', + payload: { + data, + rawData: JSON.stringify(t), + metadata: { + licenseNumber: t.skirteinisNumer?.toString() ?? '', + subtitle: formatMessage(m.licenseNumberVariant, { + arg: t.skirteinisNumer?.toString() ?? formatMessage(m.unknown), + }), + licenseId: DEFAULT_LICENSE_ID, + expired: isExpired, + displayTag: expiryTag(formatMessage, isExpired), + name: formatMessage(m.heavyMachineryLicense), + title: formatMessage(m.yourMachineLicense), + description: [ + { text: formatMessage(m.yourMachineLicenseDescription) }, + ], + }, }, } - }, - ) + }) return mappedPayload } parseVvrRights( rights: VinnuvelaRettindiDto, - locale: Locale = 'is', - labels?: GenericLicenseLabels, + formatMessage: FormatMessage, ): Array | undefined { const fields = new Array() - const label = labels?.labels if (rights.stjorna) { fields.push({ type: GenericLicenseDataFieldType.Value, - label: getLabel('control', locale, label), + label: formatMessage(m.control), value: rights.stjorna, }) } if (rights.kenna) { fields.push({ type: GenericLicenseDataFieldType.Value, - label: getLabel('teach', locale, label), + label: formatMessage(m.teach), value: rights.kenna, }) } diff --git a/libs/api/domains/license-service/src/lib/mappers/pCardMapper.ts b/libs/api/domains/license-service/src/lib/mappers/pCardMapper.ts index a2ab4feaaca7..014c16c916dc 100644 --- a/libs/api/domains/license-service/src/lib/mappers/pCardMapper.ts +++ b/libs/api/domains/license-service/src/lib/mappers/pCardMapper.ts @@ -1,94 +1,120 @@ import isAfter from 'date-fns/isAfter' import { Locale } from '@island.is/shared/types' import { - GenericLicenseDataField, - GenericLicenseDataFieldType, - GenericLicenseLabels, - GenericLicenseMapper, - GenericUserLicensePayload, -} from '../licenceService.type' -import { DEFAULT_LICENSE_ID } from '../licenseService.constants' -import { getLabel } from '../utils/translations' + DEFAULT_LICENSE_ID, + LICENSE_NAMESPACE, +} from '../licenseService.constants' import { Injectable } from '@nestjs/common' import { Staediskortamal } from '@island.is/clients/p-card' import { isDefined } from '@island.is/shared/utils' import { format } from 'kennitala' +import { m } from '../messages' +import { IntlService } from '@island.is/cms-translations' +import { expiryTag } from '../utils/expiryTag' +import { formatDate } from '../utils' +import { + GenericLicenseDataFieldType, + GenericLicenseMappedPayloadResponse, + GenericLicenseMapper, +} from '../licenceService.type' +import { GenericLicenseDataField } from '../dto/GenericLicenseDataField.dto' @Injectable() export class PCardPayloadMapper implements GenericLicenseMapper { - parsePayload( + constructor(private readonly intlService: IntlService) {} + async parsePayload( payload: Array, locale: Locale = 'is', - labels?: GenericLicenseLabels, - ): Array { - if (!payload) return [] + ): Promise> { + if (!payload) return Promise.resolve([]) const typedPayload = payload as Array - const label = labels?.labels - const mappedPayload: Array = typedPayload.map( - (t) => { - const expired = t.gildistimi + const { formatMessage } = await this.intlService.useIntl( + [LICENSE_NAMESPACE], + locale, + ) + + const mappedPayload: Array = + typedPayload.map((t) => { + const isExpired = t.gildistimi ? !isAfter(new Date(t.gildistimi.toISOString()), new Date()) - : null + : undefined const data: Array = [ t.nafn ? { type: GenericLicenseDataFieldType.Value, - label: getLabel('name', locale, label), + label: formatMessage(m.name), value: t.nafn, } : null, t.kennitala ? { type: GenericLicenseDataFieldType.Value, - label: getLabel('nationalId', locale, label), + label: formatMessage(m.nationalId), value: format(t.kennitala), } : null, t.malsnumer ? { type: GenericLicenseDataFieldType.Value, - label: getLabel('cardNumber', locale, label), + label: formatMessage(m.cardNumber), value: t.malsnumer, } : null, t.utgafudagur ? { type: GenericLicenseDataFieldType.Value, - label: getLabel('publishedDate', locale, label), - value: t.utgafudagur.toISOString(), + label: formatMessage(m.publishedDate), + value: formatDate(t.utgafudagur), } : null, t.gildistimi ? { type: GenericLicenseDataFieldType.Value, - label: getLabel('validTo', locale, label), - value: t.gildistimi.toISOString(), + label: formatMessage(m.validTo), + value: formatDate(t.gildistimi), + tag: expiryTag( + formatMessage, + isExpired, + formatMessage(m.validUntil, { + arg: formatDate(t.gildistimi), + }), + ), } : null, t.utgefandi ? { type: GenericLicenseDataFieldType.Value, - label: getLabel('publisher', locale, label), + label: formatMessage(m.publisher), value: t.utgefandi, } : null, ].filter(isDefined) return { - data, - rawData: JSON.stringify(t), - metadata: { - licenseNumber: t.malsnumer?.toString() ?? '', - licenseId: DEFAULT_LICENSE_ID, - expired, - expireDate: t.gildistimi?.toISOString(), + licenseName: formatMessage(m.pCard), + type: 'user', + payload: { + data, + rawData: JSON.stringify(t), + metadata: { + licenseNumber: t.malsnumer?.toString() ?? '', + subtitle: formatMessage(m.licenseNumberVariant, { + arg: t.malsnumer?.toString() ?? formatMessage(m.unknown), + }), + licenseId: DEFAULT_LICENSE_ID, + expired: isExpired, + expireDate: t.gildistimi?.toISOString(), + displayTag: expiryTag(formatMessage, isExpired), + name: formatMessage(m.pCard), + title: formatMessage(m.yourPCard), + description: [{ text: formatMessage(m.yourPCardDescription) }], + }, }, } - }, - ) + }) return mappedPayload } } diff --git a/libs/api/domains/license-service/src/lib/mappers/passportMapper.ts b/libs/api/domains/license-service/src/lib/mappers/passportMapper.ts new file mode 100644 index 000000000000..9310db17dc05 --- /dev/null +++ b/libs/api/domains/license-service/src/lib/mappers/passportMapper.ts @@ -0,0 +1,265 @@ +import { Locale } from '@island.is/shared/types' +import { LICENSE_NAMESPACE } from '../licenseService.constants' +import { Injectable } from '@nestjs/common' +import { isDefined } from '@island.is/shared/utils' +import { + type IdentityDocument, + type IdentityDocumentChild, +} from '@island.is/clients/passports' +import { FormatMessage, IntlService } from '@island.is/cms-translations' +import { m } from '../messages' +import { GenericUserLicenseAlert } from '../dto/GenericUserLicenseAlert.dto' +import { GenericUserLicenseMetaLinks } from '../dto/GenericUserLicenseMetaLinks.dto' +import { GenericUserLicenseMetaTag } from '../dto/GenericUserLicenseMetaTag.dto' +import { capitalize, capitalizeEveryWord } from '../utils/capitalize' +import { Payload } from '../dto/Payload.dto' +import { formatDate } from '../utils' +import { + AlertType, + GenericLicenseDataFieldType, + GenericLicenseMappedPayloadResponse, + GenericLicenseMapper, + GenericUserLicenseDataFieldTagColor, + GenericUserLicenseDataFieldTagType, + GenericUserLicenseMetaLinksType, +} from '../licenceService.type' +import { GenericLicenseDataField } from '../dto/GenericLicenseDataField.dto' + +const isChildPassport = ( + passport: IdentityDocument | IdentityDocumentChild, +): passport is IdentityDocumentChild => { + return (passport as IdentityDocumentChild).childNationalId !== undefined +} + +@Injectable() +export class PassportMapper implements GenericLicenseMapper { + constructor(private readonly intlService: IntlService) {} + async parsePayload( + payload: Array, + locale: Locale = 'is', + ): Promise> { + const { formatMessage } = await this.intlService.useIntl( + [LICENSE_NAMESPACE], + locale, + ) + + const emptyPassport = { + licenseName: formatMessage(m.passport), + type: 'user' as const, + payload: { + data: [], + rawData: '', + metadata: { + title: formatMessage(m.passport), + name: formatMessage(m.passport), + subtitle: formatMessage(m.noValidPassport), + ctaLink: { + label: formatMessage(m.apply), + value: formatMessage(m.applyPassportUrl), + }, + }, + }, + } + + if (!payload.length) { + return [emptyPassport] + } + + const typedPayload = payload as Array< + IdentityDocument | IdentityDocumentChild + > + + const mappedLicenses: Array = + typedPayload + .map((t) => { + if (isChildPassport(t)) { + return this.mapChildDocument(t, formatMessage).map((document) => ({ + licenseName: formatMessage(m.passport), + type: 'child' as const, + payload: document, + })) + } else { + return { + licenseName: formatMessage(m.passport), + type: 'user' as const, + payload: this.mapDocument(t, formatMessage), + } + } + }) + .flat() + + return mappedLicenses + } + + private mapChildDocument( + document: IdentityDocumentChild, + formatMessage: FormatMessage, + ): Array { + if (document.passports?.length) { + return ( + document.passports?.map((p) => this.mapDocument(p, formatMessage)) ?? [] + ) + } + + return [ + { + data: [], + metadata: { + name: document.childName ?? '', + title: document.childName ?? '', + subtitle: formatMessage(m.noValidPassport), + ctaLink: { + label: formatMessage(m.apply), + value: formatMessage(m.applyPassportUrl), + }, + }, + }, + ] + } + + private mapDocument( + document: IdentityDocument, + formatMessage: FormatMessage, + ): Payload { + const isExpired = document.expiryStatus === 'EXPIRED' + const isLost = document.expiryStatus === 'LOST' + const isExpiring = document.expiresWithinNoticeTime + const isInvalid = document.status + ? document.status.toLowerCase() === 'invalid' + : undefined + + const displayTag: GenericUserLicenseMetaTag | undefined = { + text: isInvalid + ? formatMessage(m.invalid) + : isExpiring + ? formatMessage(m.expiresWithin, { + arg: formatMessage(m.sixMonths), + }) + : formatMessage(m.valid), + color: isInvalid || isExpiring ? 'red' : 'blue', + icon: isInvalid + ? GenericUserLicenseDataFieldTagType.closeCircle + : GenericUserLicenseDataFieldTagType.checkmarkCircle, + iconColor: isInvalid + ? GenericUserLicenseDataFieldTagColor.red + : isExpiring + ? GenericUserLicenseDataFieldTagColor.yellow + : GenericUserLicenseDataFieldTagColor.green, + iconText: isInvalid + ? formatMessage(isLost ? m.lost : m.expired) + : formatMessage(m.valid), + } + + const data: Array = [ + document.displayFirstName && document.displayLastName + ? { + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.personName), + value: capitalizeEveryWord( + `${document.displayFirstName} ${document.displayLastName}`, + ), + } + : null, + document.numberWithType + ? { + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.number), + value: document.numberWithType, + } + : null, + document.issuingDate + ? { + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.publishedDate), + value: formatDate(document.issuingDate), + } + : null, + document.expirationDate + ? { + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.expiryDate), + value: formatDate(document.expirationDate), + tag: displayTag, + } + : null, + document.mrzFirstName && document.mrzLastName + ? { + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.passportNameComputer), + value: `${document.mrzLastName} ${document.mrzFirstName}`, + } + : null, + document.sex + ? { + type: GenericLicenseDataFieldType.Value, + label: formatMessage(m.sex), + value: + document.sex === 'M' + ? formatMessage(m.male) + : document.sex === 'F' + ? formatMessage(m.female) + : formatMessage(m.otherGender), + } + : null, + ].filter(isDefined) + + const alert: GenericUserLicenseAlert | undefined = + isExpired || isExpiring || isLost + ? { + title: + isExpired || isLost + ? formatMessage(m.licenseInvalid, { + arg: formatMessage(m.passport), + }) + : formatMessage(m.expiresWithin, { + arg: formatMessage(m.sixMonths), + }), + message: + isExpired || isLost + ? formatMessage(m.invalidPassportText) + : formatMessage(m.expiringPassportText), + type: AlertType.WARNING, + } + : undefined + + const links: Array = [ + { + label: formatMessage(m.notifyLostPassport), + value: formatMessage(m.lostPassportUrl), + type: GenericUserLicenseMetaLinksType.External, + }, + isExpired || isExpiring || isLost + ? { + label: formatMessage(m.renewLicense, { + arg: formatMessage(m.passport).toLowerCase(), + }), + value: formatMessage(m.applyPassportUrl), + type: GenericUserLicenseMetaLinksType.External, + } + : undefined, + ].filter(isDefined) + + return { + data, + rawData: JSON.stringify(document), + metadata: { + links, + licenseNumber: document.number?.toString() ?? '', + licenseId: document.number?.toString(), + expired: isExpired, + expireDate: document.expirationDate?.toISOString() ?? undefined, + displayTag, + name: document.verboseType ?? undefined, + title: document.verboseType ?? undefined, + subtitle: formatMessage(m.passportNumberDisplay, { + arg: + document.subType && document.number + ? `${document.subType}${document.number}` + : formatMessage(m.unknown), + }), + description: [{ text: formatMessage(m.passportDescription) }], + alert, + }, + } + } +} diff --git a/libs/api/domains/license-service/src/lib/messages.ts b/libs/api/domains/license-service/src/lib/messages.ts new file mode 100644 index 000000000000..237b543f7aad --- /dev/null +++ b/libs/api/domains/license-service/src/lib/messages.ts @@ -0,0 +1,372 @@ +import { defineMessages } from 'react-intl' + +export const m = defineMessages({ + adrLicense: { + id: 'api.license-service:adr-license', + defaultMessage: 'ADR réttindi', + }, + disabilityCard: { + id: 'api.license-service:disability-card', + defaultMessage: 'Örorkuskírteini', + }, + drivingLicense: { + id: 'api.license-service:driving-license', + defaultMessage: 'Ökuskírteini', + }, + ehicCard: { + id: 'api.license-service:ehic-card', + defaultMessage: 'Evrópska sjúkratryggingakortið', + }, + firearmLicense: { + id: 'api.license-service:firearm-license', + defaultMessage: 'Skotvopnaleyfi', + }, + huntingCard: { + id: 'api.license-service:hunting-card', + defaultMessage: 'Almennt veiðikort', + }, + heavyMachineryLicense: { + id: 'api.license-service:heavy-machinery-license', + defaultMessage: 'Vinnuvélaréttindi', + }, + passport: { + id: 'api.license-service:passport', + defaultMessage: 'Vegabréf', + }, + pCard: { + id: 'api.license-service:p-card', + defaultMessage: 'P-kort', + }, + basicInfoLicense: { + id: 'api.license-service:basic-info-license', + defaultMessage: 'Grunnupplýsingar skírteinis', + }, + basicInfoDisabilityLicense: { + id: 'api.license-service:basic-info-disability-license', + defaultMessage: 'Grunnupplýsingar örorkuskírteinis', + }, + tanks: { + id: 'api.license-service:tanks', + defaultMessage: 'Tankar', + }, + validTo: { + id: 'api.license-service:valid-to', + defaultMessage: 'Gildir til', + }, + fullName: { + id: 'api.license-service:full-name', + defaultMessage: 'Fullt nafn', + }, + publisher: { + id: 'api.license-service:publisher', + defaultMessage: 'Útgefandi', + }, + otherThanTanks: { + id: 'api.license-service:other-than-tanks', + defaultMessage: 'Annað en í tanki', + }, + licenseNumber: { + id: 'api.license-service:license-number', + defaultMessage: 'Skírteinisnúmer', + }, + licenseNumberVariant: { + id: 'api.license-service:license-number-variant', + defaultMessage: 'Skírteinisnúmer: {arg}', + }, + publishedDate: { + id: 'api.license-service:published-date', + defaultMessage: 'Útgáfudagur', + }, + classesOfRights: { + id: 'api.license-service:classes-of-rights', + defaultMessage: 'Réttindaflokkar', + }, + expiryDate: { + id: 'api.license-service:expiry-date', + defaultMessage: 'Lokadagur', + }, + comments: { + id: 'api.license-service:comments', + defaultMessage: 'Athugasemdir', + }, + renewLicense: { + id: 'api.license-service:renew-license', + defaultMessage: 'Endurnýja {arg}', + }, + nationalId: { + id: 'api.license-service:national-id', + defaultMessage: 'Kennitala', + }, + cardNumber: { + id: 'api.license-service:card-number', + defaultMessage: 'Númer korts', + }, + downloadCard: { + id: 'api.license-service:download-card', + defaultMessage: 'Hlaða niður korti', + }, + apply: { + id: 'api.license-service:apply', + defaultMessage: 'Sækja um', + }, + applyForNewCard: { + id: 'api.license-service:apply-for-new-card', + defaultMessage: 'Sækja um nýtt kort', + }, + collectorLicenseValidTo: { + id: 'api.license-service:collector-license-valid-to', + defaultMessage: 'Safnaraskírteini gildir til', + }, + firearmProperties: { + id: 'api.license-service:firearm-properties', + defaultMessage: 'Skotvopn í eigu leyfishafa', + }, + category: { + id: 'api.license-service:category', + defaultMessage: 'Flokkur', + }, + firearmStatus: { + id: 'api.license-service:firearm-status', + defaultMessage: 'Staða skotvopns', + }, + type: { + id: 'api.license-service:type', + defaultMessage: 'Tegund', + }, + name: { + id: 'api.license-service:name', + defaultMessage: 'Heiti', + }, + number: { + id: 'api.license-service:number', + defaultMessage: 'Númer', + }, + countryNumber: { + id: 'api.license-service:country-number', + defaultMessage: 'Landsnúmer', + }, + caliber: { + id: 'api.license-service:caliber', + defaultMessage: 'Hlaupvídd', + }, + limitations: { + id: 'api.license-service:limitations', + defaultMessage: 'Takmarkanir', + }, + personName: { + id: 'api.license-service:person-name', + defaultMessage: 'Nafn einstaklings', + }, + legalAddress: { + id: 'api.license-service:legal-address', + defaultMessage: 'Lögheimili', + }, + validDuration: { + id: 'api.license-service:valid-duration', + defaultMessage: 'Gildistími', + }, + huntingPermitValidFor: { + id: 'api.license-service:hunting-permit-valid-for', + defaultMessage: 'Kortið gildir fyrir veiðar á', + }, + huntingPermitBenefits: { + id: 'api.license-service:hunting-permit-benefits', + defaultMessage: 'Hlunnindajarðir', + }, + placeOfIssue: { + id: 'api.license-service:place-of-issue', + defaultMessage: 'Útgáfustaður', + }, + firstPublishedDate: { + id: 'api.license-service:first-published-date', + defaultMessage: 'Fyrsti útgáfudagur', + }, + seeRights: { + id: 'api.license-service:see-rights', + defaultMessage: 'Sjá réttindi', + }, + drivingLicenseNumber: { + id: 'api.license-service:driving-license-number', + defaultMessage: 'Ökuskírteinisnúmer', + }, + control: { + id: 'api.license-service:control', + defaultMessage: 'Stjórna', + }, + teach: { + id: 'api.license-service:teach', + defaultMessage: 'Kenna', + }, + sex: { + id: 'api.license-service:sex', + defaultMessage: 'Kyn', + }, + expired: { + id: 'api.license-service:expired', + defaultMessage: 'Útrunnið', + }, + expiresWithin: { + id: 'api.license-service:expires-within', + defaultMessage: 'Rennur út innan {arg}', + }, + validUntil: { + id: 'api.license-service:valid-until', + defaultMessage: 'Í gildi til {arg}', + }, + valid: { + id: 'api.license-service:valid', + defaultMessage: 'Í gildi', + }, + lost: { + id: 'api.license-service:lost', + defaultMessage: 'Glatað', + }, + invalid: { + id: 'api.license-service:invalid', + defaultMessage: 'Ógilt', + }, + unknown: { + id: 'api.license-service:unknown', + defaultMessage: 'Óþekkt', + }, + unknownStatus: { + id: 'api.license-service:unknown-status', + defaultMessage: 'Óþekkt staða', + }, + licenseInvalid: { + id: 'api.license-service:license-invalid', + defaultMessage: '{arg} ógilt', + }, + sixMonths: { + id: 'api.license-service:six-months', + defaultMessage: '6 mánaða', + }, + invalidPassportText: { + id: 'api.license-service:passport-invalid-text', + defaultMessage: 'Athugið að vegabréf er ógilt og þarf að sækja um nýtt', + }, + expiringPassportText: { + id: 'api.license-service:passport-expiring-text', + defaultMessage: + 'Athugið að vegabréfið þitt mun renna út innan næstu 6 mánaða. Þeir sem hyggast ferðast utan EES verða að hafa vegabréf sem gilda í amk 6 mánuði frá áætluðum ferðalokum.', + }, + renewPassport: { + id: 'api.license-service:passport-renew', + defaultMessage: 'Endurnýja vegabréf', + }, + notifyLostPassport: { + id: 'api.license-service:passport-notify-lost', + defaultMessage: 'Tilkynna glatað vegabréf', + }, + lostPassportUrl: { + id: 'api.license-service:passport-lost-url', + defaultMessage: 'https://island.is/stolidtynt-vegabref', + }, + applyPassportUrl: { + id: 'api.license-service:passport-apply-url', + defaultMessage: 'https://island.is/vegabref', + }, + passportNumberDisplay: { + id: 'api.license-service:passport-number-display', + defaultMessage: 'Númer vegabréfs: {arg}', + }, + passportDescription: { + id: 'api.license-service:passport-description', + defaultMessage: + 'Hér birtast upplýsingar um vegabréfið þitt. Athugaðu að þetta eru aðeins upplýsingar en ekki gilt vegabréf', + }, + passportNameComputer: { + id: 'api.license-service:passport-name-computer', + defaultMessage: 'Nafn á tölvulesanlegu formi', + }, + male: { + id: 'api.license-service:gender-male', + defaultMessage: 'Karl', + }, + female: { + id: 'api.license-service:gender-female', + defaultMessage: 'Kona', + }, + otherGender: { + id: 'api.license-service:gender-other', + defaultMessage: 'Kynsegin/Annað', + }, + yourADRLicense: { + id: 'api.license-service:your-adr-license', + defaultMessage: 'ADR réttindin þín', + }, + yourFirearmLicense: { + id: 'api.license-service:your-firearm-license', + defaultMessage: 'Skotvopnaleyfið þitt', + }, + yourDrivingLicense: { + id: 'api.license-service:your-driving-license', + defaultMessage: 'Ökuréttindin þín', + }, + yourDisabilityLicense: { + id: 'api.license-service:your-disability-license', + defaultMessage: 'Örorkuskírteinið þitt', + }, + yourMachineLicense: { + id: 'api.license-service:your-machine-license', + defaultMessage: 'Vinnuvélaréttindin þín', + }, + yourPCard: { + id: 'api.license-service:your-p-card', + defaultMessage: 'P-kortið þitt', + }, + yourPCardDescription: { + id: 'api.license-service:p-card-description', + defaultMessage: 'Stæðiskort fyrir hreyfihamlaða', + }, + ehicDescription: { + id: 'api.license-service:ehic-description', + defaultMessage: + 'Evrópska sjúkratryggingakortið veitir korthafa rétt til heilbrigðisþjónustu í öðrum EES löndum, og Sviss.', + }, + ehicDescription2: { + id: 'api.license-service:ehic-description-2', + defaultMessage: 'Nánar um kortið.', + }, + ehicDescriptionLink: { + id: 'api.license-service:ehic-description-link', + defaultMessage: 'https://island.is/evropska-sjukratryggingakortid', + }, + yourAdrLicenseDescription: { + id: 'api.license-service:your-adr-license-description', + defaultMessage: + 'Hér birtast upplýsingar um ADR skírteini þitt ásamt þeim réttindum sem þú ert með í gildi á hverjum tíma.', + }, + yourFirearmLicenseDescription: { + id: 'api.license-service:your-firearm-license-description', + defaultMessage: + 'Hér birtast upplýsingar um skoptvopnaleyfið þitt ásamt skotvopnum skráð í þinni eigu.', + }, + yourDisabilityLicenseDescription: { + id: 'api.license-service:your-disability-license-description', + defaultMessage: 'Hér birtast upplýsingar um örorkuskírteinið þitt.', + }, + huntingLicenseDescription: { + id: 'api.license-service:hunting-license-description', + defaultMessage: + 'Allir sem stunda veiðar á fuglum og spendýrum hér á landi þurfa að hafa veiðikort. Til að fá veiðikort í fyrsta sinn þurfa menn að hafa tekið próf fyrir verðandi veiðimenn.', + }, + yourMachineLicenseDescription: { + id: 'api.license-service:your-machine-license-description', + defaultMessage: + 'Hér birtast upplýsingar um vinnuvélaskírteini þitt ásamt þeim réttindum sem þú ert með í gildi á hverjum tíma.', + }, + yourDrivingLicenseDescription: { + id: 'api.license-service:your-driving-license-description', + defaultMessage: + 'Hér birtast upplýsingar um ökuskírteini þitt ásamt þeim ökuréttindum sem þú ert með í gildi á hverjum tíma.', + }, + yourHuntingCard: { + id: 'api.license-service:your-hunting-card', + defaultMessage: 'Veiðikortið þitt', + }, + noValidPassport: { + id: 'api.license-service:no-valid-passport', + defaultMessage: 'Engin gild vegabréf', + }, +}) diff --git a/libs/api/domains/license-service/src/lib/providers/licenseMapper.provider.ts b/libs/api/domains/license-service/src/lib/providers/licenseMapper.provider.ts index befda836cc10..164ce55332f3 100644 --- a/libs/api/domains/license-service/src/lib/providers/licenseMapper.provider.ts +++ b/libs/api/domains/license-service/src/lib/providers/licenseMapper.provider.ts @@ -12,6 +12,7 @@ import { FirearmLicensePayloadMapper } from '../mappers/firearmLicenseMapper' import { MachineLicensePayloadMapper } from '../mappers/machineLicenseMapper' import { PCardPayloadMapper } from '../mappers/pCardMapper' import { HuntingLicensePayloadMapper } from '../mappers/huntingLicenseMapper' +import { PassportMapper } from '../mappers/passportMapper' export const LicenseMapperProvider: FactoryProvider = { provide: LICENSE_MAPPER_FACTORY, @@ -25,6 +26,7 @@ export const LicenseMapperProvider: FactoryProvider = { pCard: PCardPayloadMapper, ehic: EHICCardPayloadMapper, hunting: HuntingLicensePayloadMapper, + passport: PassportMapper, ) => async (type: GenericLicenseType): Promise => { switch (type) { @@ -44,6 +46,8 @@ export const LicenseMapperProvider: FactoryProvider = { return pCard case GenericLicenseType.Ehic: return ehic + case GenericLicenseType.Passport: + return passport default: return null } @@ -57,5 +61,6 @@ export const LicenseMapperProvider: FactoryProvider = { PCardPayloadMapper, EHICCardPayloadMapper, HuntingLicensePayloadMapper, + PassportMapper, ], } diff --git a/libs/api/domains/license-service/src/lib/resolvers/licenseCollection.resolver.ts b/libs/api/domains/license-service/src/lib/resolvers/licenseCollection.resolver.ts new file mode 100644 index 000000000000..2cc6e34ef319 --- /dev/null +++ b/libs/api/domains/license-service/src/lib/resolvers/licenseCollection.resolver.ts @@ -0,0 +1,71 @@ +import type { User } from '@island.is/auth-nest-tools' +import { + CurrentUser, + IdsUserGuard, + Scopes, + ScopesGuard, +} from '@island.is/auth-nest-tools' +import { ApiScope } from '@island.is/auth/scopes' +import { Audit } from '@island.is/nest/audit' + +import type { Locale } from '@island.is/shared/types' +import { UseGuards } from '@nestjs/common' +import { Args, Query, Resolver } from '@nestjs/graphql' +import { GetGenericLicensesInput } from '../dto/GetGenericLicenses.input' +import { LicenseService } from '../licenseService.service' +import { LicenseCollection } from '../dto/GenericLicenseCollection.dto' +import { GenericUserLicense } from '../dto/GenericUserLicense.dto' + +@UseGuards(IdsUserGuard, ScopesGuard) +@Scopes(ApiScope.internal, ApiScope.licenses) +@Resolver(() => LicenseCollection) +@Audit({ namespace: '@island.is/api/license-service' }) +export class LicenseCollectionResolver { + constructor(private readonly licenseServiceService: LicenseService) {} + + @Query(() => [GenericUserLicense], { + deprecationReason: 'Use genericLicenseCollection instead', + }) + @Audit() + async genericLicenses( + @CurrentUser() user: User, + @Args('locale', { type: () => String, nullable: true }) + locale: Locale = 'is', + @Args('input', { nullable: true }) input?: GetGenericLicensesInput, + ) { + const collection = await this.licenseServiceService.getLicenseCollection( + user, + locale, + { + ...input, + includedTypes: input?.includedTypes ?? ['DriversLicense'], + excludedTypes: input?.excludedTypes, + force: input?.force, + onlyList: input?.onlyList, + }, + ) + + return collection.licenses + } + + @Query(() => LicenseCollection, { + name: 'genericLicenseCollection', + }) + @Audit() + async genericLicenseCollection( + @CurrentUser() user: User, + @Args('locale', { type: () => String, nullable: true }) + locale: Locale = 'is', + @Args('input') input: GetGenericLicensesInput, + ) { + const licenseCollection = + await this.licenseServiceService.getLicenseCollection(user, locale, { + ...input, + includedTypes: input?.includedTypes ?? ['DriversLicense'], + excludedTypes: input?.excludedTypes, + force: input?.force, + onlyList: input?.onlyList, + }) + return licenseCollection + } +} diff --git a/libs/api/domains/license-service/src/lib/resolvers/pkPass.resolver.ts b/libs/api/domains/license-service/src/lib/resolvers/pkPass.resolver.ts new file mode 100644 index 000000000000..d78ea015d5e5 --- /dev/null +++ b/libs/api/domains/license-service/src/lib/resolvers/pkPass.resolver.ts @@ -0,0 +1,87 @@ +import type { User } from '@island.is/auth-nest-tools' +import { + CurrentUser, + IdsUserGuard, + Scopes, + ScopesGuard, +} from '@island.is/auth-nest-tools' +import { ApiScope } from '@island.is/auth/scopes' +import { Audit } from '@island.is/nest/audit' + +import { UseGuards } from '@nestjs/common' +import { Args, Mutation, Resolver } from '@nestjs/graphql' +import { GeneratePkPassInput } from '../dto/GeneratePkPass.input' +import { GenericPkPass } from '../dto/GenericPkPass.dto' +import { GenericPkPassQrCode } from '../dto/GenericPkPassQrCode.dto' +import { VerifyLicenseBarcodeInput } from '../dto/VerifyLicenseBarcodeInput' +import { VerifyLicenseBarcodeResult } from '../dto/VerifyLicenseBarcodeResult.dto' +import { LicenseService } from '../licenseService.service' +import { GenericPkPassVerification } from '../dto/GenericPkPassVerification.dto' +import { VerifyPkPassInput } from '../dto/VerifyPkPass.input' + +@UseGuards(IdsUserGuard, ScopesGuard) +@Resolver(() => GenericPkPass) +@Audit({ namespace: '@island.is/api/license-service' }) +export class PkPassResolver { + constructor(private readonly licenseServiceService: LicenseService) {} + + @Mutation(() => GenericPkPass, { + name: 'generatePkPass', + }) + @Audit() + async generatePkPass( + @CurrentUser() user: User, + @Args('input') input: GeneratePkPassInput, + ): Promise { + const pkpassUrl = await this.licenseServiceService.generatePkPassUrl( + user, + input.licenseType, + ) + + return { + pkpassUrl, + } + } + + @Mutation(() => GenericPkPassQrCode, { + name: 'generatePkPassQrCode', + }) + @Audit() + async generatePkPassQrCode( + @CurrentUser() user: User, + @Args('input') input: GeneratePkPassInput, + ): Promise { + const pkpassQRCode = await this.licenseServiceService.generatePkPassQRCode( + user, + input.licenseType, + ) + + return { + pkpassQRCode, + } + } + + @Scopes(ApiScope.internal, ApiScope.licensesVerify) + @Mutation(() => VerifyLicenseBarcodeResult, { + name: 'verifyLicenseBarcode', + }) + @Audit() + async verifyLicenseBarcode( + @Args('input') input: VerifyLicenseBarcodeInput, + ): Promise { + return this.licenseServiceService.verifyLicenseBarcode(input.data) + } + + @Scopes(ApiScope.internal, ApiScope.licensesVerify) + @Mutation(() => GenericPkPassVerification, { + deprecationReason: + 'Should use verifyLicenseBarcode instead of verifyPkPass', + }) + @Audit() + async verifyPkPass( + @Args('input') + input: VerifyPkPassInput, + ): Promise { + return this.licenseServiceService.verifyPkPassDeprecated(input.data) + } +} diff --git a/libs/api/domains/license-service/src/lib/resolvers/provider.resolver.ts b/libs/api/domains/license-service/src/lib/resolvers/provider.resolver.ts new file mode 100644 index 000000000000..f9d28c55d613 --- /dev/null +++ b/libs/api/domains/license-service/src/lib/resolvers/provider.resolver.ts @@ -0,0 +1,54 @@ +import { IdsUserGuard, Scopes, ScopesGuard } from '@island.is/auth-nest-tools' +import { ApiScope } from '@island.is/auth/scopes' +import { Audit } from '@island.is/nest/audit' +import { UseGuards } from '@nestjs/common' +import { Parent, ResolveField, Resolver } from '@nestjs/graphql' +import { GenericLicenseProvider } from '../dto/GenericLicenseProvider.dto' +import { + type OrganizationLogoByReferenceIdDataLoader, + OrganizationLogoByReferenceIdLoader, + type OrganizationTitleByReferenceIdDataLoader, + OrganizationTitleByReferenceIdLoader, +} from '@island.is/cms' +import { Loader } from '@island.is/nest/dataloader' + +@UseGuards(IdsUserGuard, ScopesGuard) +@Scopes(ApiScope.internal, ApiScope.licenses) +@Resolver(() => GenericLicenseProvider) +@Audit({ namespace: '@island.is/api/license-service' }) +export class LicenseProviderResolver { + @ResolveField('providerName', () => String, { + nullable: true, + }) + async resolveProviderName( + @Parent() license: GenericLicenseProvider, + @Loader(OrganizationTitleByReferenceIdLoader) + organizationTitleLoader: OrganizationTitleByReferenceIdDataLoader, + ): Promise { + const { referenceId } = license + if (!referenceId) { + return + } + + const title: string | null = await organizationTitleLoader.load(referenceId) + return title ?? undefined + } + + @ResolveField('providerLogo', () => String, { + nullable: true, + }) + async resolveProviderLogo( + @Parent() license: GenericLicenseProvider, + @Loader(OrganizationLogoByReferenceIdLoader) + organizationLogoLoader: OrganizationLogoByReferenceIdDataLoader, + ): Promise { + const { referenceId } = license + if (!referenceId) { + return + } + + const logo = await organizationLogoLoader.load(referenceId) + + return logo ?? undefined + } +} diff --git a/libs/api/domains/license-service/src/lib/resolvers/userLicense.resolver.ts b/libs/api/domains/license-service/src/lib/resolvers/userLicense.resolver.ts new file mode 100644 index 000000000000..ba5f75e6c076 --- /dev/null +++ b/libs/api/domains/license-service/src/lib/resolvers/userLicense.resolver.ts @@ -0,0 +1,63 @@ +import type { User } from '@island.is/auth-nest-tools' +import { + CurrentUser, + IdsUserGuard, + Scopes, + ScopesGuard, +} from '@island.is/auth-nest-tools' +import { ApiScope, LicenseApiScope } from '@island.is/auth/scopes' +import { Audit } from '@island.is/nest/audit' +import type { Locale } from '@island.is/shared/types' +import { ForbiddenException, UseGuards } from '@nestjs/common' +import { Args, Parent, Query, ResolveField, Resolver } from '@nestjs/graphql' +import { CreateBarcodeResult } from '../dto/CreateBarcodeResult.dto' +import { GenericUserLicense } from '../dto/GenericUserLicense.dto' +import { GetGenericLicenseInput } from '../dto/GetGenericLicense.input' +import { LicenseService } from '../licenseService.service' +import { GenericLicenseError } from '../dto/GenericLicenseError.dto' + +@UseGuards(IdsUserGuard, ScopesGuard) +@Scopes(ApiScope.internal, ApiScope.licenses) +@Resolver(() => GenericUserLicense) +@Audit({ namespace: '@island.is/api/license-service' }) +export class UserLicenseResolver { + constructor(private readonly licenseServiceService: LicenseService) {} + + @Query(() => GenericUserLicense, { + nullable: true, + }) + @Audit() + async genericLicense( + @CurrentUser() user: User, + @Args('locale', { type: () => String, nullable: true }) + locale: Locale = 'is', + @Args('input') input: GetGenericLicenseInput, + ) { + const license = await this.licenseServiceService.getLicense( + user, + locale, + input.licenseType, + input.licenseId, + ) + + if (license instanceof GenericLicenseError) { + return null + } + + return license + } + + @ResolveField('barcode', () => CreateBarcodeResult, { nullable: true }) + async resolveBarcode( + @CurrentUser() user: User, + @Parent() genericUserLicense: GenericUserLicense, + ): Promise { + if (!user.scope.includes(LicenseApiScope.licensesBarcode)) { + throw new ForbiddenException( + 'User does not have permission to create barcode', + ) + } + + return this.licenseServiceService.createBarcode(user, genericUserLicense) + } +} diff --git a/libs/api/domains/license-service/src/lib/utils/capitalize.ts b/libs/api/domains/license-service/src/lib/utils/capitalize.ts new file mode 100644 index 000000000000..114734fe6ebe --- /dev/null +++ b/libs/api/domains/license-service/src/lib/utils/capitalize.ts @@ -0,0 +1,17 @@ +export const capitalizeEveryWord = (s?: string) => { + if (typeof s !== 'string') return '' + + const arr = s.split(' ') + + const capitalized = arr.map( + (item) => item.charAt(0).toUpperCase() + item.slice(1).toLowerCase(), + ) + + const word = capitalized.join(' ') + return word +} + +export const capitalize = (s?: string) => { + if (typeof s !== 'string') return '' + return s.charAt(0).toUpperCase() + s.slice(1) +} diff --git a/libs/api/domains/license-service/src/lib/utils/expiryTag.ts b/libs/api/domains/license-service/src/lib/utils/expiryTag.ts new file mode 100644 index 000000000000..33b7f1e187b5 --- /dev/null +++ b/libs/api/domains/license-service/src/lib/utils/expiryTag.ts @@ -0,0 +1,39 @@ +import { FormatMessage } from '@island.is/cms-translations' +import { GenericUserLicenseMetaTag } from '../dto/GenericUserLicenseMetaTag.dto' +import { m } from '../messages' +import { + GenericUserLicenseDataFieldTagColor, + GenericUserLicenseDataFieldTagType, +} from '../licenceService.type' + +export const expiryTag = ( + formatMessage: FormatMessage, + isExpired?: boolean, + validityText?: string, + expiryText?: string, +): GenericUserLicenseMetaTag => { + const expiredText = expiryText ?? formatMessage(m.expired) + const validText = validityText ?? formatMessage(m.valid) + + if (isExpired === undefined) { + return { + text: formatMessage(m.unknownStatus), + color: 'red', + icon: GenericUserLicenseDataFieldTagType.closeCircle, + iconColor: GenericUserLicenseDataFieldTagColor.red, + iconText: formatMessage(m.unknownStatus), + } + } + + return { + text: isExpired ? expiredText : validText, + color: isExpired ? 'red' : 'blue', + icon: isExpired + ? GenericUserLicenseDataFieldTagType.closeCircle + : GenericUserLicenseDataFieldTagType.checkmarkCircle, + iconColor: isExpired + ? GenericUserLicenseDataFieldTagColor.red + : GenericUserLicenseDataFieldTagColor.green, + iconText: isExpired ? formatMessage(m.expired) : formatMessage(m.valid), + } +} diff --git a/libs/api/domains/license-service/src/lib/utils/formatDate.ts b/libs/api/domains/license-service/src/lib/utils/formatDate.ts new file mode 100644 index 000000000000..9d37c053ca77 --- /dev/null +++ b/libs/api/domains/license-service/src/lib/utils/formatDate.ts @@ -0,0 +1,3 @@ +import format from 'date-fns/format' + +export const formatDate = (date: Date) => format(date, 'dd.MM.yyyy') diff --git a/libs/api/domains/license-service/src/lib/utils/index.ts b/libs/api/domains/license-service/src/lib/utils/index.ts new file mode 100644 index 000000000000..bc3247ffc84f --- /dev/null +++ b/libs/api/domains/license-service/src/lib/utils/index.ts @@ -0,0 +1,3 @@ +export { capitalizeEveryWord, capitalize } from './capitalize' +export { expiryTag } from './expiryTag' +export { formatDate } from './formatDate' diff --git a/libs/api/domains/license-service/src/lib/utils/translations.ts b/libs/api/domains/license-service/src/lib/utils/translations.ts deleted file mode 100644 index e359d4aeec6e..000000000000 --- a/libs/api/domains/license-service/src/lib/utils/translations.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { Locale } from '@island.is/shared/types' -import { LicenseLabelsObject } from '../licenceService.type' - -export const getLabel = ( - labelKey: string, - locale: Locale, - label?: LicenseLabelsObject, -) => { - return label - ? label[labelKey] - : Object.entries(i18n).find((x) => x[0] === labelKey)?.[1][locale] || '' -} - -export const i18n = { - licenseNumber: { - is: 'Númer skírteinis', - en: 'License number', - }, - fullName: { - is: 'Fullt nafn', - en: 'Full name', - }, - publisher: { - is: 'Útgefandi', - en: 'Publisher', - }, - publishedDate: { - is: 'Útgáfudagur', - en: 'Published date', - }, - firstPublishedDate: { - is: 'Fyrsti útgáfudagur', - en: 'First published date', - }, - placeOfIssue: { - is: 'Útgáfustaður', - en: 'Place of issue', - }, - validTo: { - is: 'Gildir til', - en: 'Valid to', - }, - tanks: { - is: 'Tankar', - en: 'Tanks', - }, - otherThanTanks: { - is: 'Annað en í tanki', - en: 'Other than tanks', - }, - classesOfRights: { - is: 'Réttindaflokkar', - en: 'Classes of rights', - }, - expiryDate: { - is: 'Lokadagur', - en: 'Expiry date', - }, - comment: { - is: 'Athugasemd', - en: 'Comment', - }, - renewDrivingLicense: { - is: 'Endurnýja ökuskírteini', - en: 'Renew driving license', - }, - renewFirearmLicense: { - is: 'Endurnýja skotvopnaleyfi', - en: 'Renew firearm license', - }, - downloadCard: { - is: 'Hlaða niður korti', - en: 'Download card', - }, - applyForNewCard: { - is: 'Sækja um nýtt kort', - en: 'Apply for a new card', - }, - collectorLicenseValidTo: { - is: 'Safnaraskírteini gildir til', - en: 'Collector license valid to', - }, - category: { - is: 'Flokkur', - en: 'Category', - }, - firearmProperties: { - is: 'Skotvopn í eigu leyfishafa', - en: 'Firearms owned by license holder', - }, - firearmStatus: { - is: 'Staða skotvopns', - en: 'Firearm status', - }, - type: { - is: 'Tegund', - en: 'Type', - }, - name: { - is: 'Heiti', - en: 'Name', - }, - number: { - is: 'Númer', - en: 'Serial number', - }, - countryNumber: { - is: 'Landsnúmer', - en: 'Country number', - }, - caliber: { - is: 'Hlaupvídd', - en: 'Caliber', - }, - limitation: { - is: 'Takmarkanir', - en: 'Limitations', - }, - drivingLicenseNumber: { - is: 'Ökuskírteinisnúmer', - en: 'Driving license number', - }, - seeRights: { - is: 'Sjá réttindi', - en: 'See rights', - }, - control: { - is: 'Stjórna', - en: 'Control', - }, - teach: { - is: 'Kenna', - en: 'Teach', - }, - basicInfoLicense: { - is: 'Grunnupplýsingar skírteinis', - en: 'Basic license information', - }, - nationalId: { - is: 'Kennitala', - en: 'National Id', - }, - cardNumber: { - is: 'Númer korts', - en: 'Card number', - }, -} diff --git a/libs/api/mocks/src/domains/license-service/factories.ts b/libs/api/mocks/src/domains/license-service/factories.ts index c0ce6818f623..41ff0c4a4ac8 100644 --- a/libs/api/mocks/src/domains/license-service/factories.ts +++ b/libs/api/mocks/src/domains/license-service/factories.ts @@ -36,7 +36,6 @@ const genericLicenseFetch = factory({ const pkPassStatus = ['Available', 'NotAvailable', 'Unknown'] export const genericLicense = factory({ - name: () => title(), pkpass: () => faker.datatype.boolean(), pkpassStatus: faker.random.arrayElement( pkPassStatus, @@ -53,7 +52,6 @@ export const genericLicense = factory({ export const metadata = factory({ expired: () => faker.datatype.boolean(), licenseNumber: () => faker.datatype.number().toString(), - logo: () => faker.image.cats(), title: () => title(), }) diff --git a/libs/clients/license-client/src/lib/clients/passport-client/passportClient.type.ts b/libs/clients/license-client/src/lib/clients/passport-client/passportClient.type.ts new file mode 100644 index 000000000000..6b9cb4b03710 --- /dev/null +++ b/libs/clients/license-client/src/lib/clients/passport-client/passportClient.type.ts @@ -0,0 +1,9 @@ +import { + IdentityDocument, + IdentityDocumentChild, +} from '@island.is/clients/passports' + +export interface Passports { + userPassport?: IdentityDocument | undefined + childPassports?: Array +} diff --git a/libs/clients/license-client/src/lib/clients/passport-client/passportsClient.service.ts b/libs/clients/license-client/src/lib/clients/passport-client/passportsClient.service.ts index 913e6a9b0183..68ba2a29c0a2 100644 --- a/libs/clients/license-client/src/lib/clients/passport-client/passportsClient.service.ts +++ b/libs/clients/license-client/src/lib/clients/passport-client/passportsClient.service.ts @@ -1,31 +1,52 @@ -import type { Logger } from '@island.is/logging' -import { LOGGER_PROVIDER } from '@island.is/logging' import { Inject, Injectable } from '@nestjs/common' import { User } from '@island.is/auth-nest-tools' import { LicenseClient, LicenseType, Result } from '../../licenseClient.type' import { FetchError } from '@island.is/clients/middlewares' -import { PassportsService, Passport } from '@island.is/clients/passports' +import { + IdentityDocument, + IdentityDocumentChild, + PassportsService, +} from '@island.is/clients/passports' +import { LOGGER_PROVIDER, type Logger } from '@island.is/logging' +import { isDefined } from '@island.is/shared/utils' @Injectable() export class PassportsClient implements LicenseClient { constructor( - @Inject(LOGGER_PROVIDER) private logger: Logger, private passportService: PassportsService, + @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} clientSupportsPkPass = false type = LicenseType.Passport - async getLicenses(user: User): Promise>> { + async getLicenses( + user: User, + ): Promise>> { try { - const licenseInfo = await this.passportService.getCurrentPassport(user) - return { ok: true, data: [licenseInfo] } + const { userPassport, childPassports } = + await this.passportService.getCurrentPassport(user) + + let passports: Array = [ + userPassport, + ].filter(isDefined) + + if (childPassports) { + passports = [...passports, ...childPassports] + } + return { + ok: true, + data: passports.filter(isDefined), + } } catch (e) { let error if (e instanceof FetchError) { //404 - no license for user, still ok! if (e.status === 404) { - return { ok: true, data: [] } + return { + ok: true, + data: [], + } } else { error = { code: 13, diff --git a/libs/clients/license-client/src/lib/licenseClient.module.ts b/libs/clients/license-client/src/lib/licenseClient.module.ts index 92115520dced..44de7a7fd454 100644 --- a/libs/clients/license-client/src/lib/licenseClient.module.ts +++ b/libs/clients/license-client/src/lib/licenseClient.module.ts @@ -30,6 +30,7 @@ import { HuntingClientModule, HuntingLicenseClient, } from './clients/hunting-license-client' +import { PassportsClient, PassportsModule } from './clients/passport-client' @Module({ imports: [ @@ -41,6 +42,7 @@ import { HuntingClientModule, PCardModule, EhicModule, + PassportsModule, ], providers: [ LicenseClientService, @@ -61,6 +63,7 @@ import { pCardClient: PCardClient, ehicCardClient: EhicClient, huntingClient: HuntingLicenseClient, + passportClient: PassportsClient, ) => async ( type: LicenseType, @@ -82,6 +85,8 @@ import { return ehicCardClient case LicenseType.HuntingLicense: return huntingClient + case LicenseType.Passport: + return passportClient default: return null } @@ -95,6 +100,7 @@ import { PCardClient, EhicClient, HuntingLicenseClient, + PassportsClient, ], }, ], diff --git a/libs/clients/license-client/src/lib/licenseClient.type.ts b/libs/clients/license-client/src/lib/licenseClient.type.ts index 113d0f132c93..a3509157fc17 100644 --- a/libs/clients/license-client/src/lib/licenseClient.type.ts +++ b/libs/clients/license-client/src/lib/licenseClient.type.ts @@ -5,12 +5,15 @@ import { OrorkuSkirteini } from '@island.is/clients/disability-license' import { DriverLicenseDto } from '@island.is/clients/driving-license' import { BasicCardInfoDTO } from '@island.is/clients/icelandic-health-insurance/rights-portal' import { Staediskortamal } from '@island.is/clients/p-card' -import { Passport } from '@island.is/clients/passports' import { Locale } from '@island.is/shared/types' import { FlattenedAdrDto } from './clients/adr-license-client' import { FirearmLicenseDto } from './clients/firearm-license-client' import { DrivingLicenseVerifyExtraData } from './clients/driving-license-client' import { HuntingLicenseDto } from '@island.is/clients/hunting-license' +import { + IdentityDocument, + IdentityDocumentChild, +} from '@island.is/clients/passports' export type LicenseLabelsObject = { [x: string]: string @@ -37,7 +40,7 @@ export interface LicenseResults { [LicenseType.FirearmLicense]: FirearmLicenseDto [LicenseType.MachineLicense]: VinnuvelaDto [LicenseType.PCard]: Staediskortamal - [LicenseType.Passport]: Passport + [LicenseType.Passport]: IdentityDocument | IdentityDocumentChild } export interface VerifyExtraDataResult { diff --git a/libs/feature-flags/src/lib/features.ts b/libs/feature-flags/src/lib/features.ts index e76d6eef8201..da14b931131c 100644 --- a/libs/feature-flags/src/lib/features.ts +++ b/libs/feature-flags/src/lib/features.ts @@ -58,6 +58,9 @@ export enum Features { //Occupational License Health directorate fetch enabled occupationalLicensesV2 = 'isOccupationalLicensesV2Enabled', + //New License service fetch enabled + licensesV2 = 'isLicensesV2Enabled', + //Possible universities isUniversityOfAkureyriEnabled = 'isUniversityOfAkureyriEnabled', isAgriculturalUniversityOfIcelandEnabled = 'isAgriculturalUniversityOfIcelandEnabled', diff --git a/libs/service-portal/assets/src/screens/VehicleMileage/VehicleMileage.tsx b/libs/service-portal/assets/src/screens/VehicleMileage/VehicleMileage.tsx index 5f5d9f613a01..ea9114a634c0 100644 --- a/libs/service-portal/assets/src/screens/VehicleMileage/VehicleMileage.tsx +++ b/libs/service-portal/assets/src/screens/VehicleMileage/VehicleMileage.tsx @@ -138,20 +138,24 @@ const VehicleMileage = () => { ( - - - {str} - - - ), - })} + introComponent={ + + {formatMessage(messages.vehicleMileageIntro, { + href: (str: React.ReactNode) => ( + + + {str} + + + ), + })} + + } serviceProviderSlug={SAMGONGUSTOFA_SLUG} serviceProviderTooltip={formatMessage(m.vehiclesTooltip)} /> diff --git a/libs/service-portal/core/src/components/ActionCard/ActionCard.tsx b/libs/service-portal/core/src/components/ActionCard/ActionCard.tsx index b39af2cfa388..064cea0afa70 100644 --- a/libs/service-portal/core/src/components/ActionCard/ActionCard.tsx +++ b/libs/service-portal/core/src/components/ActionCard/ActionCard.tsx @@ -27,6 +27,8 @@ type ActionCardProps = { eyebrow?: string loading?: boolean backgroundColor?: 'white' | 'blue' | 'red' | 'blueberry' + borderColor?: 'red100' | 'blue100' | 'blue200' + headingColor?: 'blue400' | 'blue600' | 'currentColor' tag?: { label: string variant?: TagVariant @@ -82,6 +84,8 @@ export const ActionCard: React.FC> = ({ text, subText, secondaryText, + borderColor, + headingColor, eyebrow, loading, backgroundColor = 'white', @@ -124,7 +128,7 @@ export const ActionCard: React.FC> = ({ capitalizeFirstLetter={capitalizeHeading} variant="h3" as="p" - color="blue400" + color={headingColor ?? 'blue400'} > {getTitleAbbreviation(heading)} @@ -315,11 +319,12 @@ export const ActionCard: React.FC> = ({ display="flex" flexDirection="column" borderColor={ - backgroundColor === 'red' - ? 'red200' + borderColor ?? + (backgroundColor === 'red' + ? 'black' : backgroundColor === 'blue' ? 'blue100' - : 'blue200' + : 'blue200') } borderRadius="large" borderWidth="standard" @@ -359,7 +364,8 @@ export const ActionCard: React.FC> = ({ variant="h4" translate={translateLabel} color={ - backgroundColor === 'blue' ? 'blue600' : 'currentColor' + headingColor ?? + (backgroundColor === 'blue' ? 'blue600' : 'currentColor') } > {heading} diff --git a/libs/service-portal/core/src/components/IntroHeader/IntroHeader.tsx b/libs/service-portal/core/src/components/IntroHeader/IntroHeader.tsx index f5dc81d528be..69723e091e6d 100644 --- a/libs/service-portal/core/src/components/IntroHeader/IntroHeader.tsx +++ b/libs/service-portal/core/src/components/IntroHeader/IntroHeader.tsx @@ -1,10 +1,11 @@ -import React, { ReactNode, useEffect, useState } from 'react' +import { ReactNode } from 'react' import { GridColumn, GridRow, Text, LoadingDots, GridColumnProps, + Box, } from '@island.is/island-ui/core' import { IntroHeaderProps } from '@island.is/portals/core' import InstitutionPanel from '../InstitutionPanel/InstitutionPanel' @@ -41,7 +42,6 @@ export const IntroHeader = (props: IntroHeaderProps & Props) => { if (props.loading) { return } - return ( @@ -54,9 +54,7 @@ export const IntroHeader = (props: IntroHeaderProps & Props) => { )} {props.introComponent && ( - - {props.introComponent} - + {props.introComponent} )} {props.children} diff --git a/libs/service-portal/core/src/components/UserInfoLine/UserInfoLine.tsx b/libs/service-portal/core/src/components/UserInfoLine/UserInfoLine.tsx index 9152351c422d..14929bcee17b 100644 --- a/libs/service-portal/core/src/components/UserInfoLine/UserInfoLine.tsx +++ b/libs/service-portal/core/src/components/UserInfoLine/UserInfoLine.tsx @@ -157,6 +157,7 @@ export const UserInfoLine: FC> = ({ > { + switch (color) { + case 'red': + return 'red600' + case 'yellow': + return 'yellow600' + case 'green': + return 'mint600' + default: + return + } +} +export const LicenseDataFields = ({ + fields, + licenseType, +}: { + fields: GenericLicenseDataField[] + licenseType?: string +}) => { + const [page, setPage] = useState(1) + const pageSize = 15 + + const mappedFields = useMemo(() => { + return fields.map((field, i) => { + if (field.hideFromServicePortal) return undefined + return ( + + {field.type === GenericLicenseDataFieldType.Value && ( + <> + ( + + {field.value} + + + {field.tag?.icon && field.tag?.iconColor && ( + + )} + + {field.tag?.text && ( + + {field.tag.iconText} + + )} + + + ) + : undefined + } + paddingY={3} + /> + + + )} + {field.type === GenericLicenseDataFieldType.Category && ( + + )} + {field.type === 'Group' && ( + <> + + {field.label} + + + + + )} + {field.type === GenericLicenseDataFieldType.Table && ( + <> + + {field.label} + + + + + {/* Double mapping needed to get to nested header and values */} + {field.fields?.map((x, xIndex) => { + return x?.fields?.map((y, yIndex) => { + return ( + xIndex === 0 && ( + + {y.label} + + ) + ) + }) + })} + + + + {field.fields + ?.slice((page - 1) * pageSize, page * pageSize) + .map((x, xIndex) => { + return ( + + {x.fields?.map((y, yIndex) => { + return ( + + {y.value} + + ) + })} + + ) + })} + + + {field.fields && field.fields.length > pageSize && ( + + ( + setPage(page)} + component="button" + > + {children} + + )} + /> + + )} + + )} + + ) + }) + }, [fields, licenseType, page]) + + return mappedFields +} diff --git a/libs/service-portal/licenses/src/lib/constants.ts b/libs/service-portal/licenses/src/lib/constants.ts index 67612d686b35..e88348c19f1c 100644 --- a/libs/service-portal/licenses/src/lib/constants.ts +++ b/libs/service-portal/licenses/src/lib/constants.ts @@ -1,2 +1,13 @@ export const passportLogo = 'https://images.ctfassets.net/8k0h54kbe6bj/2ETBroMeCKRQptFKNg83rW/2e1799555b5bf0f98b7ed985ce648b99/logo-square-400.png?w=100&h=100&fit=pad&bg=transparent' + +export type LicensePathType = + | 'adrrettindi' + | 'okurettindi' + | 'ororkuskirteini' + | 'skotvopnaleyfi' + | 'vinnuvelarettindi' + | 'veidikort' + | 'pkort' + | 'ehic' + | 'vegabref' diff --git a/libs/service-portal/licenses/src/lib/messages.ts b/libs/service-portal/licenses/src/lib/messages.ts index 4069d9b5bf55..7bce71786017 100644 --- a/libs/service-portal/licenses/src/lib/messages.ts +++ b/libs/service-portal/licenses/src/lib/messages.ts @@ -332,4 +332,13 @@ export const m = defineMessages({ id: 'sp.license:url-apply-passport', defaultMessage: 'https://island.is/vegabref', }, + errorFetchingLicenses: { + id: 'sp.license:error-fetch-licenses', + defaultMessage: 'Ekki tókst að sækja gögn um öll skírteini', + }, + errorFetchingLicensesDetail: { + id: 'sp.license:error-fetch-licenses-detail', + defaultMessage: + 'Einhverjar tengingar virðast hafa rofnað svo ekki tókst að sækja gögn frá eftirfarandi aðilum: {arg}. Verið er að vinna í að lagfæra tenginguna.', + }, }) diff --git a/libs/service-portal/licenses/src/lib/navigation.ts b/libs/service-portal/licenses/src/lib/navigation.ts index 48e4fb207b99..4a83da63c769 100644 --- a/libs/service-portal/licenses/src/lib/navigation.ts +++ b/libs/service-portal/licenses/src/lib/navigation.ts @@ -13,6 +13,11 @@ export const licenseNavigation: PortalNavigationItem = { name: m.myLicenses, path: LicensePaths.LicensesRoot, }, + { + navHide: true, + name: m.detailInfo, + path: LicensePaths.LicensesDetailV2, + }, { navHide: true, name: m.detailInfo, diff --git a/libs/service-portal/licenses/src/lib/paths.ts b/libs/service-portal/licenses/src/lib/paths.ts index 705eb0f2a33c..76e11c11dabe 100644 --- a/libs/service-portal/licenses/src/lib/paths.ts +++ b/libs/service-portal/licenses/src/lib/paths.ts @@ -13,4 +13,5 @@ export enum LicensePaths { LicensesPassportDetail = '/skirteini/tjodskra/vegabref/:id', LicensesDetail = '/skirteini/:provider/:type/:id', + LicensesDetailV2 = '/skirteini/:type/:id', } diff --git a/libs/service-portal/licenses/src/module.tsx b/libs/service-portal/licenses/src/module.tsx index 51f481b8d005..2eed60a3030d 100644 --- a/libs/service-portal/licenses/src/module.tsx +++ b/libs/service-portal/licenses/src/module.tsx @@ -8,10 +8,13 @@ import { translationLoader } from './screens/Translation.loader' const LicensesOverview = lazy(() => import('./screens/LicensesOverview')) const LicenseDetail = lazy(() => - import('./screens/LicenseDetail/LicenseDetail'), + import('./screens/v1/LicenseDetail/LicenseDetail'), +) +const LicenseDetailV2 = lazy(() => + import('./screens/v2/LicenseDetail/LicenseDetail'), ) const PassportDetail = lazy(() => - import('./screens/PassportDetail/PassportDetail'), + import('./screens/v1/PassportDetail/PassportDetail'), ) export const licensesModule: PortalModule = { @@ -28,6 +31,12 @@ export const licensesModule: PortalModule = { loader: translationLoader({ userInfo, ...rest }), element: , }, + { + name: 'Skírteini', + path: LicensePaths.LicensesDetailV2, + enabled: userInfo.scopes.includes(ApiScope.licenses), + element: , + }, { name: 'Skírteini', path: LicensePaths.LicensesDetail, diff --git a/libs/service-portal/licenses/src/screens/LicensesOverview.tsx b/libs/service-portal/licenses/src/screens/LicensesOverview.tsx new file mode 100644 index 000000000000..54f081c99e5c --- /dev/null +++ b/libs/service-portal/licenses/src/screens/LicensesOverview.tsx @@ -0,0 +1,42 @@ +import { Features } from '@island.is/feature-flags' + +import { FeatureFlagClient } from '@island.is/feature-flags' +import { useFeatureFlagClient } from '@island.is/react/feature-flags' +import { useEffect, useState } from 'react' +import LicensesOverview from './v1/LicensesOverview' +import LicensesOverviewV2 from './v2/LicensesOverview/LicensesOverview' +import { CardLoader } from '@island.is/service-portal/core' + +export type VersionType = 'v1' | 'v2' | 'initial' + +const Overview = () => { + const featureFlagClient: FeatureFlagClient = useFeatureFlagClient() + const [version, setVersion] = useState('initial') + useEffect(() => { + const isFlagEnabled = async () => { + const ffEnabled = await featureFlagClient.getValue( + Features.licensesV2, + false, + ) + + if (ffEnabled) { + setVersion('v2') + } else { + setVersion('v1') + } + } + isFlagEnabled() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + switch (version) { + case 'v1': + return + case 'v2': + return + default: + return + } +} + +export default Overview diff --git a/libs/service-portal/licenses/src/screens/v1/LicenseDetail/LicenseDetail.css.ts b/libs/service-portal/licenses/src/screens/v1/LicenseDetail/LicenseDetail.css.ts new file mode 100644 index 000000000000..5a0bac717e08 --- /dev/null +++ b/libs/service-portal/licenses/src/screens/v1/LicenseDetail/LicenseDetail.css.ts @@ -0,0 +1,82 @@ +import { style, globalStyle } from '@vanilla-extract/css' +import { theme, themeUtils } from '@island.is/island-ui/theme' + +export const line = style({ + width: 1, + height: theme.spacing[3], + background: theme.color.dark200, +}) + +export const animatedContent = style({ + paddingTop: theme.spacing[1], +}) + +const listStyle = { + fontWeight: theme.typography.light, + listStyle: 'auto', + paddingLeft: theme.spacing[3], + paddingTop: theme.spacing[2], + lineHeight: theme.typography.baseLineHeight, +} +const textStyle = { + fontWeight: theme.typography.light, + lineHeight: theme.typography.baseLineHeight, +} + +export const gridWrapper = style({ + display: 'grid', + gridTemplateColumns: '1fr auto', +}) + +export const expandButtonWrapper = style({ + display: 'flex', + alignItems: 'center', +}) + +export const centerColumn = style({ + display: 'flex', + alignItems: 'center', + justifyContent: 'flex-start', + position: 'relative', + + ...themeUtils.responsiveStyle({ + xl: { + selectors: { + '&:first-child::after': { + content: '', + display: 'block', + position: 'relative', + height: '50%', + marginLeft: theme.spacing[3], + width: 1, + background: theme.color.dark200, + }, + }, + }, + lg: { + selectors: { + '&:first-child::after': { + content: 'unset', + }, + }, + }, + }), +}) + +export const gridRow = style({ + display: 'grid', + + ...themeUtils.responsiveStyle({ + xl: { + gridTemplateColumns: 'auto 1fr 1fr 1fr', + }, + lg: { + gridTemplateColumns: '1fr', + }, + }), +}) + +export const text = style({}) + +globalStyle(`${text} ol`, listStyle) +globalStyle(`${text} p`, textStyle) diff --git a/libs/service-portal/licenses/src/screens/LicenseDetail/LicenseDetail.tsx b/libs/service-portal/licenses/src/screens/v1/LicenseDetail/LicenseDetail.tsx similarity index 86% rename from libs/service-portal/licenses/src/screens/LicenseDetail/LicenseDetail.tsx rename to libs/service-portal/licenses/src/screens/v1/LicenseDetail/LicenseDetail.tsx index 5d8f5a4ef1f6..cbcf81b00922 100644 --- a/libs/service-portal/licenses/src/screens/LicenseDetail/LicenseDetail.tsx +++ b/libs/service-portal/licenses/src/screens/v1/LicenseDetail/LicenseDetail.tsx @@ -1,6 +1,5 @@ -import React, { useState } from 'react' +import { useState } from 'react' import { useUserProfile } from '@island.is/service-portal/graphql' - import { useLocale, useNamespaces } from '@island.is/localization' import { Box, @@ -20,10 +19,9 @@ import { ErrorScreen, m as coreMessages, } from '@island.is/service-portal/core' -import ExpandableLine from './ExpandableLine' -import { m } from '../../lib/messages' +import { m } from '../../../lib/messages' import { gql, useQuery } from '@apollo/client' -import { useLocation, useParams } from 'react-router-dom' +import { useParams } from 'react-router-dom' import format from 'date-fns/format' import { dateFormat } from '@island.is/shared/constants' import { @@ -31,14 +29,14 @@ import { GenericUserLicenseMetaLinksType, Query, } from '@island.is/api/schema' -import { PkPass } from '../../components/QRCodeModal/PkPass' +import { PkPass } from '../../../components/QRCodeModal/PkPass' import { getLicenseDetailHeading, getTypeFromPath, -} from '../../utils/dataMapper' -import { isExpired } from '../../utils/dateUtils' -import isValid from 'date-fns/isValid' +} from '../../../utils/dataMapper' import { isDefined } from '@island.is/shared/utils' +import ExpandableLine from '../../../components/ExpandableLine/ExpandableLine' +import React from 'react' const dataFragment = gql` fragment genericLicenseDataFieldFragment on GenericLicenseDataField { @@ -112,18 +110,14 @@ const GenericLicenseQuery = gql` ${dataFragment} ` -const checkLicenseExpired = (date?: string) => { - if (!date) return false - - return isExpired(new Date(), new Date(date)) -} - const DataFields = ({ fields, licenseType, + expired, }: { fields: GenericLicenseDataField[] licenseType?: string + expired?: boolean }) => { const { formatMessage } = useLocale() const [page, setPage] = useState(1) @@ -157,20 +151,14 @@ const DataFields = ({ } renderContent={ field.value && + field.value !== 'Sjá réttindi' && (field.label?.toLowerCase().includes('gildir til') || field.label?.toLowerCase().includes('gildistími') || field.label?.toLowerCase().includes('valid to')) && - isValid(new Date(field.value)) + expired !== undefined ? () => ( - - {field.value && isJSONDate(field.value) - ? format( - +new Date(field.value).getTime(), - dateFormat.is, - ) - : field.value} - + {field.value} - {checkLicenseExpired(field.value ?? undefined) + {expired ? formatMessage(m.isExpired) : formatMessage(m.isValid)} @@ -228,13 +206,9 @@ const DataFields = ({ )} {field.type === 'Category' && ( )} @@ -252,6 +226,7 @@ const DataFields = ({ )} @@ -454,6 +429,7 @@ const LicenseDetail = () => { )} diff --git a/libs/service-portal/licenses/src/screens/LicensesOverview/ChildrenLicenses.tsx b/libs/service-portal/licenses/src/screens/v1/LicensesOverview/ChildrenLicenses.tsx similarity index 94% rename from libs/service-portal/licenses/src/screens/LicensesOverview/ChildrenLicenses.tsx rename to libs/service-portal/licenses/src/screens/v1/LicensesOverview/ChildrenLicenses.tsx index c5641afbfc68..4f6298997ffd 100644 --- a/libs/service-portal/licenses/src/screens/LicensesOverview/ChildrenLicenses.tsx +++ b/libs/service-portal/licenses/src/screens/v1/LicensesOverview/ChildrenLicenses.tsx @@ -8,7 +8,7 @@ import { import { Box } from '@island.is/island-ui/core' -import LicenseCards from '../../components/LicenseCards/LicenseCards' +import LicenseCards from '../../../components/LicenseCards/LicenseCards' import { IdentityDocumentModelChild } from '@island.is/api/schema' interface Props { diff --git a/libs/service-portal/licenses/src/screens/LicensesOverview/UserLicenses.tsx b/libs/service-portal/licenses/src/screens/v1/LicensesOverview/UserLicenses.tsx similarity index 94% rename from libs/service-portal/licenses/src/screens/LicensesOverview/UserLicenses.tsx rename to libs/service-portal/licenses/src/screens/v1/LicensesOverview/UserLicenses.tsx index 42a96477fbcc..ee29a9150e4d 100644 --- a/libs/service-portal/licenses/src/screens/LicensesOverview/UserLicenses.tsx +++ b/libs/service-portal/licenses/src/screens/v1/LicensesOverview/UserLicenses.tsx @@ -6,17 +6,17 @@ import { CardLoader, formatDate, } from '@island.is/service-portal/core' -import { m } from '../../lib/messages' +import { m } from '../../../lib/messages' import { GenericLicenseType } from '@island.is/service-portal/graphql' import { GenericUserLicense, IdentityDocumentModel, } from '@island.is/api/schema' import { Box } from '@island.is/island-ui/core' -import { getPathFromType, getTitleAndLogo } from '../../utils/dataMapper' +import { getPathFromType, getTitleAndLogo } from '../../../utils/dataMapper' -import LicenseCards from '../../components/LicenseCards/LicenseCards' -import { getExpiresIn } from '../../utils/dateUtils' +import LicenseCards from '../../../components/LicenseCards/LicenseCards' +import { getExpiresIn } from '../../../utils/dateUtils' interface Props { isLoading: boolean diff --git a/libs/service-portal/licenses/src/screens/LicensesOverview/index.tsx b/libs/service-portal/licenses/src/screens/v1/LicensesOverview/index.tsx similarity index 99% rename from libs/service-portal/licenses/src/screens/LicensesOverview/index.tsx rename to libs/service-portal/licenses/src/screens/v1/LicensesOverview/index.tsx index a09f9cbf3dd3..c135f33f8969 100644 --- a/libs/service-portal/licenses/src/screens/LicensesOverview/index.tsx +++ b/libs/service-portal/licenses/src/screens/v1/LicensesOverview/index.tsx @@ -7,7 +7,7 @@ import { m as coreMessage, ISLAND_SYSLUMENN_SLUG, } from '@island.is/service-portal/core' -import { m } from '../../lib/messages' +import { m } from '../../../lib/messages' import { gql, useQuery } from '@apollo/client' import { Locale } from '@island.is/shared/types' import { @@ -19,11 +19,12 @@ import { import { Query } from '@island.is/api/schema' import { Box, Tabs } from '@island.is/island-ui/core' import { usePassport } from '@island.is/service-portal/graphql' -import UserLicenses from './UserLicenses' -import ChildrenLicenses from './ChildrenLicenses' + import { useFeatureFlagClient } from '@island.is/react/feature-flags' import { useState, useEffect } from 'react' import { OrganizationSlugType } from '@island.is/shared/constants' +import UserLicenses from './UserLicenses' +import ChildrenLicenses from './ChildrenLicenses' const dataFragment = gql` fragment genericLicenseDataFieldFragment on GenericLicenseDataField { diff --git a/libs/service-portal/licenses/src/screens/PassportDetail/PassportDetail.css.ts b/libs/service-portal/licenses/src/screens/v1/PassportDetail/PassportDetail.css.ts similarity index 100% rename from libs/service-portal/licenses/src/screens/PassportDetail/PassportDetail.css.ts rename to libs/service-portal/licenses/src/screens/v1/PassportDetail/PassportDetail.css.ts diff --git a/libs/service-portal/licenses/src/screens/PassportDetail/PassportDetail.tsx b/libs/service-portal/licenses/src/screens/v1/PassportDetail/PassportDetail.tsx similarity index 97% rename from libs/service-portal/licenses/src/screens/PassportDetail/PassportDetail.tsx rename to libs/service-portal/licenses/src/screens/v1/PassportDetail/PassportDetail.tsx index 23a06d170434..8d4cc2c27eda 100644 --- a/libs/service-portal/licenses/src/screens/PassportDetail/PassportDetail.tsx +++ b/libs/service-portal/licenses/src/screens/v1/PassportDetail/PassportDetail.tsx @@ -1,4 +1,3 @@ -import React from 'react' import { useParams } from 'react-router-dom' import { useLocale, useNamespaces } from '@island.is/localization' import { @@ -15,16 +14,16 @@ import { } from '@island.is/island-ui/core' import { LinkResolver, UserInfoLine } from '@island.is/service-portal/core' import { defineMessage } from 'react-intl' -import { formatDate } from '../../utils/dateUtils' -import { m } from '../../lib/messages' +import { formatDate } from '../../../utils/dateUtils' +import { m } from '../../../lib/messages' import { IdentityDocumentModelChild, useChildrenPassport, usePassport, } from '@island.is/service-portal/graphql' import * as styles from './PassportDetail.css' -import { Gender, GenderType } from '../../types/passport.type' -import { capitalizeEveryWord } from '../../utils/capitalize' +import { Gender, GenderType } from '../../../types/passport.type' +import { capitalizeEveryWord } from '../../../utils/capitalize' const getCurrentPassport = ( id: string | undefined, diff --git a/libs/service-portal/licenses/src/screens/v2/LicenseDetail/LicenseDetail.graphql b/libs/service-portal/licenses/src/screens/v2/LicenseDetail/LicenseDetail.graphql new file mode 100644 index 000000000000..08da45453698 --- /dev/null +++ b/libs/service-portal/licenses/src/screens/v2/LicenseDetail/LicenseDetail.graphql @@ -0,0 +1,108 @@ +query GenericLicense($input: GetGenericLicenseInput!, $locale: String!) { + genericLicense(input: $input, locale: $locale) { + __typename + nationalId + isOwnerChildOfUser + license { + type + pkpass + pkpassVerify + pkpassStatus + } + payload { + data { + type + name + description + label + value + tag { + text + color + icon + iconColor + iconText + } + link { + label + value + name + type + } + hideFromServicePortal + fields { + type + name + label + description + value + tag { + text + color + icon + iconColor + iconText + } + link { + label + value + name + type + } + hideFromServicePortal + fields { + type + name + description + label + value + tag { + text + color + icon + iconColor + iconText + } + link { + label + value + name + type + } + hideFromServicePortal + } + } + } + metadata { + links { + label + value + name + type + } + licenseId + licenseNumber + title + name + description { + text + linkInText + linkIconType + } + displayTag { + text + color + icon + iconColor + iconText + } + expired + alert { + title + type + message + } + } + } + } +} diff --git a/libs/service-portal/licenses/src/screens/v2/LicenseDetail/LicenseDetail.tsx b/libs/service-portal/licenses/src/screens/v2/LicenseDetail/LicenseDetail.tsx new file mode 100644 index 000000000000..327fffc93f62 --- /dev/null +++ b/libs/service-portal/licenses/src/screens/v2/LicenseDetail/LicenseDetail.tsx @@ -0,0 +1,163 @@ +import { + GenericLicenseType, + GenericUserLicenseMetaLinksType, + GenericUserLicensePkPassStatus, +} from '@island.is/api/schema' +import { AlertMessage, Box, Inline, Text } from '@island.is/island-ui/core' +import { useLocale, useNamespaces } from '@island.is/localization' +import { Problem } from '@island.is/react-spa/shared' +import { + CardLoader, + IntroHeader, + LinkButton, + m as coreMessages, +} from '@island.is/service-portal/core' +import { useUserProfile } from '@island.is/service-portal/graphql' +import { isDefined } from '@island.is/shared/utils' +import { useEffect, useMemo } from 'react' +import { useParams } from 'react-router-dom' +import { getTypeFromPath, isLicenseTypePath } from '../../../utils/mapPaths' +import { PkPass } from '../../../components/QRCodeModal/PkPass' +import { useGenericLicenseLazyQuery } from './LicenseDetail.generated' +import { LicenseDataFields } from '../../../components/LicenseDataFields/LicenseDataFields' + +type UseParams = { + type: string | undefined + provider: string + id: string +} + +const LicenseDetail = () => { + useNamespaces('sp.license') + const { formatMessage } = useLocale() + const { data: userProfile } = useUserProfile() + const locale = userProfile?.locale ?? 'is' + const { type, id } = useParams() as UseParams + + const [genericLicenseQuery, { data, loading, error }] = + useGenericLicenseLazyQuery() + + const licenseType: GenericLicenseType | undefined = useMemo(() => { + if (type && isLicenseTypePath(type)) { + return getTypeFromPath(type) + } + return + }, [type]) + + useEffect(() => { + if (licenseType) { + genericLicenseQuery({ + variables: { + locale, + input: { + licenseId: id, + licenseType, + }, + }, + }) + } + }, [genericLicenseQuery, id, locale, licenseType]) + + const genericLicense = data?.genericLicense ?? null + + return ( + <> + { + if (!message.linkInText) { + return ( + + {message.text} +
+
+ ) + } + if (message.linkInText && message.linkIconType) { + return ( + + ) + } + return null + }) + .filter(isDefined)} + marginBottom={4} + > + {genericLicense?.payload?.metadata.alert ? ( + + + + ) : undefined} + {genericLicense?.payload?.metadata?.links || + (genericLicense?.license.pkpassStatus === + GenericUserLicensePkPassStatus.Available && + licenseType) ? ( + + + {genericLicense.license.pkpassStatus === + GenericUserLicensePkPassStatus.Available && + licenseType && } + {genericLicense?.payload?.metadata.links + ?.map((link, index) => { + if (link.label && link.value && link.type) { + return ( + + ) + } + return null + }) + .filter(isDefined)} + + + ) : undefined} +
+ {error && !loading && }{' '} + {!error && !loading && !genericLicense && ( + + )} + {!error && loading && } + + + ) +} + +export default LicenseDetail diff --git a/libs/service-portal/licenses/src/screens/v2/LicensesOverview/LicensesOverview.graphql b/libs/service-portal/licenses/src/screens/v2/LicensesOverview/LicensesOverview.graphql new file mode 100644 index 000000000000..40ff107fa7a3 --- /dev/null +++ b/libs/service-portal/licenses/src/screens/v2/LicensesOverview/LicensesOverview.graphql @@ -0,0 +1,64 @@ +query GenericLicenseCollection( + $input: GetGenericLicensesInput! + $locale: String! +) { + genericLicenseCollection(input: $input, locale: $locale) { + licenses { + __typename + nationalId + isOwnerChildOfUser + fetch { + status + updated + } + license { + type + provider { + id + referenceId + providerName + providerLogo + } + pkpass + pkpassVerify + status + pkpassStatus + } + payload { + data { + type + } + metadata { + licenseId + licenseNumber + subtitle + displayTag { + text + color + } + ctaLink { + value + label + } + title + name + } + } + } + errors { + __typename + type + provider { + id + providerName + } + fetch { + status + updated + } + code + message + extraData + } + } +} diff --git a/libs/service-portal/licenses/src/screens/v2/LicensesOverview/LicensesOverview.tsx b/libs/service-portal/licenses/src/screens/v2/LicensesOverview/LicensesOverview.tsx new file mode 100644 index 000000000000..5592af50203c --- /dev/null +++ b/libs/service-portal/licenses/src/screens/v2/LicensesOverview/LicensesOverview.tsx @@ -0,0 +1,154 @@ +import { + GenericLicenseType, + GenericUserLicense, + GenericLicenseError, +} from '@island.is/api/schema' +import { useLocale, useNamespaces } from '@island.is/localization' +import { useUserProfile } from '@island.is/service-portal/graphql' +import { Locale } from '@island.is/shared/types' +import { useGenericLicenseCollectionQuery } from './LicensesOverview.generated' +import { Box, Stack, Tabs, TagVariant } from '@island.is/island-ui/core' +import { + ActionCard, + CardLoader, + IntroHeader, + m as coreMessages, +} from '@island.is/service-portal/core' +import { m } from '../../../lib/messages' +import { Problem } from '@island.is/react-spa/shared' +import { getPathFromType } from '../../../utils/mapPaths' + +export const LicensesOverviewV2 = () => { + useNamespaces('sp.license') + const { formatMessage } = useLocale() + const { data: userProfile } = useUserProfile() + const locale = (userProfile?.locale as Locale) ?? 'is' + + const includedTypes = [ + GenericLicenseType.AdrLicense, + GenericLicenseType.DisabilityLicense, + GenericLicenseType.DriversLicense, + GenericLicenseType.Ehic, + GenericLicenseType.FirearmLicense, + GenericLicenseType.HuntingLicense, + GenericLicenseType.MachineLicense, + GenericLicenseType.PCard, + GenericLicenseType.Passport, + ] + + const { data, loading, error } = useGenericLicenseCollectionQuery({ + variables: { + locale, + input: { + includedTypes, + }, + }, + }) + + const generateLicense = (userLicense: GenericUserLicense, index: number) => { + const isPayloadEmpty = (userLicense.payload?.data.length ?? 0) <= 0 ?? true + return ( + + ) + } + + const generateLicenseStack = (data: Array) => ( + + + {data.map((license, index) => generateLicense(license, index))} + + + ) + + const errors: Array = + data?.genericLicenseCollection?.errors ?? [] + const licenses: Array = + data?.genericLicenseCollection?.licenses ?? [] + + return ( + <> + + {error && !loading && }{' '} + {!error && !loading && !errors?.length && !licenses?.length && ( + + )} + {!error && loading && ( + + + + )} + {!error && + !loading && + licenses?.some((license) => license.isOwnerChildOfUser) ? ( + + !license.isOwnerChildOfUser), + ), + }, + { + label: formatMessage(m.licenseTabSecondary), + content: generateLicenseStack( + licenses.filter((license) => license.isOwnerChildOfUser), + ), + }, + ]} + /> + + ) : ( + + {licenses + .filter((license) => !license.isOwnerChildOfUser) + .map((license, index) => generateLicense(license, index))} + + )} + + ) +} + +export default LicensesOverviewV2 diff --git a/libs/service-portal/licenses/src/utils/capitalize.ts b/libs/service-portal/licenses/src/utils/capitalize.ts index 2bc170796769..65a77a3258dd 100644 --- a/libs/service-portal/licenses/src/utils/capitalize.ts +++ b/libs/service-portal/licenses/src/utils/capitalize.ts @@ -1,12 +1,4 @@ -export const capitalizeEveryWord = (s: string) => { - if (typeof s !== 'string') return '' +import capitalize from 'lodash/capitalize' - const arr = s.split(' ') - - const capitalized = arr.map( - (item) => item.charAt(0).toUpperCase() + item.slice(1).toLowerCase(), - ) - - const word = capitalized.join(' ') - return word -} +export const capitalizeEveryWord = (str: string): string => + str.split(' ').map(capitalize).join(' ') diff --git a/libs/service-portal/licenses/src/utils/mapPaths.ts b/libs/service-portal/licenses/src/utils/mapPaths.ts new file mode 100644 index 000000000000..2b6e4d361659 --- /dev/null +++ b/libs/service-portal/licenses/src/utils/mapPaths.ts @@ -0,0 +1,62 @@ +import { GenericLicenseType } from '@island.is/api/schema' +import { LicensePathType } from '../lib/constants' + +export const isLicenseTypePath = (str: string): str is LicensePathType => { + return ( + str === 'adrrettindi' || + str === 'okurettindi' || + str === 'ororkuskirteini' || + str === 'skotvopnaleyfi' || + str === 'vinnuvelarettindi' || + str === 'veidikort' || + str === 'pkort' || + str === 'ehic' || + str === 'vegabref' + ) +} + +export const getTypeFromPath = (path: LicensePathType): GenericLicenseType => { + switch (path) { + case 'adrrettindi': + return GenericLicenseType.AdrLicense + case 'okurettindi': + return GenericLicenseType.DriversLicense + case 'ororkuskirteini': + return GenericLicenseType.DisabilityLicense + case 'skotvopnaleyfi': + return GenericLicenseType.FirearmLicense + case 'vinnuvelarettindi': + return GenericLicenseType.MachineLicense + case 'veidikort': + return GenericLicenseType.HuntingLicense + case 'pkort': + return GenericLicenseType.PCard + case 'ehic': + return GenericLicenseType.Ehic + case 'vegabref': + return GenericLicenseType.Passport + } +} + +export const getPathFromType = (type: GenericLicenseType): LicensePathType => { + switch (type) { + case GenericLicenseType.AdrLicense: + return 'adrrettindi' + case GenericLicenseType.DriversLicense: + return 'okurettindi' + case GenericLicenseType.DisabilityLicense: + return 'ororkuskirteini' + case GenericLicenseType.FirearmLicense: + return 'skotvopnaleyfi' + case GenericLicenseType.MachineLicense: + return 'vinnuvelarettindi' + case GenericLicenseType.HuntingLicense: + return 'veidikort' + case GenericLicenseType.PCard: + return 'pkort' + case GenericLicenseType.Ehic: + return 'ehic' + case GenericLicenseType.Passport: + return 'vegabref' + } +} diff --git a/libs/service-portal/occupational-licenses/src/screens/v2/OccupationalLicensesDetail/OccupationalLicensesDetail.tsx b/libs/service-portal/occupational-licenses/src/screens/v2/OccupationalLicensesDetail/OccupationalLicensesDetail.tsx index 3ddd41937da8..ef1b2387dee0 100644 --- a/libs/service-portal/occupational-licenses/src/screens/v2/OccupationalLicensesDetail/OccupationalLicensesDetail.tsx +++ b/libs/service-portal/occupational-licenses/src/screens/v2/OccupationalLicensesDetail/OccupationalLicensesDetail.tsx @@ -72,7 +72,6 @@ const OccupationalLicenseDetail = () => { if (!a) { return null } - if (a.type === OccupationalLicensesV2LinkType.FILE) { return (