From 69c02b2336298401bc6593e2860fa03113be63ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:49:30 +0000 Subject: [PATCH 01/57] feat(web): WHODAS Calculator - Add text to top of component (#17136) * Add text to top of whodas calculator * Add key to prevent focus on click --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../connected/WHODAS/Calculator.strings.ts | 11 ++ .../connected/WHODAS/Calculator.tsx | 104 ++++++++++-------- 2 files changed, 70 insertions(+), 45 deletions(-) diff --git a/apps/web/components/connected/WHODAS/Calculator.strings.ts b/apps/web/components/connected/WHODAS/Calculator.strings.ts index d1b82b8593f5..5794e1786e71 100644 --- a/apps/web/components/connected/WHODAS/Calculator.strings.ts +++ b/apps/web/components/connected/WHODAS/Calculator.strings.ts @@ -2,6 +2,11 @@ import { defineMessages } from 'react-intl' export const m = { form: defineMessages({ + topDescription: { + id: 'web.whodas.calculator:form.topDescription#markdown', + defaultMessage: 'Merktu við einn valkost fyrir hverja spurningu.', + description: 'Texti fyrir ofan spurningalista', + }, previousStep: { id: 'web.whodas.calculator:form.previousStep', defaultMessage: 'Fyrra skref', @@ -51,6 +56,12 @@ export const m = { }, }), results: defineMessages({ + topDescription: { + id: 'web.whodas.calculator:results.topDescription#markdown', + defaultMessage: + 'Takk fyrir að svara spurningalistanum, Mat á færni þinni. Mat þitt á færni er stuðningur við að meta þörf þína fyrir heimaþjónustu. Ef þú hefur ekki svarað öllum spurningum getur það haft áhrif á niðurstöðuna.', + description: 'Texti fyrir ofan niðurstöðuskjá', + }, mainHeading: { id: 'web.whodas.calculator:form.mainHeading', defaultMessage: 'Niðurstaða mats á færni', diff --git a/apps/web/components/connected/WHODAS/Calculator.tsx b/apps/web/components/connected/WHODAS/Calculator.tsx index 9ee19cc6bf89..99ff866974b7 100644 --- a/apps/web/components/connected/WHODAS/Calculator.tsx +++ b/apps/web/components/connected/WHODAS/Calculator.tsx @@ -257,62 +257,76 @@ export const WHODASCalculator = ({ slice }: WHODASCalculatorProps) => { totalScore += score } return ( - + + + {formatMessage(m.results.topDescription)} + + + ) } return ( - - - - - - {formatMessage(m.form.progress, { - stepIndex: stepIndex + 1, - stepCount: steps.length, - })} - - + + + {formatMessage(m.form.topDescription)} + + + + + + + {formatMessage(m.form.progress, { + stepIndex: stepIndex + 1, + stepCount: steps.length, + })} + + + + - - - - - {stepIndex > 0 && ( + + + {stepIndex > 0 && ( + { + setStepIndex((s) => s - 1) + }} + > + {formatMessage(m.form.previousStep)} + + )} { - setStepIndex((s) => s - 1) + setStepIndex((s) => s + 1) }} > - {formatMessage(m.form.previousStep)} + {formatMessage( + stepIndex >= steps.length - 1 + ? m.form.seeResults + : m.form.nextStep, + )} - )} - { - setStepIndex((s) => s + 1) - }} - > - {formatMessage( - stepIndex >= steps.length - 1 ? m.form.seeResults : m.form.nextStep, - )} - - + + ) } From 55aacf7935422425da140c2e254d3d25fdeb2660 Mon Sep 17 00:00:00 2001 From: Ylfa <55542991+ylfahfa@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:39:54 +0000 Subject: [PATCH 02/57] feat(old-age-pension): Show conclusion page based on income plan status (#17087) * start changes to conclusion page * add component for dynamic conclusion page * format * amendments after review plus small text change * remove unused import * chore: nx format:write update dirty files --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../income-plan/src/fields/Review/index.tsx | 4 +- .../income-plan/src/lib/messages.ts | 5 + .../src/dataProviders/index.ts | 6 + .../src/fields/Conclusion/index.tsx | 104 ++++++++++++++++++ .../old-age-pension/src/fields/index.ts | 1 + .../src/forms/OldAgePensionForm.ts | 52 ++++----- .../src/forms/Prerequisites.ts | 5 + .../src/lib/OldAgePensionTemplate.ts | 2 + .../old-age-pension/src/lib/messages.ts | 12 ++ .../src/lib/oldAgePensionUtils.ts | 6 + 10 files changed, 171 insertions(+), 26 deletions(-) create mode 100644 libs/application/templates/social-insurance-administration/old-age-pension/src/fields/Conclusion/index.tsx diff --git a/libs/application/templates/social-insurance-administration/income-plan/src/fields/Review/index.tsx b/libs/application/templates/social-insurance-administration/income-plan/src/fields/Review/index.tsx index 09323c06371b..9a420ccf1e56 100644 --- a/libs/application/templates/social-insurance-administration/income-plan/src/fields/Review/index.tsx +++ b/libs/application/templates/social-insurance-administration/income-plan/src/fields/Review/index.tsx @@ -119,7 +119,9 @@ export const Review: FC = ({ - {formatMessage(inReviewFormMessages.description)} + {state === `${States.TRYGGINGASTOFNUN_SUBMITTED}` + ? formatMessage(inReviewFormMessages.description) + : formatMessage(inReviewFormMessages.reviewDescription)} diff --git a/libs/application/templates/social-insurance-administration/income-plan/src/lib/messages.ts b/libs/application/templates/social-insurance-administration/income-plan/src/lib/messages.ts index f77dd9c57de4..a989acf7c4ad 100644 --- a/libs/application/templates/social-insurance-administration/income-plan/src/lib/messages.ts +++ b/libs/application/templates/social-insurance-administration/income-plan/src/lib/messages.ts @@ -315,6 +315,11 @@ export const inReviewFormMessages = defineMessages({ defaultMessage: 'Tekjuáætlun þín hefur ekki verið tekin til vinnslu.', description: 'Your income plan has not been processed.', }, + reviewDescription: { + id: 'ip.application:inReview.review.description', + defaultMessage: 'Tekjuáætlun þín hefur verið tekin til vinnslu.', + description: 'Your income plan has been processed.', + }, }) export const statesMessages = defineMessages({ diff --git a/libs/application/templates/social-insurance-administration/old-age-pension/src/dataProviders/index.ts b/libs/application/templates/social-insurance-administration/old-age-pension/src/dataProviders/index.ts index e61226395830..8fabc0f9086b 100644 --- a/libs/application/templates/social-insurance-administration/old-age-pension/src/dataProviders/index.ts +++ b/libs/application/templates/social-insurance-administration/old-age-pension/src/dataProviders/index.ts @@ -34,3 +34,9 @@ export const SocialInsuranceAdministrationCurrenciesApi = defineTemplateApi({ externalDataId: 'socialInsuranceAdministrationCurrencies', namespace: 'SocialInsuranceAdministration', }) + +export const SocialInsuranceAdministrationLatestIncomePlan = defineTemplateApi({ + action: 'getLatestIncomePlan', + externalDataId: 'socialInsuranceAdministrationLatestIncomePlan', + namespace: 'SocialInsuranceAdministration', +}) diff --git a/libs/application/templates/social-insurance-administration/old-age-pension/src/fields/Conclusion/index.tsx b/libs/application/templates/social-insurance-administration/old-age-pension/src/fields/Conclusion/index.tsx new file mode 100644 index 000000000000..f9a2da047eab --- /dev/null +++ b/libs/application/templates/social-insurance-administration/old-age-pension/src/fields/Conclusion/index.tsx @@ -0,0 +1,104 @@ +import { + FieldBaseProps, + FieldComponents, + FieldTypes, +} from '@island.is/application/types' +import { Markdown } from '@island.is/shared/components' +import { + Box, + AlertMessage, + AccordionCard, + BulletList, +} from '@island.is/island-ui/core' +import { useLocale } from '@island.is/localization' +import { FC } from 'react' +import { MessageWithLinkButtonFormField } from '@island.is/application/ui-fields' +import { socialInsuranceAdministrationMessage } from '@island.is/application/templates/social-insurance-administration-core/lib/messages' +import { getApplicationExternalData } from '../../lib/oldAgePensionUtils' +import { oldAgePensionFormMessage } from '../../lib/messages' +import { coreMessages } from '@island.is/application/core' + +export const Conclusion: FC> = ( + props, +) => { + const { application } = props + const { formatMessage } = useLocale() + + const { hasIncomePlanStatus } = getApplicationExternalData( + application.externalData, + ) + + return ( + + + + + + + + + {formatMessage( + oldAgePensionFormMessage.conclusionScreen.nextStepsText, + )} + + + + + {formatMessage( + oldAgePensionFormMessage.conclusionScreen.bulletList, + )} + + + + + + + + + ) +} diff --git a/libs/application/templates/social-insurance-administration/old-age-pension/src/fields/index.ts b/libs/application/templates/social-insurance-administration/old-age-pension/src/fields/index.ts index b61a866f7ec2..49440420ab7b 100644 --- a/libs/application/templates/social-insurance-administration/old-age-pension/src/fields/index.ts +++ b/libs/application/templates/social-insurance-administration/old-age-pension/src/fields/index.ts @@ -2,3 +2,4 @@ export { ResidenceHistory } from './ResidenceHistory' export { Review } from './Review' export { default as EmployersOverview } from './EmployersOverview' export { default as EmployersRatioMonthly } from './EmployersRatioMonthly' +export { Conclusion } from './Conclusion' diff --git a/libs/application/templates/social-insurance-administration/old-age-pension/src/forms/OldAgePensionForm.ts b/libs/application/templates/social-insurance-administration/old-age-pension/src/forms/OldAgePensionForm.ts index 2d086a88fa4f..cf01c8a2b190 100644 --- a/libs/application/templates/social-insurance-administration/old-age-pension/src/forms/OldAgePensionForm.ts +++ b/libs/application/templates/social-insurance-administration/old-age-pension/src/forms/OldAgePensionForm.ts @@ -39,10 +39,7 @@ import { NO, YES, } from '@island.is/application/types' -import { - applicantInformationMultiField, - buildFormConclusionSection, -} from '@island.is/application/ui-forms' +import { applicantInformationMultiField } from '@island.is/application/ui-forms' import isEmpty from 'lodash/isEmpty' import { ApplicationType, Employment, RatioType } from '../lib/constants' import { oldAgePensionFormMessage } from '../lib/messages' @@ -787,27 +784,32 @@ export const OldAgePensionForm: Form = buildForm({ }), ], }), - buildFormConclusionSection({ - multiFieldTitle: - socialInsuranceAdministrationMessage.conclusionScreen - .receivedAwaitingIncomePlanTitle, - alertTitle: - socialInsuranceAdministrationMessage.conclusionScreen - .receivedAwaitingIncomePlanTitle, - alertMessage: - socialInsuranceAdministrationMessage.conclusionScreen - .incomePlanAlertMessage, - alertType: 'warning', - expandableDescription: - oldAgePensionFormMessage.conclusionScreen.bulletList, - expandableIntro: oldAgePensionFormMessage.conclusionScreen.nextStepsText, - bottomButtonLink: 'https://minarsidur.tr.is/forsendur/tekjuaetlun', - bottomButtonLabel: - socialInsuranceAdministrationMessage.conclusionScreen - .incomePlanCardLabel, - bottomButtonMessage: - socialInsuranceAdministrationMessage.conclusionScreen - .incomePlanCardText, + buildSection({ + id: 'conclusionSection', + title: socialInsuranceAdministrationMessage.conclusionScreen.section, + children: [ + buildMultiField({ + id: 'conclusion.multifield', + title: (application: Application) => { + const { hasIncomePlanStatus } = getApplicationExternalData( + application.externalData, + ) + return hasIncomePlanStatus + ? socialInsuranceAdministrationMessage.conclusionScreen + .receivedTitle + : socialInsuranceAdministrationMessage.conclusionScreen + .receivedAwaitingIncomePlanTitle + }, + children: [ + buildCustomField({ + component: 'Conclusion', + id: 'conclusion', + title: '', + description: '', + }), + ], + }), + ], }), ], }) diff --git a/libs/application/templates/social-insurance-administration/old-age-pension/src/forms/Prerequisites.ts b/libs/application/templates/social-insurance-administration/old-age-pension/src/forms/Prerequisites.ts index e614d43380c3..66bb30ca3601 100644 --- a/libs/application/templates/social-insurance-administration/old-age-pension/src/forms/Prerequisites.ts +++ b/libs/application/templates/social-insurance-administration/old-age-pension/src/forms/Prerequisites.ts @@ -35,6 +35,7 @@ import { SocialInsuranceAdministrationIsApplicantEligibleApi, SocialInsuranceAdministrationApplicantApi, SocialInsuranceAdministrationCurrenciesApi, + SocialInsuranceAdministrationLatestIncomePlan, } from '../dataProviders' export const PrerequisitesForm: Form = buildForm({ @@ -163,6 +164,10 @@ export const PrerequisitesForm: Form = buildForm({ provider: SocialInsuranceAdministrationCurrenciesApi, title: '', }), + buildDataProviderItem({ + provider: SocialInsuranceAdministrationLatestIncomePlan, + title: '', + }), ], }), ], diff --git a/libs/application/templates/social-insurance-administration/old-age-pension/src/lib/OldAgePensionTemplate.ts b/libs/application/templates/social-insurance-administration/old-age-pension/src/lib/OldAgePensionTemplate.ts index eb2343ca151d..493e6f50f44b 100644 --- a/libs/application/templates/social-insurance-administration/old-age-pension/src/lib/OldAgePensionTemplate.ts +++ b/libs/application/templates/social-insurance-administration/old-age-pension/src/lib/OldAgePensionTemplate.ts @@ -38,6 +38,7 @@ import { SocialInsuranceAdministrationIsApplicantEligibleApi, SocialInsuranceAdministrationApplicantApi, SocialInsuranceAdministrationCurrenciesApi, + SocialInsuranceAdministrationLatestIncomePlan, } from '../dataProviders' import { determineNameFromApplicationAnswers, @@ -97,6 +98,7 @@ const OldAgePensionTemplate: ApplicationTemplate< SocialInsuranceAdministrationIsApplicantEligibleApi, SocialInsuranceAdministrationApplicantApi, SocialInsuranceAdministrationCurrenciesApi, + SocialInsuranceAdministrationLatestIncomePlan, ], delete: true, }, diff --git a/libs/application/templates/social-insurance-administration/old-age-pension/src/lib/messages.ts b/libs/application/templates/social-insurance-administration/old-age-pension/src/lib/messages.ts index 47a792a7f72c..4ecdcbc26838 100644 --- a/libs/application/templates/social-insurance-administration/old-age-pension/src/lib/messages.ts +++ b/libs/application/templates/social-insurance-administration/old-age-pension/src/lib/messages.ts @@ -261,6 +261,18 @@ export const oldAgePensionFormMessage: MessageDir = { }), conclusionScreen: defineMessages({ + title: { + id: 'oap.application:conclusionScreen.expandableDescriptionField.title', + defaultMessage: 'Hvað gerist næst?', + description: 'What happens next', + }, + alertMessage: { + id: 'oap.application:conclusionScreen.alertMessage', + defaultMessage: + 'Umsókn um ellilífeyri hefur verið send til Tryggingastofnunar', + description: + 'An application for old age pension has been sent to the Social Insurance Administration', + }, bulletList: { id: `oap.application:conclusionScreen.bulletList#markdown`, defaultMessage: `* Þú verður að skila inn tekjuáætlun, ef ekki búið nú þegar.\n* Tryggingastofnun fer yfir umsóknina og staðfestir að allar upplýsingar eru réttar.\n* Ef þörf er á er kallað eftir frekari upplýsingum/gögnum.\n* Þegar öll nauðsynleg gögn hafa borist, fer Tryggingastofnun yfir umsókn og er afstaða tekin til elllífeyris. Vinnslutími umsókna um ellilífeyri er fjórar til sex vikur.\n* **Þú gætir átt rétt á:**\n\t* Heimilisuppbót\n\t* Barnalífeyri\n\t* Uppbót á lífeyri\n\t* Ellilífeyri vegna EES.`, diff --git a/libs/application/templates/social-insurance-administration/old-age-pension/src/lib/oldAgePensionUtils.ts b/libs/application/templates/social-insurance-administration/old-age-pension/src/lib/oldAgePensionUtils.ts index 7ddec140d8ad..23dc874ee38f 100644 --- a/libs/application/templates/social-insurance-administration/old-age-pension/src/lib/oldAgePensionUtils.ts +++ b/libs/application/templates/social-insurance-administration/old-age-pension/src/lib/oldAgePensionUtils.ts @@ -254,6 +254,11 @@ export const getApplicationExternalData = ( 'socialInsuranceAdministrationCurrencies.data', ) as Array + const hasIncomePlanStatus = getValueViaPath( + externalData, + 'socialInsuranceAdministrationLatestIncomePlan.data.status', + ) as string + return { residenceHistory, applicantName, @@ -268,6 +273,7 @@ export const getApplicationExternalData = ( currencies, userProfileEmail, userProfilePhoneNumber, + hasIncomePlanStatus, } } From f3a5069610c8ebfd9b751c3c59014fa1605a0ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Thu, 5 Dec 2024 12:53:23 +0000 Subject: [PATCH 03/57] feat(web): WHODAS calculator - Change how we calculate score (#17139) * Change how we calculate score * Add text to bottom of form * Add fallback for calculating total score * Simplify conditionals * Make sure printing does not make results go on two pages --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../connected/WHODAS/Calculator.css.ts | 14 +- .../connected/WHODAS/Calculator.strings.ts | 22 +-- .../connected/WHODAS/Calculator.tsx | 165 +++++++++++------- 3 files changed, 126 insertions(+), 75 deletions(-) diff --git a/apps/web/components/connected/WHODAS/Calculator.css.ts b/apps/web/components/connected/WHODAS/Calculator.css.ts index c98463701223..81dca8be11dc 100644 --- a/apps/web/components/connected/WHODAS/Calculator.css.ts +++ b/apps/web/components/connected/WHODAS/Calculator.css.ts @@ -2,8 +2,20 @@ import { style } from '@vanilla-extract/css' import { theme } from '@island.is/island-ui/theme' +const leftWidth = 180 + export const breakdownRowContainer = style({ display: 'grid', - gridTemplateColumns: '180px 1fr', + gridTemplateColumns: `${leftWidth}px 1fr`, gap: theme.spacing[5], }) + +export const totalScoreRowContainer = style({ + display: 'grid', + gridTemplateColumns: `${leftWidth + 24}px 1fr`, + gap: theme.spacing[5], +}) + +export const stayOnSinglePageWhenPrinting = style({ + pageBreakInside: 'avoid', +}) diff --git a/apps/web/components/connected/WHODAS/Calculator.strings.ts b/apps/web/components/connected/WHODAS/Calculator.strings.ts index 5794e1786e71..8d81c1bb4750 100644 --- a/apps/web/components/connected/WHODAS/Calculator.strings.ts +++ b/apps/web/components/connected/WHODAS/Calculator.strings.ts @@ -62,6 +62,18 @@ export const m = { 'Takk fyrir að svara spurningalistanum, Mat á færni þinni. Mat þitt á færni er stuðningur við að meta þörf þína fyrir heimaþjónustu. Ef þú hefur ekki svarað öllum spurningum getur það haft áhrif á niðurstöðuna.', description: 'Texti fyrir ofan niðurstöðuskjá', }, + totalScore: { + id: 'web.whodas.calculator:results.totalScore', + defaultMessage: 'Stig samtals', + description: 'Stig samtals', + }, + resultDisclaimer: { + id: 'web.whodas.calculator:results.resultDisclaimer', + defaultMessage: + 'Gott er að prenta út eða senda sér svarið. Það getur gangast við umsókn um heimaþjónustu.', + description: + 'Gott er að prenta út eða senda sér svarið. Það getur gangast við umsókn um heimaþjónustu.', + }, mainHeading: { id: 'web.whodas.calculator:form.mainHeading', defaultMessage: 'Niðurstaða mats á færni', @@ -77,16 +89,6 @@ export const m = { defaultMessage: 'Heildarstig', description: 'Heildarstig', }, - firstBracketScoreText: { - id: 'web.whodas.calculator:form.firstBracketScoreText', - defaultMessage: '0 til 16,9 stig', - description: 'Lítil skerðing á færni - "Score" texti', - }, - secondBracketScoreText: { - id: 'web.whodas.calculator:form.secondBracketScoreText', - defaultMessage: '17 til 100 stig', - description: 'Talsverð skerðing á færni - "Score" texti', - }, firstBracketInterpretationText: { id: 'web.whodas.calculator:form.firstBracketInterpretationText', defaultMessage: 'Lítil skerðing á færni', diff --git a/apps/web/components/connected/WHODAS/Calculator.tsx b/apps/web/components/connected/WHODAS/Calculator.tsx index 99ff866974b7..109eb573ccfa 100644 --- a/apps/web/components/connected/WHODAS/Calculator.tsx +++ b/apps/web/components/connected/WHODAS/Calculator.tsx @@ -6,6 +6,7 @@ import { useState, } from 'react' import { useIntl } from 'react-intl' +import round from 'lodash/round' import { Box, @@ -25,6 +26,10 @@ import { MarkdownText } from '../../Organization' import { m } from './Calculator.strings' import * as styles from './Calculator.css' +const formatScore = (score: number) => { + return String(round(score, 1)).replace('.', ',') +} + interface Question { question: string answerOptions: { @@ -41,6 +46,7 @@ interface Step { interface CheckboxState { steps: { title: string + maxScorePossible: number questions: { selectedAnswerIndex: number answerScore: number @@ -119,74 +125,73 @@ interface WHODASResultsProps { steps: (Pick & { scoreForStep: number })[] } bracket: 1 | 2 + totalScore: number } -const WHODASResults = ({ results, bracket }: WHODASResultsProps) => { +const WHODASResults = ({ + results, + bracket, + totalScore, +}: WHODASResultsProps) => { const { format } = useDateUtils() const date = format(new Date(), 'do MMMM yyyy') const { formatMessage } = useIntl() return ( - - - - {formatMessage(m.results.mainHeading)} - - { - window.print() - }} - > - {formatMessage(m.results.print)} - - - {date} - - - {formatMessage(m.results.scoreHeading)} - - + + + + + {formatMessage(m.results.mainHeading)} + + { + window.print() + }} + > + {formatMessage(m.results.print)} + + + {date} + + + {formatMessage(m.results.scoreHeading)} + + + {formatScore(totalScore)} + + + + + {formatMessage(m.results.interpretationHeading)} + {formatMessage( bracket === 1 - ? m.results.firstBracketScoreText - : m.results.secondBracketScoreText, + ? m.results.firstBracketInterpretationText + : m.results.secondBracketInterpretationText, )} - - - - - {formatMessage(m.results.interpretationHeading)} - - - {formatMessage( - bracket === 1 - ? m.results.firstBracketInterpretationText - : m.results.secondBracketInterpretationText, - )} - - - - - - {formatMessage(m.results.adviceHeading)} - - - {formatMessage( - bracket === 1 - ? m.results.firstBracketAdviceText - : m.results.secondBracketAdviceText, - )} - - + - {bracket !== 1 && ( + + {formatMessage(m.results.adviceHeading)} + + + {formatMessage( + bracket === 1 + ? m.results.firstBracketAdviceText + : m.results.secondBracketAdviceText, + )} + + + + {formatMessage(m.results.breakdownHeading)} @@ -195,13 +200,27 @@ const WHODASResults = ({ results, bracket }: WHODASResultsProps) => { {step.title} - {step.scoreForStep} + {formatScore(step.scoreForStep)} ))} + + + {formatMessage(m.results.totalScore)} + + {formatScore(totalScore)} + - )} + + + {formatMessage(m.results.resultDisclaimer)} + ) } @@ -225,6 +244,13 @@ export const WHODASCalculator = ({ slice }: WHODASCalculatorProps) => { steps: steps.map(({ title, description, questions }) => ({ title, description, + maxScorePossible: questions.reduce( + (prev, acc) => + prev + acc.answerOptions.length > 0 + ? acc.answerOptions[acc.answerOptions.length - 1].score + : 0, + 0, + ), questions: questions.map(() => ({ selectedAnswerIndex: 0, answerScore: 0, @@ -245,30 +271,41 @@ export const WHODASCalculator = ({ slice }: WHODASCalculatorProps) => { if (showResults) { let totalScore = 0 + let totalMaxScorePossible = 0 const results: WHODASResultsProps['results'] = { steps: [], } for (const stateStep of state.steps) { + totalMaxScorePossible += stateStep.maxScorePossible let score = 0 for (const question of stateStep.questions) { score += question.answerScore } - results.steps.push({ ...stateStep, scoreForStep: score }) totalScore += score + if (stateStep.maxScorePossible > 0) { + score = (score * 100) / stateStep.maxScorePossible + } + results.steps.push({ ...stateStep, scoreForStep: score }) + } + if (totalMaxScorePossible > 0) { + totalScore = (totalScore * 100) / totalMaxScorePossible } return ( {formatMessage(m.results.topDescription)} - + + + ) } From 4ff38652689212853d7777bc337c640c5368a8e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20Bj=C3=B6rn=20Magn=C3=BAsson?= Date: Thu, 5 Dec 2024 13:05:19 +0000 Subject: [PATCH 04/57] chore(application-system-api-worker): Add missing user notification url (#17140) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../api/infra/application-system-api.ts | 151 +++++++++--------- charts/islandis/values.dev.yaml | 1 + charts/islandis/values.prod.yaml | 1 + charts/islandis/values.staging.yaml | 1 + .../values.dev.yaml | 1 + .../values.prod.yaml | 1 + .../values.staging.yaml | 1 + infra/src/uber-charts/islandis.ts | 4 +- 8 files changed, 86 insertions(+), 75 deletions(-) diff --git a/apps/application-system/api/infra/application-system-api.ts b/apps/application-system/api/infra/application-system-api.ts index 0c3b5c273739..9cb0aa3406ea 100644 --- a/apps/application-system/api/infra/application-system-api.ts +++ b/apps/application-system/api/infra/application-system-api.ts @@ -49,80 +49,83 @@ export const GRAPHQL_API_URL_ENV_VAR_NAME = 'GRAPHQL_API_URL' // This property i const namespace = 'application-system' const serviceAccount = 'application-system-api' -export const workerSetup = - (): ServiceBuilder<'application-system-api-worker'> => - service('application-system-api-worker') - .namespace(namespace) - .image('application-system-api') - .db() - .serviceAccount('application-system-api-worker') - .redis() - .codeOwner(CodeOwners.NordaApplications) - .env({ - IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/application-system', - IDENTITY_SERVER_ISSUER_URL: { - dev: 'https://identity-server.dev01.devland.is', - staging: 'https://identity-server.staging01.devland.is', - prod: 'https://innskra.island.is', - }, - XROAD_CHARGE_FJS_V2_PATH: { - dev: 'IS-DEV/GOV/10021/FJS-Public/chargeFJS_v2', - staging: 'IS-TEST/GOV/10021/FJS-Public/chargeFJS_v2', - prod: 'IS/GOV/5402697509/FJS-Public/chargeFJS_v2', - }, - APPLICATION_ATTACHMENT_BUCKET: { - dev: 'island-is-dev-storage-application-system', - staging: 'island-is-staging-storage-application-system', - prod: 'island-is-prod-storage-application-system', - }, - FILE_SERVICE_PRESIGN_BUCKET: { - dev: 'island-is-dev-fs-presign-bucket', - staging: 'island-is-staging-fs-presign-bucket', - prod: 'island-is-prod-fs-presign-bucket', - }, - FILE_STORAGE_UPLOAD_BUCKET: { - dev: 'island-is-dev-upload-api', - staging: 'island-is-staging-upload-api', - prod: 'island-is-prod-upload-api', - }, - CLIENT_LOCATION_ORIGIN: { - dev: 'https://beta.dev01.devland.is/umsoknir', - staging: 'https://beta.staging01.devland.is/umsoknir', - prod: 'https://island.is/umsoknir', - local: 'http://localhost:4200/umsoknir', - }, - }) - .xroad(Base, Client, Payment, Inna, EHIC, WorkMachines) - .secrets({ - IDENTITY_SERVER_CLIENT_SECRET: - '/k8s/application-system/api/IDENTITY_SERVER_CLIENT_SECRET', - SYSLUMENN_HOST: '/k8s/application-system-api/SYSLUMENN_HOST', - SYSLUMENN_USERNAME: '/k8s/application-system/api/SYSLUMENN_USERNAME', - SYSLUMENN_PASSWORD: '/k8s/application-system/api/SYSLUMENN_PASSWORD', - DRIVING_LICENSE_BOOK_XROAD_PATH: - '/k8s/application-system-api/DRIVING_LICENSE_BOOK_XROAD_PATH', - DRIVING_LICENSE_BOOK_USERNAME: - '/k8s/application-system-api/DRIVING_LICENSE_BOOK_USERNAME', - DRIVING_LICENSE_BOOK_PASSWORD: - '/k8s/application-system-api/DRIVING_LICENSE_BOOK_PASSWORD', - DOKOBIT_ACCESS_TOKEN: - '/k8s/application-system/api/DOKOBIT_ACCESS_TOKEN', - DOKOBIT_URL: '/k8s/application-system-api/DOKOBIT_URL', - ARK_BASE_URL: '/k8s/application-system-api/ARK_BASE_URL', - DOMSYSLA_PASSWORD: '/k8s/application-system-api/DOMSYSLA_PASSWORD', - DOMSYSLA_USERNAME: '/k8s/application-system-api/DOMSYSLA_USERNAME', - }) - .args('main.js', '--job', 'worker') - .command('node') - .extraAttributes({ - dev: { schedule: '*/30 * * * *' }, - staging: { schedule: '*/30 * * * *' }, - prod: { schedule: '*/30 * * * *' }, - }) - .resources({ - limits: { cpu: '400m', memory: '768Mi' }, - requests: { cpu: '150m', memory: '384Mi' }, - }) +export const workerSetup = (services: { + userNotificationService: ServiceBuilder<'services-user-notification'> +}): ServiceBuilder<'application-system-api-worker'> => + service('application-system-api-worker') + .namespace(namespace) + .image('application-system-api') + .db() + .serviceAccount('application-system-api-worker') + .redis() + .codeOwner(CodeOwners.NordaApplications) + .env({ + IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/application-system', + IDENTITY_SERVER_ISSUER_URL: { + dev: 'https://identity-server.dev01.devland.is', + staging: 'https://identity-server.staging01.devland.is', + prod: 'https://innskra.island.is', + }, + XROAD_CHARGE_FJS_V2_PATH: { + dev: 'IS-DEV/GOV/10021/FJS-Public/chargeFJS_v2', + staging: 'IS-TEST/GOV/10021/FJS-Public/chargeFJS_v2', + prod: 'IS/GOV/5402697509/FJS-Public/chargeFJS_v2', + }, + APPLICATION_ATTACHMENT_BUCKET: { + dev: 'island-is-dev-storage-application-system', + staging: 'island-is-staging-storage-application-system', + prod: 'island-is-prod-storage-application-system', + }, + FILE_SERVICE_PRESIGN_BUCKET: { + dev: 'island-is-dev-fs-presign-bucket', + staging: 'island-is-staging-fs-presign-bucket', + prod: 'island-is-prod-fs-presign-bucket', + }, + FILE_STORAGE_UPLOAD_BUCKET: { + dev: 'island-is-dev-upload-api', + staging: 'island-is-staging-upload-api', + prod: 'island-is-prod-upload-api', + }, + CLIENT_LOCATION_ORIGIN: { + dev: 'https://beta.dev01.devland.is/umsoknir', + staging: 'https://beta.staging01.devland.is/umsoknir', + prod: 'https://island.is/umsoknir', + local: 'http://localhost:4200/umsoknir', + }, + USER_NOTIFICATION_API_URL: ref( + (h) => `http://${h.svc(services.userNotificationService)}`, + ), + }) + .xroad(Base, Client, Payment, Inna, EHIC, WorkMachines) + .secrets({ + IDENTITY_SERVER_CLIENT_SECRET: + '/k8s/application-system/api/IDENTITY_SERVER_CLIENT_SECRET', + SYSLUMENN_HOST: '/k8s/application-system-api/SYSLUMENN_HOST', + SYSLUMENN_USERNAME: '/k8s/application-system/api/SYSLUMENN_USERNAME', + SYSLUMENN_PASSWORD: '/k8s/application-system/api/SYSLUMENN_PASSWORD', + DRIVING_LICENSE_BOOK_XROAD_PATH: + '/k8s/application-system-api/DRIVING_LICENSE_BOOK_XROAD_PATH', + DRIVING_LICENSE_BOOK_USERNAME: + '/k8s/application-system-api/DRIVING_LICENSE_BOOK_USERNAME', + DRIVING_LICENSE_BOOK_PASSWORD: + '/k8s/application-system-api/DRIVING_LICENSE_BOOK_PASSWORD', + DOKOBIT_ACCESS_TOKEN: '/k8s/application-system/api/DOKOBIT_ACCESS_TOKEN', + DOKOBIT_URL: '/k8s/application-system-api/DOKOBIT_URL', + ARK_BASE_URL: '/k8s/application-system-api/ARK_BASE_URL', + DOMSYSLA_PASSWORD: '/k8s/application-system-api/DOMSYSLA_PASSWORD', + DOMSYSLA_USERNAME: '/k8s/application-system-api/DOMSYSLA_USERNAME', + }) + .args('main.js', '--job', 'worker') + .command('node') + .extraAttributes({ + dev: { schedule: '*/30 * * * *' }, + staging: { schedule: '*/30 * * * *' }, + prod: { schedule: '*/30 * * * *' }, + }) + .resources({ + limits: { cpu: '400m', memory: '768Mi' }, + requests: { cpu: '150m', memory: '384Mi' }, + }) export const serviceSetup = (services: { documentsService: ServiceBuilder<'services-documents'> diff --git a/charts/islandis/values.dev.yaml b/charts/islandis/values.dev.yaml index 0a6d35e252cf..0dd1634ad8ad 100644 --- a/charts/islandis/values.dev.yaml +++ b/charts/islandis/values.dev.yaml @@ -852,6 +852,7 @@ application-system-api-worker: NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init' REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379"]' SERVERSIDE_FEATURES_ON: '' + USER_NOTIFICATION_API_URL: 'http://web-user-notification.user-notification.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.dev01.devland.is' XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.dev01.devland.is/r1/IS-DEV' XROAD_CHARGE_FJS_V2_PATH: 'IS-DEV/GOV/10021/FJS-Public/chargeFJS_v2' diff --git a/charts/islandis/values.prod.yaml b/charts/islandis/values.prod.yaml index cbc83be17d50..04619e6f6b0a 100644 --- a/charts/islandis/values.prod.yaml +++ b/charts/islandis/values.prod.yaml @@ -840,6 +840,7 @@ application-system-api-worker: NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init' REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.whakos.euw1.cache.amazonaws.com:6379"]' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' + USER_NOTIFICATION_API_URL: 'http://web-user-notification.user-notification.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.island.is' XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.island.is/r1/IS' XROAD_CHARGE_FJS_V2_PATH: 'IS/GOV/5402697509/FJS-Public/chargeFJS_v2' diff --git a/charts/islandis/values.staging.yaml b/charts/islandis/values.staging.yaml index 36116c21bc12..a2192f932e41 100644 --- a/charts/islandis/values.staging.yaml +++ b/charts/islandis/values.staging.yaml @@ -849,6 +849,7 @@ application-system-api-worker: NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init' REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' SERVERSIDE_FEATURES_ON: '' + USER_NOTIFICATION_API_URL: 'http://web-user-notification.user-notification.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.staging01.devland.is' XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.staging01.devland.is/r1/IS-TEST' XROAD_CHARGE_FJS_V2_PATH: 'IS-TEST/GOV/10021/FJS-Public/chargeFJS_v2' diff --git a/charts/services/application-system-api-worker/values.dev.yaml b/charts/services/application-system-api-worker/values.dev.yaml index 5f8102c047e2..91699959fa25 100644 --- a/charts/services/application-system-api-worker/values.dev.yaml +++ b/charts/services/application-system-api-worker/values.dev.yaml @@ -41,6 +41,7 @@ env: NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init' REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379"]' SERVERSIDE_FEATURES_ON: '' + USER_NOTIFICATION_API_URL: 'http://web-user-notification.user-notification.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.dev01.devland.is' XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.dev01.devland.is/r1/IS-DEV' XROAD_CHARGE_FJS_V2_PATH: 'IS-DEV/GOV/10021/FJS-Public/chargeFJS_v2' diff --git a/charts/services/application-system-api-worker/values.prod.yaml b/charts/services/application-system-api-worker/values.prod.yaml index e8c6b233d0c0..e484a5d20bb8 100644 --- a/charts/services/application-system-api-worker/values.prod.yaml +++ b/charts/services/application-system-api-worker/values.prod.yaml @@ -41,6 +41,7 @@ env: NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init' REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.whakos.euw1.cache.amazonaws.com:6379"]' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' + USER_NOTIFICATION_API_URL: 'http://web-user-notification.user-notification.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.island.is' XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.island.is/r1/IS' XROAD_CHARGE_FJS_V2_PATH: 'IS/GOV/5402697509/FJS-Public/chargeFJS_v2' diff --git a/charts/services/application-system-api-worker/values.staging.yaml b/charts/services/application-system-api-worker/values.staging.yaml index 1b3b2c57186f..a654300a66ef 100644 --- a/charts/services/application-system-api-worker/values.staging.yaml +++ b/charts/services/application-system-api-worker/values.staging.yaml @@ -41,6 +41,7 @@ env: NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init' REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' SERVERSIDE_FEATURES_ON: '' + USER_NOTIFICATION_API_URL: 'http://web-user-notification.user-notification.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.staging01.devland.is' XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.staging01.devland.is/r1/IS-TEST' XROAD_CHARGE_FJS_V2_PATH: 'IS-TEST/GOV/10021/FJS-Public/chargeFJS_v2' diff --git a/infra/src/uber-charts/islandis.ts b/infra/src/uber-charts/islandis.ts index 705e3c1113f0..569b921bc2bf 100644 --- a/infra/src/uber-charts/islandis.ts +++ b/infra/src/uber-charts/islandis.ts @@ -85,7 +85,9 @@ const appSystemApi = appSystemApiSetup({ servicePortalApi, userNotificationService, }) -const appSystemApiWorker = appSystemApiWorkerSetup() +const appSystemApiWorker = appSystemApiWorkerSetup({ + userNotificationService, +}) const nameRegistryBackend = serviceNameRegistryBackendSetup() From 40991f36835b7bf4d96a388fdcb8b0234b2b7dac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:16:29 +0000 Subject: [PATCH 05/57] fix(web): Generic List - Reset page number when filters are changed (#17141) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/web/components/GenericList/GenericList.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/web/components/GenericList/GenericList.tsx b/apps/web/components/GenericList/GenericList.tsx index d717d1616388..bbc7592e5172 100644 --- a/apps/web/components/GenericList/GenericList.tsx +++ b/apps/web/components/GenericList/GenericList.tsx @@ -311,6 +311,7 @@ export const GenericList = ({ key={value} active={true} onClick={() => { + setPage(null) setParameters((prevParameters) => { const updatedParameters = { ...prevParameters, @@ -465,6 +466,7 @@ export const GenericList = ({ if (!category) { return } + setPage(null) setParameters((prevParameters) => ({ ...prevParameters, [category]: ( From d5d438371c72772cd483c33d261ff14c5737c836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gunnlaugur=20Gu=C3=B0mundsson?= <34029342+GunnlaugurG@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:49:19 +0000 Subject: [PATCH 06/57] feat(ids-admin): General Mandate delegation for companies (#17026) * initial push for company general mandate * added tests for company general mandate * merge conflict with main resolved * chore: nx format:write update dirty files * fix executor command for dev --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/services/auth/admin-api/project.json | 11 + .../services/auth/delegation-api/project.json | 11 + apps/services/auth/ids-api/project.json | 11 + .../test/delegations.controller.spec.ts | 263 +++++++++++++++--- .../auth/ids-api/test/stubs/genericStubs.ts | 3 + .../admin/delegation-admin-custom.service.ts | 13 +- .../delegations/delegation-scope.service.ts | 10 +- .../delegations-incoming-custom.service.ts | 35 ++- .../delegations-incoming.service.ts | 24 ++ 9 files changed, 339 insertions(+), 42 deletions(-) diff --git a/apps/services/auth/admin-api/project.json b/apps/services/auth/admin-api/project.json index 2866faa0019a..e5d91fd92127 100644 --- a/apps/services/auth/admin-api/project.json +++ b/apps/services/auth/admin-api/project.json @@ -57,6 +57,17 @@ }, "docker-express": { "executor": "Intentionally left blank, only so this target is valid when using `nx show projects --with-target docker-express`" + }, + "dev": { + "executor": "nx:run-commands", + "options": { + "commands": [ + { + "command": "yarn start --project services-auth-admin-api" + } + ], + "parallel": true + } } } } diff --git a/apps/services/auth/delegation-api/project.json b/apps/services/auth/delegation-api/project.json index 366532998b91..66890516fc3c 100644 --- a/apps/services/auth/delegation-api/project.json +++ b/apps/services/auth/delegation-api/project.json @@ -28,6 +28,17 @@ "buildTarget": "services-auth-delegation-api:build" } }, + "dev": { + "executor": "nx:run-commands", + "options": { + "commands": [ + { + "command": "yarn start services-auth-delegation-api" + } + ], + "parallel": true + } + }, "lint": { "executor": "@nx/eslint:lint" }, diff --git a/apps/services/auth/ids-api/project.json b/apps/services/auth/ids-api/project.json index cfefdd77acda..b64e70508680 100644 --- a/apps/services/auth/ids-api/project.json +++ b/apps/services/auth/ids-api/project.json @@ -175,6 +175,17 @@ ], "parallel": true } + }, + "dev": { + "executor": "nx:run-commands", + "options": { + "commands": [ + { + "command": "yarn start services-auth-ids-api" + } + ], + "parallel": true + } } } } diff --git a/apps/services/auth/ids-api/src/app/delegations/test/delegations.controller.spec.ts b/apps/services/auth/ids-api/src/app/delegations/test/delegations.controller.spec.ts index f2dee16513d6..faee06788837 100644 --- a/apps/services/auth/ids-api/src/app/delegations/test/delegations.controller.spec.ts +++ b/apps/services/auth/ids-api/src/app/delegations/test/delegations.controller.spec.ts @@ -1,7 +1,7 @@ import { getModelToken } from '@nestjs/sequelize' -import addDays from 'date-fns/addDays' import request from 'supertest' import { uuid } from 'uuidv4' +import addDays from 'date-fns/addDays' import { ApiScope, @@ -27,12 +27,16 @@ import { } from '@island.is/shared/types' import { createCurrentUser, + createNationalId, createNationalRegistryUser, } from '@island.is/testing/fixtures' import { TestApp } from '@island.is/testing/nest' import { defaultScopes, setupWithAuth } from '../../../../test/setup' -import { getFakeNationalId } from '../../../../test/stubs/genericStubs' +import { + getFakeCompanyNationalId, + getFakeNationalId, +} from '../../../../test/stubs/genericStubs' describe('DelegationsController', () => { describe.each([false, true])( @@ -57,6 +61,8 @@ describe('DelegationsController', () => { clientId: '@island.is/webapp', }) + const representeeNationalId = createNationalId('person') + const scopeValid1 = 'scope/valid1' const scopeValid2 = 'scope/valid2' const scopeValid1and2 = 'scope/valid1and2' @@ -76,7 +82,7 @@ describe('DelegationsController', () => { scopeName: s, })) - const userNationalId = getFakeNationalId() + const userNationalId = createNationalId('person') const user = createCurrentUser({ nationalId: userNationalId, @@ -85,6 +91,8 @@ describe('DelegationsController', () => { }) const domain = createDomain() + let nationalRegistryApiSpy: jest.SpyInstance + let nationalRegistryV3ApiSpy: jest.SpyInstance beforeAll(async () => { app = await setupWithAuth({ @@ -117,36 +125,214 @@ describe('DelegationsController', () => { >(getModelToken(DelegationDelegationType)) nationalRegistryApi = app.get(NationalRegistryClientService) nationalRegistryV3Api = app.get(NationalRegistryV3ClientService) + factory = new FixtureFactory(app) + + client.supportedDelegationTypes = [ + AuthDelegationType.GeneralMandate, + AuthDelegationType.LegalGuardian, + AuthDelegationType.ProcurationHolder, + ] + await factory.createClient(client) + + nationalRegistryApiSpy = jest + .spyOn(nationalRegistryApi, 'getIndividual') + .mockImplementation(async (id) => { + const user = createNationalRegistryUser({ + nationalId: representeeNationalId, + }) + + return user ?? null + }) + + nationalRegistryV3ApiSpy = jest + .spyOn(nationalRegistryV3Api, 'getAllDataIndividual') + .mockImplementation(async () => { + const user = createNationalRegistryUser({ + nationalId: representeeNationalId, + }) + + return { kennitala: user.nationalId, nafn: user.name } + }) + const nationalRegistryV3FeatureService = app.get( NationalRegistryV3FeatureService, ) jest .spyOn(nationalRegistryV3FeatureService, 'getValue') .mockImplementation(async () => featureFlag) - factory = new FixtureFactory(app) }) afterAll(async () => { await app.cleanUp() + nationalRegistryV3ApiSpy.mockClear() + nationalRegistryApiSpy.mockClear() }) - describe('GET with general mandate delegation type', () => { - const representeeNationalId = getFakeNationalId() - let nationalRegistryApiSpy: jest.SpyInstance - let nationalRegistryV3ApiSpy: jest.SpyInstance + describe('GET with general mandate delegation type for company', () => { + const companyNationalId = getFakeCompanyNationalId() + const scopeNames = [ 'api-scope/generalMandate1', 'api-scope/generalMandate2', 'api-scope/generalMandate3', + 'api-scope/procuration1', + 'api-scope/procuration2', ] beforeAll(async () => { - client.supportedDelegationTypes = [ - AuthDelegationType.GeneralMandate, - AuthDelegationType.LegalGuardian, + const delegations = await delegationModel.create({ + id: uuid(), + fromDisplayName: 'Company', + fromNationalId: companyNationalId, + toNationalId: userNationalId, + toName: 'Person', + }) + + await delegationDelegationTypeModel.create({ + delegationId: delegations.id, + delegationTypeId: AuthDelegationType.GeneralMandate, + }) + + await apiScopeModel.bulkCreate( + scopeNames.map((name) => ({ + name, + domainName: domain.name, + enabled: true, + description: `${name}: description`, + displayName: `${name}: display name`, + })), + ) + + await apiScopeDelegationTypeModel.bulkCreate([ + { + apiScopeName: scopeNames[0], + delegationType: AuthDelegationType.GeneralMandate, + }, + { + apiScopeName: scopeNames[1], + delegationType: AuthDelegationType.GeneralMandate, + }, + { + apiScopeName: scopeNames[3], + delegationType: AuthDelegationType.ProcurationHolder, + }, + { + apiScopeName: scopeNames[4], + delegationType: AuthDelegationType.ProcurationHolder, + }, + ]) + }) + + afterAll(async () => { + await apiScopeDelegationTypeModel.destroy({ + where: {}, + cascade: true, + truncate: true, + force: true, + }) + await apiScopeModel.destroy({ + where: {}, + cascade: true, + truncate: true, + force: true, + }) + await delegationDelegationTypeModel.destroy({ + where: {}, + cascade: true, + truncate: true, + force: true, + }) + await delegationModel.destroy({ + where: {}, + cascade: true, + truncate: true, + force: true, + }) + }) + + it('should return mergedDelegationDTO with the generalMandate', async () => { + const response = await server.get('/v2/delegations') + + expect(response.status).toEqual(200) + expect(response.body).toHaveLength(1) + }) + + it('should return all general mandate scopes and other preset scopes', async () => { + const response = await server.get('/delegations/scopes').query({ + fromNationalId: companyNationalId, + delegationType: [ + AuthDelegationType.GeneralMandate, + AuthDelegationType.ProcurationHolder, + ], + }) + + const expected = [ + scopeNames[0], + scopeNames[1], + scopeNames[3], + scopeNames[4], + ] + + expect(response.status).toEqual(200) + expect(response.body).toEqual(expect.arrayContaining(expected)) + expect(response.body).toHaveLength(expected.length) + }) + + it('should return all general mandate scopes and all procuration scopes', async () => { + const response = await server.get('/delegations/scopes').query({ + fromNationalId: companyNationalId, + delegationType: [AuthDelegationType.GeneralMandate], + }) + + const expected = [ + scopeNames[0], + scopeNames[1], + scopeNames[3], + scopeNames[4], ] - await factory.createClient(client) + expect(response.status).toEqual(200) + expect(response.body).toEqual(expect.arrayContaining(expected)) + expect(response.body).toHaveLength(expected.length) + }) + + it('should return all general mandate scopes, and not procuration scopes since from nationalId is person', async () => { + // Assert + const delegation = await delegationModel.create({ + id: uuid(), + fromDisplayName: 'FromPersonPerson', + fromNationalId: representeeNationalId, + toNationalId: userNationalId, + toName: 'Person', + }) + + await delegationDelegationTypeModel.create({ + delegationId: delegation.id, + delegationTypeId: AuthDelegationType.GeneralMandate, + }) + + // Act + const response = await server.get('/delegations/scopes').query({ + fromNationalId: representeeNationalId, + delegationType: [AuthDelegationType.GeneralMandate], + }) + + const expected = [scopeNames[0], scopeNames[1]] + + expect(response.status).toEqual(200) + expect(response.body).toEqual(expect.arrayContaining(expected)) + expect(response.body).toHaveLength(expected.length) + }) + }) + + describe('GET with general mandate delegation type', () => { + const scopeNames = [ + 'api-scope/generalMandate1', + 'api-scope/generalMandate2', + 'api-scope/generalMandate3', + ] + + beforeAll(async () => { const delegations = await delegationModel.create({ id: uuid(), fromDisplayName: 'Test', @@ -187,32 +373,39 @@ describe('DelegationsController', () => { delegationType: AuthDelegationType.GeneralMandate, }, ]) - - nationalRegistryApiSpy = jest - .spyOn(nationalRegistryApi, 'getIndividual') - .mockImplementation(async (id) => { - const user = createNationalRegistryUser({ - nationalId: representeeNationalId, - }) - - return user ?? null - }) - - nationalRegistryV3ApiSpy = jest - .spyOn(nationalRegistryV3Api, 'getAllDataIndividual') - .mockImplementation(async () => { - const user = createNationalRegistryUser({ - nationalId: representeeNationalId, - }) - - return { kennitala: user.nationalId, nafn: user.name } - }) }) afterAll(async () => { - await app.cleanUp() - nationalRegistryApiSpy.mockClear() - nationalRegistryV3ApiSpy.mockClear() + await apiScopeDelegationTypeModel.destroy({ + where: {}, + cascade: true, + truncate: true, + force: true, + }) + await apiScopeModel.destroy({ + where: {}, + cascade: true, + truncate: true, + force: true, + }) + await delegationProviderModel.destroy({ + where: {}, + cascade: true, + truncate: true, + force: true, + }) + await delegationDelegationTypeModel.destroy({ + where: {}, + cascade: true, + truncate: true, + force: true, + }) + await delegationModel.destroy({ + where: {}, + cascade: true, + truncate: true, + force: true, + }) }) it('should return mergedDelegationDTO with the generalMandate', async () => { diff --git a/apps/services/auth/ids-api/test/stubs/genericStubs.ts b/apps/services/auth/ids-api/test/stubs/genericStubs.ts index 91956b00fcc5..177ad31ae023 100644 --- a/apps/services/auth/ids-api/test/stubs/genericStubs.ts +++ b/apps/services/auth/ids-api/test/stubs/genericStubs.ts @@ -6,6 +6,8 @@ export type NameIdTuple = [name: string, id: string] export const getFakeNationalId = () => faker.helpers.replaceSymbolWithNumber('##########') +export const getFakeCompanyNationalId = () => createNationalId('company') + export const getFakeName = () => faker.fake('{{name.firstName}} {{name.lastName}}') @@ -18,4 +20,5 @@ export default { getFakeNationalId, getFakeName, getFakePerson, + getFakeCompanyNationalId, } diff --git a/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts b/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts index 5cbb11d913b5..e1de9e93df1a 100644 --- a/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts @@ -12,6 +12,7 @@ import { TicketStatus, ZendeskService, } from '@island.is/clients/zendesk' +import { CompanyRegistryClientService } from '@island.is/clients/rsk/company-registry' import { Delegation } from '../models/delegation.model' import { DelegationAdminCustomDto } from '../dto/delegation-admin-custom.dto' @@ -45,6 +46,7 @@ export class DelegationAdminCustomService { private delegationScopeService: DelegationScopeService, private namesService: NamesService, private sequelize: Sequelize, + private rskCompanyInfoService: CompanyRegistryClientService, ) {} private getZendeskCustomFields(ticket: Ticket): { @@ -276,9 +278,7 @@ export class DelegationAdminCustomService { }) } - if ( - !(kennitala.isPerson(fromNationalId) && kennitala.isPerson(toNationalId)) - ) { + if (!kennitala.isPerson(toNationalId)) { throw new BadRequestException({ message: 'National Ids are not valid', error: ErrorCodes.INPUT_VALIDATION_INVALID_PERSON, @@ -300,7 +300,11 @@ export class DelegationAdminCustomService { }, ): Promise { const [fromDisplayName, toName] = await Promise.all([ - this.namesService.getPersonName(delegation.fromNationalId), + kennitala.isPerson(delegation.fromNationalId) + ? this.namesService.getPersonName(delegation.fromNationalId) + : this.rskCompanyInfoService + .getCompany(delegation.fromNationalId) + .then((company) => company?.name ?? ''), this.namesService.getPersonName(delegation.toNationalId), ]) @@ -310,6 +314,7 @@ export class DelegationAdminCustomService { { fromNationalId: delegation.fromNationalId, toNationalId: delegation.toNationalId, + domainName: null, }, { referenceId: delegation.referenceId, diff --git a/libs/auth-api-lib/src/lib/delegations/delegation-scope.service.ts b/libs/auth-api-lib/src/lib/delegations/delegation-scope.service.ts index d12b1d8af8e4..bb74ed0a4d6e 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegation-scope.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegation-scope.service.ts @@ -5,6 +5,7 @@ import addDays from 'date-fns/addDays' import startOfDay from 'date-fns/startOfDay' import { Op, Transaction } from 'sequelize' import { uuid } from 'uuidv4' +import * as kennitala from 'kennitala' import { SyslumennService } from '@island.is/clients/syslumenn' import { logger } from '@island.is/logging' @@ -209,7 +210,14 @@ export class DelegationScopeService { { model: ApiScopeDelegationType, where: { - delegationType: AuthDelegationType.GeneralMandate, + delegationType: { + [Op.or]: kennitala.isCompany(fromNationalId) + ? [ + AuthDelegationType.GeneralMandate, + AuthDelegationType.ProcurationHolder, + ] + : [AuthDelegationType.GeneralMandate], + }, }, }, ], diff --git a/libs/auth-api-lib/src/lib/delegations/delegations-incoming-custom.service.ts b/libs/auth-api-lib/src/lib/delegations/delegations-incoming-custom.service.ts index ee60fb61c692..e11e509095bd 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegations-incoming-custom.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegations-incoming-custom.service.ts @@ -196,17 +196,48 @@ export class DelegationsIncomingCustomService { ) } + /** + * Finds all companies that have a general mandate for the user. + * @param user + * @param clientAllowedApiScopes + * @param requireApiScopes + */ + async findCompanyGeneralMandate( + user: User, + clientAllowedApiScopes: ApiScopeInfo[], + requireApiScopes: boolean, + ): Promise { + const delegations = await this.findAllAvailableGeneralMandate( + user, + clientAllowedApiScopes, + requireApiScopes, + [AuthDelegationType.ProcurationHolder], + ) + + return delegations.filter((d) => kennitala.isCompany(d.fromNationalId)) + } + + /** + * Finds all individuals that have a general mandate for the user. + * @param user + * @param clientAllowedApiScopes + * @param requireApiScopes + * @param supportedDelegationTypes + */ async findAllAvailableGeneralMandate( user: User, clientAllowedApiScopes: ApiScopeInfo[], requireApiScopes: boolean, + supportedDelegationTypes = [AuthDelegationType.GeneralMandate], ): Promise { const customApiScopes = clientAllowedApiScopes.filter( (s) => !s.isAccessControlled && this.filterByCustomScopeRule(s) && - s.supportedDelegationTypes?.some( - (dt) => dt.delegationType === AuthDelegationType.GeneralMandate, + s.supportedDelegationTypes?.some((dt) => + supportedDelegationTypes.includes( + dt.delegationType as AuthDelegationType, + ), ), ) diff --git a/libs/auth-api-lib/src/lib/delegations/delegations-incoming.service.ts b/libs/auth-api-lib/src/lib/delegations/delegations-incoming.service.ts index b2e0775d3b48..05ddc050ee0f 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegations-incoming.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegations-incoming.service.ts @@ -176,6 +176,25 @@ export class DelegationsIncomingService { ) } + // If procuration holder is enabled, we need to get the general mandate delegations + if (types?.includes(AuthDelegationType.ProcurationHolder)) { + const isGeneralMandateDelegationEnabled = + await this.featureFlagService.getValue( + Features.isGeneralMandateDelegationEnabled, + false, + user, + ) + if (isGeneralMandateDelegationEnabled) { + delegationPromises.push( + this.delegationsIncomingCustomService.findCompanyGeneralMandate( + user, + clientAllowedApiScopes, + client.requireApiScopes, + ), + ) + } + } + if (providers.includes(AuthDelegationProvider.CompanyRegistry)) { delegationPromises.push( this.incomingDelegationsCompanyService @@ -293,6 +312,11 @@ export class DelegationsIncomingService { new Map(), ) + // Remove duplicate delegationTypes.. + mergedDelegationMap.forEach((delegation) => { + delegation.types = Array.from(new Set(delegation.types)) + }) + return [...mergedDelegationMap.values()] } From 5a1b1a6b3f4e0dae5d177a3a4c20e9772b7cc893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnea=20R=C3=BAn=20Vignisd=C3=B3ttir?= Date: Thu, 5 Dec 2024 14:41:05 +0000 Subject: [PATCH 07/57] feat(auth-admin): Admin notifications (#16993) * display notifications in admin portal * fix type * chore: nx format:write update dirty files * use correct scope, controller should not return sensitive data * chore: nx format:write update dirty files --------- Co-authored-by: andes-it --- .../me-notifications.controller.ts | 2 +- .../notifications/notifications.controller.ts | 23 ++++ .../notifications/notifications.service.ts | 22 +++- .../tests/notifications.service.spec.ts | 4 +- .../src/lib/notifications.model.ts | 20 +++ .../src/lib/notifications.module.ts | 4 + .../src/lib/notificationsAdmin.resolver.ts | 60 +++++++++ .../src/lib/notificationsAdmin.service.ts | 52 ++++++++ .../notifications/src/utils/helpers.ts | 12 +- libs/nest/pagination/src/lib/paginate.ts | 3 + .../admin/service-desk/src/lib/messages.ts | 8 ++ .../src/screens/User/User.graphql | 21 +++ .../service-desk/src/screens/User/User.tsx | 120 +++++++++++++++++- .../src/screens/Users/Users.action.ts | 5 +- 14 files changed, 342 insertions(+), 14 deletions(-) create mode 100644 libs/api/domains/notifications/src/lib/notificationsAdmin.resolver.ts create mode 100644 libs/api/domains/notifications/src/lib/notificationsAdmin.service.ts diff --git a/apps/services/user-notification/src/app/modules/notifications/me-notifications.controller.ts b/apps/services/user-notification/src/app/modules/notifications/me-notifications.controller.ts index da5d5c7e63a9..26deb3a189a6 100644 --- a/apps/services/user-notification/src/app/modules/notifications/me-notifications.controller.ts +++ b/apps/services/user-notification/src/app/modules/notifications/me-notifications.controller.ts @@ -52,7 +52,7 @@ export class MeNotificationsController { @CurrentUser() user: User, @Query() query: ExtendedPaginationDto, ): Promise { - return this.notificationService.findMany(user, query) + return this.notificationService.findManyWithTemplate(user.nationalId, query) } @Get('/unread-count') diff --git a/apps/services/user-notification/src/app/modules/notifications/notifications.controller.ts b/apps/services/user-notification/src/app/modules/notifications/notifications.controller.ts index a1425841a8b8..c8f578ce6397 100644 --- a/apps/services/user-notification/src/app/modules/notifications/notifications.controller.ts +++ b/apps/services/user-notification/src/app/modules/notifications/notifications.controller.ts @@ -3,10 +3,13 @@ import { Body, Controller, Get, + Headers, + HttpStatus, Inject, Param, Post, Query, + UseGuards, Version, } from '@nestjs/common' import { ApiTags } from '@nestjs/swagger' @@ -19,6 +22,12 @@ import { CreateHnippNotificationDto } from './dto/createHnippNotification.dto' import { HnippTemplate } from './dto/hnippTemplate.response' import { NotificationsService } from './notifications.service' import type { Locale } from '@island.is/shared/types' +import { + ExtendedPaginationDto, + PaginatedNotificationDto, +} from './dto/notification.dto' +import { IdsUserGuard, Scopes, ScopesGuard } from '@island.is/auth-nest-tools' +import { AdminPortalScope } from '@island.is/auth/scopes' @Controller('notifications') @ApiTags('notifications') @@ -85,6 +94,20 @@ export class NotificationsController { return await this.notificationsService.getTemplate(templateId, locale) } + @UseGuards(IdsUserGuard, ScopesGuard) + @Scopes(AdminPortalScope.serviceDesk) + @Get('/') + @Documentation({ + summary: 'Returns a paginated list of notifications for a national id', + response: { status: HttpStatus.OK, type: PaginatedNotificationDto }, + }) + findMany( + @Headers('X-Query-National-Id') nationalId: string, + @Query() query: ExtendedPaginationDto, + ): Promise { + return this.notificationsService.findMany(nationalId, query) + } + @Documentation({ summary: 'Creates a new notification and adds to queue', includeNoContentResponse: true, diff --git a/apps/services/user-notification/src/app/modules/notifications/notifications.service.ts b/apps/services/user-notification/src/app/modules/notifications/notifications.service.ts index 8d8e937ff0f7..4116120d2744 100644 --- a/apps/services/user-notification/src/app/modules/notifications/notifications.service.ts +++ b/apps/services/user-notification/src/app/modules/notifications/notifications.service.ts @@ -260,8 +260,24 @@ export class NotificationsService { ) } - async findMany( - user: User, + findMany( + nationalId: string, + query: ExtendedPaginationDto, + ): Promise { + return paginate({ + Model: this.notificationModel, + limit: query.limit || 10, + after: query.after || '', + before: query.before, + primaryKeyField: 'id', + orderOption: [['id', 'DESC']], + where: { recipient: nationalId }, + attributes: ['id', 'messageId', 'senderId', 'created', 'updated'], + }) + } + + async findManyWithTemplate( + nationalId: string, query: ExtendedPaginationDto, ): Promise { const locale = mapToLocale(query.locale as Locale) @@ -273,7 +289,7 @@ export class NotificationsService { before: query.before, primaryKeyField: 'id', orderOption: [['id', 'DESC']], - where: { recipient: user.nationalId }, + where: { recipient: nationalId }, }) const formattedNotifications = await Promise.all( diff --git a/apps/services/user-notification/src/app/modules/notifications/tests/notifications.service.spec.ts b/apps/services/user-notification/src/app/modules/notifications/tests/notifications.service.spec.ts index 8820d8ddd2cb..8456f9a5333e 100644 --- a/apps/services/user-notification/src/app/modules/notifications/tests/notifications.service.spec.ts +++ b/apps/services/user-notification/src/app/modules/notifications/tests/notifications.service.spec.ts @@ -159,7 +159,9 @@ describe('NotificationsService', () => { .spyOn(service, 'findMany') .mockImplementation(async () => mockedResponse) - expect(await service.findMany(user, query)).toBe(mockedResponse) + expect(await service.findMany(user.nationalId, query)).toBe( + mockedResponse, + ) }) }) diff --git a/libs/api/domains/notifications/src/lib/notifications.model.ts b/libs/api/domains/notifications/src/lib/notifications.model.ts index 64436d89583a..2c83f4a8107c 100644 --- a/libs/api/domains/notifications/src/lib/notifications.model.ts +++ b/libs/api/domains/notifications/src/lib/notifications.model.ts @@ -89,6 +89,21 @@ export class Notification { message!: NotificationMessage } +@ObjectType() +export class AdminNotification { + @Field(() => Int) + id!: number + + @Field(() => ID) + notificationId!: string + + @Field(() => NotificationSender) + sender!: NotificationSender + + @Field(() => GraphQLISODateTime) + sent!: Date +} + @InputType() export class NotificationsInput extends PaginationInput() {} @@ -101,6 +116,11 @@ export class NotificationsResponse extends PaginatedResponse(Notification) { unseenCount?: number } +@ObjectType('AdminNotifications') +export class AdminNotificationsResponse extends PaginatedResponse( + AdminNotification, +) {} + @ObjectType() export class NotificationResponse { @Field(() => Notification) diff --git a/libs/api/domains/notifications/src/lib/notifications.module.ts b/libs/api/domains/notifications/src/lib/notifications.module.ts index 745702eb28bb..231c0d96c879 100644 --- a/libs/api/domains/notifications/src/lib/notifications.module.ts +++ b/libs/api/domains/notifications/src/lib/notifications.module.ts @@ -8,6 +8,8 @@ import { NotificationSenderResolver, } from './notificationsList.resolver' import { NotificationsService } from './notifications.service' +import { NotificationsAdminResolver } from './notificationsAdmin.resolver' +import { NotificationsAdminService } from './notificationsAdmin.service' @Module({ imports: [UserNotificationClientModule], @@ -15,7 +17,9 @@ import { NotificationsService } from './notifications.service' NotificationsResolver, NotificationsListResolver, NotificationSenderResolver, + NotificationsAdminResolver, NotificationsService, + NotificationsAdminService, ], exports: [], }) diff --git a/libs/api/domains/notifications/src/lib/notificationsAdmin.resolver.ts b/libs/api/domains/notifications/src/lib/notificationsAdmin.resolver.ts new file mode 100644 index 000000000000..9afd668e3173 --- /dev/null +++ b/libs/api/domains/notifications/src/lib/notificationsAdmin.resolver.ts @@ -0,0 +1,60 @@ +import { Args, Query, Resolver } from '@nestjs/graphql' +import { IdsUserGuard, CurrentUser } from '@island.is/auth-nest-tools' +import type { User } from '@island.is/auth-nest-tools' +import { Audit } from '@island.is/nest/audit' +import { Inject, UseGuards } from '@nestjs/common' + +import { NotificationsAdminService } from './notificationsAdmin.service' +import { + NotificationsInput, + AdminNotificationsResponse, +} from './notifications.model' +import type { Locale } from '@island.is/shared/types' +import { LOGGER_PROVIDER, type Logger } from '@island.is/logging' + +const LOG_CATEGORY = 'notification-admin-resolver' +export const AUDIT_NAMESPACE = 'notifications-admin-resolver' + +@UseGuards(IdsUserGuard) +@Resolver(() => AdminNotificationsResponse) +@Audit({ namespace: AUDIT_NAMESPACE }) +export class NotificationsAdminResolver { + constructor( + private readonly service: NotificationsAdminService, + @Inject(LOGGER_PROVIDER) private readonly logger: Logger, + ) {} + + @Query(() => AdminNotificationsResponse, { + name: 'adminNotifications', + nullable: true, + }) + @Audit() + async getNotifications( + @Args('nationalId') nationalId: string, + @Args('input', { type: () => NotificationsInput, nullable: true }) + input: NotificationsInput, + @CurrentUser() user: User, + @Args('locale', { type: () => String, nullable: true }) + locale: Locale = 'is', + ): Promise { + let notifications: AdminNotificationsResponse | null + + try { + notifications = await this.service.getNotifications( + locale, + nationalId, + user, + input, + ) + } catch (e) { + this.logger.error('failed to get admin notifications', { + locale, + category: LOG_CATEGORY, + error: e, + }) + throw e + } + + return notifications + } +} diff --git a/libs/api/domains/notifications/src/lib/notificationsAdmin.service.ts b/libs/api/domains/notifications/src/lib/notificationsAdmin.service.ts new file mode 100644 index 000000000000..76089eff1156 --- /dev/null +++ b/libs/api/domains/notifications/src/lib/notificationsAdmin.service.ts @@ -0,0 +1,52 @@ +import { Auth, AuthMiddleware, User } from '@island.is/auth-nest-tools' +import { LOGGER_PROVIDER } from '@island.is/logging' +import type { Logger } from '@island.is/logging' +import { Inject, Injectable } from '@nestjs/common' +import { NotificationsApi } from '@island.is/clients/user-notification' +import type { Locale } from '@island.is/shared/types' +import { + AdminNotificationsResponse, + NotificationsInput, +} from './notifications.model' +import { adminNotificationMapper } from '../utils/helpers' + +@Injectable() +export class NotificationsAdminService { + constructor( + @Inject(LOGGER_PROVIDER) + private logger: Logger, + private notificationsApi: NotificationsApi, + ) {} + + notificationsWAuth(auth: Auth) { + return this.notificationsApi.withMiddleware(new AuthMiddleware(auth)) + } + + async getNotifications( + locale: Locale, + nationalId: string, + user: User, + input?: NotificationsInput, + ): Promise { + const notifications = await this.notificationsWAuth( + user, + ).notificationsControllerFindMany({ + xQueryNationalId: nationalId, + locale, + limit: input?.limit, + before: input?.before, + after: input?.after, + }) + + if (!notifications.data) { + this.logger.debug('no admin notification found') + return null + } + + return { + data: notifications.data.map((item) => adminNotificationMapper(item)), + totalCount: notifications.totalCount, + pageInfo: notifications.pageInfo, + } + } +} diff --git a/libs/api/domains/notifications/src/utils/helpers.ts b/libs/api/domains/notifications/src/utils/helpers.ts index 3baac1e1b7fc..87932276f1e9 100644 --- a/libs/api/domains/notifications/src/utils/helpers.ts +++ b/libs/api/domains/notifications/src/utils/helpers.ts @@ -1,5 +1,5 @@ import { RenderedNotificationDto } from '@island.is/clients/user-notification' -import { Notification } from '../lib/notifications.model' +import { AdminNotification, Notification } from '../lib/notifications.model' const cleanString = (str?: string) => { if (!str) { @@ -40,3 +40,13 @@ export const notificationMapper = ( }, }, }) +export const adminNotificationMapper = ( + notification: RenderedNotificationDto, +): AdminNotification => ({ + id: notification.id, + notificationId: notification.messageId, + sent: notification.created, + sender: { + id: notification.senderId, + }, +}) diff --git a/libs/nest/pagination/src/lib/paginate.ts b/libs/nest/pagination/src/lib/paginate.ts index e289af92c053..87410488a3f0 100644 --- a/libs/nest/pagination/src/lib/paginate.ts +++ b/libs/nest/pagination/src/lib/paginate.ts @@ -117,6 +117,7 @@ export interface PaginateInput { primaryKeyField: string orderOption: any where?: any + attributes?: any after: string before?: string limit: number @@ -138,6 +139,7 @@ export async function paginate({ after, before, limit, + attributes, ...queryArgs }: PaginateInput): Promise<{ totalCount: number @@ -164,6 +166,7 @@ export async function paginate({ where: paginationWhere, limit, order, + attributes, ...queryArgs, } diff --git a/libs/portals/admin/service-desk/src/lib/messages.ts b/libs/portals/admin/service-desk/src/lib/messages.ts index 8560dcf08ef2..9e9c4008fcfd 100644 --- a/libs/portals/admin/service-desk/src/lib/messages.ts +++ b/libs/portals/admin/service-desk/src/lib/messages.ts @@ -65,6 +65,14 @@ export const m = defineMessages({ id: 'admin-portal.service-desk:info', defaultMessage: 'Upplýsingar: ', }, + notifications: { + id: 'admin-portal.service-desk:notifications', + defaultMessage: 'Tilkynningar: ', + }, + loadMore: { + id: 'admin-portal.service-desk:load-more', + defaultMessage: 'Sjá meira', + }, email: { id: 'admin-portal.service-desk:email', defaultMessage: 'Netfang', diff --git a/libs/portals/admin/service-desk/src/screens/User/User.graphql b/libs/portals/admin/service-desk/src/screens/User/User.graphql index 1d65b549f8b6..dd1f8410069b 100644 --- a/libs/portals/admin/service-desk/src/screens/User/User.graphql +++ b/libs/portals/admin/service-desk/src/screens/User/User.graphql @@ -32,3 +32,24 @@ mutation UpdateUserProfile( locale } } + +query GetAdminNotifications($nationalId: String!, $input: NotificationsInput!) { + adminNotifications(nationalId: $nationalId, input: $input) { + data { + id + notificationId + sender { + id + logoUrl + } + sent + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + totalCount + } +} diff --git a/libs/portals/admin/service-desk/src/screens/User/User.tsx b/libs/portals/admin/service-desk/src/screens/User/User.tsx index 7b16758ac36a..3a132e6512df 100644 --- a/libs/portals/admin/service-desk/src/screens/User/User.tsx +++ b/libs/portals/admin/service-desk/src/screens/User/User.tsx @@ -1,18 +1,34 @@ import format from 'date-fns/format' import { useLoaderData, useNavigate, useRevalidator } from 'react-router-dom' -import { ActionCard, Box, Stack, Text } from '@island.is/island-ui/core' +import { + ActionCard, + Box, + Stack, + Text, + Table as T, + LoadingDots, + SkeletonLoader, +} from '@island.is/island-ui/core' import { useLocale } from '@island.is/localization' import { BackButton } from '@island.is/portals/admin/core' import { IntroHeader, formatNationalId } from '@island.is/portals/core' import { dateFormat } from '@island.is/shared/constants' +import InfiniteScroll from 'react-infinite-scroller' -import { ServiceDeskPaths } from '../../lib/paths' -import { UserProfileResult } from './User.loader' import { m } from '../../lib/messages' -import { useUpdateUserProfileMutation } from './User.generated' +import { + GetAdminNotificationsQuery, + useUpdateUserProfileMutation, +} from './User.generated' import { UpdateUserProfileInput } from '@island.is/api/schema' import React from 'react' +import { isValidDate } from '@island.is/shared/utils' +import { useGetAdminNotificationsQuery } from './User.generated' +import { UserProfileResult } from './User.loader' +import { Problem } from '@island.is/react-spa/shared' + +const DEFAULT_PAGE_SIZE = 10 const User = () => { const { formatMessage } = useLocale() @@ -22,6 +38,18 @@ const User = () => { const [updateProfile] = useUpdateUserProfileMutation() const { revalidate } = useRevalidator() + const { + data: notifications, + loading, + error, + fetchMore, + } = useGetAdminNotificationsQuery({ + variables: { + nationalId: user.nationalId, + input: { limit: DEFAULT_PAGE_SIZE }, + }, + }) + const handleUpdateProfile = async (input: UpdateUserProfileInput) => { try { const updatedProfile = await updateProfile({ @@ -39,6 +67,38 @@ const User = () => { } } + const loadMore = async () => { + if ( + loading || + !notifications || + !notifications?.adminNotifications?.pageInfo.hasNextPage + ) { + return + } + + await fetchMore({ + variables: { + nationalId: user.nationalId, + input: { + limit: DEFAULT_PAGE_SIZE, + after: + notifications?.adminNotifications?.pageInfo.endCursor ?? undefined, + }, + }, + updateQuery: (prev, { fetchMoreResult }): GetAdminNotificationsQuery => { + return { + adminNotifications: { + ...fetchMoreResult.adminNotifications, + data: [ + ...(prev.adminNotifications?.data || []), + ...(fetchMoreResult.adminNotifications?.data || []), + ], + } as GetAdminNotificationsQuery['adminNotifications'], + } + }, + }) + } + return ( { + + {formatMessage(m.notifications)} + {error ? ( + + ) : loading ? ( + + ) : ( + + + + } + > + + + + ID + Message ID + Sender ID + Sent + + + + {notifications?.adminNotifications?.data.map( + (notification, index) => ( + + {notification.id} + {notification.notificationId} + {notification.sender.id} + + {notification.sent && + isValidDate(new Date(notification.sent)) + ? format(new Date(notification.sent), 'dd.MM.yyyy') + : ''} + + + ), + )} + + + + )} + ) } diff --git a/libs/portals/admin/service-desk/src/screens/Users/Users.action.ts b/libs/portals/admin/service-desk/src/screens/Users/Users.action.ts index 13454e204aaa..bfe8cbfb1104 100644 --- a/libs/portals/admin/service-desk/src/screens/Users/Users.action.ts +++ b/libs/portals/admin/service-desk/src/screens/Users/Users.action.ts @@ -1,23 +1,20 @@ import { z } from 'zod' -import { redirect } from 'react-router-dom' import { RawRouterActionResponse, WrappedActionFn, } from '@island.is/portals/core' import { - replaceParams, validateFormData, ValidateFormDataResult, } from '@island.is/react-spa/shared' -import { maskString, isSearchTermValid } from '@island.is/shared/utils' +import { isSearchTermValid } from '@island.is/shared/utils' import { GetPaginatedUserProfilesDocument, GetPaginatedUserProfilesQuery, type GetPaginatedUserProfilesQueryVariables, } from './Users.generated' -import { ServiceDeskPaths } from '../../lib/paths' export enum ErrorType { // Add more error types here when needed From 4704971a14bff6c75849976a5830465f239bb380 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rey=20J=C3=B3na?= Date: Thu, 5 Dec 2024 15:06:00 +0000 Subject: [PATCH 08/57] feat(native-app): update vehicle list query in app (#16824) * feat: update vehicleList endpoint to V2 * fix: remove comment * fix: update typo after fix from Hugsmidjan --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../fragments/vehicle.fragment.graphql | 37 ++----------------- .../app/src/graphql/queries/vehicles.graphql | 4 +- apps/native/app/src/screens/home/home.tsx | 6 +-- .../app/src/screens/home/vehicles-module.tsx | 14 +++---- .../vehicles/components/vehicle-item.tsx | 14 +++---- .../app/src/screens/vehicles/vehicles.tsx | 28 +++++++------- 6 files changed, 36 insertions(+), 67 deletions(-) diff --git a/apps/native/app/src/graphql/fragments/vehicle.fragment.graphql b/apps/native/app/src/graphql/fragments/vehicle.fragment.graphql index 244ad841d50a..0ab686f97ab0 100644 --- a/apps/native/app/src/graphql/fragments/vehicle.fragment.graphql +++ b/apps/native/app/src/graphql/fragments/vehicle.fragment.graphql @@ -1,39 +1,10 @@ -fragment VehicleFragment on VehiclesVehicle { - isCurrent +fragment VehicleFragment on VehicleListed { permno regno - vin - type - color - firstRegDate + make + colorName modelYear - productYear - registrationType - role - operatorStartDate - operatorEndDate - outOfUse - otherOwners - termination - buyerPersidno - ownerPersidno - vehicleStatus - useGroup - vehGroup - plateStatus - nextInspection { - nextInspectionDate - nextInspectionDateIfPassedInspectionToday - } - operatorNumber - primaryOperator - ownerSsid - ownerName - lastInspectionResult - lastInspectionDate - lastInspectionType - nextInspectionDate - nextAvailableMileageReadDate requiresMileageRegistration canRegisterMileage + nextMainInspection } diff --git a/apps/native/app/src/graphql/queries/vehicles.graphql b/apps/native/app/src/graphql/queries/vehicles.graphql index da2ad3c69212..a88bb380e3b7 100644 --- a/apps/native/app/src/graphql/queries/vehicles.graphql +++ b/apps/native/app/src/graphql/queries/vehicles.graphql @@ -1,5 +1,5 @@ -query ListVehicles($input: GetVehiclesForUserInput!) { - vehiclesList(input: $input) { +query ListVehiclesV2($input: GetVehiclesListV2Input!) { + vehiclesListV2(input: $input) { vehicleList { ...VehicleFragment } diff --git a/apps/native/app/src/screens/home/home.tsx b/apps/native/app/src/screens/home/home.tsx index 057b658de585..f228c0843a88 100644 --- a/apps/native/app/src/screens/home/home.tsx +++ b/apps/native/app/src/screens/home/home.tsx @@ -55,7 +55,7 @@ import { } from './licenses-module' import { OnboardingModule } from './onboarding-module' import { - useListVehiclesQuery, + useListVehiclesV2Query, validateVehiclesInitialData, VehiclesModule, } from './vehicles-module' @@ -174,13 +174,11 @@ export const MainHomeScreen: NavigationFunctionComponent = ({ skip: !airDiscountWidgetEnabled, }) - const vehiclesRes = useListVehiclesQuery({ + const vehiclesRes = useListVehiclesV2Query({ variables: { input: { page: 1, pageSize: 15, - showDeregeristered: false, - showHistory: false, }, }, skip: !vehiclesWidgetEnabled, diff --git a/apps/native/app/src/screens/home/vehicles-module.tsx b/apps/native/app/src/screens/home/vehicles-module.tsx index 02975f50a8e2..34c5c709c86d 100644 --- a/apps/native/app/src/screens/home/vehicles-module.tsx +++ b/apps/native/app/src/screens/home/vehicles-module.tsx @@ -17,8 +17,8 @@ import illustrationSrc from '../../assets/illustrations/le-moving-s4.png' import { navigateTo } from '../../lib/deep-linking' import { VehicleItem } from '../vehicles/components/vehicle-item' import { - ListVehiclesQuery, - useListVehiclesQuery, + ListVehiclesV2Query, + useListVehiclesV2Query, } from '../../graphql/types/schema' import { screenWidth } from '../../utils/dimensions' @@ -30,7 +30,7 @@ const validateVehiclesInitialData = ({ data, loading, }: { - data: ListVehiclesQuery | undefined + data: ListVehiclesV2Query | undefined loading: boolean }) => { if (loading) { @@ -38,7 +38,7 @@ const validateVehiclesInitialData = ({ } // Only show widget initially if there are vehicles that require mileage registration if ( - data?.vehiclesList?.vehicleList?.some( + data?.vehiclesListV2?.vehicleList?.some( (vehicle) => vehicle.requiresMileageRegistration, ) ) { @@ -49,7 +49,7 @@ const validateVehiclesInitialData = ({ } interface VehiclesModuleProps { - data: ListVehiclesQuery | undefined + data: ListVehiclesV2Query | undefined loading: boolean error?: ApolloError | undefined } @@ -59,7 +59,7 @@ const VehiclesModule = React.memo( const theme = useTheme() const intl = useIntl() - const vehicles = data?.vehiclesList?.vehicleList + const vehicles = data?.vehiclesListV2?.vehicleList // Reorder vehicles so vehicles that require mileage registration are shown first const reorderedVehicles = useMemo( @@ -170,4 +170,4 @@ const VehiclesModule = React.memo( }, ) -export { VehiclesModule, validateVehiclesInitialData, useListVehiclesQuery } +export { VehiclesModule, validateVehiclesInitialData, useListVehiclesV2Query } diff --git a/apps/native/app/src/screens/vehicles/components/vehicle-item.tsx b/apps/native/app/src/screens/vehicles/components/vehicle-item.tsx index 100dc377366b..afe6b8c607a5 100644 --- a/apps/native/app/src/screens/vehicles/components/vehicle-item.tsx +++ b/apps/native/app/src/screens/vehicles/components/vehicle-item.tsx @@ -3,7 +3,7 @@ import React from 'react' import { FormattedDate, FormattedMessage } from 'react-intl' import { SafeAreaView, TouchableHighlight, View, ViewStyle } from 'react-native' import styled, { useTheme } from 'styled-components/native' -import { ListVehiclesQuery } from '../../../graphql/types/schema' +import { ListVehiclesV2Query } from '../../../graphql/types/schema' import { navigateTo } from '../../../lib/deep-linking' function differenceInMonths(a: Date, b: Date) { @@ -11,7 +11,7 @@ function differenceInMonths(a: Date, b: Date) { } type VehicleListItem = NonNullable< - NonNullable['vehicleList'] + NonNullable['vehicleList'] >[0] const Cell = styled(TouchableHighlight)` @@ -31,8 +31,8 @@ export const VehicleItem = React.memo( style?: ViewStyle }) => { const theme = useTheme() - const nextInspection = item?.nextInspection?.nextInspectionDate - ? new Date(item?.nextInspection.nextInspectionDate) + const nextInspection = item?.nextMainInspection + ? new Date(item?.nextMainInspection) : null const isInspectionDeadline = @@ -51,14 +51,14 @@ export const VehicleItem = React.memo( onPress={() => { navigateTo(`/vehicle/`, { id: item.permno, - title: item.type, + title: item.make, }) }} > ['vehicleList'] + NonNullable['vehicleList'] >[0] type ListItem = @@ -61,8 +61,6 @@ const Empty = () => ( const input = { page: 1, pageSize: 10, - showDeregeristered: false, - showHistory: false, } export const VehiclesScreen: NavigationFunctionComponent = ({ @@ -77,7 +75,7 @@ export const VehiclesScreen: NavigationFunctionComponent = ({ const scrollY = useRef(new Animated.Value(0)).current const loadingTimeout = useRef>() - const res = useListVehiclesQuery({ + const res = useListVehiclesV2Query({ variables: { input, }, @@ -135,7 +133,9 @@ export const VehiclesScreen: NavigationFunctionComponent = ({ // Extract key of data const keyExtractor = useCallback( (item: ListItem, index: number) => - item.__typename === 'Skeleton' ? String(item.id) : `${item.vin}${index}`, + item.__typename === 'Skeleton' + ? String(item.id) + : `${item.permno}${index}`, [], ) @@ -147,7 +147,7 @@ export const VehiclesScreen: NavigationFunctionComponent = ({ __typename: 'Skeleton', })) } - return res?.data?.vehiclesList?.vehicleList || [] + return res?.data?.vehiclesListV2?.vehicleList || [] }, [res.data, res.loading]) return ( @@ -184,8 +184,8 @@ export const VehiclesScreen: NavigationFunctionComponent = ({ if (res.loading) { return } - const pageNumber = res.data?.vehiclesList?.paging?.pageNumber ?? 1 - const totalPages = res.data?.vehiclesList?.paging?.totalPages ?? 1 + const pageNumber = res.data?.vehiclesListV2?.paging?.pageNumber ?? 1 + const totalPages = res.data?.vehiclesListV2?.paging?.totalPages ?? 1 if (pageNumber >= totalPages) { return } @@ -200,11 +200,11 @@ export const VehiclesScreen: NavigationFunctionComponent = ({ }, updateQuery(prev, { fetchMoreResult }) { return { - vehiclesList: { - ...fetchMoreResult.vehiclesList, + vehiclesListV2: { + ...fetchMoreResult.vehiclesListV2, vehicleList: [ - ...(prev.vehiclesList?.vehicleList ?? []), - ...(fetchMoreResult.vehiclesList?.vehicleList ?? []), + ...(prev.vehiclesListV2?.vehicleList ?? []), + ...(fetchMoreResult.vehiclesListV2?.vehicleList ?? []), ], }, } From 2804be70aa9f78dbdd1c81a6c9038305d0033575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3n=20Bjarni=20=C3=93lafsson?= <92530555+jonbjarnio@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:24:25 +0000 Subject: [PATCH 09/57] fix(ojoi): Updated controllers and bugfixes (#17146) * Updated data schema to make less api calls, fixed bug when user selected a previous advert the inputs fields were not updating. Switched out controller components since we have our own. * Returning false instead of undefined. --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../components/input/OJOIInputController.tsx | 5 +- .../components/input/OJOISelectController.tsx | 68 ++++++---- .../src/fields/Advert.tsx | 96 +++++++------- .../src/fields/AdvertModal.tsx | 122 ++++++------------ .../src/fields/Preview.tsx | 11 +- .../src/fields/Publishing.tsx | 58 ++++----- .../src/fields/Summary.tsx | 38 ++---- .../src/hooks/useTypes.ts | 38 ++++-- .../src/lib/OJOIApplication.ts | 2 +- .../src/lib/dataSchema.ts | 43 +++--- .../src/lib/types.ts | 9 +- .../src/lib/utils.ts | 6 + .../src/screens/AdvertScreen.tsx | 15 +-- 13 files changed, 234 insertions(+), 277 deletions(-) diff --git a/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIInputController.tsx b/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIInputController.tsx index 44bee990cc2e..ce66ffa0bc15 100644 --- a/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIInputController.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIInputController.tsx @@ -1,6 +1,5 @@ -import { SkeletonLoader } from '@island.is/island-ui/core' +import { Input, SkeletonLoader } from '@island.is/island-ui/core' import { useLocale } from '@island.is/localization' -import { InputController } from '@island.is/shared/form-fields' import { MessageDescriptor } from 'react-intl' import { OJOI_INPUT_HEIGHT } from '../../lib/constants' import { useApplication } from '../../hooks/useUpdateApplication' @@ -60,7 +59,7 @@ export const OJOIInputController = ({ } return ( - = { label: string - value: string + value: T } -type Props = { +type Props = { name: string label: string | MessageDescriptor placeholder: string | MessageDescriptor - options?: OJOISelectControllerOption[] - defaultValue?: string + options?: SelectOption[] + defaultValue?: T loading?: boolean applicationId: string disabled?: boolean - onChange?: (label: string, value: string) => void + onBeforeChange?: (answers: OJOIApplication['answers'], value: T) => void + onChange?: (value: T) => void } -export const OJOISelectController = ({ +export const OJOISelectController = ({ name, label, placeholder, @@ -33,30 +36,41 @@ export const OJOISelectController = ({ loading, applicationId, disabled, + onBeforeChange, onChange, -}: Props) => { +}: Props) => { const { formatMessage: f } = useLocale() - const { updateApplication, application } = useApplication({ applicationId }) + const { updateApplication, application } = useApplication({ + applicationId, + }) + + const { setValue } = useFormContext() const placeholderText = typeof placeholder === 'string' ? placeholder : f(placeholder) const labelText = typeof label === 'string' ? label : f(label) - const handleChange = (label: string, value: string) => { + const handleChange = (value: T) => { const currentAnswers = structuredClone(application.answers) const newAnswers = set(currentAnswers, name, value) + onBeforeChange && onBeforeChange(newAnswers, value) - // we must reset the selected typeId if the department changes - if (name === InputFields.advert.departmentId) { - set(newAnswers, InputFields.advert.typeId, '') - } - + setValue(name, value) updateApplication(newAnswers) - onChange && onChange(label, value) + onChange && onChange(value) } + const defaultVal = getValueViaPath(application.answers, name, defaultValue) + const defaultOpt = options?.find((opt) => { + if (isBaseEntity(opt.value) && isBaseEntity(defaultVal)) { + return opt.value.id === defaultVal.id + } + + return false + }) + if (loading) { return ( handleChange(opt.label, opt.value)} + defaultValue={defaultOpt} + onChange={(opt) => { + if (!opt?.value) return + return handleChange(opt.value) + }} /> ) } diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/Advert.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/Advert.tsx index fe9cc36440ae..9cb1b1371782 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/Advert.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/Advert.tsx @@ -1,4 +1,3 @@ -import { useCallback } from 'react' import { InputFields, OJOIFieldBaseProps } from '../lib/types' import { Box } from '@island.is/island-ui/core' import { FormGroup } from '../components/form/FormGroup' @@ -15,86 +14,80 @@ import set from 'lodash/set' import { HTMLEditor } from '../components/htmlEditor/HTMLEditor' import { getAdvertMarkup } from '../lib/utils' -type Props = OJOIFieldBaseProps & { - timeStamp: string -} - -export const Advert = ({ application, timeStamp }: Props) => { +export const Advert = ({ application }: OJOIFieldBaseProps) => { const { setValue } = useFormContext() - const { application: currentApplication, updateApplication } = useApplication( - { - applicationId: application.id, - }, - ) + const { application: currentApplication } = useApplication({ + applicationId: application.id, + }) const { departments, loading: loadingDepartments } = useDepartments() const { - useLazyTypes, + getLazyTypes, types, loading: loadingTypes, } = useTypes({ - initalDepartmentId: application.answers?.advert?.departmentId, + initalDepartmentId: application.answers?.advert?.department?.id, }) - const handleDepartmentChange = useCallback( - (value: string) => { - // eslint-disable-next-line react-hooks/rules-of-hooks - useLazyTypes({ - params: { - department: value, - pageSize: 100, - }, - }) - }, - [useLazyTypes], - ) - - const updateTypeHandler = (name: string, id: string) => { - let currentAnswers = structuredClone(currentApplication.answers) - currentAnswers = set(currentAnswers, InputFields.advert.typeName, name) - - currentAnswers = set(currentAnswers, InputFields.advert.typeId, id) - - updateApplication(currentAnswers) - } - const titlePreview = getAdvertMarkup({ - type: currentApplication.answers.advert?.typeName, + type: currentApplication.answers.advert?.type?.title, title: currentApplication.answers.advert?.title, }) + const departmentOptions = departments?.map((d) => ({ + label: d.title, + value: { + id: d.id, + title: d.title, + slug: d.slug, + }, + })) + + const typeOptions = types?.map((d) => ({ + label: d.title, + value: { + id: d.id, + title: d.title, + slug: d.slug, + }, + })) + return ( <> ({ - label: d.title, - value: d.id, - }))} - onChange={(_, value) => handleDepartmentChange(value)} + options={departmentOptions} + defaultValue={application.answers?.advert?.department} + onBeforeChange={(answers) => { + setValue(InputFields.advert.type, null) + set(answers, InputFields.advert.type, null) + }} + onChange={(value) => + getLazyTypes({ + variables: { + params: { + department: value.id, + pageSize: 100, + }, + }, + }) + } /> ({ - label: d.title, - value: d.id, - }))} - onChange={(label, value) => { - updateTypeHandler(label, value) - }} + options={typeOptions} /> @@ -132,7 +125,6 @@ export const Advert = ({ application, timeStamp }: Props) => { applicationId={application.id} name={InputFields.advert.html} defaultValue={currentApplication.answers?.advert?.html} - editorKey={timeStamp} // we have use setValue from useFormContext to update the value // because this is not a controlled component onChange={(value) => setValue(InputFields.advert.html, value)} diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/AdvertModal.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/AdvertModal.tsx index ba50ca2999dc..8327cefa5e93 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/AdvertModal.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/AdvertModal.tsx @@ -1,5 +1,4 @@ import { - AlertMessage, Box, Button, Icon, @@ -23,11 +22,10 @@ import { DEFAULT_PAGE_SIZE, OJOI_INPUT_HEIGHT, } from '../lib/constants' -import { useAdvert } from '../hooks/useAdvert' import debounce from 'lodash/debounce' -import set from 'lodash/set' import { InputFields } from '../lib/types' import { useFormContext } from 'react-hook-form' +import { OfficialJournalOfIcelandAdvert } from '@island.is/api/schema' type Props = { applicationId: string visible: boolean @@ -35,55 +33,30 @@ type Props = { onConfirmChange?: () => void } -type UpdateAdvertFields = { - title: string - departmentId: string - typeId: string - html: string - categories: string[] -} - export const AdvertModal = ({ applicationId, visible, setVisible, onConfirmChange, }: Props) => { - const [page, setPage] = useState(DEFAULT_PAGE) - const [search, setSearch] = useState('') - const [selectedAdvertId, setSelectedAdvertId] = useState(null) - const { formatMessage: f } = useLocale() const { setValue } = useFormContext() const { application, updateApplication } = useApplication({ applicationId, }) + const [page, setPage] = useState(DEFAULT_PAGE) + const [search, setSearch] = useState('') + const [selectedAdvert, setSelectedAdvert] = + useState(null) + const { adverts, paging, loading } = useAdverts({ page: page, search: search, }) - const [updateAdvertFields, setUpdateAdvertFields] = - useState(null) - - const { loading: loadingAdvert, error: advertError } = useAdvert({ - advertId: selectedAdvertId, - onCompleted: (ad) => { - setUpdateAdvertFields({ - title: ad.title, - departmentId: ad.department.id, - typeId: ad.type.id, - html: ad.document.html, - categories: ad.categories.map((c) => c.id), - }) - }, - }) - - const disableConfirmButton = !selectedAdvertId || !!advertError - - const onSelectAdvert = (advertId: string) => { - setSelectedAdvertId(advertId) + const onSelectAdvert = (advert: OfficialJournalOfIcelandAdvert) => { + setSelectedAdvert(advert) } const onSearchChange = (value: string) => { @@ -97,46 +70,41 @@ export const AdvertModal = ({ debouncedSearch(e.target.value) } - const onConfirm = () => { - if (!updateAdvertFields) { + const onConfirm = (advert: OfficialJournalOfIcelandAdvert | null) => { + if (!advert) { return } - const currentAnswers = structuredClone(application.answers) + const clean = (obj: { + __typename?: string + id: string + title: string + slug: string + }) => { + const { __typename: _, ...rest } = obj + return rest + } + + const department = clean(advert.department) + const type = clean(advert.type) - let updatedAnswers = set( - currentAnswers, - InputFields.advert.title, - updateAdvertFields.title, - ) - updatedAnswers = set( - updatedAnswers, - InputFields.advert.departmentId, - updateAdvertFields.departmentId, - ) - updatedAnswers = set( - updatedAnswers, - InputFields.advert.typeId, - updateAdvertFields.typeId, - ) - updatedAnswers = set( - updatedAnswers, - InputFields.advert.html, - updateAdvertFields.html, - ) - updatedAnswers = set( - updatedAnswers, - InputFields.advert.categories, - updateAdvertFields.categories, - ) + const categories = advert.categories.map((category) => clean(category)) - setValue(InputFields.advert.title, updateAdvertFields.title) - setValue(InputFields.advert.departmentId, updateAdvertFields.departmentId) - setValue(InputFields.advert.typeId, updateAdvertFields.typeId) - setValue(InputFields.advert.html, updateAdvertFields.html) - setValue(InputFields.advert.categories, updateAdvertFields.categories) + setValue(InputFields.advert.department, department) + setValue(InputFields.advert.type, type) + setValue(InputFields.advert.title, advert.title) + + updateApplication({ + ...application.answers, + advert: { + department, + type, + categories, + title: advert.title, + html: advert.document.html, + }, + }) - updateApplication(updatedAnswers) onConfirmChange && onConfirmChange() setVisible(false) } @@ -173,13 +141,6 @@ export const AdvertModal = ({ onChange={handleSearchChange} /> - {!!advertError && ( - - )} onSelectAdvert(advert.id)} + checked={selectedAdvert?.id === advert.id} + onChange={() => onSelectAdvert(advert)} /> ))} @@ -233,9 +194,8 @@ export const AdvertModal = ({ {f(general.cancel)} onConfirm(selectedAdvert)} > {f(general.confirm)} diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/Preview.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/Preview.tsx index 3255b2e091e5..82257eeee648 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/Preview.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/Preview.tsx @@ -21,7 +21,6 @@ import { import { Routes, SignatureTypes } from '../lib/constants' import { useApplication } from '../hooks/useUpdateApplication' import { advert, error, preview, signatures } from '../lib/messages' -import { useType } from '../hooks/useType' import { previewValidationSchema, signatureValidationSchema, @@ -39,10 +38,6 @@ export const Preview = ({ application, goToScreen }: OJOIFieldBaseProps) => { const { formatMessage: f } = useLocale() - const { type } = useType({ - typeId: currentApplication.answers.advert?.typeId, - }) - const { fetchPdf, error: pdfError, @@ -60,7 +55,7 @@ export const Preview = ({ application, goToScreen }: OJOIFieldBaseProps) => { const url = URL.createObjectURL(blob) let downloadName - const type = currentApplication.answers.advert?.typeName + const type = currentApplication.answers.advert?.type?.title if (type) { downloadName = type.replace('.', '') } @@ -103,14 +98,14 @@ export const Preview = ({ application, goToScreen }: OJOIFieldBaseProps) => { }) const advertMarkup = getAdvertMarkup({ - type: type?.title, + type: currentApplication.answers.advert?.type?.title, title: currentApplication.answers.advert?.title, html: currentApplication.answers.advert?.html, }) const hasMarkup = !!currentApplication.answers.advert?.html || - type?.title || + currentApplication.answers.advert?.type?.title || currentApplication.answers.advert?.title const combinedHtml = hasMarkup diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/Publishing.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/Publishing.tsx index b2f7d27726a9..2b2b845b4092 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/Publishing.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/Publishing.tsx @@ -8,6 +8,7 @@ import { AlertMessage, Box, Icon, + Inline, Select, SkeletonLoader, Tag, @@ -18,6 +19,8 @@ import set from 'lodash/set' import addYears from 'date-fns/addYears' import { addWeekdays, getFastTrack, getWeekendDates } from '../lib/utils' import { useState } from 'react' +import { baseEntitySchema } from '../lib/dataSchema' +import { z } from 'zod' export const Publishing = ({ application }: OJOIFieldBaseProps) => { const { formatMessage: f } = useLocale() @@ -52,7 +55,7 @@ export const Publishing = ({ application }: OJOIFieldBaseProps) => { getFastTrack(new Date(defaultDate)).fastTrack, ) - const onCategoryChange = (value?: string) => { + const onCategoryChange = (value?: z.infer) => { setIsUpdatingCategory(true) if (!value) { setIsUpdatingCategory(false) @@ -62,8 +65,8 @@ export const Publishing = ({ application }: OJOIFieldBaseProps) => { const currentAnswers = structuredClone(currentApplication.answers) const selectedCategories = currentAnswers.advert?.categories || [] - const newCategories = selectedCategories.includes(value) - ? selectedCategories.filter((c) => c !== value) + const newCategories = selectedCategories.find((cat) => cat.id === value.id) + ? selectedCategories.filter((c) => c.id !== value.id) : [...selectedCategories, value] const updatedAnswers = set( @@ -77,19 +80,12 @@ export const Publishing = ({ application }: OJOIFieldBaseProps) => { }) } - const defaultCategory = { - label: f(publishing.inputs.contentCategories.placeholder), - value: '', - } - const mappedCategories = categories?.map((c) => ({ label: c.title, - value: c.id, + value: c, })) - const selectedCategories = categories?.filter((c) => - currentApplication.answers.advert?.categories?.includes(c.id), - ) + const selectedCategories = currentApplication.answers.advert?.categories return ( @@ -124,30 +120,26 @@ export const Publishing = ({ application }: OJOIFieldBaseProps) => { size="sm" label={f(publishing.inputs.contentCategories.label)} backgroundColor="blue" - defaultValue={defaultCategory} options={mappedCategories} + defaultValue={mappedCategories?.[0]} onChange={(opt) => onCategoryChange(opt?.value)} /> - - {selectedCategories?.map((c) => ( - onCategoryChange(c.id)} - outlined - key={c.id} - > - - {c.title} - - - - ))} + + + {selectedCategories?.map((c) => ( + onCategoryChange(c)} + outlined + key={c.id} + > + + {c.title} + + + + ))} + > )} diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/Summary.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/Summary.tsx index 946abdbf7d5b..dc1799e2a3ef 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/Summary.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/Summary.tsx @@ -14,12 +14,9 @@ import { useUserInfo } from '@island.is/react-spa/bff' import { useEffect } from 'react' import { ZodCustomIssue } from 'zod' import { Property } from '../components/property/Property' -import { useCategories } from '../hooks/useCategories' -import { useDepartment } from '../hooks/useDepartment' import { usePrice } from '../hooks/usePrice' -import { useType } from '../hooks/useType' import { useApplication } from '../hooks/useUpdateApplication' -import { MINIMUM_WEEKDAYS, Routes } from '../lib/constants' +import { Routes } from '../lib/constants' import { advertValidationSchema, publishingValidationSchema, @@ -28,7 +25,7 @@ import { import { advert, error, publishing, summary } from '../lib/messages' import { signatures } from '../lib/messages/signatures' import { OJOIFieldBaseProps } from '../lib/types' -import { addWeekdays, getFastTrack, parseZodIssue } from '../lib/utils' +import { getFastTrack, parseZodIssue } from '../lib/utils' export const Summary = ({ application, @@ -42,26 +39,11 @@ export const Summary = ({ const user = useUserInfo() - const { type, loading: loadingType } = useType({ - typeId: currentApplication.answers.advert?.typeId, - }) - const { price, loading: loadingPrice } = usePrice({ applicationId: application.id, }) - const { department, loading: loadingDepartment } = useDepartment({ - departmentId: currentApplication.answers.advert?.departmentId, - }) - - const { categories, loading: loadingCategories } = useCategories() - - const selectedCategories = categories?.filter((c) => - currentApplication.answers?.advert?.categories?.includes(c.id), - ) - - const today = new Date() - const estimatedDate = addWeekdays(today, MINIMUM_WEEKDAYS) + const selectedCategories = application.answers?.advert?.categories const advertValidationCheck = advertValidationSchema.safeParse( currentApplication.answers, @@ -86,6 +68,10 @@ export const Summary = ({ } else { setSubmitButtonDisabled && setSubmitButtonDisabled(true) } + + return () => { + setSubmitButtonDisabled && setSubmitButtonDisabled(false) + } }, [ advertValidationCheck, signatureValidationCheck, @@ -227,18 +213,16 @@ export const Summary = ({ value={user.profile.name} /> c.title).join(', ')} /> @@ -289,7 +272,6 @@ export const Summary = ({ } /> diff --git a/libs/application/templates/official-journal-of-iceland/src/hooks/useTypes.ts b/libs/application/templates/official-journal-of-iceland/src/hooks/useTypes.ts index ca15965c5c74..8cd4ff5678ed 100644 --- a/libs/application/templates/official-journal-of-iceland/src/hooks/useTypes.ts +++ b/libs/application/templates/official-journal-of-iceland/src/hooks/useTypes.ts @@ -1,4 +1,4 @@ -import { NetworkStatus, useQuery } from '@apollo/client' +import { useLazyQuery, useQuery } from '@apollo/client' import { OfficialJournalOfIcelandAdvertsTypesResponse } from '@island.is/api/schema' import { TYPES_QUERY } from '../graphql/queries' @@ -40,21 +40,35 @@ export const useTypes = ({ params.pageSize = 1000 } - const { data, loading, error, refetch, networkStatus } = useQuery< - TypesResponse, - TypesVariables - >(TYPES_QUERY, { - variables: { - params: params, + const { data, loading, error } = useQuery( + TYPES_QUERY, + { + variables: { + params: params, + }, + onCompleted: onCompleted, }, - notifyOnNetworkStatusChange: true, - onCompleted: onCompleted, + ) + + const [ + getLazyTypes, + { data: lazyTypes, loading: lazyTypesLoading, error: lazyTypesError }, + ] = useLazyQuery(TYPES_QUERY, { + fetchPolicy: 'network-only', }) + const currentTypes = lazyTypes + ? lazyTypes.officialJournalOfIcelandTypes.types + : data?.officialJournalOfIcelandTypes.types + return { - useLazyTypes: refetch, - types: data?.officialJournalOfIcelandTypes.types, - loading: loading || networkStatus === NetworkStatus.refetch, + lazyTypes: lazyTypes?.officialJournalOfIcelandTypes.types, + lazyTypesLoading, + lazyTypesError, + getLazyTypes, + types: currentTypes, + initalTypes: data?.officialJournalOfIcelandTypes.types, + loading: loading || lazyTypesLoading, error, } } diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/OJOIApplication.ts b/libs/application/templates/official-journal-of-iceland/src/lib/OJOIApplication.ts index 3e7cc7fc9289..5baf45b63a8d 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/OJOIApplication.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/OJOIApplication.ts @@ -41,7 +41,7 @@ const getApplicationName = (application: Application) => { const type = getValueViaPath( application.answers, - InputFields.advert.typeName, + `${InputFields.advert.type}.title`, '', ) diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/dataSchema.ts b/libs/application/templates/official-journal-of-iceland/src/lib/dataSchema.ts index f617921467b5..fed28c133461 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/dataSchema.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/dataSchema.ts @@ -43,6 +43,12 @@ export const regularSignatureSchema = z .array(regularSignatureItemSchema) .optional() +export const baseEntitySchema = z.object({ + id: z.string(), + title: z.string(), + slug: z.string(), +}) + export const signatureInstitutionSchema = z.enum(['institution', 'date']) export const committeeSignatureSchema = regularSignatureItemSchema @@ -61,13 +67,12 @@ export const channelSchema = z const advertSchema = z .object({ - departmentId: z.string().optional(), - typeName: z.string().optional(), - typeId: z.string().optional(), + department: baseEntitySchema.optional(), + type: baseEntitySchema.optional().nullable(), title: z.string().optional(), html: z.string().optional(), requestedDate: z.string().optional(), - categories: z.array(z.string()).optional(), + categories: z.array(baseEntitySchema).optional(), channels: z.array(channelSchema).optional(), message: z.string().optional(), additions: additionSchema.optional(), @@ -108,16 +113,16 @@ export const partialSchema = z.object({ // We make properties optional to throw custom error messages export const advertValidationSchema = z.object({ advert: z.object({ - departmentId: z - .string() + department: baseEntitySchema .optional() - .refine((value) => value && value.length > 0, { + .nullable() + .refine((value) => value !== null && value !== undefined, { params: error.missingDepartment, }), - typeId: z - .string() + type: baseEntitySchema .optional() - .refine((value) => value && value.length > 0, { + .nullable() + .refine((value) => value !== null && value !== undefined, { params: error.missingType, }), title: z @@ -137,17 +142,17 @@ export const advertValidationSchema = z.object({ export const previewValidationSchema = z.object({ advert: z.object({ - departmentId: z - .string() + department: baseEntitySchema .optional() - .refine((value) => value && value.length > 0, { - params: error.missingPreviewDepartment, + .nullable() + .refine((value) => value !== null && value !== undefined, { + params: error.missingDepartment, }), - typeId: z - .string() + type: baseEntitySchema .optional() - .refine((value) => value && value.length > 0, { - params: error.missingPreviewType, + .nullable() + .refine((value) => value !== null && value !== undefined, { + params: error.missingType, }), title: z .string() @@ -173,7 +178,7 @@ export const publishingValidationSchema = z.object({ params: error.missingRequestedDate, }), categories: z - .array(z.string()) + .array(baseEntitySchema) .optional() .refine((value) => Array.isArray(value) && value.length > 0, { params: error.noCategorySelected, diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/types.ts b/libs/application/templates/official-journal-of-iceland/src/lib/types.ts index ccae26abdb38..385aec190a50 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/types.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/types.ts @@ -11,9 +11,8 @@ export const InputFields = { approveExternalData: 'requirements.approveExternalData', }, [Routes.ADVERT]: { - departmentId: 'advert.departmentId', - typeName: 'advert.typeName', - typeId: 'advert.typeId', + department: 'advert.department', + type: 'advert.type', title: 'advert.title', html: 'advert.html', requestedDate: 'advert.requestedDate', @@ -39,8 +38,8 @@ export const InputFields = { export const RequiredInputFieldsNames = { [Routes.ADVERT]: { - departmentId: 'Deild', - typeId: 'Tegund', + department: 'Deild', + type: 'Tegund', title: 'Titill', html: 'Auglýsing', requestedDate: 'Útgáfudagur', diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/utils.ts b/libs/application/templates/official-journal-of-iceland/src/lib/utils.ts index 2e6f17b53156..96d1b2fb0893 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/utils.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/utils.ts @@ -3,6 +3,7 @@ import addYears from 'date-fns/addYears' import { z } from 'zod' import { additionSchema, + baseEntitySchema, committeeSignatureSchema, memberItemSchema, partialSchema, @@ -130,6 +131,11 @@ export const getSignatureDefaultValues = (signature: any, index?: number) => { return { institution: signature.institution, date: signature.date } } +export const isBaseEntity = ( + entity: unknown, +): entity is z.infer => + baseEntitySchema.safeParse(entity).success + export const isAddition = ( addition: unknown, ): addition is z.infer => diff --git a/libs/application/templates/official-journal-of-iceland/src/screens/AdvertScreen.tsx b/libs/application/templates/official-journal-of-iceland/src/screens/AdvertScreen.tsx index 56a115f61241..92d9a3911d77 100644 --- a/libs/application/templates/official-journal-of-iceland/src/screens/AdvertScreen.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/screens/AdvertScreen.tsx @@ -11,13 +11,6 @@ export const AdvertScreen = (props: OJOIFieldBaseProps) => { const { formatMessage: f } = useLocale() const [modalVisible, setModalVisability] = useState(false) - const generateTimestamp = () => new Date().toISOString() - - /** - * This state here is for force rerendering of the HTML editor when a value is received from the modal - */ - const [timestamp, setTimestamp] = useState(generateTimestamp()) - return ( { } > - + setTimestamp(generateTimestamp())} + onConfirmChange={() => { + setTimeout(() => { + props.refetch && props.refetch() + }, 300) + }} /> ) From 4281d75e88c2fb187652ecaddb129f1274c37680 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:15:26 +0000 Subject: [PATCH 10/57] fix(web): WHODAS calculator - Add parenthesis to max score calculation (#17142) * Add parenthesis to equation * Remove console.log * Hide breakdown for bracket 1 --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../connected/WHODAS/Calculator.tsx | 67 ++++++++++--------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/apps/web/components/connected/WHODAS/Calculator.tsx b/apps/web/components/connected/WHODAS/Calculator.tsx index 109eb573ccfa..53d09cfad3cb 100644 --- a/apps/web/components/connected/WHODAS/Calculator.tsx +++ b/apps/web/components/connected/WHODAS/Calculator.tsx @@ -191,36 +191,42 @@ const WHODASResults = ({ - - - {formatMessage(m.results.breakdownHeading)} - - - {results.steps.map((step) => ( - - - {step.title} - {formatScore(step.scoreForStep)} + {bracket > 1 && ( + + + + + {formatMessage(m.results.breakdownHeading)} + + + {results.steps.map((step) => ( + + + {step.title} + {formatScore(step.scoreForStep)} + + + ))} + + + + {formatMessage(m.results.totalScore)} + + {formatScore(totalScore)} - - ))} - - - - {formatMessage(m.results.totalScore)} - - {formatScore(totalScore)} - - + + + + {formatMessage(m.results.resultDisclaimer)} + + + )} - - {formatMessage(m.results.resultDisclaimer)} - ) } @@ -246,9 +252,10 @@ export const WHODASCalculator = ({ slice }: WHODASCalculatorProps) => { description, maxScorePossible: questions.reduce( (prev, acc) => - prev + acc.answerOptions.length > 0 + prev + + (acc.answerOptions.length > 0 ? acc.answerOptions[acc.answerOptions.length - 1].score - : 0, + : 0), 0, ), questions: questions.map(() => ({ From 2421d10c9a6d64308c465ce22f0a40b3415437ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Thu, 5 Dec 2024 21:26:35 +0000 Subject: [PATCH 11/57] feat(j-s): Upload Additional Files to Court (#17152) --- .../src/app/modules/case/case.service.ts | 4 ++++ .../src/app/modules/file/file.service.ts | 23 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/apps/judicial-system/backend/src/app/modules/case/case.service.ts b/apps/judicial-system/backend/src/app/modules/case/case.service.ts index 6454603536c8..d20ffd16d104 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.service.ts @@ -775,12 +775,16 @@ export class CaseService { CaseFileCategory.CRIMINAL_RECORD, CaseFileCategory.COST_BREAKDOWN, CaseFileCategory.CASE_FILE, + CaseFileCategory.PROSECUTOR_CASE_FILE, + CaseFileCategory.DEFENDANT_CASE_FILE, ] : [ CaseFileCategory.INDICTMENT, CaseFileCategory.CRIMINAL_RECORD, CaseFileCategory.COST_BREAKDOWN, CaseFileCategory.CASE_FILE, + CaseFileCategory.PROSECUTOR_CASE_FILE, + CaseFileCategory.DEFENDANT_CASE_FILE, ] const deliverCaseFileToCourtMessages = diff --git a/apps/judicial-system/backend/src/app/modules/file/file.service.ts b/apps/judicial-system/backend/src/app/modules/file/file.service.ts index 397f488a5c0c..fbf3b50a9d5e 100644 --- a/apps/judicial-system/backend/src/app/modules/file/file.service.ts +++ b/apps/judicial-system/backend/src/app/modules/file/file.service.ts @@ -148,12 +148,12 @@ export class FileService { courtDocumentFolder = CourtDocumentFolder.INDICTMENT_DOCUMENTS break case CaseFileCategory.COURT_RECORD: - courtDocumentFolder = CourtDocumentFolder.COURT_DOCUMENTS - break case CaseFileCategory.RULING: courtDocumentFolder = CourtDocumentFolder.COURT_DOCUMENTS break case CaseFileCategory.CASE_FILE: + case CaseFileCategory.PROSECUTOR_CASE_FILE: + case CaseFileCategory.DEFENDANT_CASE_FILE: case undefined: case null: courtDocumentFolder = CourtDocumentFolder.CASE_DOCUMENTS @@ -388,6 +388,25 @@ export class FileService { }, ]) } + + if ( + isIndictmentCase(theCase.type) && + file.category && + [ + CaseFileCategory.PROSECUTOR_CASE_FILE, + CaseFileCategory.DEFENDANT_CASE_FILE, + ].includes(file.category) + ) { + await this.messageService.sendMessagesToQueue([ + { + type: MessageType.DELIVERY_TO_COURT_CASE_FILE, + user, + caseId: theCase.id, + elementId: file.id, + }, + ]) + } + return file } From 090c75d91795f6e683820c35d697400d8e4779f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Thu, 5 Dec 2024 22:11:10 +0000 Subject: [PATCH 12/57] feat(j-s): Show indictment decision type to public prosecutor users (#17079) * Add court abbr to getAllCases call * Refactoring * Refactoring * Refactoring * Refactoring * Add indictment decision to tables --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../case-list/models/caseList.model.ts | 3 + .../src/app/modules/case/case.service.ts | 1 + .../case/interceptors/caseList.interceptor.ts | 2 +- .../web/messages/Core/tables.ts | 11 ++++ .../web/src/components/Table/Table.tsx | 40 ++++++++----- .../Tables/CasesAwaitingReview.tsx | 26 +++++++-- .../Tables/CasesForReview.tsx | 58 ++++++++++++++----- .../PublicProsecutor/Tables/CasesReviewed.tsx | 50 ++++++++++++---- .../web/src/routes/Shared/Cases/cases.graphql | 4 ++ .../formatters/src/lib/formatters.ts | 23 ++++++++ 10 files changed, 172 insertions(+), 46 deletions(-) diff --git a/apps/judicial-system/api/src/app/modules/case-list/models/caseList.model.ts b/apps/judicial-system/api/src/app/modules/case-list/models/caseList.model.ts index 3b2982563026..6372ac2e17d2 100644 --- a/apps/judicial-system/api/src/app/modules/case-list/models/caseList.model.ts +++ b/apps/judicial-system/api/src/app/modules/case-list/models/caseList.model.ts @@ -77,6 +77,9 @@ export class CaseListEntry { @Field(() => String, { nullable: true }) readonly prosecutorPostponedAppealDate?: string + @Field(() => Institution, { nullable: true }) + readonly court?: Institution + @Field(() => User, { nullable: true }) readonly creatingProsecutor?: User diff --git a/apps/judicial-system/backend/src/app/modules/case/case.service.ts b/apps/judicial-system/backend/src/app/modules/case/case.service.ts index d20ffd16d104..097e9c4d5b4a 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.service.ts @@ -409,6 +409,7 @@ export const include: Includeable[] = [ ] export const caseListInclude: Includeable[] = [ + { model: Institution, as: 'court' }, { model: Institution, as: 'prosecutorsOffice' }, { model: Defendant, diff --git a/apps/judicial-system/backend/src/app/modules/case/interceptors/caseList.interceptor.ts b/apps/judicial-system/backend/src/app/modules/case/interceptors/caseList.interceptor.ts index dbc56797bcb1..bee2da1a9852 100644 --- a/apps/judicial-system/backend/src/app/modules/case/interceptors/caseList.interceptor.ts +++ b/apps/judicial-system/backend/src/app/modules/case/interceptors/caseList.interceptor.ts @@ -23,7 +23,6 @@ export class CaseListInterceptor implements NestInterceptor { // WARNING: Be careful when adding to this list. No sensitive information should be returned. // If you need to add sensitive information, then you should consider adding a new endpoint // for defenders and other user roles that are not allowed to see sensitive information. - return { id: theCase.id, created: theCase.created, @@ -65,6 +64,7 @@ export class CaseListInterceptor implements NestInterceptor { indictmentRulingDecision: theCase.indictmentRulingDecision, courtSessionType: theCase.courtSessionType, eventLogs: theCase.eventLogs, + court: theCase.court, } }), ), diff --git a/apps/judicial-system/web/messages/Core/tables.ts b/apps/judicial-system/web/messages/Core/tables.ts index 66879b64ce6a..b2b2500d65bb 100644 --- a/apps/judicial-system/web/messages/Core/tables.ts +++ b/apps/judicial-system/web/messages/Core/tables.ts @@ -134,4 +134,15 @@ export const tables = defineMessages({ defaultMessage: 'Sent', description: 'Notaður sem titill fyrir sent dálk í lista yfir mál.', }, + fineTag: { + id: 'judicial.system.core:tables.fine_tag', + defaultMessage: 'Viðurlagaákvörðun', + description: + 'Notaðir sem texti í tagg þegar mál endar sem viðurlagaákvörðun', + }, + rulingTag: { + id: 'judicial.system.core:tables.ruling_tag', + defaultMessage: 'Dómur', + description: 'Notaðir sem texti í tagg þegar mál endar sem dómur', + }, }) diff --git a/apps/judicial-system/web/src/components/Table/Table.tsx b/apps/judicial-system/web/src/components/Table/Table.tsx index 45b6398dfe04..8e572422391c 100644 --- a/apps/judicial-system/web/src/components/Table/Table.tsx +++ b/apps/judicial-system/web/src/components/Table/Table.tsx @@ -6,7 +6,10 @@ import { AnimatePresence, motion } from 'framer-motion' import { Box, Text } from '@island.is/island-ui/core' import { theme } from '@island.is/island-ui/theme' -import { formatDate } from '@island.is/judicial-system/formatters' +import { + districtCourtAbbreviation, + formatDate, +} from '@island.is/judicial-system/formatters' import { CaseType, isCompletedCase, @@ -168,25 +171,30 @@ const Table: FC = (props) => { return null } + const getColumnValue = ( + entry: CaseListEntry, + column: keyof CaseListEntry, + ) => { + const courtAbbreviation = districtCourtAbbreviation(entry.court?.name) + + switch (column) { + case 'defendants': + return entry.defendants?.[0]?.name ?? '' + case 'courtCaseNumber': + return courtAbbreviation + ? `${courtAbbreviation}: ${entry.courtCaseNumber}` + : entry.courtCaseNumber ?? '' + default: + return entry[column]?.toString() ?? '' + } + } + useMemo(() => { if (sortConfig) { data.sort((a: CaseListEntry, b: CaseListEntry) => { - const getColumnValue = (entry: CaseListEntry) => { - if ( - sortConfig.column === 'defendants' && - entry.defendants && - entry.defendants.length > 0 && - entry.defendants[0].name - ) { - return entry.defendants[0].name - } - - return entry[sortConfig.column]?.toString() - } - const compareResult = compareLocaleIS( - getColumnValue(a), - getColumnValue(b), + getColumnValue(a, sortConfig.column), + getColumnValue(b, sortConfig.column), ) return sortConfig.direction === 'ascending' diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesAwaitingReview.tsx b/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesAwaitingReview.tsx index 5f1cc2d3dac2..ec030bbaa80b 100644 --- a/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesAwaitingReview.tsx +++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesAwaitingReview.tsx @@ -2,7 +2,7 @@ import { FC } from 'react' import { useIntl } from 'react-intl' import { AnimatePresence } from 'framer-motion' -import { Text } from '@island.is/island-ui/core' +import { Tag, Text } from '@island.is/island-ui/core' import { capitalize, formatDate } from '@island.is/judicial-system/formatters' import { core, tables } from '@island.is/judicial-system-web/messages' import { SectionHeading } from '@island.is/judicial-system-web/src/components' @@ -18,7 +18,10 @@ import TableInfoContainer from '@island.is/judicial-system-web/src/components/Ta import TagCaseState, { mapIndictmentCaseStateToTagVariant, } from '@island.is/judicial-system-web/src/components/TagCaseState/TagCaseState' -import { CaseListEntry } from '@island.is/judicial-system-web/src/graphql/schema' +import { + CaseIndictmentRulingDecision, + CaseListEntry, +} from '@island.is/judicial-system-web/src/graphql/schema' import { strings } from './CasesAwaitingReview.strings' @@ -48,6 +51,7 @@ const CasesForReview: FC = ({ loading, cases }) => { ), sortable: { isSortable: true, key: 'defendants' }, }, + { title: formatMessage(tables.type) }, { title: formatMessage(tables.state) }, { title: formatMessage(tables.deadline), @@ -58,9 +62,9 @@ const CasesForReview: FC = ({ loading, cases }) => { }, ]} data={cases} - generateContextMenuItems={(row) => { - return [openCaseInNewTabMenuItem(row.id)] - }} + generateContextMenuItems={(row) => [ + openCaseInNewTabMenuItem(row.id), + ]} columns={[ { cell: (row) => ( @@ -74,6 +78,18 @@ const CasesForReview: FC = ({ loading, cases }) => { { cell: (row) => , }, + { + cell: (row) => ( + + {formatMessage( + row.indictmentRulingDecision === + CaseIndictmentRulingDecision.FINE + ? tables.fineTag + : tables.rulingTag, + )} + + ), + }, { cell: (row) => ( = ({ loading, cases }) => { thead={[ { title: formatMessage(tables.caseNumber), + sortable: { + isSortable: true, + key: 'courtCaseNumber', + }, }, { title: capitalize( @@ -51,6 +62,7 @@ const CasesForReview: FC = ({ loading, cases }) => { key: 'defendants', }, }, + { title: formatMessage(tables.type) }, { title: formatMessage(tables.state) }, { title: formatMessage(tables.prosecutorName) }, { @@ -62,22 +74,42 @@ const CasesForReview: FC = ({ loading, cases }) => { }, ]} data={cases} - generateContextMenuItems={(row) => { - return [openCaseInNewTabMenuItem(row.id)] - }} + generateContextMenuItems={(row) => [ + openCaseInNewTabMenuItem(row.id), + ]} columns={[ { - cell: (row) => ( - - ), + cell: (row) => { + const courtAbbreviation = districtCourtAbbreviation( + row.court?.name, + ) + + return ( + + ) + }, }, { cell: (row) => , }, + { + cell: (row) => ( + + {formatMessage( + row.indictmentRulingDecision === + CaseIndictmentRulingDecision.FINE + ? tables.fineTag + : tables.rulingTag, + )} + + ), + }, { cell: (row) => ( = ({ loading, cases }) => { thead={[ { title: formatMessage(tables.caseNumber), + sortable: { + isSortable: true, + key: 'courtCaseNumber', + }, }, { title: capitalize( @@ -96,27 +103,48 @@ const CasesReviewed: FC = ({ loading, cases }) => { ), sortable: { isSortable: true, key: 'defendants' }, }, + { title: formatMessage(tables.type) }, { title: formatMessage(tables.reviewDecision) }, { title: formatMessage(tables.verdictViewState) }, { title: formatMessage(tables.prosecutorName) }, ]} data={cases} - generateContextMenuItems={(row) => { - return [openCaseInNewTabMenuItem(row.id)] - }} + generateContextMenuItems={(row) => [ + openCaseInNewTabMenuItem(row.id), + ]} columns={[ { - cell: (row) => ( - - ), + cell: (row) => { + const courtAbbreviation = districtCourtAbbreviation( + row.court?.name, + ) + + return ( + + ) + }, }, { cell: (row) => , }, + { + cell: (row) => ( + + {formatMessage( + row.indictmentRulingDecision === + CaseIndictmentRulingDecision.FINE + ? tables.fineTag + : tables.rulingTag, + )} + + ), + }, { cell: (row) => ( diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql b/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql index d35f7fb9af54..4001b83c6fcb 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql +++ b/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql @@ -34,6 +34,10 @@ query Cases { initialRulingDate rulingDate rulingSignatureDate + court { + id + name + } judge { id created diff --git a/libs/judicial-system/formatters/src/lib/formatters.ts b/libs/judicial-system/formatters/src/lib/formatters.ts index 7d9d260ddd88..91cc3013da3a 100644 --- a/libs/judicial-system/formatters/src/lib/formatters.ts +++ b/libs/judicial-system/formatters/src/lib/formatters.ts @@ -218,6 +218,29 @@ export const indictmentSubtypes: IndictmentSubtypes = { THEFT: 'þjófnaður', } +export const districtCourtAbbreviation = (courtName?: string | null) => { + switch (courtName) { + case 'Héraðsdómur Reykjavíkur': + return 'HDR' + case 'Héraðsdómur Reykjaness': + return 'HDRN' + case 'Héraðsdómur Vesturlands': + return 'HDV' + case 'Héraðsdómur Suðurlands': + return 'HDS' + case 'Héraðsdómur Norðurlands eystra': + return 'HDNE' + case 'Héraðsdómur Norðurlands vestra': + return 'HDNV' + case 'Héraðsdómur Austurlands': + return 'HDA' + case 'Héraðsdómur Vestfjarða': + return 'HDVF' + default: + return '' + } +} + export const getAppealResultTextByValue = ( value?: CaseAppealRulingDecision | null, ) => { From ab805859a074eef75377529844381d5a46b38aff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Thu, 5 Dec 2024 22:43:17 +0000 Subject: [PATCH 13/57] fix(j-s): Fix Empty National Id (#17149) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../Indictments/Advocates/SelectDefender.tsx | 10 +++++----- .../Indictments/Defendant/Defendant.tsx | 19 ++----------------- .../InvestigationCase/Defendant/Defendant.tsx | 18 ++---------------- 3 files changed, 9 insertions(+), 38 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Advocates/SelectDefender.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Advocates/SelectDefender.tsx index 70711fe486d7..56033fefc230 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Advocates/SelectDefender.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Advocates/SelectDefender.tsx @@ -43,16 +43,16 @@ const SelectDefender: FC = ({ defendant }) => { caseId, defendantId: defendant.id, defenderNationalId: defendantWaivesRightToCounsel - ? '' - : defendant.defenderNationalId, + ? null + : defendant.defenderNationalId || null, defenderName: defendantWaivesRightToCounsel - ? '' + ? null : defendant.defenderName, defenderEmail: defendantWaivesRightToCounsel - ? '' + ? null : defendant.defenderEmail, defenderPhoneNumber: defendantWaivesRightToCounsel - ? '' + ? null : defendant.defenderPhoneNumber, defenderChoice: defendantWaivesRightToCounsel === true diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/Defendant.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/Defendant.tsx index 2bae92a4bbd9..5f5e15f5b14a 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/Defendant.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/Defendant.tsx @@ -370,7 +370,6 @@ const Defendant = () => { router.push(`${destination}/${createdCase.id}`) } else { toast.error(formatMessage(errors.createCase)) - return } } else { router.push(`${destination}/${workingCase.id}`) @@ -416,14 +415,7 @@ const Defendant = () => { const handleCreateDefendantClick = async () => { if (workingCase.id) { - const defendantId = await createDefendant({ - caseId: workingCase.id, - gender: undefined, - name: '', - address: '', - nationalId: null, - citizenship: '', - }) + const defendantId = await createDefendant({ caseId: workingCase.id }) createEmptyDefendant(defendantId) } else { @@ -438,14 +430,7 @@ const Defendant = () => { ...prevWorkingCase, defendants: prevWorkingCase.defendants && [ ...prevWorkingCase.defendants, - { - id: defendantId || uuid(), - gender: undefined, - name: '', - nationalId: null, - address: '', - citizenship: '', - } as TDefendant, + { id: defendantId || uuid() }, ], })) } diff --git a/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/Defendant/Defendant.tsx b/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/Defendant/Defendant.tsx index 2be75ba20374..119bca8574ae 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/Defendant/Defendant.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/InvestigationCase/Defendant/Defendant.tsx @@ -192,14 +192,7 @@ const Defendant = () => { const handleCreateDefendantClick = async () => { if (workingCase.id) { - const defendantId = await createDefendant({ - caseId: workingCase.id, - gender: undefined, - name: '', - address: '', - nationalId: '', - citizenship: '', - }) + const defendantId = await createDefendant({ caseId: workingCase.id }) createEmptyDefendant(defendantId) } else { @@ -214,14 +207,7 @@ const Defendant = () => { ...prevWorkingCase, defendants: prevWorkingCase.defendants && [ ...prevWorkingCase.defendants, - { - id: defendantId || uuid(), - gender: undefined, - name: '', - nationalId: '', - address: '', - citizenship: '', - } as TDefendant, + { id: defendantId || uuid() }, ], })) } From 500eef824b9de6fdb6103581647365773d06921c Mon Sep 17 00:00:00 2001 From: unakb Date: Thu, 5 Dec 2024 22:51:05 +0000 Subject: [PATCH 14/57] fix(j-s): Display confirmation modal before completing case (#17112) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(j-s): Display confirmation modal before completing case * Update apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts --------- Co-authored-by: Ívar Oddsson Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../Indictments/Summary/Summary.strings.ts | 32 +++++++++++++------ .../Court/Indictments/Summary/Summary.tsx | 26 ++++++++------- 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts index 0d0db14b68a4..e5c50b59327c 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts @@ -28,14 +28,28 @@ export const strings = defineMessages({ description: 'Notaður sem titill á Staðfesta takka á Samantektarskjá ákæru.', }, - completedCaseModalTitle: { - id: 'judicial.system.core:indictments.summary.completed_case_modal_title', - defaultMessage: 'Máli hefur verið lokið', - description: 'Notaður sem titill á staðfestingarglugga um að mál sé lokið.', - }, - completedCaseModalBody: { - id: 'judicial.system.core:indictments.summary.completed_case_modal_body_v2', - defaultMessage: 'Gögn hafa verið send ákæranda og verjanda.', - description: 'Notaður sem texti í staðfestingarglugga um að mál sé lokið.', + completeCaseModalTitle: { + id: 'judicial.system.core:indictments.summary.complete_case_modal_title', + defaultMessage: 'Viltu ljúka máli?', + description: + 'Notaður sem titill á staðfestingarglugga um að hvort eigi að ljúka máli.', + }, + completeCaseModalBody: { + id: 'judicial.system.core:indictments.summary.complete_case_modal_body', + defaultMessage: 'Niðurstaða málsins verður send ákæranda og verjanda.', + description: + 'Notaður sem texti í staðfestingarglugga um hvort eigi að ljúka máli.', + }, + completeCaseModalPrimaryButton: { + id: 'judicial.system.core:indictments.summary.complete_case_modal_primary_button', + defaultMessage: 'Já, ljúka máli', + description: + 'Notaður sem texti á aðalhnapp í staðfestingarglugga um hvort eigi að ljúka máli.', + }, + completeCaseModalSecondaryButton: { + id: 'judicial.system.core:indictments.summary.complete_case_modal_secondary_button', + defaultMessage: 'Hætta við', + description: + 'Notaður sem texti á aukahnapp í staðfestingarglugga um hvort eigi að ljúka máli.', }, }) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx index d1186e033ce4..51d109336b5e 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx @@ -52,7 +52,7 @@ const Summary: FC = () => { return router.push(`${destination}/${workingCase.id}`) } - const handleNextButtonClick = async () => { + const handleModalPrimaryButtonClick = async () => { const transitionSuccess = await transitionCase( workingCase.id, CaseTransition.COMPLETE, @@ -63,7 +63,7 @@ const Summary: FC = () => { return } - setModalVisible('CONFIRM_INDICTMENT') + router.push(`${constants.INDICTMENTS_COMPLETED_ROUTE}/${workingCase.id}`) } const [courtRecordFiles, rulingFiles] = (workingCase.caseFiles || []).reduce( @@ -158,20 +158,24 @@ const Summary: FC = () => { previousUrl={`${constants.INDICTMENTS_CONCLUSION_ROUTE}/${workingCase.id}`} nextButtonIcon="checkmark" nextButtonText={formatMessage(strings.nextButtonText)} - onNextButtonClick={async () => await handleNextButtonClick()} - nextIsDisabled={isTransitioningCase} + onNextButtonClick={() => setModalVisible('CONFIRM_INDICTMENT')} /> {modalVisible === 'CONFIRM_INDICTMENT' && ( - router.push( - `${constants.INDICTMENTS_COMPLETED_ROUTE}/${workingCase.id}`, - ) + title={formatMessage(strings.completeCaseModalTitle)} + text={formatMessage(strings.completeCaseModalBody)} + primaryButtonText={formatMessage( + strings.completeCaseModalPrimaryButton, + )} + onPrimaryButtonClick={async () => + await handleModalPrimaryButtonClick() } + secondaryButtonText={formatMessage( + strings.completeCaseModalSecondaryButton, + )} + onSecondaryButtonClick={() => setModalVisible(undefined)} + isPrimaryButtonLoading={isTransitioningCase} /> )} From 3d206080776affaba7099b772783b0d0a9dc4a74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Thu, 5 Dec 2024 23:31:19 +0000 Subject: [PATCH 15/57] feat(j-s): Upload Confirmed Court Record to Court (#17150) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../backend/src/app/modules/case/case.service.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/judicial-system/backend/src/app/modules/case/case.service.ts b/apps/judicial-system/backend/src/app/modules/case/case.service.ts index 097e9c4d5b4a..c89bbc8c0e0c 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.service.ts @@ -932,12 +932,10 @@ export class CaseService { (caseFile) => caseFile.state === CaseFileState.STORED_IN_RVG && caseFile.key && - caseFile.category === CaseFileCategory.RULING && - theCase.indictmentRulingDecision && - [ - CaseIndictmentRulingDecision.RULING, - CaseIndictmentRulingDecision.DISMISSAL, - ].includes(theCase.indictmentRulingDecision), + caseFile.category && + [CaseFileCategory.COURT_RECORD, CaseFileCategory.RULING].includes( + caseFile.category, + ), ) .map((caseFile) => ({ type: MessageType.DELIVERY_TO_COURT_CASE_FILE, From 28a77112a633f8567b27b77a228c0c4ded80a8a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:06:18 +0000 Subject: [PATCH 16/57] feat(web): Organization parent subpage - Use organization layout in case theme is not set to 'standalone' (#17144) * Add different layout in case theme is not set to 'standalone' * Simplify translation code --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../Wrapper/OrganizationWrapper.tsx | 2 +- apps/web/pages/s/[...slugs]/index.tsx | 38 +++++- .../screens/Organization/ParentSubpage.tsx | 123 ++++++++++++++++++ .../Organization/Standalone/ParentSubpage.tsx | 4 +- apps/web/screens/Organization/SubPage.tsx | 44 ++++--- 5 files changed, 185 insertions(+), 26 deletions(-) create mode 100644 apps/web/screens/Organization/ParentSubpage.tsx diff --git a/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx b/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx index d42d13d63913..44e38cb37d59 100644 --- a/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx +++ b/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx @@ -128,7 +128,7 @@ interface NavigationData { interface WrapperProps { pageTitle: string pageDescription?: string - pageFeaturedImage?: Image + pageFeaturedImage?: Image | null organizationPage: OrganizationPage breadcrumbItems?: BreadCrumbItem[] mainContent?: ReactNode diff --git a/apps/web/pages/s/[...slugs]/index.tsx b/apps/web/pages/s/[...slugs]/index.tsx index 9eca5605ac4c..e5bee55e773d 100644 --- a/apps/web/pages/s/[...slugs]/index.tsx +++ b/apps/web/pages/s/[...slugs]/index.tsx @@ -25,6 +25,9 @@ import OrganizationNewsArticle, { import OrganizationNewsList, { type OrganizationNewsListProps, } from '@island.is/web/screens/Organization/OrganizationNews/OrganizationNewsList' +import OrganizationParentSubpage, { + type OrganizationParentSubpageProps, +} from '@island.is/web/screens/Organization/ParentSubpage' import PublishedMaterial, { type PublishedMaterialProps, } from '@island.is/web/screens/Organization/PublishedMaterial/PublishedMaterial' @@ -54,6 +57,7 @@ enum PageType { STANDALONE_PARENT_SUBPAGE = 'standalone-parent-subpage', STANDALONE_LEVEL1_SITEMAP = 'standalone-level1-sitemap', STANDALONE_LEVEL2_SITEMAP = 'standalone-level2-sitemap', + PARENT_SUBPAGE = 'parent-subpage', SUBPAGE = 'subpage', ALL_NEWS = 'news', PUBLISHED_MATERIAL = 'published-material', @@ -76,6 +80,9 @@ const pageMap: Record> = { [PageType.STANDALONE_LEVEL2_SITEMAP]: (props) => ( ), + [PageType.PARENT_SUBPAGE]: (props) => ( + + ), [PageType.SUBPAGE]: (props) => , [PageType.ALL_NEWS]: (props) => , [PageType.PUBLISHED_MATERIAL]: (props) => , @@ -112,6 +119,13 @@ interface Props { type: PageType.STANDALONE_LEVEL2_SITEMAP props: StandaloneLevel2SitemapProps } + | { + type: PageType.PARENT_SUBPAGE + props: { + layoutProps: LayoutProps + componentProps: OrganizationParentSubpageProps + } + } | { type: PageType.SUBPAGE props: { @@ -282,10 +296,18 @@ Component.getProps = async (context) => { } try { + if (isStandaloneTheme) { + return { + page: { + type: PageType.STANDALONE_PARENT_SUBPAGE, + props: await StandaloneParentSubpage.getProps(modifiedContext), + }, + } + } return { page: { - type: PageType.STANDALONE_PARENT_SUBPAGE, - props: await StandaloneParentSubpage.getProps(modifiedContext), + type: PageType.PARENT_SUBPAGE, + props: await OrganizationParentSubpage.getProps(modifiedContext), }, } } catch (error) { @@ -360,10 +382,18 @@ Component.getProps = async (context) => { } try { + if (isStandaloneTheme) { + return { + page: { + type: PageType.STANDALONE_PARENT_SUBPAGE, + props: await StandaloneParentSubpage.getProps(modifiedContext), + }, + } + } return { page: { - type: PageType.STANDALONE_PARENT_SUBPAGE, - props: await StandaloneParentSubpage.getProps(modifiedContext), + type: PageType.PARENT_SUBPAGE, + props: await OrganizationParentSubpage.getProps(modifiedContext), }, } } catch (error) { diff --git a/apps/web/screens/Organization/ParentSubpage.tsx b/apps/web/screens/Organization/ParentSubpage.tsx new file mode 100644 index 000000000000..3ae364be44fb --- /dev/null +++ b/apps/web/screens/Organization/ParentSubpage.tsx @@ -0,0 +1,123 @@ +import { useRouter } from 'next/router' + +import { + Box, + GridColumn, + GridContainer, + GridRow, + Stack, + TableOfContents, + Text, +} from '@island.is/island-ui/core' +import { OrganizationWrapper } from '@island.is/web/components' +import { Query } from '@island.is/web/graphql/schema' +import { useLinkResolver, useNamespace } from '@island.is/web/hooks' +import useContentfulId from '@island.is/web/hooks/useContentfulId' +import { useI18n } from '@island.is/web/i18n' +import { withMainLayout } from '@island.is/web/layouts/main' +import type { Screen, ScreenContext } from '@island.is/web/types' + +import { + getProps, + StandaloneParentSubpageProps, +} from './Standalone/ParentSubpage' +import { getSubpageNavList, SubPageContent } from './SubPage' + +type OrganizationParentSubpageScreenContext = ScreenContext & { + organizationPage?: Query['getOrganizationPage'] +} + +export type OrganizationParentSubpageProps = StandaloneParentSubpageProps + +const OrganizationParentSubpage: Screen< + OrganizationParentSubpageProps, + OrganizationParentSubpageScreenContext +> = ({ + organizationPage, + parentSubpage, + selectedHeadingId, + subpage, + tableOfContentHeadings, + namespace, +}) => { + const router = useRouter() + const { activeLocale } = useI18n() + const { linkResolver } = useLinkResolver() + const n = useNamespace(namespace) + useContentfulId(organizationPage.id, parentSubpage.id, subpage.id) + + return ( + + + + + + + {parentSubpage.childLinks.length > 1 && ( + + + {parentSubpage.title} + + { + const href = tableOfContentHeadings.find( + (heading) => heading.headingId === headingId, + )?.href + if (href) { + router.push(href) + } + }} + tableOfContentsTitle={ + namespace?.['OrganizationTableOfContentsTitle'] ?? + activeLocale === 'is' + ? 'Efnisyfirlit' + : 'Table of contents' + } + selectedHeadingId={selectedHeadingId} + /> + + )} + + + + + 1 ? 'h2' : 'h1' + } + /> + + + ) +} + +OrganizationParentSubpage.getProps = getProps + +export default withMainLayout(OrganizationParentSubpage) diff --git a/apps/web/screens/Organization/Standalone/ParentSubpage.tsx b/apps/web/screens/Organization/Standalone/ParentSubpage.tsx index 9f30ee2920a4..7191f959e12a 100644 --- a/apps/web/screens/Organization/Standalone/ParentSubpage.tsx +++ b/apps/web/screens/Organization/Standalone/ParentSubpage.tsx @@ -138,7 +138,7 @@ const StandaloneParentSubpage: Screen< ) } -StandaloneParentSubpage.getProps = async ({ +export const getProps: typeof StandaloneParentSubpage['getProps'] = async ({ apolloClient, locale, query, @@ -262,4 +262,6 @@ StandaloneParentSubpage.getProps = async ({ } } +StandaloneParentSubpage.getProps = getProps + export default StandaloneParentSubpage diff --git a/apps/web/screens/Organization/SubPage.tsx b/apps/web/screens/Organization/SubPage.tsx index 6d81780073b2..dd2850892088 100644 --- a/apps/web/screens/Organization/SubPage.tsx +++ b/apps/web/screens/Organization/SubPage.tsx @@ -1,4 +1,4 @@ -import { useRouter } from 'next/router' +import { NextRouter, useRouter } from 'next/router' import { ParsedUrlQuery } from 'querystring' import { SliceType } from '@island.is/island-ui/contentful' @@ -27,6 +27,7 @@ import { import { SLICE_SPACING } from '@island.is/web/constants' import { ContentLanguage, + OrganizationPage, Query, QueryGetNamespaceArgs, QueryGetOrganizationPageArgs, @@ -214,6 +215,27 @@ export const SubPageContent = ({ ) } +export const getSubpageNavList = ( + organizationPage: OrganizationPage | null | undefined, + router: NextRouter, +): NavigationItem[] => { + const pathname = new URL(router.asPath, 'https://island.is').pathname + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore make web strict + return organizationPage?.menuLinks.map(({ primaryLink, childrenLinks }) => ({ + title: primaryLink?.text, + href: primaryLink?.url, + active: + primaryLink?.url === pathname || + childrenLinks.some((link) => link.url === pathname), + items: childrenLinks.map(({ text, url }) => ({ + title: text, + href: url, + active: url === pathname, + })), + })) +} + type SubPageScreenContext = ScreenContext & { organizationPage?: Query['getOrganizationPage'] } @@ -239,24 +261,6 @@ const SubPage: Screen = ({ useContentfulId(...contentfulIds) - const pathname = new URL(router.asPath, 'https://island.is').pathname - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore make web strict - const navList: NavigationItem[] = organizationPage?.menuLinks.map( - ({ primaryLink, childrenLinks }) => ({ - title: primaryLink?.text, - href: primaryLink?.url, - active: - primaryLink?.url === pathname || - childrenLinks.some((link) => link.url === pathname), - items: childrenLinks.map(({ text, url }) => ({ - title: text, - href: url, - active: url === pathname, - })), - }), - ) - return ( = ({ } navigationData={{ title: n('navigationTitle', 'Efnisyfirlit'), - items: navList, + items: getSubpageNavList(organizationPage, router), }} > {customContent ? ( From dfc8becb82e6d54cbd90c0f708586f93f6f0f6d6 Mon Sep 17 00:00:00 2001 From: valurefugl <65780958+valurefugl@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:24:15 +0000 Subject: [PATCH 17/57] chore(ids-api): Try fix flaky tests (#17154) * Increase timeout for beforeAll hook. * Reset mocks. * Ensure person nationalIds. --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- ...personal-representative.controller.spec.ts | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/apps/services/auth/ids-api/src/app/delegations/delegations-personal-representative.controller.spec.ts b/apps/services/auth/ids-api/src/app/delegations/delegations-personal-representative.controller.spec.ts index 5c07ba95334d..bcf7ed73dab1 100644 --- a/apps/services/auth/ids-api/src/app/delegations/delegations-personal-representative.controller.spec.ts +++ b/apps/services/auth/ids-api/src/app/delegations/delegations-personal-representative.controller.spec.ts @@ -36,16 +36,13 @@ import { } from '@island.is/shared/types' import { createCurrentUser, + createNationalId, createNationalRegistryUser, } from '@island.is/testing/fixtures' import { TestApp } from '@island.is/testing/nest' import { defaultScopes, setupWithAuth } from '../../../test/setup' -import { - getFakeName, - getFakeNationalId, - NameIdTuple, -} from '../../../test/stubs/genericStubs' +import { getFakeName, NameIdTuple } from '../../../test/stubs/genericStubs' import { delegationTypes, getPersonalRepresentativeRelationship, @@ -55,6 +52,10 @@ import { personalRepresentativeType, } from '../../../test/stubs/personalRepresentativeStubs' +const getFakeNationalId = () => createNationalId('person') + +const setupHookTimeout = 10000 + describe('Personal Representative DelegationsController', () => { describe.each([false, true])( 'national registry v3 featureflag: %s', @@ -73,6 +74,7 @@ describe('Personal Representative DelegationsController', () => { let prTypeModel: typeof PersonalRepresentativeType let prDelegationTypeModel: typeof PersonalRepresentativeDelegationTypeModel let delegationTypeModel: typeof DelegationTypeModel + let nationalRegistryV3FeatureService: NationalRegistryV3FeatureService let nationalRegistryApi: NationalRegistryClientService let nationalRegistryV3Api: NationalRegistryV3ClientService let delegationProviderModel: typeof DelegationProviderModel @@ -152,17 +154,14 @@ describe('Personal Representative DelegationsController', () => { getModelToken(DelegationProviderModel), ) clientModel = app.get(getModelToken(Client)) + nationalRegistryV3FeatureService = app.get( + NationalRegistryV3FeatureService, + ) nationalRegistryApi = app.get(NationalRegistryClientService) nationalRegistryV3Api = app.get(NationalRegistryV3ClientService) delegationIndexService = app.get(DelegationsIndexService) - const nationalRegistryV3FeatureService = app.get( - NationalRegistryV3FeatureService, - ) - jest - .spyOn(nationalRegistryV3FeatureService, 'getValue') - .mockImplementation(async () => featureFlag) factory = new FixtureFactory(app) - }) + }, setupHookTimeout) const createDelegationTypeAndProvider = async (rightCode: string[]) => { const newDelegationProvider = await delegationProviderModel.create({ @@ -403,6 +402,10 @@ describe('Personal Representative DelegationsController', () => { ), ] + jest + .spyOn(nationalRegistryV3FeatureService, 'getValue') + .mockImplementation(async () => featureFlag) + nationalRegistryApiSpy = jest .spyOn(nationalRegistryApi, 'getIndividual') .mockImplementation(async (id) => { @@ -461,7 +464,7 @@ describe('Personal Representative DelegationsController', () => { }) afterAll(async () => { - jest.clearAllMocks() + jest.resetAllMocks() await prRightsModel.destroy({ where: {}, cascade: true, From a135b0fba6e2521f6ea7751f48aa5d3c48bcdc44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnea=20R=C3=BAn=20Vignisd=C3=B3ttir?= Date: Fri, 6 Dec 2024 11:08:36 +0000 Subject: [PATCH 18/57] feat(delegation-api): Use national registry v3 for name service (#17088) * use national regestry v3 for name service * chore: nx format:write update dirty files * uncomment code not supposed to be commented out * remove duplicate * Update app.module.ts * Update app.module.ts * use nafn instead of fulltNafn * fix test --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../app/v2/delegations/test/delegation-admin.spec.ts | 8 ++++---- libs/auth-api-lib/src/lib/delegations/names.service.ts | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/services/auth/admin-api/src/app/v2/delegations/test/delegation-admin.spec.ts b/apps/services/auth/admin-api/src/app/v2/delegations/test/delegation-admin.spec.ts index 6d0637535db9..f4c0faeccff7 100644 --- a/apps/services/auth/admin-api/src/app/v2/delegations/test/delegation-admin.spec.ts +++ b/apps/services/auth/admin-api/src/app/v2/delegations/test/delegation-admin.spec.ts @@ -24,7 +24,7 @@ import { AuthDelegationType } from '@island.is/shared/types' import { getModelToken } from '@nestjs/sequelize' import { faker } from '@island.is/shared/mocking' import { TicketStatus, ZendeskService } from '@island.is/clients/zendesk' -import { NationalRegistryClientService } from '@island.is/clients/national-registry-v2' +import { NationalRegistryV3ClientService } from '@island.is/clients/national-registry-v3' import { ErrorCodes } from '@island.is/shared/utils' const currentUser = createCurrentUser({ @@ -36,7 +36,7 @@ describe('DelegationAdmin - With authentication', () => { let server: request.SuperTest let factory: FixtureFactory let zendeskService: ZendeskService - let nationalRegistryApi: NationalRegistryClientService + let nationalRegistryApi: NationalRegistryV3ClientService let delegationIndexServiceApi: DelegationsIndexService beforeEach(async () => { @@ -51,7 +51,7 @@ describe('DelegationAdmin - With authentication', () => { factory = new FixtureFactory(app) zendeskService = app.get(ZendeskService) - nationalRegistryApi = app.get(NationalRegistryClientService) + nationalRegistryApi = app.get(NationalRegistryV3ClientService) delegationIndexServiceApi = app.get(DelegationsIndexService) jest @@ -212,7 +212,7 @@ describe('DelegationAdmin - With authentication', () => { const mockNationalRegistryService = () => { nationalRegistryApiSpy = jest - .spyOn(nationalRegistryApi, 'getIndividual') + .spyOn(nationalRegistryApi, 'getAllDataIndividual') .mockImplementation(async (id) => { const user = createNationalRegistryUser({ nationalId: id, diff --git a/libs/auth-api-lib/src/lib/delegations/names.service.ts b/libs/auth-api-lib/src/lib/delegations/names.service.ts index 463f412a1ffd..43a87f8dcb9c 100644 --- a/libs/auth-api-lib/src/lib/delegations/names.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/names.service.ts @@ -6,7 +6,7 @@ import { createEnhancedFetch, EnhancedFetchAPI, } from '@island.is/clients/middlewares' -import { NationalRegistryClientService } from '@island.is/clients/national-registry-v2' +import { NationalRegistryV3ClientService } from '@island.is/clients/national-registry-v3' import { DelegationConfig } from './DelegationConfig' @@ -17,7 +17,7 @@ export class NamesService { constructor( @Inject(DelegationConfig.KEY) private delegationConfig: ConfigType, - private nationalRegistryClient: NationalRegistryClientService, + private nationalRegistryClient: NationalRegistryV3ClientService, ) { this.authFetch = createEnhancedFetch({ name: 'delegation-auth-client' }) } @@ -32,12 +32,14 @@ export class NamesService { } async getPersonName(nationalId: string) { - const person = await this.nationalRegistryClient.getIndividual(nationalId) + const person = await this.nationalRegistryClient.getAllDataIndividual( + nationalId, + ) if (!person) { throw new BadRequestException( `A person with nationalId<${nationalId}> could not be found`, ) } - return person.fullName ?? person.name + return person.nafn ?? '' } } From d1f95c5f173ea4f84e92d269e11f838ecc529fa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rey=20J=C3=B3na?= Date: Fri, 6 Dec 2024 11:27:26 +0000 Subject: [PATCH 19/57] fix(native-app): change border radius circle to be full (#17155) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/native/app/src/ui/utils/theme.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/native/app/src/ui/utils/theme.ts b/apps/native/app/src/ui/utils/theme.ts index ce97652ea88a..baec29a5de00 100644 --- a/apps/native/app/src/ui/utils/theme.ts +++ b/apps/native/app/src/ui/utils/theme.ts @@ -82,7 +82,7 @@ export const theme = { standard: '4px', large: '8px', extraLarge: '16px', - circle: '100px', + full: '100px', }, width: { standard: 1, From 202d2b1d90f1042084c5ad4887161df09e95c7fe Mon Sep 17 00:00:00 2001 From: unakb Date: Fri, 6 Dec 2024 11:53:59 +0000 Subject: [PATCH 20/57] fix(j-s): Fix subpoena pdf (#17158) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../judicial-system/api/src/app/modules/file/file.controller.ts | 2 +- .../backend/src/app/modules/subpoena/subpoena.controller.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/judicial-system/api/src/app/modules/file/file.controller.ts b/apps/judicial-system/api/src/app/modules/file/file.controller.ts index a729124f5f6e..283b548d0efa 100644 --- a/apps/judicial-system/api/src/app/modules/file/file.controller.ts +++ b/apps/judicial-system/api/src/app/modules/file/file.controller.ts @@ -196,7 +196,7 @@ export class FileController { } for defendant ${defendantId} of case ${id} as a pdf document`, ) - const subpoenaIdInjection = subpoenaId ? `/${subpoenaId}` : '' + const subpoenaIdInjection = subpoenaId ? `/${subpoenaId}/pdf` : '' const queryInjection = arraignmentDate ? `?arraignmentDate=${arraignmentDate}&location=${location}&subpoenaType=${subpoenaType}` : '' diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.controller.ts b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.controller.ts index 8242d7867970..ed673bbf76b5 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.controller.ts @@ -105,7 +105,7 @@ export class SubpoenaController { districtCourtRegistrarRule, districtCourtAssistantRule, ) - @Get(['', ':subpoenaId']) + @Get(['', ':subpoenaId/pdf']) @UseGuards(SubpoenaExistsOptionalGuard) @Header('Content-Type', 'application/pdf') @ApiOkResponse({ From c4685ef0d0b59ad4e838c8bfa9976da1438a66f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3r=C3=B0ur=20H?= Date: Fri, 6 Dec 2024 12:34:20 +0000 Subject: [PATCH 21/57] fix(documents): Sort by publication v1 and v2 by default (#17157) * Sort by publication v1 and v2 by default * Remove logg --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../domains/documents/src/lib/dto/getDocumentListInput.ts | 2 +- .../documents/src/lib/models/v2/documents.input.ts | 8 ++++++-- .../documents-v2/src/lib/dto/listDocuments.input.ts | 2 +- libs/clients/documents/src/lib/documentClient.ts | 2 +- libs/clients/documents/src/lib/models/DocumentInput.ts | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/libs/api/domains/documents/src/lib/dto/getDocumentListInput.ts b/libs/api/domains/documents/src/lib/dto/getDocumentListInput.ts index e0900dd2d914..0ab2fcd80590 100644 --- a/libs/api/domains/documents/src/lib/dto/getDocumentListInput.ts +++ b/libs/api/domains/documents/src/lib/dto/getDocumentListInput.ts @@ -21,7 +21,7 @@ export class GetDocumentListInput { typeId?: string @Field({ nullable: true }) - sortBy?: 'Date' | 'Category' | 'Type' | 'Subject' | 'Sender' + sortBy?: 'Date' | 'Category' | 'Type' | 'Subject' | 'Sender' | 'Publication' @Field({ nullable: true }) order?: 'Ascending' | 'Descending' diff --git a/libs/api/domains/documents/src/lib/models/v2/documents.input.ts b/libs/api/domains/documents/src/lib/models/v2/documents.input.ts index f8615073aa99..ed3eb3d52849 100644 --- a/libs/api/domains/documents/src/lib/models/v2/documents.input.ts +++ b/libs/api/domains/documents/src/lib/models/v2/documents.input.ts @@ -17,7 +17,8 @@ import { } from 'class-validator' export enum DocumentPageSort { - Date = 'Date', + Date = 'Date', // Date is document date + Publication = 'Publication', // Publication is document publication date (default) Category = 'Category', Type = 'Type', Sender = 'Sender', @@ -79,7 +80,10 @@ export class DocumentsInput { @IsBoolean() readonly opened?: boolean - @Field(() => DocumentPageSort, { nullable: true, defaultValue: 'Date' }) + @Field(() => DocumentPageSort, { + nullable: true, + defaultValue: 'Publication', + }) @IsOptional() @IsEnum(DocumentPageSort) readonly sortBy?: DocumentPageSort diff --git a/libs/clients/documents-v2/src/lib/dto/listDocuments.input.ts b/libs/clients/documents-v2/src/lib/dto/listDocuments.input.ts index 3070a27c91ba..5da074477c8e 100644 --- a/libs/clients/documents-v2/src/lib/dto/listDocuments.input.ts +++ b/libs/clients/documents-v2/src/lib/dto/listDocuments.input.ts @@ -8,7 +8,7 @@ export type ListDocumentsInputDto = { typeId?: string subjectContains?: string archived?: boolean - sortBy?: 'Date' | 'Category' | 'Type' | 'Sender' | 'Subject' + sortBy?: 'Date' | 'Category' | 'Type' | 'Sender' | 'Subject' | 'Publication' order?: 'Ascending' | 'Descending' opened?: boolean page?: number diff --git a/libs/clients/documents/src/lib/documentClient.ts b/libs/clients/documents/src/lib/documentClient.ts index 476024d24c5a..6b9d130b18a7 100644 --- a/libs/clients/documents/src/lib/documentClient.ts +++ b/libs/clients/documents/src/lib/documentClient.ts @@ -133,7 +133,7 @@ export class DocumentClient { type ExcludesFalse = (x: T | null | undefined | false | '') => x is T const inputs = [ - sortBy ? `sortBy=${sortBy}` : 'sortBy=Date', // first in array to skip & + sortBy ? `sortBy=${sortBy}` : 'sortBy=Publication', // first in array to skip & order ? `orderBy=${order}` : 'order=Descending', page ? `page=${page}` : 'page=1', pageSize ? `pageSize=${pageSize}` : 'pageSize=15', diff --git a/libs/clients/documents/src/lib/models/DocumentInput.ts b/libs/clients/documents/src/lib/models/DocumentInput.ts index 59af47786535..35f861663e37 100644 --- a/libs/clients/documents/src/lib/models/DocumentInput.ts +++ b/libs/clients/documents/src/lib/models/DocumentInput.ts @@ -5,7 +5,7 @@ export type GetDocumentListInput = { categoryId?: string subjectContains?: string typeId?: string - sortBy?: 'Date' | 'Category' | 'Type' | 'Subject' | 'Sender' + sortBy?: 'Date' | 'Category' | 'Type' | 'Subject' | 'Sender' | 'Publication' order?: 'Ascending' | 'Descending' opened?: boolean archived?: boolean From d8a037915a8ad469d7f3ae96432033745a1b34fa Mon Sep 17 00:00:00 2001 From: albinagu <47886428+albinagu@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:45:04 +0000 Subject: [PATCH 22/57] fix(inheritance-report): adding funeral service cost (#17159) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../inheritance-report/src/fields/FuneralCost/index.tsx | 4 +++- .../src/fields/Overview/OverviewDebts/index.tsx | 6 ++++++ .../src/forms/sections/debtsAndFuneralCost.ts | 4 ++++ .../templates/inheritance-report/src/lib/messages.ts | 7 ++++++- 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/libs/application/templates/inheritance-report/src/fields/FuneralCost/index.tsx b/libs/application/templates/inheritance-report/src/fields/FuneralCost/index.tsx index 3cfe9050e922..6222ecda7f0b 100644 --- a/libs/application/templates/inheritance-report/src/fields/FuneralCost/index.tsx +++ b/libs/application/templates/inheritance-report/src/fields/FuneralCost/index.tsx @@ -76,6 +76,7 @@ export const FuneralCost: FC< const rentCost = valueToNumber(values?.rent) const foodCost = valueToNumber(values?.food) const tombstoneCost = valueToNumber(values?.tombstone) + const servicesCost = valueToNumber(values?.service) let total = buildCost + @@ -85,7 +86,8 @@ export const FuneralCost: FC< musicCost + rentCost + foodCost + - tombstoneCost + tombstoneCost + + servicesCost if (hasOther) { total += valueToNumber(values?.other) diff --git a/libs/application/templates/inheritance-report/src/fields/Overview/OverviewDebts/index.tsx b/libs/application/templates/inheritance-report/src/fields/Overview/OverviewDebts/index.tsx index 8ed7095c84c0..b7367e2a7eca 100644 --- a/libs/application/templates/inheritance-report/src/fields/Overview/OverviewDebts/index.tsx +++ b/libs/application/templates/inheritance-report/src/fields/Overview/OverviewDebts/index.tsx @@ -94,6 +94,12 @@ export const OverviewDebts: FC> = ({ getValueViaPath(answers, 'funeralCost.tombstone') || '0', )} /> + (answers, 'funeralCost.service') || '0', + )} + /> Date: Fri, 6 Dec 2024 13:12:45 +0000 Subject: [PATCH 23/57] chore(inao-pp): refactor out custom components and enhance validation (#17137) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: suggestion of new look * chore: refactor out custom components * chore: revert changes to cemetery application * chore: remove console.log * chore: use getvalueviapath to get values for overview * Update libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/conclusionSection/index.ts Co-authored-by: Þórarinn Gunnar Árnason --------- Co-authored-by: Þórarinn Gunnar Árnason Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../application/core/src/lib/fieldBuilders.ts | 2 + ...ncial-statement-political-party.service.ts | 4 +- .../mappers/mapValuesToPartyTypes.ts | 47 ++-- .../components/AssetDebtEquityOverview.tsx | 43 +++- .../src/components/Total.tsx | 39 ---- .../ElectionEquities/ElectionEquities.tsx | 213 ------------------ .../ElectionStatement/ElectionStatement.tsx | 84 ------- .../KeyNumbersCapital/KeyNumbersCapital.tsx | 93 -------- .../src/fields/Overview/Overview.tsx | 170 -------------- .../PartyOperatingIncome/PartyExpenses.tsx | 56 ----- .../PartyOperatingIncome/PartyIncome.tsx | 206 ----------------- .../PartyOperatingIncome.tsx | 89 -------- .../fields/PartyOverview/PartyOverview.tsx | 46 ---- .../src/fields/Success/Success.tsx | 54 ----- .../src/fields/index.ts | 6 - .../conclusionSection/index.ts | 19 ++ .../financialStatementSection/index.ts | 4 +- .../src/forms/applicationForm/index.ts | 8 +- .../capitalNumbersSubsection.ts | 43 ++++ .../capitalNumbersSubsection/index.ts | 28 --- .../equitiesAndLiabilitiesSubsection.ts | 122 ++++++++++ .../equitiesAndLiabilitiesSubsection/index.ts | 17 -- .../keyNumbersSection/index.ts | 4 +- .../operatingCostSubsection.ts | 129 +++++++++++ .../operatingCostSubsection/index.ts | 17 -- .../overviewSection/index.ts | 9 - .../overviewSection/overviewMultiField.ts | 37 --- .../applicationForm/overviewSection/index.ts | 51 +++++ .../src/forms/done/conclusionSection/index.ts | 24 -- .../src/forms/done/index.ts | 2 +- .../src/lib/dataSchema.ts | 55 +++-- .../src/lib/messages.ts | 17 +- .../src/utils/constants.ts | 10 +- .../src/utils/helpers.ts | 177 ++++++++++++++- libs/application/types/src/lib/Fields.ts | 1 + .../lib/DisplayFormField/DisplayFormField.tsx | 99 ++++---- .../formConclusionSection.ts | 28 ++- 37 files changed, 749 insertions(+), 1304 deletions(-) delete mode 100644 libs/application/templates/inao/financial-statement-political-party/src/components/Total.tsx delete mode 100644 libs/application/templates/inao/financial-statement-political-party/src/fields/ElectionEquities/ElectionEquities.tsx delete mode 100644 libs/application/templates/inao/financial-statement-political-party/src/fields/ElectionStatement/ElectionStatement.tsx delete mode 100644 libs/application/templates/inao/financial-statement-political-party/src/fields/KeyNumbersCapital/KeyNumbersCapital.tsx delete mode 100644 libs/application/templates/inao/financial-statement-political-party/src/fields/Overview/Overview.tsx delete mode 100644 libs/application/templates/inao/financial-statement-political-party/src/fields/PartyOperatingIncome/PartyExpenses.tsx delete mode 100644 libs/application/templates/inao/financial-statement-political-party/src/fields/PartyOperatingIncome/PartyIncome.tsx delete mode 100644 libs/application/templates/inao/financial-statement-political-party/src/fields/PartyOperatingIncome/PartyOperatingIncome.tsx delete mode 100644 libs/application/templates/inao/financial-statement-political-party/src/fields/Success/Success.tsx create mode 100644 libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/conclusionSection/index.ts rename libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/{keyNumbersSection => }/financialStatementSection/index.ts (88%) create mode 100644 libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/capitalNumbersSubsection.ts delete mode 100644 libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/capitalNumbersSubsection/index.ts create mode 100644 libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/equitiesAndLiabilitiesSubsection.ts delete mode 100644 libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/equitiesAndLiabilitiesSubsection/index.ts create mode 100644 libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/operatingCostSubsection.ts delete mode 100644 libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/operatingCostSubsection/index.ts delete mode 100644 libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/overviewSection/index.ts delete mode 100644 libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/overviewSection/overviewMultiField.ts create mode 100644 libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/overviewSection/index.ts delete mode 100644 libs/application/templates/inao/financial-statement-political-party/src/forms/done/conclusionSection/index.ts diff --git a/libs/application/core/src/lib/fieldBuilders.ts b/libs/application/core/src/lib/fieldBuilders.ts index d5390fb6bbb6..a1035cbc3b55 100644 --- a/libs/application/core/src/lib/fieldBuilders.ts +++ b/libs/application/core/src/lib/fieldBuilders.ts @@ -991,6 +991,7 @@ export const buildDisplayField = ( value, suffix, rightAlign, + halfWidthOwnline, } = data return { ...extractCommonFields(data), @@ -1006,5 +1007,6 @@ export const buildDisplayField = ( value, suffix, rightAlign, + halfWidthOwnline, } } diff --git a/libs/application/template-api-modules/src/lib/modules/templates/financial-statement-political-party/financial-statement-political-party.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/financial-statement-political-party/financial-statement-political-party.service.ts index 0d4ddd01cad9..3118c379c23e 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/financial-statement-political-party/financial-statement-political-party.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/financial-statement-political-party/financial-statement-political-party.service.ts @@ -140,11 +140,11 @@ export class FinancialStatementPoliticalPartyTemplateService extends BaseTemplat } private getOperatingYear(application: Application) { - const year = getValueViaPath( + const year = getValueViaPath( application.answers, 'conditionalAbout.operatingYear', ) - if (typeof year !== 'string') { + if (!year) { throw new Error('Operating year not found or invalid') } return year diff --git a/libs/application/template-api-modules/src/lib/modules/templates/financial-statement-political-party/mappers/mapValuesToPartyTypes.ts b/libs/application/template-api-modules/src/lib/modules/templates/financial-statement-political-party/mappers/mapValuesToPartyTypes.ts index bcd5b4a2a2b4..79aa40980f38 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/financial-statement-political-party/mappers/mapValuesToPartyTypes.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/financial-statement-political-party/mappers/mapValuesToPartyTypes.ts @@ -4,46 +4,59 @@ import { FormValue } from '@island.is/application/types' export const mapValuesToPartyTypes = (answers: FormValue) => { return { contributionsFromTheTreasury: Number( - getValueViaPath(answers, 'partyIncome.contributionsFromTheTreasury'), + getValueViaPath( + answers, + 'partyIncome.contributionsFromTheTreasury', + ), ), parliamentaryPartySupport: Number( - getValueViaPath(answers, 'partyIncome.parliamentaryPartySupport'), + getValueViaPath(answers, 'partyIncome.parliamentaryPartySupport'), ), municipalContributions: Number( - getValueViaPath(answers, 'partyIncome.municipalContributions'), + getValueViaPath(answers, 'partyIncome.municipalContributions'), ), contributionsFromLegalEntities: Number( - getValueViaPath(answers, 'partyIncome.contributionsFromLegalEntities'), + getValueViaPath( + answers, + 'partyIncome.contributionsFromLegalEntities', + ), ), contributionsFromIndividuals: Number( - getValueViaPath(answers, 'partyIncome.contributionsFromIndividuals'), + getValueViaPath( + answers, + 'partyIncome.contributionsFromIndividuals', + ), ), generalMembershipFees: Number( - getValueViaPath(answers, 'partyIncome.generalMembershipFees'), + getValueViaPath(answers, 'partyIncome.generalMembershipFees'), ), - otherIncome: Number(getValueViaPath(answers, 'partyIncome.otherIncome')), - capitalIncome: Number( - getValueViaPath(answers, 'capitalNumbers.capitalIncome'), + otherIncome: Number( + getValueViaPath(answers, 'partyIncome.otherIncome'), ), officeOperations: Number( - getValueViaPath(answers, 'partyExpense.electionOffice'), + getValueViaPath(answers, 'partyExpense.electionOffice'), ), otherOperatingExpenses: Number( - getValueViaPath(answers, 'partyExpense.otherCost'), + getValueViaPath(answers, 'partyExpense.otherCost'), + ), + capitalIncome: Number( + getValueViaPath(answers, 'capitalNumbers.capitalIncome'), ), financialExpenses: Number( - getValueViaPath(answers, 'capitalNumbers.capitalCost'), + getValueViaPath(answers, 'capitalNumbers.capitalCost'), ), fixedAssetsTotal: Number( - getValueViaPath(answers, 'asset.fixedAssetsTotal'), + getValueViaPath(answers, 'asset.fixedAssetsTotal'), + ), + currentAssets: Number( + getValueViaPath(answers, 'asset.currentAssets'), ), - currentAssets: Number(getValueViaPath(answers, 'asset.currentAssets')), longTermLiabilitiesTotal: Number( - getValueViaPath(answers, 'liability.longTerm'), + getValueViaPath(answers, 'liability.longTerm'), ), shortTermLiabilitiesTotal: Number( - getValueViaPath(answers, 'liability.shortTerm'), + getValueViaPath(answers, 'liability.shortTerm'), ), - equityTotal: Number(getValueViaPath(answers, 'equity.totalEquity')), + equityTotal: Number(getValueViaPath(answers, 'equity.totalEquity')), } } diff --git a/libs/application/templates/inao/financial-statement-political-party/src/components/AssetDebtEquityOverview.tsx b/libs/application/templates/inao/financial-statement-political-party/src/components/AssetDebtEquityOverview.tsx index 2f4c1335863a..330aa0ed82b8 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/components/AssetDebtEquityOverview.tsx +++ b/libs/application/templates/inao/financial-statement-political-party/src/components/AssetDebtEquityOverview.tsx @@ -5,6 +5,7 @@ import { formatCurrency } from '../utils/helpers' import { m } from '../lib/messages' import { FinancialStatementPoliticalParty } from '../lib/dataSchema' import { sectionColumn } from './css/overviewStyles.css' +import { getValueViaPath } from '@island.is/application/core' type Props = { answers: FinancialStatementPoliticalParty @@ -13,6 +14,32 @@ type Props = { export const AssetDebtEquityOverview = ({ answers }: Props) => { const { formatMessage } = useLocale() + const fixedAssetsTotal = getValueViaPath( + answers, + 'asset.fixedAssetsTotal', + ) + const currentAssets = getValueViaPath(answers, 'asset.currentAssets') + const assetsTotal = getValueViaPath( + answers, + 'equityAndLiabilitiesTotals.assetsTotal', + ) + const longTermLiability = getValueViaPath( + answers, + 'liability.longTerm', + ) + const shortTermLiability = getValueViaPath( + answers, + 'liability.shortTerm', + ) + const totalLiabilities = getValueViaPath( + answers, + 'equityAndLiabilitiesTotals.liabilitiesTotal', + ) + const totalEquity = getValueViaPath(answers, 'equity.totalEquity') + const equityAndLiabilitiesTotal = getValueViaPath( + answers, + 'equityAndLiabilitiesTotals.equityAndLiabilitiesTotal', + ) return ( @@ -23,15 +50,15 @@ export const AssetDebtEquityOverview = ({ answers }: Props) => { @@ -44,25 +71,25 @@ export const AssetDebtEquityOverview = ({ answers }: Props) => { diff --git a/libs/application/templates/inao/financial-statement-political-party/src/components/Total.tsx b/libs/application/templates/inao/financial-statement-political-party/src/components/Total.tsx deleted file mode 100644 index f0cd9b51798f..000000000000 --- a/libs/application/templates/inao/financial-statement-political-party/src/components/Total.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useEffect } from 'react' -import { Box, Text } from '@island.is/island-ui/core' -import { InputController } from '@island.is/shared/form-fields' -import { useFormContext } from 'react-hook-form' - -type Props = { - name: string - total: number - label: string - title?: string -} - -export const Total = ({ name, total, label, title }: Props) => { - const { setValue } = useFormContext() - - useEffect(() => { - setValue(name, total.toString()) - }, [total]) - - return ( - - {title ? ( - - {title} - - ) : null} - - - - ) -} diff --git a/libs/application/templates/inao/financial-statement-political-party/src/fields/ElectionEquities/ElectionEquities.tsx b/libs/application/templates/inao/financial-statement-political-party/src/fields/ElectionEquities/ElectionEquities.tsx deleted file mode 100644 index cedb1027a555..000000000000 --- a/libs/application/templates/inao/financial-statement-political-party/src/fields/ElectionEquities/ElectionEquities.tsx +++ /dev/null @@ -1,213 +0,0 @@ -import { useState, useEffect } from 'react' -import debounce from 'lodash/debounce' -import { useFormContext } from 'react-hook-form' -import { FieldBaseProps } from '@island.is/application/types' -import { - AlertBanner, - Box, - GridColumn, - GridContainer, - GridRow, - Text, -} from '@island.is/island-ui/core' -import { useLocale } from '@island.is/localization' -import { InputController } from '@island.is/shared/form-fields' -import { getErrorViaPath } from '@island.is/application/core' -import { m } from '../../lib/messages' -import { useTotals } from '../../hooks/useTotals' -import { - EQUITIESANDLIABILITIESIDS, - INPUTCHANGEINTERVAL, - VALIDATOR, -} from '../../utils/constants' -import { getTotal } from '../../utils/helpers' -import { Total } from '../../components/Total' - -export const ElectionEquities = ({ - setBeforeSubmitCallback, -}: FieldBaseProps) => { - const { formatMessage } = useLocale() - - const { - formState: { errors }, - clearErrors, - getValues, - setError, - } = useFormContext() - - const [getTotalEquity, totalEquity] = useTotals( - EQUITIESANDLIABILITIESIDS.equityPrefix, - ) - const [getTotalAssets, totalAssets] = useTotals( - EQUITIESANDLIABILITIESIDS.assetPrefix, - ) - const [getTotalLiabilities, totalLiabilities] = useTotals( - EQUITIESANDLIABILITIESIDS.liabilityPrefix, - ) - - const [equityAndDebts, setEquityAndDebts] = useState(0) - - useEffect(() => { - const total = totalEquity + totalLiabilities - setEquityAndDebts(total) - }, [totalEquity, totalLiabilities]) - - useEffect(() => { - clearErrors(VALIDATOR) - }, [totalEquity, totalLiabilities, totalAssets, clearErrors]) - - setBeforeSubmitCallback && - setBeforeSubmitCallback(async () => { - const values = getValues() - const assets = getTotal(values, 'asset') - const liabilties = getTotal(values, 'liability') - const equities = getTotal(values, 'equity') - - // assets should equal liabilties + equities - const isValid = liabilties + equities === assets - if (!isValid) { - setError(VALIDATOR, { - type: 'custom', - message: formatMessage(m.equityDebtsAssetsValidatorError), - }) - return [false, formatMessage(m.equityDebtsAssetsValidatorError)] - } - return [true, null] - }) - - return ( - - - - - {formatMessage(m.properties)} - - - { - getTotalAssets() - clearErrors(EQUITIESANDLIABILITIESIDS.fixedAssetsTotal) - }, INPUTCHANGEINTERVAL)} - backgroundColor="blue" - currency - /> - - - { - getTotalAssets() - clearErrors(EQUITIESANDLIABILITIESIDS.currentAssets) - }, INPUTCHANGEINTERVAL)} - label={formatMessage(m.currentAssets)} - backgroundColor="blue" - currency - /> - - - - - - {formatMessage(m.debtsAndEquity)} - - - { - getTotalLiabilities() - clearErrors(EQUITIESANDLIABILITIESIDS.longTerm) - }, INPUTCHANGEINTERVAL)} - error={ - errors && - getErrorViaPath(errors, EQUITIESANDLIABILITIESIDS.longTerm) - } - label={formatMessage(m.longTerm)} - backgroundColor="blue" - currency - /> - - - { - getTotalLiabilities() - clearErrors(EQUITIESANDLIABILITIESIDS.shortTerm) - }, INPUTCHANGEINTERVAL)} - error={ - errors && - getErrorViaPath(errors, EQUITIESANDLIABILITIESIDS.shortTerm) - } - label={formatMessage(m.shortTerm)} - backgroundColor="blue" - currency - /> - - - - { - getTotalEquity() - clearErrors(EQUITIESANDLIABILITIESIDS.totalEquity) - }, INPUTCHANGEINTERVAL)} - error={ - errors && - getErrorViaPath(errors, EQUITIESANDLIABILITIESIDS.totalEquity) - } - label={formatMessage(m.equity)} - backgroundColor="blue" - currency - /> - - - - - - - {errors && errors.validator ? ( - - - - ) : null} - - ) -} diff --git a/libs/application/templates/inao/financial-statement-political-party/src/fields/ElectionStatement/ElectionStatement.tsx b/libs/application/templates/inao/financial-statement-political-party/src/fields/ElectionStatement/ElectionStatement.tsx deleted file mode 100644 index 89b4df521eaa..000000000000 --- a/libs/application/templates/inao/financial-statement-political-party/src/fields/ElectionStatement/ElectionStatement.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { getErrorViaPath, getValueViaPath } from '@island.is/application/core' -import { AlertBanner, Box, InputError, Text } from '@island.is/island-ui/core' -import { m } from '../../lib/messages' -import { useLocale } from '@island.is/localization' -import { DefaultEvents, FieldBaseProps } from '@island.is/application/types' -import { format as formatNationalId } from 'kennitala' -import { useSubmitApplication } from '../../hooks/useSubmitApplication' -import BottomBar from '../../components/BottomBar' -import { useFormContext } from 'react-hook-form' -import { FinancialStatementPoliticalParty } from '../../lib/dataSchema' -import { ELECTIONLIMIT, GREATER } from '../../utils/constants' -import { formatNumber } from '../../utils/helpers' - -export const ElectionStatement = ({ - application, - goToScreen, - refetch, -}: FieldBaseProps) => { - const { formatMessage } = useLocale() - const { - formState: { errors }, - } = useFormContext() - const answers = application.answers as FinancialStatementPoliticalParty - const email = getValueViaPath(answers, 'about.email') - const [submitApplication, { loading }] = useSubmitApplication({ - application, - refetch, - event: DefaultEvents.SUBMIT, - }) - - const onBackButtonClick = () => { - const incomeLimit = getValueViaPath(answers, 'election.incomeLimit') - - if (incomeLimit === GREATER) { - goToScreen?.('attachments.file') - } else { - goToScreen?.('election') - } - } - - const onSendButtonClick = () => { - submitApplication() - } - - return ( - - - - {`${answers.about.fullName}, - ${formatMessage(m.nationalId)}: ${formatNationalId( - answers.about.nationalId, - )}, ${formatMessage(m.participated)} - ${answers.election.genitiveName}`} - - - - {`${formatMessage(m.electionDeclare)} ${formatNumber( - ELECTIONLIMIT, - )}`} - - - {formatMessage(m.electionStatementLaw)} - - - - - {errors && getErrorViaPath(errors, 'applicationApprove') ? ( - - ) : null} - - - ) -} diff --git a/libs/application/templates/inao/financial-statement-political-party/src/fields/KeyNumbersCapital/KeyNumbersCapital.tsx b/libs/application/templates/inao/financial-statement-political-party/src/fields/KeyNumbersCapital/KeyNumbersCapital.tsx deleted file mode 100644 index 17329d216f88..000000000000 --- a/libs/application/templates/inao/financial-statement-political-party/src/fields/KeyNumbersCapital/KeyNumbersCapital.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { useState, useEffect } from 'react' -import { - Box, - GridColumn, - GridContainer, - GridRow, -} from '@island.is/island-ui/core' -import { getValueViaPath } from '@island.is/application/core' -import debounce from 'lodash/debounce' -import { useFormContext } from 'react-hook-form' -import { useLocale } from '@island.is/localization' -import { InputController } from '@island.is/shared/form-fields' -import { m } from '../../lib/messages' -import { getErrorViaPath } from '@island.is/application/core' -import { CAPITALNUMBERS, INPUTCHANGEINTERVAL } from '../../utils/constants' -import { Total } from '../../components/Total' - -export const KeyNumbersCapital = () => { - const { formatMessage } = useLocale() - const [totalCapital, setTotalCapital] = useState(0) - const { - clearErrors, - formState: { errors }, - getValues, - } = useFormContext() - - const getTotalCapital = () => { - const values = getValues() - - const income = getValueViaPath(values, CAPITALNUMBERS.capitalIncome) || '0' - const expense = getValueViaPath(values, CAPITALNUMBERS.capitalCost) || '0' - const total = Number(income) - Number(expense) - setTotalCapital(total) - } - - useEffect(() => { - getTotalCapital() - }, [getTotalCapital]) - - return ( - - - - - { - getTotalCapital() - clearErrors(CAPITALNUMBERS.capitalIncome) - }, INPUTCHANGEINTERVAL)} - label={formatMessage(m.capitalIncome)} - backgroundColor="blue" - rightAlign - currency - /> - - - - - { - getTotalCapital() - clearErrors(CAPITALNUMBERS.capitalCost) - }, INPUTCHANGEINTERVAL)} - label={formatMessage(m.capitalCost)} - error={ - errors && getErrorViaPath(errors, CAPITALNUMBERS.capitalCost) - } - backgroundColor="blue" - rightAlign - currency - /> - - - - - - - - - - ) -} diff --git a/libs/application/templates/inao/financial-statement-political-party/src/fields/Overview/Overview.tsx b/libs/application/templates/inao/financial-statement-political-party/src/fields/Overview/Overview.tsx deleted file mode 100644 index 5748df5a3806..000000000000 --- a/libs/application/templates/inao/financial-statement-political-party/src/fields/Overview/Overview.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import { useState } from 'react' -import { DefaultEvents, FieldBaseProps } from '@island.is/application/types' - -import { - AlertBanner, - Box, - Checkbox, - Divider, - GridColumn, - GridRow, - InputError, - Text, -} from '@island.is/island-ui/core' -import { Controller, useFormContext } from 'react-hook-form' -import { getErrorViaPath, getValueViaPath } from '@island.is/application/core' -import { useLocale } from '@island.is/localization' -import { m } from '../../lib/messages' -import { useSubmitApplication } from '../../hooks/useSubmitApplication' -import BottomBar from '../../components/BottomBar' -import { FinancialStatementPoliticalParty } from '../../lib/dataSchema' -import { AboutOverview } from '../../components/AboutOverview' -import { GREATER } from '../../utils/constants' -import { - sectionColumn, - starterColumnStyle, -} from '../../components/css/overviewStyles.css' -import { CapitalNumberOverview } from '../../components/CapitalNumberOverview' -import { AssetDebtEquityOverview } from '../../components/AssetDebtEquityOverview' -import { FileValueLine } from '../../components/FileValueLine' - -export const Overview = ({ - application, - goToScreen, - refetch, -}: FieldBaseProps) => { - const { formatMessage } = useLocale() - const { - formState: { errors }, - setError, - setValue, - } = useFormContext() - - const [approveOverview, setApproveOverview] = useState(false) - - const answers = application.answers as FinancialStatementPoliticalParty - const fileName = answers.attachments?.file?.[0]?.name - - const [submitApplication, { error: submitError, loading }] = - useSubmitApplication({ - application, - refetch, - event: DefaultEvents.SUBMIT, - }) - - const onBackButtonClick = () => { - const incomeLimit = getValueViaPath(answers, 'election.incomeLimit') - - if (incomeLimit === GREATER) { - goToScreen?.('attachments.file') - } else { - goToScreen?.('election') - } - } - - const onSendButtonClick = () => { - if (approveOverview) { - submitApplication() - } else { - setError('applicationApprove', { - type: 'error', - }) - } - } - - return ( - - - - - - - - - - {formatMessage(m.expensesIncome)} - - - - - - - {formatMessage(m.expenses)} - - - - - - - - - - - - - - {formatMessage(m.propertiesAndDebts)} - - - - - - - - {fileName ? ( - <> - - - > - ) : null} - - - - - {formatMessage(m.overview)} - - - - { - return ( - { - onChange(e.target.checked) - setApproveOverview(e.target.checked) - setValue('applicationApprove' as string, e.target.checked) - }} - checked={value} - name="applicationApprove" - id="applicationApprove" - label={formatMessage(m.overviewCorrect)} - large - /> - ) - }} - /> - - {errors && getErrorViaPath(errors, 'applicationApprove') ? ( - - ) : null} - {submitError ? ( - - - - ) : null} - - - ) -} diff --git a/libs/application/templates/inao/financial-statement-political-party/src/fields/PartyOperatingIncome/PartyExpenses.tsx b/libs/application/templates/inao/financial-statement-political-party/src/fields/PartyOperatingIncome/PartyExpenses.tsx deleted file mode 100644 index 5a3eadacb74b..000000000000 --- a/libs/application/templates/inao/financial-statement-political-party/src/fields/PartyOperatingIncome/PartyExpenses.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { Fragment } from 'react' -import debounce from 'lodash/debounce' -import { RecordObject } from '@island.is/application/types' -import { InputController } from '@island.is/shared/form-fields' -import { Box } from '@island.is/island-ui/core' -import { useFormContext } from 'react-hook-form' -import { useLocale } from '@island.is/localization' -import { getErrorViaPath } from '@island.is/application/core' -import { m } from '../../lib/messages' -import { INPUTCHANGEINTERVAL, PARTYOPERATIONIDS } from '../../utils/constants' - -interface PropTypes { - getSum: () => void - errors: RecordObject | undefined -} - -export const PartyExpenses = ({ errors, getSum }: PropTypes): JSX.Element => { - const { formatMessage } = useLocale() - const { clearErrors } = useFormContext() - - const onInputChange = debounce((fieldId: string) => { - getSum() - clearErrors(fieldId) - }, INPUTCHANGEINTERVAL) - - return ( - - - onInputChange(PARTYOPERATIONIDS.electionOffice)} - error={ - errors && getErrorViaPath(errors, PARTYOPERATIONIDS.electionOffice) - } - rightAlign - backgroundColor="blue" - currency - /> - - - onInputChange(PARTYOPERATIONIDS.otherCost)} - error={errors && getErrorViaPath(errors, PARTYOPERATIONIDS.otherCost)} - rightAlign - backgroundColor="blue" - currency - /> - - - ) -} diff --git a/libs/application/templates/inao/financial-statement-political-party/src/fields/PartyOperatingIncome/PartyIncome.tsx b/libs/application/templates/inao/financial-statement-political-party/src/fields/PartyOperatingIncome/PartyIncome.tsx deleted file mode 100644 index 26c702129598..000000000000 --- a/libs/application/templates/inao/financial-statement-political-party/src/fields/PartyOperatingIncome/PartyIncome.tsx +++ /dev/null @@ -1,206 +0,0 @@ -import React, { Fragment, useEffect } from 'react' -import debounce from 'lodash/debounce' -import { RecordObject } from '@island.is/application/types' -import { Box } from '@island.is/island-ui/core' -import { InputController } from '@island.is/shared/form-fields' -import { useFormContext } from 'react-hook-form' -import { useLocale } from '@island.is/localization' -import { getErrorViaPath, getValueViaPath } from '@island.is/application/core' -import { m } from '../../lib/messages' -import { FinancialStatementsInaoTaxInfo } from '@island.is/api/schema' -import { INPUTCHANGEINTERVAL, PARTYOPERATIONIDS } from '../../utils/constants' - -interface PropTypes { - data?: { - financialStatementsInaoTaxInfo: FinancialStatementsInaoTaxInfo[] - } | null - loading: boolean - getSum: () => void - errors: RecordObject | undefined -} - -export const PartyIncome = ({ - data, - loading, - errors, - getSum, -}: PropTypes): JSX.Element => { - const { formatMessage } = useLocale() - const { clearErrors, getValues, setValue } = useFormContext() - useEffect(() => { - const values = getValues() - - const contributionsFromTheTreasury = getValueViaPath( - values, - PARTYOPERATIONIDS.contributionsFromTheTreasury, - ) - const parliamentaryPartySupport = getValueViaPath( - values, - PARTYOPERATIONIDS.parliamentaryPartySupport, - ) - const municipalContributions = getValueViaPath( - values, - PARTYOPERATIONIDS.municipalContributions, - ) - - if (data?.financialStatementsInaoTaxInfo) { - if (!contributionsFromTheTreasury) { - setValue( - PARTYOPERATIONIDS.contributionsFromTheTreasury, - data.financialStatementsInaoTaxInfo?.[0]?.value?.toString() ?? '', - ) - } - if (!parliamentaryPartySupport) { - setValue( - PARTYOPERATIONIDS.parliamentaryPartySupport, - data.financialStatementsInaoTaxInfo?.[1]?.value?.toString() ?? '', - ) - } - if (!municipalContributions) { - setValue( - PARTYOPERATIONIDS.municipalContributions, - data.financialStatementsInaoTaxInfo?.[2]?.value?.toString() ?? '', - ) - } - } - getSum() - }, [data, getSum, setValue]) - - const onInputChange = debounce((fieldId: string) => { - getSum() - clearErrors(fieldId) - }, INPUTCHANGEINTERVAL) - - return ( - - - - onInputChange(PARTYOPERATIONIDS.contributionsFromTheTreasury) - } - rightAlign - backgroundColor="blue" - loading={loading} - currency - error={ - errors && - getErrorViaPath( - errors, - PARTYOPERATIONIDS.contributionsFromTheTreasury, - ) - } - /> - - - - onInputChange(PARTYOPERATIONIDS.parliamentaryPartySupport) - } - loading={loading} - backgroundColor="blue" - rightAlign - currency - error={ - errors && - getErrorViaPath(errors, PARTYOPERATIONIDS.parliamentaryPartySupport) - } - /> - - - - onInputChange(PARTYOPERATIONIDS.municipalContributions) - } - loading={loading} - backgroundColor="blue" - rightAlign - currency - error={ - errors && - getErrorViaPath(errors, PARTYOPERATIONIDS.municipalContributions) - } - /> - - - - onInputChange(PARTYOPERATIONIDS.contributionsFromLegalEntities) - } - backgroundColor="blue" - currency - error={ - errors && - getErrorViaPath( - errors, - PARTYOPERATIONIDS.contributionsFromLegalEntities, - ) - } - /> - - - - onInputChange(PARTYOPERATIONIDS.contributionsFromIndividuals) - } - backgroundColor="blue" - currency - error={ - errors && - getErrorViaPath( - errors, - PARTYOPERATIONIDS.contributionsFromIndividuals, - ) - } - /> - - - - onInputChange(PARTYOPERATIONIDS.generalMembershipFees) - } - rightAlign - backgroundColor="blue" - currency - error={ - errors && - getErrorViaPath(errors, PARTYOPERATIONIDS.generalMembershipFees) - } - /> - - - onInputChange(PARTYOPERATIONIDS.otherIncome)} - rightAlign - backgroundColor="blue" - currency - error={ - errors && getErrorViaPath(errors, PARTYOPERATIONIDS.otherIncome) - } - /> - - - ) -} diff --git a/libs/application/templates/inao/financial-statement-political-party/src/fields/PartyOperatingIncome/PartyOperatingIncome.tsx b/libs/application/templates/inao/financial-statement-political-party/src/fields/PartyOperatingIncome/PartyOperatingIncome.tsx deleted file mode 100644 index 14c43d56e667..000000000000 --- a/libs/application/templates/inao/financial-statement-political-party/src/fields/PartyOperatingIncome/PartyOperatingIncome.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import React from 'react' -import { useFormContext } from 'react-hook-form' -import { useQuery } from '@apollo/client' -import { - GridColumn, - GridContainer, - GridRow, - Text, -} from '@island.is/island-ui/core' -import { useLocale } from '@island.is/localization' - -import { m } from '../../lib/messages' -import { PartyIncome } from './PartyIncome' -import { PartyExpenses } from './PartyExpenses' - -import { TaxInfoQuery } from '../../graphql' -import { getValueViaPath } from '@island.is/application/core' -import { FieldBaseProps } from '@island.is/application/types' -import { Total } from '../../components/Total' -import { useTotals } from '../../hooks/useTotals' -import { OPERATINGCOST, PARTYOPERATIONIDS } from '../../utils/constants' - -export const PartyOperatingIncome = ({ application }: FieldBaseProps) => { - const { answers } = application - const operatingYear = getValueViaPath( - answers, - 'conditionalAbout.operatingYear', - ) - - const { data, loading } = useQuery(TaxInfoQuery, { - variables: { year: operatingYear }, - }) - - const { - formState: { errors }, - } = useFormContext() - - const [getTotalIncome, totalIncome] = useTotals( - PARTYOPERATIONIDS.incomePrefix, - ) - const [getTotalExpense, totalExpense] = useTotals( - PARTYOPERATIONIDS.expensePrefix, - ) - const { formatMessage } = useLocale() - - return ( - - - - - {formatMessage(m.income)} - - - - - - - {formatMessage(m.expenses)} - - - - - - - - - - - - ) -} diff --git a/libs/application/templates/inao/financial-statement-political-party/src/fields/PartyOverview/PartyOverview.tsx b/libs/application/templates/inao/financial-statement-political-party/src/fields/PartyOverview/PartyOverview.tsx index 363492b42d03..ed5081de3586 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/fields/PartyOverview/PartyOverview.tsx +++ b/libs/application/templates/inao/financial-statement-political-party/src/fields/PartyOverview/PartyOverview.tsx @@ -171,52 +171,6 @@ export const PartyOverview = ({ > ) : null} - - - {formatMessage(m.overview)} - - - - { - return ( - { - onChange(e.target.checked) - setApproveOverview(e.target.checked) - setValue('applicationApprove' as string, e.target.checked) - }} - checked={value} - name="applicationApprove" - id="applicationApprove" - label={formatMessage(m.overviewCorrect)} - large - /> - ) - }} - /> - - {errors && getErrorViaPath(errors, 'applicationApprove') ? ( - - ) : null} - {submitError ? ( - - - - ) : null} - ) } diff --git a/libs/application/templates/inao/financial-statement-political-party/src/fields/Success/Success.tsx b/libs/application/templates/inao/financial-statement-political-party/src/fields/Success/Success.tsx deleted file mode 100644 index 1603815ae9d1..000000000000 --- a/libs/application/templates/inao/financial-statement-political-party/src/fields/Success/Success.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { - Box, - ContentBlock, - ActionCard, - AlertMessage, -} from '@island.is/island-ui/core' -import { useLocale } from '@island.is/localization' -import { CustomField, FieldBaseProps } from '@island.is/application/types' -import format from 'date-fns/format' -import { m } from '../../lib/messages' -import { FinancialStatementPoliticalParty } from '../../lib/dataSchema' - -interface PropTypes extends FieldBaseProps { - field: CustomField -} - -export const Success = ({ application }: PropTypes) => { - const applicationAnswers = - application.answers as FinancialStatementPoliticalParty - const { formatMessage } = useLocale() - - const getDescriptionText = () => { - const currentDate = format(new Date(), "dd.MM.yyyy 'kl.' kk:mm") - return `${formatMessage(m.operatingYearMsgFirst)} ${ - applicationAnswers.conditionalAbout.operatingYear - } - ${formatMessage(m.individualReceivedMsgSecond)} ${currentDate}` - } - - return ( - - - - - - - window.open('/minarsidur/postholf', '_blank'), - }} - backgroundColor="blue" - /> - - - - ) -} diff --git a/libs/application/templates/inao/financial-statement-political-party/src/fields/index.ts b/libs/application/templates/inao/financial-statement-political-party/src/fields/index.ts index 4d9e7b24f065..9665239da2a3 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/fields/index.ts +++ b/libs/application/templates/inao/financial-statement-political-party/src/fields/index.ts @@ -1,9 +1,3 @@ export { OperatingYear } from './OperatingYear/OperatingYear' export { PowerOfAttorneyFields } from './PowerOfAttorneyFields/PowerOfAttorneyFields' -export { PartyOperatingIncome } from './PartyOperatingIncome/PartyOperatingIncome' -export { KeyNumbersCapital } from './KeyNumbersCapital/KeyNumbersCapital' -export { ElectionEquities } from './ElectionEquities/ElectionEquities' export { PartyOverview } from './PartyOverview/PartyOverview' -export { Overview } from './Overview/Overview' -export { ElectionStatement } from './ElectionStatement/ElectionStatement' -export { Success } from './Success/Success' diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/conclusionSection/index.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/conclusionSection/index.ts new file mode 100644 index 000000000000..1ca0e1529209 --- /dev/null +++ b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/conclusionSection/index.ts @@ -0,0 +1,19 @@ +import { buildFormConclusionSection } from '@island.is/application/ui-forms' +import { m } from '../../../lib/messages' +import { getValueViaPath } from '@island.is/application/core' + +export const conclusionSection = buildFormConclusionSection({ + multiFieldTitle: m.received, + alertTitle: m.returned, + alertMessage: (application) => { + const year = getValueViaPath( + application.answers, + 'conditionalAbout.operatingYear', + ) + return { + ...m.conclusionAlertMessage, + values: { value1: year }, + } + }, + accordion: false, +}) diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/financialStatementSection/index.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/financialStatementSection/index.ts similarity index 88% rename from libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/financialStatementSection/index.ts rename to libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/financialStatementSection/index.ts index 6f40b92fc14d..5ff1a5cf83d2 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/financialStatementSection/index.ts +++ b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/financialStatementSection/index.ts @@ -3,8 +3,8 @@ import { buildSection, getValueViaPath, } from '@island.is/application/core' -import { m } from '../../../../lib/messages' -import { LESS } from '../../../../utils/constants' +import { LESS } from '../../../utils/constants' +import { m } from '../../../lib/messages' export const financialStatementSection = buildSection({ id: 'documents', diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/index.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/index.ts index bca7ac51bc34..58fe15ed2a7e 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/index.ts +++ b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/index.ts @@ -4,19 +4,21 @@ import { Form, FormModes } from '@island.is/application/types' import Logo from '../../components/Logo' import { clientInfoSection } from './clientInfoSection' import { keyNumbersSection } from './keyNumbersSection' -import { financialStatementSection } from './keyNumbersSection/financialStatementSection' -import { overviewSection } from './keyNumbersSection/overviewSection' +import { financialStatementSection } from './financialStatementSection' +import { overviewSection } from './overviewSection' +import { conclusionSection } from './conclusionSection' export const FinancialStatementPoliticalPartyForm: Form = buildForm({ id: 'FinancialStatementPoliticalPartyForm', title: m.applicationTitle, mode: FormModes.DRAFT, - renderLastScreenButton: false, + renderLastScreenButton: true, logo: Logo, children: [ clientInfoSection, keyNumbersSection, financialStatementSection, overviewSection, + conclusionSection, ], }) diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/capitalNumbersSubsection.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/capitalNumbersSubsection.ts new file mode 100644 index 000000000000..14d7b80a02f2 --- /dev/null +++ b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/capitalNumbersSubsection.ts @@ -0,0 +1,43 @@ +import { + buildDisplayField, + buildMultiField, + buildSubSection, + buildTextField, +} from '@island.is/application/core' +import { m } from '../../../lib/messages' +import { CAPITALNUMBERS } from '../../../utils/constants' +import { sumCapitalNumbers } from '../../../utils/helpers' + +export const capitalNumbersSubsection = buildSubSection({ + id: 'keynumbers.capitalNumbers', + title: m.capitalNumbers, + children: [ + buildMultiField({ + id: 'capitalNumber', + title: m.capitalNumbersSectionTitle, + children: [ + buildTextField({ + id: CAPITALNUMBERS.capitalIncome, + title: m.capitalIncome, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CAPITALNUMBERS.capitalCost, + title: m.capitalCost, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: CAPITALNUMBERS.total, + title: m.totalCapital, + value: sumCapitalNumbers, + variant: 'currency', + rightAlign: true, + }), + ], + }), + ], +}) diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/capitalNumbersSubsection/index.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/capitalNumbersSubsection/index.ts deleted file mode 100644 index 42e21bf1259e..000000000000 --- a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/capitalNumbersSubsection/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { - buildCustomField, - buildMultiField, - buildSubSection, -} from '@island.is/application/core' -import { m } from '../../../../lib/messages' -import { CAPITALNUMBERS } from '../../../../utils/constants' - -export const capitalNumbersSubsection = buildSubSection({ - id: 'keynumbers.capitalNumbers', - title: m.capitalNumbers, - children: [ - buildMultiField({ - id: 'capitalNumber', - title: m.capitalNumbersSectionTitle, - description: m.fillOutAppopriate, - children: [ - buildCustomField({ - id: 'capitalNumberField', - title: '', - description: '', - component: 'KeyNumbersCapital', - childInputIds: Object.values(CAPITALNUMBERS), - }), - ], - }), - ], -}) diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/equitiesAndLiabilitiesSubsection.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/equitiesAndLiabilitiesSubsection.ts new file mode 100644 index 000000000000..fcd85ee165de --- /dev/null +++ b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/equitiesAndLiabilitiesSubsection.ts @@ -0,0 +1,122 @@ +import { + buildAlertMessageField, + buildDescriptionField, + buildDisplayField, + buildMultiField, + buildSubSection, + buildTextField, +} from '@island.is/application/core' +import { m } from '../../../lib/messages' +import { + EQUITIESANDLIABILITIESIDS, + EQUITYANDLIABILITIESTOTALS, +} from '../../../utils/constants' +import { + showEquitiesAndLiabilitiesAlert, + sumDebts, + sumEquityAndDebts, + sumProperties, +} from '../../../utils/helpers' + +export const equitiesAndLiabilitiesSubsection = buildSubSection({ + id: 'keyNumbers.equitiesAndLiabilities', + title: m.propertiesAndDebts, + children: [ + buildMultiField({ + id: 'equitiesAndLiabilitiesMultiField', + title: m.keyNumbersDebt, + children: [ + // Assets + buildDescriptionField({ + id: 'propertiesDescription', + title: m.properties, + titleVariant: 'h3', + }), + buildTextField({ + id: EQUITIESANDLIABILITIESIDS.fixedAssetsTotal, + title: m.fixedAssetsTotal, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: EQUITIESANDLIABILITIESIDS.currentAssets, + title: m.currentAssets, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: EQUITYANDLIABILITIESTOTALS.assetsTotal, + title: '', + label: m.totalAssets, + value: sumProperties, + variant: 'currency', + rightAlign: true, + }), + + // Debts + buildDescriptionField({ + id: 'debtsDescription', + title: m.debts, + titleVariant: 'h3', + }), + buildTextField({ + id: EQUITIESANDLIABILITIESIDS.longTerm, + title: m.longTerm, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: EQUITIESANDLIABILITIESIDS.shortTerm, + title: m.shortTerm, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: EQUITYANDLIABILITIESTOTALS.liabilitiesTotal, + title: '', + label: m.totalDebts, + value: sumDebts, + variant: 'currency', + rightAlign: true, + }), + + // Equity + buildDescriptionField({ + id: 'equityDescription', + title: m.equity, + titleVariant: 'h3', + }), + buildTextField({ + id: EQUITIESANDLIABILITIESIDS.totalEquity, + title: m.equity, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + + // Total Equity and Liabilities + buildDisplayField({ + id: EQUITYANDLIABILITIESTOTALS.equityAndLiabilitiesTotal, + title: m.debtsAndCash, + titleVariant: 'h3', + value: sumEquityAndDebts, + variant: 'currency', + rightAlign: true, + }), + + // Alert if total equity and liabilities do not match total assets + buildAlertMessageField({ + condition: showEquitiesAndLiabilitiesAlert, + id: 'equitiesAndLiabilitiesAlert', + title: m.equityErrorTitle, + message: m.equityDebtsAssetsValidatorError, + alertType: 'error', + }), + ], + }), + ], +}) diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/equitiesAndLiabilitiesSubsection/index.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/equitiesAndLiabilitiesSubsection/index.ts deleted file mode 100644 index 6920f444dac6..000000000000 --- a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/equitiesAndLiabilitiesSubsection/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { buildCustomField, buildSubSection } from '@island.is/application/core' -import { m } from '../../../../lib/messages' -import { EQUITIESANDLIABILITIESIDS } from '../../../../utils/constants' - -export const equitiesAndLiabilitiesSubsection = buildSubSection({ - id: 'keyNumbers.equitiesAndLiabilities', - title: m.propertiesAndDebts, - children: [ - buildCustomField({ - id: 'equitiesAndLiabilities', - title: m.keyNumbersDebt, - description: m.fillOutAppopriate, - component: 'ElectionEquities', - childInputIds: Object.values(EQUITIESANDLIABILITIESIDS), - }), - ], -}) diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/index.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/index.ts index dee0b831047e..d23e0707d9c1 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/index.ts +++ b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/index.ts @@ -1,8 +1,8 @@ import { buildSection } from '@island.is/application/core' import { m } from '../../../lib/messages' -import { operatingCostSubsection } from './operatingCostSubsection' -import { capitalNumbersSubsection } from './capitalNumbersSubsection' import { equitiesAndLiabilitiesSubsection } from './equitiesAndLiabilitiesSubsection' +import { capitalNumbersSubsection } from './capitalNumbersSubsection' +import { operatingCostSubsection } from './operatingCostSubsection' export const keyNumbersSection = buildSection({ id: 'keyNumbers', diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/operatingCostSubsection.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/operatingCostSubsection.ts new file mode 100644 index 000000000000..5ea3e3b7e771 --- /dev/null +++ b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/operatingCostSubsection.ts @@ -0,0 +1,129 @@ +import { + buildDescriptionField, + buildDisplayField, + buildMultiField, + buildSubSection, + buildTextField, +} from '@island.is/application/core' +import { m } from '../../../lib/messages' +import { OPERATINGCOST, PARTYOPERATIONIDS } from '../../../utils/constants' +import { sumExpenses, sumIncome, sumTotal } from '../../../utils/helpers' + +export const operatingCostSubsection = buildSubSection({ + id: 'operatingCost', + title: m.expensesIncome, + children: [ + buildMultiField({ + id: 'operatingCostMultiField', + title: m.keyNumbersIncomeAndExpenses, + children: [ + // Income + buildDescriptionField({ + id: 'incomeDescription', + title: m.income, + titleVariant: 'h3', + }), + buildTextField({ + id: PARTYOPERATIONIDS.contributionsFromTheTreasury, + title: m.contributionsFromTheTreasury, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: PARTYOPERATIONIDS.parliamentaryPartySupport, + title: m.parliamentaryPartySupport, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: PARTYOPERATIONIDS.municipalContributions, + title: m.municipalContributions, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: PARTYOPERATIONIDS.contributionsFromLegalEntities, + title: m.contributionsFromLegalEntities, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: PARTYOPERATIONIDS.contributionsFromIndividuals, + title: m.contributionsFromIndividuals, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: PARTYOPERATIONIDS.generalMembershipFees, + title: m.generalMembershipFees, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: PARTYOPERATIONIDS.otherIncome, + title: m.otherIncome, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: PARTYOPERATIONIDS.totalIncome, + title: '', + label: m.totalIncome, + value: sumIncome, + variant: 'currency', + rightAlign: true, + }), + + // Expenses + buildDescriptionField({ + id: 'expensesDescription', + title: m.expenses, + titleVariant: 'h3', + }), + buildTextField({ + id: PARTYOPERATIONIDS.electionOffice, + title: m.electionOffice, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: PARTYOPERATIONIDS.otherCost, + title: m.otherOperationalCost, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: PARTYOPERATIONIDS.totalExpense, + title: '', + label: m.totalExpenses, + value: sumExpenses, + variant: 'currency', + rightAlign: true, + }), + + // Total + buildDescriptionField({ + id: 'totalDescription', + title: m.operatingCost, + titleVariant: 'h3', + }), + buildDisplayField({ + id: OPERATINGCOST.total, + title: '', + value: sumTotal, + variant: 'currency', + rightAlign: true, + }), + ], + }), + ], +}) diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/operatingCostSubsection/index.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/operatingCostSubsection/index.ts deleted file mode 100644 index fbc76af55a1b..000000000000 --- a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/operatingCostSubsection/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { buildCustomField, buildSubSection } from '@island.is/application/core' -import { m } from '../../../../lib/messages' -import { PARTYOPERATIONIDS } from '../../../../utils/constants' - -export const operatingCostSubsection = buildSubSection({ - id: 'operatingCost', - title: m.expensesIncome, - children: [ - buildCustomField({ - id: 'partyOperations', - title: m.keyNumbersIncomeAndExpenses, - description: m.fillOutAppopriate, - component: 'PartyOperatingIncome', - childInputIds: Object.values(PARTYOPERATIONIDS), - }), - ], -}) diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/overviewSection/index.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/overviewSection/index.ts deleted file mode 100644 index 543a96b5fa01..000000000000 --- a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/overviewSection/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { buildSection } from '@island.is/application/core' -import { overviewMultiField } from './overviewMultiField' -import { m } from '../../../../lib/messages' - -export const overviewSection = buildSection({ - id: 'overviewSection', - title: m.overviewSectionTitle, - children: [overviewMultiField], -}) diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/overviewSection/overviewMultiField.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/overviewSection/overviewMultiField.ts deleted file mode 100644 index d466187af16c..000000000000 --- a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/keyNumbersSection/overviewSection/overviewMultiField.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - buildCustomField, - buildMultiField, - getValueViaPath, -} from '@island.is/application/core' -import { m } from '../../../../lib/messages' -import { GREATER, LESS } from '../../../../utils/constants' - -export const overviewMultiField = buildMultiField({ - id: 'overview', - title: m.yearlyOverview, - description: m.review, - children: [ - buildCustomField({ - id: 'overviewPartyField', - title: '', - doesNotRequireAnswer: true, - component: 'PartyOverview', - }), - buildCustomField({ - id: 'overviewField', - title: '', - condition: (answers) => - getValueViaPath(answers, 'election.incomeLimit') === GREATER, - doesNotRequireAnswer: true, - component: 'Overview', - }), - buildCustomField({ - id: 'overviewStatementField', - title: '', - condition: (answers) => - getValueViaPath(answers, 'election.incomeLimit') === LESS, - doesNotRequireAnswer: true, - component: 'ElectionStatement', - }), - ], -}) diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/overviewSection/index.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/overviewSection/index.ts new file mode 100644 index 000000000000..c118b90933cc --- /dev/null +++ b/libs/application/templates/inao/financial-statement-political-party/src/forms/applicationForm/overviewSection/index.ts @@ -0,0 +1,51 @@ +import { + buildCheckboxField, + buildCustomField, + buildMultiField, + buildSection, + buildSubmitField, + YES, +} from '@island.is/application/core' +import { m } from '../../../lib/messages' +import { DefaultEvents } from '@island.is/application/types' + +export const overviewSection = buildSection({ + id: 'overviewSection', + title: m.overviewSectionTitle, + children: [ + buildMultiField({ + id: 'overview', + title: m.yearlyOverview, + description: m.review, + children: [ + buildCustomField({ + id: 'overviewPartyField', + title: '', + doesNotRequireAnswer: true, + component: 'PartyOverview', + }), + buildCheckboxField({ + id: 'approveOverview', + title: '', + options: [ + { + label: m.overviewCorrect, + value: YES, + }, + ], + }), + buildSubmitField({ + id: 'overview.submit', + title: '', + actions: [ + { + event: DefaultEvents.SUBMIT, + name: m.send, + type: 'primary', + }, + ], + }), + ], + }), + ], +}) diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/done/conclusionSection/index.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/done/conclusionSection/index.ts deleted file mode 100644 index d6b9add9c2e4..000000000000 --- a/libs/application/templates/inao/financial-statement-political-party/src/forms/done/conclusionSection/index.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - buildCustomField, - buildMultiField, - buildSection, -} from '@island.is/application/core' -import { m } from '../../../lib/messages' - -export const conclusionSection = buildSection({ - id: 'conclusionSection', - title: '', - children: [ - buildMultiField({ - id: 'conclusion', - title: m.received, - children: [ - buildCustomField({ - id: 'overview', - component: 'Success', - title: m.applicationAccept, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/done/index.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/done/index.ts index bfb95dec1cee..2f4ee479f333 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/forms/done/index.ts +++ b/libs/application/templates/inao/financial-statement-political-party/src/forms/done/index.ts @@ -1,6 +1,6 @@ import { buildForm } from '@island.is/application/core' import { Form, FormModes } from '@island.is/application/types' -import { conclusionSection } from './conclusionSection' +import { conclusionSection } from '../applicationForm/conclusionSection' export const done: Form = buildForm({ id: 'done', diff --git a/libs/application/templates/inao/financial-statement-political-party/src/lib/dataSchema.ts b/libs/application/templates/inao/financial-statement-political-party/src/lib/dataSchema.ts index 33f3ea983270..cb85a78c893a 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/lib/dataSchema.ts +++ b/libs/application/templates/inao/financial-statement-political-party/src/lib/dataSchema.ts @@ -3,6 +3,7 @@ import { m } from './messages' import * as kennitala from 'kennitala' import { parsePhoneNumberFromString } from 'libphonenumber-js/min' import { checkIfNegative } from '../utils/helpers' +import { YES } from '@island.is/application/core' const requiredNonNegativeString = z .string() @@ -16,9 +17,11 @@ const FileSchema = z.object({ key: z.string(), url: z.string().optional(), }) + const conditionalAbout = z.object({ operatingYear: requiredString, }) + const about = z.object({ nationalId: z .string() @@ -38,17 +41,7 @@ const about = z.object({ email: z.string().email(), }) -const election = z.object({ - selectElection: z.string().optional(), - electionName: z.string().optional(), - genitiveName: z.string().optional(), - incomeLimit: requiredString, -}) - -const operatingCost = z.object({ - total: requiredString, -}) - +// Key numbers - operating cost -income const partyIncome = z.object({ contributionsFromTheTreasury: requiredNonNegativeString, parliamentaryPartySupport: requiredNonNegativeString, @@ -60,54 +53,70 @@ const partyIncome = z.object({ total: z.string(), }) +// Key numbers - operating cost - expenses const partyExpense = z.object({ electionOffice: requiredNonNegativeString, otherCost: requiredNonNegativeString, total: z.string(), }) +// Key numbers - operating cost - total +const operatingCost = z.object({ + total: requiredString, +}) + +// Key numbers - capital numbers const capitalNumbers = z.object({ capitalIncome: requiredNonNegativeString, capitalCost: requiredNonNegativeString, total: z.string(), }) -const equityAndLiabilities = z.object({ - total: z.string(), -}) - +// Key numbers - equities and liabilities - assets const asset = z.object({ currentAssets: requiredNonNegativeString, fixedAssetsTotal: requiredNonNegativeString, - total: requiredString, -}) - -const equity = z.object({ - totalEquity: requiredString, }) +// Key numbers - equities and liabilities - liabilities const liability = z.object({ longTerm: requiredNonNegativeString, shortTerm: requiredNonNegativeString, - total: requiredNonNegativeString, }) +// Key numbers - equities and liabilities - equity +const equity = z.object({ + totalEquity: requiredString, +}) + +// Key numbers - equities and liabilities - total +const equityAndLiabilitiesTotals = z + .object({ + assetsTotal: z.string(), + liabilitiesTotal: z.string(), + equityAndLiabilitiesTotal: z.string(), + }) + .refine((x) => x.assetsTotal === x.equityAndLiabilitiesTotal, { + message: 'equityAndLiabilities.total must match assets.total', + path: ['equityAndLiabilitiesTotals', 'equityAndLiabilitiesTotal'], + }) + export const dataSchema = z.object({ approveExternalData: z.literal(true), conditionalAbout, about, - election, // Needed?? operatingCost, partyIncome, partyExpense, capitalNumbers, - equityAndLiabilities, + equityAndLiabilitiesTotals, asset, equity, liability, attachments: z.object({ file: z.array(FileSchema).nonempty(), }), + approveOverview: z.array(z.literal(YES)).length(1), }) export type FinancialStatementPoliticalParty = z.TypeOf diff --git a/libs/application/templates/inao/financial-statement-political-party/src/lib/messages.ts b/libs/application/templates/inao/financial-statement-political-party/src/lib/messages.ts index 917fa4927876..846a336cbe53 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/lib/messages.ts +++ b/libs/application/templates/inao/financial-statement-political-party/src/lib/messages.ts @@ -242,7 +242,7 @@ export const m = defineMessages({ }, capitalNumbersSectionTitle: { id: 'fspp.application:income.capitalNumbersSectionTitle', - defaultMessage: 'Lykiltölur Fjármagnsliðir', + defaultMessage: 'Lykiltölur - Fjármagnsliðir', description: 'capital numbers', }, capitalIncome: { @@ -267,12 +267,12 @@ export const m = defineMessages({ }, keyNumbersDebt: { id: 'fspp.application:keyNumbers.debt', - defaultMessage: 'Lykiltölur - Eignir, Skuldir og eigið fé', + defaultMessage: 'Lykiltölur - Eignir, skuldir og eigið fé', description: 'Statement debts', }, equityDebtsAssetsValidatorError: { id: 'fspp.application:equityValidatorError', - defaultMessage: 'Skuldir og eigið fé þarf að vera jafnt og eignir samtals', + defaultMessage: 'Skuldir og eigið fé þurfa að vera jöfn og eignum samtals', description: 'Equity + debts shout equal assets', }, properties: { @@ -305,6 +305,11 @@ export const m = defineMessages({ defaultMessage: 'Eignir samtals', description: 'Total assets', }, + debts: { + id: 'fspp.application:keyNumbers.debts', + defaultMessage: 'Skuldir', + description: 'debts', + }, debtsAndEquity: { id: 'fspp.application:keyNumbers.debtsAndEquity', defaultMessage: 'Skuldir og eigið fé', @@ -570,4 +575,10 @@ export const m = defineMessages({ 'Ef þú telur að þessi kennitala ætti að vera skráð sem stjórnmálasamtök þá bendum við þér á að hafa samband við Ríkisendurskoðun í síma 448 8800', description: 'Descriptionwhen user is not allowed to apply', }, + conclusionAlertMessage: { + id: 'fspp.application:conclusionAlertMessage', + defaultMessage: + 'Ársreikning fyrir rekstrarárið {value1} hefur verið skilað', + description: 'Conclusion alert message', + }, }) diff --git a/libs/application/templates/inao/financial-statement-political-party/src/utils/constants.ts b/libs/application/templates/inao/financial-statement-political-party/src/utils/constants.ts index 6a4de5c9f7b5..3027e4f74b38 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/utils/constants.ts +++ b/libs/application/templates/inao/financial-statement-political-party/src/utils/constants.ts @@ -51,17 +51,21 @@ export const EQUITIESANDLIABILITIESIDS = { assetPrefix: 'asset', currentAssets: 'asset.currentAssets', fixedAssetsTotal: 'asset.fixedAssetsTotal', - assetTotal: 'asset.total', liabilityPrefix: 'liability', longTerm: 'liability.longTerm', shortTerm: 'liability.shortTerm', asset: 'liability.asset', - totalLiability: 'liability.total', operationResult: 'equity.operationResult', equityPrefix: 'equity', totalEquity: 'equity.totalEquity', totalCash: 'equity.total', - totalEquityAndLiabilities: 'equityAndLiabilities.total', +} + +export const EQUITYANDLIABILITIESTOTALS = { + assetsTotal: 'equityAndLiabilitiesTotals.assetsTotal', + liabilitiesTotal: 'equityAndLiabilitiesTotals.liabilitiesTotal', + equityAndLiabilitiesTotal: + 'equityAndLiabilitiesTotals.equityAndLiabilitiesTotal', } // Error helpers diff --git a/libs/application/templates/inao/financial-statement-political-party/src/utils/helpers.ts b/libs/application/templates/inao/financial-statement-political-party/src/utils/helpers.ts index 82ac09edeb37..723bad714d36 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/utils/helpers.ts +++ b/libs/application/templates/inao/financial-statement-political-party/src/utils/helpers.ts @@ -1,7 +1,15 @@ import { Config } from '../types/types' import subYears from 'date-fns/subYears' import getYear from 'date-fns/getYear' -import { TOTAL } from './constants' +import { + CAPITALNUMBERS, + EQUITIESANDLIABILITIESIDS, + EQUITYANDLIABILITIESTOTALS, + PARTYOPERATIONIDS, + TOTAL, +} from './constants' +import { FormValue } from '@island.is/application/types' +import { getValueViaPath } from '@island.is/application/core' export const getConfigInfoForKey = (config: Config[], configKey: string) => { return config?.filter((config: Config) => config.key === configKey)[0].value @@ -42,3 +50,170 @@ export const formatCurrency = (answer: string) => export const formatNumber = (num: number) => num.toLocaleString('de-DE') export const checkIfNegative = (inputNumber: string) => Number(inputNumber) >= 0 + +export const sumIncome = (answers: FormValue) => { + const contributionsFromTheTreasury = getValueViaPath( + answers, + PARTYOPERATIONIDS.contributionsFromTheTreasury, + ) + const parliamentaryPartySupport = getValueViaPath( + answers, + PARTYOPERATIONIDS.parliamentaryPartySupport, + ) + const municipalContributions = getValueViaPath( + answers, + PARTYOPERATIONIDS.municipalContributions, + ) + const contributionsFromLegalEntities = getValueViaPath( + answers, + PARTYOPERATIONIDS.contributionsFromLegalEntities, + ) + const contributionsFromIndividuals = getValueViaPath( + answers, + PARTYOPERATIONIDS.contributionsFromIndividuals, + ) + const generalMembershipFees = getValueViaPath( + answers, + PARTYOPERATIONIDS.generalMembershipFees, + ) + const otherIncome = getValueViaPath( + answers, + PARTYOPERATIONIDS.otherIncome, + ) + + return `${ + parseInt(contributionsFromTheTreasury || '0') + + parseInt(parliamentaryPartySupport || '0') + + parseInt(municipalContributions || '0') + + parseInt(contributionsFromLegalEntities || '0') + + parseInt(contributionsFromIndividuals || '0') + + parseInt(generalMembershipFees || '0') + + parseInt(otherIncome || '0') + }` +} + +export const sumExpenses = (answers: FormValue) => { + const electionOffice = getValueViaPath( + answers, + PARTYOPERATIONIDS.electionOffice, + ) + const otherCost = getValueViaPath( + answers, + PARTYOPERATIONIDS.otherCost, + ) + + return `${parseInt(electionOffice || '0') + parseInt(otherCost || '0')}` +} + +export const sumTotal = (answers: FormValue) => { + const income = getValueViaPath(answers, PARTYOPERATIONIDS.totalIncome) + const expenses = getValueViaPath( + answers, + PARTYOPERATIONIDS.totalExpense, + ) + return `${parseInt(income || '0') - parseInt(expenses || '0')}` +} + +export const sumCapitalNumbers = (answers: FormValue) => { + const capitalIncome = getValueViaPath( + answers, + CAPITALNUMBERS.capitalIncome, + ) + const capitalCost = getValueViaPath( + answers, + CAPITALNUMBERS.capitalCost, + ) + return `${parseInt(capitalIncome || '0') - parseInt(capitalCost || '0')}` +} + +export const sumProperties = (answers: FormValue) => { + const fixedAssetsTotal = getValueViaPath( + answers, + EQUITIESANDLIABILITIESIDS.fixedAssetsTotal, + ) + const currentAssets = getValueViaPath( + answers, + EQUITIESANDLIABILITIESIDS.currentAssets, + ) + return `${parseInt(fixedAssetsTotal || '0') + parseInt(currentAssets || '0')}` +} + +export const sumDebts = (answers: FormValue) => { + const longTerm = getValueViaPath( + answers, + EQUITIESANDLIABILITIESIDS.longTerm, + ) + const shortTerm = getValueViaPath( + answers, + EQUITIESANDLIABILITIESIDS.shortTerm, + ) + return `${parseInt(longTerm || '0') + parseInt(shortTerm || '0')}` +} + +export const sumEquityAndDebts = (answers: FormValue) => { + const totalEquity = getValueViaPath( + answers, + EQUITIESANDLIABILITIESIDS.totalEquity, + ) + const totalLiability = getValueViaPath( + answers, + EQUITYANDLIABILITIESTOTALS.liabilitiesTotal, + ) + return `${parseInt(totalEquity || '0') + parseInt(totalLiability || '0')}` +} + +export const showEquitiesAndLiabilitiesAlert = (answers: FormValue) => { + // Assets + const fixedAssetsTotal = getValueViaPath( + answers, + EQUITIESANDLIABILITIESIDS.fixedAssetsTotal, + ) + const currentAssets = getValueViaPath( + answers, + EQUITIESANDLIABILITIESIDS.currentAssets, + ) + const totalAssets = getValueViaPath( + answers, + EQUITYANDLIABILITIESTOTALS.assetsTotal, + ) + + // Debts + const longTerm = getValueViaPath( + answers, + EQUITIESANDLIABILITIESIDS.longTerm, + ) + const shortTerm = getValueViaPath( + answers, + EQUITIESANDLIABILITIESIDS.shortTerm, + ) + const totalLiability = getValueViaPath( + answers, + EQUITYANDLIABILITIESTOTALS.liabilitiesTotal, + ) + + // Equity + const totalEquity = getValueViaPath( + answers, + EQUITIESANDLIABILITIESIDS.totalEquity, + ) + + // Total Equity and Liabilities + const totalEquityAndLiabilities = getValueViaPath( + answers, + EQUITYANDLIABILITIESTOTALS.equityAndLiabilitiesTotal, + ) + + if ( + !fixedAssetsTotal || + !currentAssets || + !totalAssets || + !longTerm || + !shortTerm || + !totalLiability || + !totalEquity || + !totalEquityAndLiabilities + ) { + return false + } + return totalAssets !== totalEquityAndLiabilities +} diff --git a/libs/application/types/src/lib/Fields.ts b/libs/application/types/src/lib/Fields.ts index 877dc89d4621..f89d41400fcf 100644 --- a/libs/application/types/src/lib/Fields.ts +++ b/libs/application/types/src/lib/Fields.ts @@ -783,6 +783,7 @@ export interface DisplayField extends BaseField { titleVariant?: TitleVariants suffix?: MessageDescriptor | string rightAlign?: boolean + halfWidthOwnline?: boolean variant?: TextFieldVariant label?: MessageDescriptor | string value: (answers: FormValue) => string diff --git a/libs/application/ui-fields/src/lib/DisplayFormField/DisplayFormField.tsx b/libs/application/ui-fields/src/lib/DisplayFormField/DisplayFormField.tsx index ae6faba48fed..58e29e6d6bcf 100644 --- a/libs/application/ui-fields/src/lib/DisplayFormField/DisplayFormField.tsx +++ b/libs/application/ui-fields/src/lib/DisplayFormField/DisplayFormField.tsx @@ -21,6 +21,7 @@ export const DisplayFormField = ({ field, application }: Props) => { variant, suffix, rightAlign = false, + halfWidthOwnline = false, } = field const { watch, setValue } = useFormContext() const allValues = watch() @@ -29,52 +30,66 @@ export const DisplayFormField = ({ field, application }: Props) => { useEffect(() => { const newDisplayValue = value(allValues) - setDisplayValue(newDisplayValue) - setValue(id, newDisplayValue) + if (newDisplayValue !== displayValue) { + setDisplayValue(newDisplayValue) + setValue(id, newDisplayValue) + } }, [allValues]) return ( - - {title ? ( - - {formatTextWithLocale( - title, - application, - locale as Locale, - formatMessage, - )} - - ) : null} + + + {title ? ( + + {formatTextWithLocale( + title, + application, + locale as Locale, + formatMessage, + )} + + ) : null} - + + ) } diff --git a/libs/application/ui-forms/src/lib/formConclusionSection/formConclusionSection.ts b/libs/application/ui-forms/src/lib/formConclusionSection/formConclusionSection.ts index 28f3428b7347..fc5669aaee68 100644 --- a/libs/application/ui-forms/src/lib/formConclusionSection/formConclusionSection.ts +++ b/libs/application/ui-forms/src/lib/formConclusionSection/formConclusionSection.ts @@ -18,6 +18,7 @@ type Props = Partial<{ secondButtonLink: StaticText secondButtonLabel: StaticText secondButtonMessage: StaticText + accordion?: boolean expandableHeader: FormText expandableIntro: FormText expandableDescription: FormText @@ -38,6 +39,7 @@ type Props = Partial<{ * @param alertMessage The message inside the green alert box. * @param alertType The type of alert, can be success, warning, error, info. * JUST ADDED * * @param multiFieldTitle Title of the conclusion section. * JUST ADDED * + * @param accordion If false, there will be no accordion. * @param expandableHeader Header of the expandable description section. * @param expandableIntro Intro text of the expandable description section. * @param expandableDescription Markdown code for the expandable description section, most applications use bulletpoints. @@ -54,6 +56,7 @@ export const buildFormConclusionSection = ({ alertMessage = conclusion.alertMessageField.message, alertType = 'success', multiFieldTitle = conclusion.information.formTitle, + accordion = true, expandableHeader = conclusion.expandableDescriptionField.title, expandableIntro = conclusion.expandableDescriptionField.introText, expandableDescription = conclusion.expandableDescriptionField.description, @@ -64,8 +67,20 @@ export const buildFormConclusionSection = ({ bottomButtonLink = '/minarsidur/umsoknir', bottomButtonLabel = coreMessages.openServicePortalButtonTitle, bottomButtonMessage = coreMessages.openServicePortalMessageText, -}: Props) => - buildSection({ +}: Props) => { + const expandableDescriptionField = accordion + ? [ + buildExpandableDescriptionField({ + id: 'uiForms.conclusionExpandableDescription', + title: expandableHeader, + introText: expandableIntro, + description: expandableDescription, + startExpanded: true, + }), + ] + : [] + + return buildSection({ id: 'uiForms.conclusionSection', title: sectionTitle, children: [ @@ -88,13 +103,7 @@ export const buildFormConclusionSection = ({ alertType: alertType, message: alertMessage, }), - buildExpandableDescriptionField({ - id: 'uiForms.conclusionExpandableDescription', - title: expandableHeader, - introText: expandableIntro, - description: expandableDescription, - startExpanded: true, - }), + ...expandableDescriptionField, buildMessageWithLinkButtonField({ id: 'uiForms.conclusionBottomLink', title: '', @@ -107,3 +116,4 @@ export const buildFormConclusionSection = ({ }), ], }) +} From 429a55f4ac2c5fae81299e4b7981f7e1035c6fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3r=C3=B0ur=20H?= Date: Fri, 6 Dec 2024 13:40:58 +0000 Subject: [PATCH 24/57] fix(domains-education): Update education inna resolver (#17156) * Inna education scope * remove flag --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- libs/api/domains/education/src/lib/graphql/inna.resolver.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/api/domains/education/src/lib/graphql/inna.resolver.ts b/libs/api/domains/education/src/lib/graphql/inna.resolver.ts index 3c8479c3063b..fc1c2b3e9d27 100644 --- a/libs/api/domains/education/src/lib/graphql/inna.resolver.ts +++ b/libs/api/domains/education/src/lib/graphql/inna.resolver.ts @@ -23,8 +23,7 @@ import { DiplomaModel } from './inna/diplomas.model' @Resolver() @UseGuards(IdsUserGuard, ScopesGuard, FeatureFlagGuard) -@Scopes(ApiScope.internal) -@FeatureFlag(Features.servicePortalSecondaryEducationPages) +@Scopes(ApiScope.education) @Audit({ namespace: '@island.is/api/education-inna' }) export class InnaResolver { constructor(private innaService: InnaClientService) {} From 8d1ad4163342b868dcf36b3703b294adf27576e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Fri, 6 Dec 2024 18:30:03 +0000 Subject: [PATCH 25/57] feat(web): WHODAS calculator - Remove total score in breakdown and update texts (#17163) * Remove total score in breakdown and update texts * Remove unused translation string --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../components/connected/WHODAS/Calculator.css.ts | 6 ------ .../connected/WHODAS/Calculator.strings.ts | 10 +++++----- .../components/connected/WHODAS/Calculator.tsx | 15 +++++++-------- 3 files changed, 12 insertions(+), 19 deletions(-) diff --git a/apps/web/components/connected/WHODAS/Calculator.css.ts b/apps/web/components/connected/WHODAS/Calculator.css.ts index 81dca8be11dc..773ba0039ecb 100644 --- a/apps/web/components/connected/WHODAS/Calculator.css.ts +++ b/apps/web/components/connected/WHODAS/Calculator.css.ts @@ -10,12 +10,6 @@ export const breakdownRowContainer = style({ gap: theme.spacing[5], }) -export const totalScoreRowContainer = style({ - display: 'grid', - gridTemplateColumns: `${leftWidth + 24}px 1fr`, - gap: theme.spacing[5], -}) - export const stayOnSinglePageWhenPrinting = style({ pageBreakInside: 'avoid', }) diff --git a/apps/web/components/connected/WHODAS/Calculator.strings.ts b/apps/web/components/connected/WHODAS/Calculator.strings.ts index 8d81c1bb4750..03fc832b8a23 100644 --- a/apps/web/components/connected/WHODAS/Calculator.strings.ts +++ b/apps/web/components/connected/WHODAS/Calculator.strings.ts @@ -62,11 +62,6 @@ export const m = { 'Takk fyrir að svara spurningalistanum, Mat á færni þinni. Mat þitt á færni er stuðningur við að meta þörf þína fyrir heimaþjónustu. Ef þú hefur ekki svarað öllum spurningum getur það haft áhrif á niðurstöðuna.', description: 'Texti fyrir ofan niðurstöðuskjá', }, - totalScore: { - id: 'web.whodas.calculator:results.totalScore', - defaultMessage: 'Stig samtals', - description: 'Stig samtals', - }, resultDisclaimer: { id: 'web.whodas.calculator:results.resultDisclaimer', defaultMessage: @@ -124,5 +119,10 @@ export const m = { defaultMessage: 'Sundurliðun á niðurstöðum', description: 'Sundurliðun á niðurstöðum', }, + outOf100: { + id: 'web.whodas.calculator:results.outOf100', + defaultMessage: 'af 100', + description: 'af 100', + }, }), } diff --git a/apps/web/components/connected/WHODAS/Calculator.tsx b/apps/web/components/connected/WHODAS/Calculator.tsx index 53d09cfad3cb..fc96577ef4f3 100644 --- a/apps/web/components/connected/WHODAS/Calculator.tsx +++ b/apps/web/components/connected/WHODAS/Calculator.tsx @@ -162,7 +162,9 @@ const WHODASResults = ({ {formatMessage(m.results.scoreHeading)} - {formatScore(totalScore)} + + {formatScore(totalScore)} {formatMessage(m.results.outOf100)} + @@ -203,17 +205,14 @@ const WHODASResults = ({ {step.title} - {formatScore(step.scoreForStep)} + + {formatScore(step.scoreForStep)}{' '} + {formatMessage(m.results.outOf100)} + ))} - - - {formatMessage(m.results.totalScore)} - - {formatScore(totalScore)} - Date: Fri, 6 Dec 2024 18:43:43 +0000 Subject: [PATCH 26/57] fix: add missing datadog ns grant to ids web (#17167) * fix: add missing datadog ns grant to ids web * chore: charts update dirty files * chore: nx format:write update dirty files * added grant for remainder of services in ns * chore: charts update dirty files --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/services/auth/ids-api/infra/identity-server.ts | 1 + apps/services/auth/ids-api/infra/ids-api.ts | 2 +- charts/identity-server/values.dev.yaml | 3 +++ charts/identity-server/values.prod.yaml | 3 +++ charts/identity-server/values.staging.yaml | 3 +++ charts/services/identity-server/values.dev.yaml | 1 + charts/services/identity-server/values.prod.yaml | 1 + charts/services/identity-server/values.staging.yaml | 1 + charts/services/services-auth-ids-api-cleanup/values.dev.yaml | 1 + charts/services/services-auth-ids-api-cleanup/values.prod.yaml | 1 + .../services/services-auth-ids-api-cleanup/values.staging.yaml | 1 + charts/services/services-auth-ids-api/values.dev.yaml | 1 + charts/services/services-auth-ids-api/values.prod.yaml | 1 + charts/services/services-auth-ids-api/values.staging.yaml | 1 + 14 files changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/services/auth/ids-api/infra/identity-server.ts b/apps/services/auth/ids-api/infra/identity-server.ts index 0e953612dc11..60fe5358f7b4 100644 --- a/apps/services/auth/ids-api/infra/identity-server.ts +++ b/apps/services/auth/ids-api/infra/identity-server.ts @@ -196,5 +196,6 @@ export const serviceSetup = (services: { 'nginx-ingress-external', 'user-notification', 'portals-admin', + 'datadog', ) } diff --git a/apps/services/auth/ids-api/infra/ids-api.ts b/apps/services/auth/ids-api/infra/ids-api.ts index 711f29537f1f..1afda547b717 100644 --- a/apps/services/auth/ids-api/infra/ids-api.ts +++ b/apps/services/auth/ids-api/infra/ids-api.ts @@ -137,7 +137,7 @@ export const serviceSetup = (): ServiceBuilder<'services-auth-ids-api'> => { min: 2, max: 15, }) - .grantNamespaces('nginx-ingress-external', 'user-notification') + .grantNamespaces('nginx-ingress-external', 'user-notification', 'datadog') } const cleanupId = 'services-auth-ids-api-cleanup' diff --git a/charts/identity-server/values.dev.yaml b/charts/identity-server/values.dev.yaml index 24f06cb5833f..a5d5ce51e239 100644 --- a/charts/identity-server/values.dev.yaml +++ b/charts/identity-server/values.dev.yaml @@ -129,6 +129,7 @@ identity-server: - 'nginx-ingress-external' - 'user-notification' - 'portals-admin' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: @@ -457,6 +458,7 @@ services-auth-ids-api: grantNamespaces: - 'nginx-ingress-external' - 'user-notification' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: @@ -565,6 +567,7 @@ services-auth-ids-api-cleanup: - 'nginx-ingress-external' - 'user-notification' - 'portals-admin' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: diff --git a/charts/identity-server/values.prod.yaml b/charts/identity-server/values.prod.yaml index e11425f90d02..2f51a29e6326 100644 --- a/charts/identity-server/values.prod.yaml +++ b/charts/identity-server/values.prod.yaml @@ -127,6 +127,7 @@ identity-server: - 'nginx-ingress-external' - 'user-notification' - 'portals-admin' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: @@ -454,6 +455,7 @@ services-auth-ids-api: grantNamespaces: - 'nginx-ingress-external' - 'user-notification' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: @@ -562,6 +564,7 @@ services-auth-ids-api-cleanup: - 'nginx-ingress-external' - 'user-notification' - 'portals-admin' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: diff --git a/charts/identity-server/values.staging.yaml b/charts/identity-server/values.staging.yaml index 1a6f05f0253d..00cbc00e4a90 100644 --- a/charts/identity-server/values.staging.yaml +++ b/charts/identity-server/values.staging.yaml @@ -129,6 +129,7 @@ identity-server: - 'nginx-ingress-external' - 'user-notification' - 'portals-admin' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: @@ -457,6 +458,7 @@ services-auth-ids-api: grantNamespaces: - 'nginx-ingress-external' - 'user-notification' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: @@ -565,6 +567,7 @@ services-auth-ids-api-cleanup: - 'nginx-ingress-external' - 'user-notification' - 'portals-admin' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: diff --git a/charts/services/identity-server/values.dev.yaml b/charts/services/identity-server/values.dev.yaml index ea8cd4ed5200..50d9763ee45a 100644 --- a/charts/services/identity-server/values.dev.yaml +++ b/charts/services/identity-server/values.dev.yaml @@ -59,6 +59,7 @@ grantNamespaces: - 'nginx-ingress-external' - 'user-notification' - 'portals-admin' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: diff --git a/charts/services/identity-server/values.prod.yaml b/charts/services/identity-server/values.prod.yaml index d086237e7757..7daf65fea874 100644 --- a/charts/services/identity-server/values.prod.yaml +++ b/charts/services/identity-server/values.prod.yaml @@ -59,6 +59,7 @@ grantNamespaces: - 'nginx-ingress-external' - 'user-notification' - 'portals-admin' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: diff --git a/charts/services/identity-server/values.staging.yaml b/charts/services/identity-server/values.staging.yaml index aa4536101391..ebe78fbb22a2 100644 --- a/charts/services/identity-server/values.staging.yaml +++ b/charts/services/identity-server/values.staging.yaml @@ -59,6 +59,7 @@ grantNamespaces: - 'nginx-ingress-external' - 'user-notification' - 'portals-admin' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: diff --git a/charts/services/services-auth-ids-api-cleanup/values.dev.yaml b/charts/services/services-auth-ids-api-cleanup/values.dev.yaml index 5a4276deb188..7922277ce890 100644 --- a/charts/services/services-auth-ids-api-cleanup/values.dev.yaml +++ b/charts/services/services-auth-ids-api-cleanup/values.dev.yaml @@ -36,6 +36,7 @@ grantNamespaces: - 'nginx-ingress-external' - 'user-notification' - 'portals-admin' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: diff --git a/charts/services/services-auth-ids-api-cleanup/values.prod.yaml b/charts/services/services-auth-ids-api-cleanup/values.prod.yaml index 88701a214d23..0dbd5e238973 100644 --- a/charts/services/services-auth-ids-api-cleanup/values.prod.yaml +++ b/charts/services/services-auth-ids-api-cleanup/values.prod.yaml @@ -36,6 +36,7 @@ grantNamespaces: - 'nginx-ingress-external' - 'user-notification' - 'portals-admin' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: diff --git a/charts/services/services-auth-ids-api-cleanup/values.staging.yaml b/charts/services/services-auth-ids-api-cleanup/values.staging.yaml index 62add51ed018..9bc51cf470c9 100644 --- a/charts/services/services-auth-ids-api-cleanup/values.staging.yaml +++ b/charts/services/services-auth-ids-api-cleanup/values.staging.yaml @@ -36,6 +36,7 @@ grantNamespaces: - 'nginx-ingress-external' - 'user-notification' - 'portals-admin' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: diff --git a/charts/services/services-auth-ids-api/values.dev.yaml b/charts/services/services-auth-ids-api/values.dev.yaml index 7ad3e73b5919..e1461ecdd4a9 100644 --- a/charts/services/services-auth-ids-api/values.dev.yaml +++ b/charts/services/services-auth-ids-api/values.dev.yaml @@ -63,6 +63,7 @@ env: grantNamespaces: - 'nginx-ingress-external' - 'user-notification' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: diff --git a/charts/services/services-auth-ids-api/values.prod.yaml b/charts/services/services-auth-ids-api/values.prod.yaml index 80f73850121a..48b414b22bb9 100644 --- a/charts/services/services-auth-ids-api/values.prod.yaml +++ b/charts/services/services-auth-ids-api/values.prod.yaml @@ -63,6 +63,7 @@ env: grantNamespaces: - 'nginx-ingress-external' - 'user-notification' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: diff --git a/charts/services/services-auth-ids-api/values.staging.yaml b/charts/services/services-auth-ids-api/values.staging.yaml index be6dc7b16fcb..5b620fa22960 100644 --- a/charts/services/services-auth-ids-api/values.staging.yaml +++ b/charts/services/services-auth-ids-api/values.staging.yaml @@ -63,6 +63,7 @@ env: grantNamespaces: - 'nginx-ingress-external' - 'user-notification' + - 'datadog' grantNamespacesEnabled: true healthCheck: liveness: From beb3272057a574fbde9a708a868c46ec79742a72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Fri, 6 Dec 2024 18:59:02 +0000 Subject: [PATCH 27/57] feat(j-s): Show district court abbreviation on mobile (#17075) * Add court abbr to getAllCases call * Refactoring * Refactoring * Refactoring * Refactoring * Add abbr to mobile as well * >merge --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../web/src/routes/Shared/Cases/MobileCase.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/MobileCase.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/MobileCase.tsx index 391d004d76d4..a14415196497 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/MobileCase.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/MobileCase.tsx @@ -7,6 +7,7 @@ import { AnimatePresence } from 'framer-motion' import { Box, FocusableBox, Text } from '@island.is/island-ui/core' import { displayFirstPlusRemaining, + districtCourtAbbreviation, formatDOB, } from '@island.is/judicial-system/formatters' import { tables } from '@island.is/judicial-system-web/messages' @@ -69,6 +70,7 @@ const MobileCase: FC> = ({ isLoading = false, }) => { const { formatMessage } = useIntl() + const courtAbbreviation = districtCourtAbbreviation(theCase.court?.name) return ( > = ({ {displayFirstPlusRemaining(theCase.policeCaseNumbers)} - {theCase.courtCaseNumber && {theCase.courtCaseNumber}} + + {theCase.courtCaseNumber && ( + {`${courtAbbreviation ? `${courtAbbreviation}: ` : ''}${ + theCase.courtCaseNumber + }`} + )} {theCase.defendants && theCase.defendants.length > 0 && ( <> From 1a6abd60efcab054af021f7136d56a20ef14ff3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3n=20Bjarni=20=C3=93lafsson?= <92530555+jonbjarnio@users.noreply.github.com> Date: Fri, 6 Dec 2024 19:13:04 +0000 Subject: [PATCH 28/57] fix(ojoi): Input validation and bugfixes (#17124) * Updating select inputs to filter from the start. * Updated the UI header component to support larger titles. * Signatures are now hyphenated and max length for each field set to 100. * Fixed state issue when users changed the type of additions * Fixed 0 rendering in an conditional check * Fixed issue where date parameters were not properly working. * Fix typo --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../OJOIAdvertCard.tsx | 2 +- .../OfficialJournalOfIceland/OJOISearch.tsx | 24 +++------- .../hooks/useAdverts.ts | 4 +- .../src/lib/models/advert.input.ts | 4 +- .../src/components/additions/Additions.tsx | 18 ++++---- .../components/input/OJOIInputController.tsx | 3 ++ .../components/input/OJOISelectController.tsx | 2 + .../src/components/signatures/Institution.tsx | 1 + .../src/components/signatures/Member.tsx | 1 + .../src/fields/Advert.tsx | 1 + .../src/fields/Attachments.tsx | 45 ++++++++++++++----- .../src/fields/Publishing.tsx | 3 ++ .../src/lib/dataSchema.ts | 2 + .../src/lib/messages/attachments.ts | 2 +- .../src/lib/types.ts | 2 + .../src/lib/utils.ts | 25 +++++++---- .../core/src/lib/Header/Header.css.ts | 4 ++ libs/island-ui/core/src/lib/Header/Header.tsx | 11 ++--- libs/island-ui/core/src/lib/Hyphen/Hyphen.tsx | 2 +- .../lib/SelectController/SelectController.tsx | 10 ++++- 20 files changed, 109 insertions(+), 57 deletions(-) diff --git a/apps/web/components/OfficialJournalOfIceland/OJOIAdvertCard.tsx b/apps/web/components/OfficialJournalOfIceland/OJOIAdvertCard.tsx index fe375bbe25fa..089ebaf2630a 100644 --- a/apps/web/components/OfficialJournalOfIceland/OJOIAdvertCard.tsx +++ b/apps/web/components/OfficialJournalOfIceland/OJOIAdvertCard.tsx @@ -66,7 +66,7 @@ export const OJOIAdvertCard = ({ marginTop={2} rowGap={1} > - {categories && categories.length && ( + {categories && categories.length > 0 && ( {categories.map((cat) => { return ( diff --git a/apps/web/screens/OfficialJournalOfIceland/OJOISearch.tsx b/apps/web/screens/OfficialJournalOfIceland/OJOISearch.tsx index 86a586ccc44d..a3c4371e88d3 100644 --- a/apps/web/screens/OfficialJournalOfIceland/OJOISearch.tsx +++ b/apps/web/screens/OfficialJournalOfIceland/OJOISearch.tsx @@ -105,12 +105,8 @@ const OJOISearchPage: CustomScreen = ({ category: [defaultSearchParams.malaflokkur], involvedParty: [defaultSearchParams.stofnun], type: [defaultSearchParams.tegund], - dateFrom: defaultSearchParams.dagsFra - ? new Date(defaultSearchParams.dagsFra) - : undefined, - dateTo: defaultSearchParams.dagsTil - ? new Date(defaultSearchParams.dagsTil) - : undefined, + dateFrom: defaultSearchParams.dagsFra, + dateTo: defaultSearchParams.dagsTil, search: defaultSearchParams.q, page: defaultSearchParams.sida, pageSize: defaultSearchParams.staerd, @@ -176,12 +172,8 @@ const OJOISearchPage: CustomScreen = ({ category: [searchValues.malaflokkur], involvedParty: [searchValues.stofnun], type: [searchValues.tegund], - dateFrom: searchValues.dagsFra - ? new Date(searchValues.dagsFra) - : undefined, - dateTo: searchValues.dagsTil - ? new Date(searchValues.dagsTil) - : undefined, + dateFrom: searchValues.dagsFra, + dateTo: searchValues.dagsTil, search: searchValues.q, page: searchValues.sida, pageSize: searchValues.staerd, @@ -596,12 +588,8 @@ OJOISearch.getProps = async ({ apolloClient, locale, query }) => { variables: { input: { category: [defaultParams.malaflokkur], - dateFrom: defaultParams.dagsFra - ? new Date(defaultParams.dagsFra) - : undefined, - dateTo: defaultParams.dagsTil - ? new Date(defaultParams.dagsTil) - : undefined, + dateFrom: defaultParams.dagsFra, + dateTo: defaultParams.dagsTil, department: [defaultParams.deild], involvedParty: [defaultParams.stofnun], page: defaultParams.sida, diff --git a/apps/web/screens/OfficialJournalOfIceland/hooks/useAdverts.ts b/apps/web/screens/OfficialJournalOfIceland/hooks/useAdverts.ts index 0d4c3092886b..235c26397441 100644 --- a/apps/web/screens/OfficialJournalOfIceland/hooks/useAdverts.ts +++ b/apps/web/screens/OfficialJournalOfIceland/hooks/useAdverts.ts @@ -20,8 +20,8 @@ export type UseAdvertsVariables = { type?: Array category?: Array involvedParty?: Array - dateFrom?: Date - dateTo?: Date + dateFrom?: string + dateTo?: string } export type UseAdvertsInput = { diff --git a/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.input.ts b/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.input.ts index c00db538679f..868f78ae3130 100644 --- a/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.input.ts +++ b/libs/api/domains/official-journal-of-iceland/src/lib/models/advert.input.ts @@ -28,10 +28,10 @@ export class AdvertsInput { @Field(() => [String], { nullable: true }) involvedParty?: string[] - @Field(() => Date, { nullable: true }) + @Field(() => String, { nullable: true }) dateFrom?: string - @Field(() => Date, { nullable: true }) + @Field(() => String, { nullable: true }) dateTo?: string } diff --git a/libs/application/templates/official-journal-of-iceland/src/components/additions/Additions.tsx b/libs/application/templates/official-journal-of-iceland/src/components/additions/Additions.tsx index fc94f0a5f275..072b2aca724e 100644 --- a/libs/application/templates/official-journal-of-iceland/src/components/additions/Additions.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/components/additions/Additions.tsx @@ -33,7 +33,9 @@ type Props = { type Addition = z.infer[number] export const Additions = ({ application }: Props) => { - const [asRoman, setAsRoman] = useState(false) + const [asRoman, setAsRoman] = useState( + application.answers.misc?.asRoman ?? false, + ) const { formatMessage: f } = useLocale() const { setValue } = useFormContext() @@ -57,8 +59,6 @@ export const Additions = ({ application }: Props) => { const onRemoveAddition = (index: number) => { const filtered = additions.filter((_, i) => i !== index) const mapped = filtered.map((addition, i) => { - if (addition.type !== 'html') return addition - const title = f(attachments.additions.title, { index: asRoman ? convertNumberToRoman(i + 1) : i + 1, }) @@ -83,10 +83,8 @@ export const Additions = ({ application }: Props) => { const onRomanChange = (val: boolean) => { const handleTitleChange = (addition: Addition, i: number) => { - if (addition.type !== 'html') return addition - const title = f(attachments.additions.title, { - index: asRoman ? convertNumberToRoman(i + 1) : i + 1, + index: val ? convertNumberToRoman(i + 1) : i + 1, }) return { ...addition, @@ -97,14 +95,17 @@ export const Additions = ({ application }: Props) => { const currentAnswers = structuredClone(currentApplication.answers) const updatedAdditions = additions.map(handleTitleChange) - const updatedAnswers = set( + let updatedAnswers = set( currentAnswers, InputFields.advert.additions, updatedAdditions, ) + updatedAnswers = set(updatedAnswers, InputFields.misc.asRoman, val) + setAsRoman(val) setValue(InputFields.advert.additions, updatedAdditions) + setValue(InputFields.misc.asRoman, val) updateApplication(updatedAnswers) } @@ -206,7 +207,8 @@ export const Additions = ({ application }: Props) => { onRemoveAddition(additionIndex)} > diff --git a/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIInputController.tsx b/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIInputController.tsx index ce66ffa0bc15..c8fecfd34c33 100644 --- a/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIInputController.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/components/input/OJOIInputController.tsx @@ -14,6 +14,7 @@ type Props = { applicationId: string disabled?: boolean textarea?: boolean + maxLength?: number onChange?: (value: string) => void } @@ -26,6 +27,7 @@ export const OJOIInputController = ({ applicationId, disabled, textarea, + maxLength, onChange, }: Props) => { const { formatMessage: f } = useLocale() @@ -70,6 +72,7 @@ export const OJOIInputController = ({ disabled={disabled} textarea={textarea} rows={4} + maxLength={maxLength} required={false} onChange={(e) => debouncedOnUpdateApplicationHandler( diff --git a/libs/application/templates/official-journal-of-iceland/src/components/input/OJOISelectController.tsx b/libs/application/templates/official-journal-of-iceland/src/components/input/OJOISelectController.tsx index a16aa3cc38ad..00eac25ff080 100644 --- a/libs/application/templates/official-journal-of-iceland/src/components/input/OJOISelectController.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/components/input/OJOISelectController.tsx @@ -91,6 +91,8 @@ export const OJOISelectController = ({ backgroundColor="blue" options={options} defaultValue={defaultOpt} + isSearchable={true} + filterConfig={{ matchFrom: 'start' }} onChange={(opt) => { if (!opt?.value) return return handleChange(opt.value) diff --git a/libs/application/templates/official-journal-of-iceland/src/components/signatures/Institution.tsx b/libs/application/templates/official-journal-of-iceland/src/components/signatures/Institution.tsx index 1f73a2f8f921..89e2f41c6e1c 100644 --- a/libs/application/templates/official-journal-of-iceland/src/components/signatures/Institution.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/components/signatures/Institution.tsx @@ -141,6 +141,7 @@ export const InstitutionSignature = ({ ) diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/Advert.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/Advert.tsx index 9cb1b1371782..0e81b3062f40 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/Advert.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/Advert.tsx @@ -98,6 +98,7 @@ export const Advert = ({ application }: OJOIFieldBaseProps) => { defaultValue={application.answers?.advert?.title} placeholder={advert.inputs.title.placeholder} textarea={true} + maxLength={600} /> diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/Attachments.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/Attachments.tsx index 8969e0d0b52d..8065ce013629 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/Attachments.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/Attachments.tsx @@ -1,4 +1,4 @@ -import { OJOIFieldBaseProps } from '../lib/types' +import { InputFields, OJOIFieldBaseProps } from '../lib/types' import { Box, Button, InputFileUpload, Stack } from '@island.is/island-ui/core' import { useFileUpload } from '../hooks/useFileUpload' import { ALLOWED_FILE_TYPES, ApplicationAttachmentType } from '../lib/constants' @@ -6,36 +6,59 @@ import { useLocale } from '@island.is/localization' import { attachments } from '../lib/messages/attachments' import { useState } from 'react' import { Additions } from '../components/additions/Additions' +import { useApplication } from '../hooks/useUpdateApplication' +import { useFormContext } from 'react-hook-form' export const Attachments = ({ application }: OJOIFieldBaseProps) => { + const { setValue } = useFormContext() const { formatMessage: f } = useLocale() const { files, onChange, onRemove } = useFileUpload({ applicationId: application.id, attachmentType: ApplicationAttachmentType.ADDITIONS, }) - const [asAddition, setAsAddition] = useState(true) + const { updateApplication, application: currentApplication } = useApplication( + { + applicationId: application.id, + }, + ) + + const [asDocument, setAsDocument] = useState( + application.answers.misc?.asDocument ?? false, + ) + + const handleChange = () => { + const current = asDocument + setAsDocument((toggle) => !toggle) + + setValue(InputFields.misc.asDocument, !current) + updateApplication({ + ...currentApplication.answers, + misc: { + ...currentApplication.answers.misc, + asDocument: !current, + }, + }) + } return ( setAsAddition((toggle) => !toggle)} + onClick={handleChange} > {f( - asAddition - ? attachments.buttons.asAttachment - : attachments.buttons.asDocument, + asDocument + ? attachments.buttons.asDocument + : attachments.buttons.asAttachment, )} - {!asAddition ? ( - - ) : ( + {asDocument ? ( { icon: 'blue200', }} /> + ) : ( + )} ) diff --git a/libs/application/templates/official-journal-of-iceland/src/fields/Publishing.tsx b/libs/application/templates/official-journal-of-iceland/src/fields/Publishing.tsx index 2b2b845b4092..1e6430800d5f 100644 --- a/libs/application/templates/official-journal-of-iceland/src/fields/Publishing.tsx +++ b/libs/application/templates/official-journal-of-iceland/src/fields/Publishing.tsx @@ -123,6 +123,9 @@ export const Publishing = ({ application }: OJOIFieldBaseProps) => { options={mappedCategories} defaultValue={mappedCategories?.[0]} onChange={(opt) => onCategoryChange(opt?.value)} + filterConfig={{ + matchFrom: 'start', + }} /> diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/dataSchema.ts b/libs/application/templates/official-journal-of-iceland/src/lib/dataSchema.ts index fed28c133461..046cbeed2664 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/dataSchema.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/dataSchema.ts @@ -83,6 +83,8 @@ const miscSchema = z .object({ signatureType: z.string().optional(), selectedTemplate: z.string().optional(), + asDocument: z.boolean().optional(), + asRoman: z.boolean().optional(), }) .partial() diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/messages/attachments.ts b/libs/application/templates/official-journal-of-iceland/src/lib/messages/attachments.ts index 0046056207a4..e8a0f839b7b6 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/messages/attachments.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/messages/attachments.ts @@ -34,7 +34,7 @@ export const attachments = { }, asAttachment: { id: 'ojoi.application:attachments.buttons.additionType.asAttachment', - defaultMessage: 'Hlaða upp skjölum', + defaultMessage: 'Bæta við viðauka', description: 'Label of the button to upload attachments', }, removeAddition: { diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/types.ts b/libs/application/templates/official-journal-of-iceland/src/lib/types.ts index 385aec190a50..1529e2d56442 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/types.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/types.ts @@ -33,6 +33,8 @@ export const InputFields = { [Routes.MISC]: { signatureType: 'misc.signatureType', selectedTemplate: 'misc.selectedTemplate', + asDocument: 'misc.asDocument', + asRoman: 'misc.asRoman', }, } diff --git a/libs/application/templates/official-journal-of-iceland/src/lib/utils.ts b/libs/application/templates/official-journal-of-iceland/src/lib/utils.ts index 96d1b2fb0893..15699f900241 100644 --- a/libs/application/templates/official-journal-of-iceland/src/lib/utils.ts +++ b/libs/application/templates/official-journal-of-iceland/src/lib/utils.ts @@ -18,6 +18,8 @@ import is from 'date-fns/locale/is' import { SignatureTypes, OJOI_DF, FAST_TRACK_DAYS } from './constants' import { MessageDescriptor } from 'react-intl' import { v4 as uuid } from 'uuid' +import Hypher from 'hypher' +import { hyphenateText } from '@island.is/island-ui/core' export const countDaysAgo = (date: Date) => { const now = new Date() @@ -190,6 +192,8 @@ export const getRegularAnswers = (answers: OJOIApplication['answers']) => { signature: null, } } +const hyphenate = (text = '') => + hyphenateText(text, { locale: 'is', minLeft: 4, minRight: 4 }) const getMembersMarkup = (member: z.infer) => { if (!member.name) return '' @@ -198,18 +202,21 @@ const getMembersMarkup = (member: z.infer) => { marginBottom: member.below ? '0' : '1.5em', } - const aboveMarkup = member.above - ? `${member.above}` - : '' - const afterMarkup = member.after ? ` ${member.after}` : '' - const belowMarkup = member.below - ? `${member.below}` + const name = hyphenate(member.name) + const above = hyphenate(member.above) + const after = hyphenate(member.after) + const below = hyphenate(member.below) + + const aboveMarkup = above + ? `${above}` : '' + const afterMarkup = after ? ` ${after}` : '' + const belowMarkup = below ? `${below}` : '' return ` ${aboveMarkup} - ${member.name}${afterMarkup} + ${name}${afterMarkup} ${belowMarkup} ` @@ -261,7 +268,9 @@ const signatureTemplate = ( .join('') const additionalMarkup = additionalSignature - ? `${additionalSignature}` + ? `${hyphenate( + additionalSignature, + )}` : '' return `${markup}${additionalMarkup}` as HTMLText diff --git a/libs/island-ui/core/src/lib/Header/Header.css.ts b/libs/island-ui/core/src/lib/Header/Header.css.ts index cf8a3492b455..c580b98e9c28 100644 --- a/libs/island-ui/core/src/lib/Header/Header.css.ts +++ b/libs/island-ui/core/src/lib/Header/Header.css.ts @@ -21,10 +21,14 @@ export const infoDescription = style({ fontWeight: 300, lineHeight: 1.5, fontSize: 14, + maxHeight: 40, + position: 'relative', + overflow: 'auto', ...themeUtils.responsiveStyle({ md: { fontSize: 18, + maxHeight: 66, }, }), }) diff --git a/libs/island-ui/core/src/lib/Header/Header.tsx b/libs/island-ui/core/src/lib/Header/Header.tsx index 15a28c13f892..1d6a01230f6d 100644 --- a/libs/island-ui/core/src/lib/Header/Header.tsx +++ b/libs/island-ui/core/src/lib/Header/Header.tsx @@ -93,6 +93,7 @@ export const Header = ({ height="full" marginLeft={[1, 1, 2, 4]} marginRight="auto" + paddingRight={[1, 1, 2, 4]} > {info.title} @@ -128,14 +129,14 @@ export const Header = ({ alignItems="center" justifyContent="spaceBetween" > - - {renderLogo()} + + {renderLogo()} {renderInfo()} - - + + {renderOldDropdown()} {headerItems} - + ) } diff --git a/libs/island-ui/core/src/lib/Hyphen/Hyphen.tsx b/libs/island-ui/core/src/lib/Hyphen/Hyphen.tsx index 9af8249a592e..d6f0a607b944 100644 --- a/libs/island-ui/core/src/lib/Hyphen/Hyphen.tsx +++ b/libs/island-ui/core/src/lib/Hyphen/Hyphen.tsx @@ -12,7 +12,7 @@ type HyphenateText = ( ) => string // TODO: import patterns dynamically -const hyphenateText: HyphenateText = ( +export const hyphenateText: HyphenateText = ( content, { minLeft, minRight, locale = 'is' }, ) => { diff --git a/libs/shared/form-fields/src/lib/SelectController/SelectController.tsx b/libs/shared/form-fields/src/lib/SelectController/SelectController.tsx index d5ec6a74d63d..06639d1913c8 100644 --- a/libs/shared/form-fields/src/lib/SelectController/SelectController.tsx +++ b/libs/shared/form-fields/src/lib/SelectController/SelectController.tsx @@ -1,7 +1,12 @@ import React from 'react' import { Controller, useFormContext, RegisterOptions } from 'react-hook-form' -import { Select, Option, InputBackgroundColor } from '@island.is/island-ui/core' +import { + Select, + SelectProps, + Option, + InputBackgroundColor, +} from '@island.is/island-ui/core' import { TestSupport } from '@island.is/island-ui/utils' import { MultiValue, SingleValue } from 'react-select' @@ -26,6 +31,7 @@ interface SelectControllerProps { rules?: RegisterOptions size?: 'xs' | 'sm' | 'md' internalKey?: string + filterConfig?: SelectProps['filterConfig'] } export const SelectController = ({ @@ -47,6 +53,7 @@ export const SelectController = ({ rules, size, internalKey, + filterConfig, }: SelectControllerProps & TestSupport) => { const { clearErrors } = useFormContext() @@ -91,6 +98,7 @@ export const SelectController = ({ placeholder={placeholder} value={getValue(value)} isSearchable={isSearchable} + filterConfig={filterConfig} isMulti={isMulti} isClearable={isClearable} size={size} From 2692a1f93e65b41ce9cb9cfb58acfe84e5d5fd6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rey=20J=C3=B3na?= Date: Mon, 9 Dec 2024 08:21:09 +0000 Subject: [PATCH 29/57] feat(native-app): use national registry person endpoint (V3) (#16877) * feat: use national registry person endpoint (V3) * feat: change Avatar to have letters of first and last name not first and second * fix: improve types * fix: improve types * fix: make sure we have the correct person for the details * fix: remove typename from queries * fix: use theme where relevant --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/native/app/src/graphql/client.ts | 3 + .../graphql/queries/national-registry.graphql | 98 ++++++++--- .../app/src/screens/family/family-details.tsx | 113 +++++++++--- .../src/screens/family/family-overview.tsx | 165 +++++++++++------- .../app/src/screens/more/personal-info.tsx | 2 +- apps/native/app/src/ui/lib/avatar/avatar.tsx | 14 +- 6 files changed, 271 insertions(+), 124 deletions(-) diff --git a/apps/native/app/src/graphql/client.ts b/apps/native/app/src/graphql/client.ts index 2e4e29eb5d34..fe5e2ea8205b 100644 --- a/apps/native/app/src/graphql/client.ts +++ b/apps/native/app/src/graphql/client.ts @@ -164,6 +164,9 @@ const cache = new InMemoryCache({ getUserProfile: { merge: true, }, + nationalRegistryPerson: { + merge: false, + }, }, }, DocumentV2: { diff --git a/apps/native/app/src/graphql/queries/national-registry.graphql b/apps/native/app/src/graphql/queries/national-registry.graphql index 84777c6e116c..764939d31273 100644 --- a/apps/native/app/src/graphql/queries/national-registry.graphql +++ b/apps/native/app/src/graphql/queries/national-registry.graphql @@ -1,5 +1,5 @@ query NationalRegistryUser { - nationalRegistryUser { + nationalRegistryPerson { nationalId fullName gender @@ -23,37 +23,83 @@ query NationalRegistryUser { } } -query NationalRegistryChildren { - nationalRegistryUser { +query NationalRegistryPerson { + nationalRegistryPerson { nationalId - fullName + name { + fullName + } spouse { - name nationalId + fullName + } + childCustody { + nationalId + fullName + } + biologicalChildren { + nationalId + fullName } } - nationalRegistryChildren { +} + +query NationalRegistryBioChild($childNationalId: String) { + nationalRegistryPerson { nationalId fullName - displayName - genderDisplay - birthplace - custody1 - custodyText1 - nameCustody1 - custody2 - custodyText2 - nameCustody2 - parent1 - nameParent1 - parent2 - nameParent2 - homeAddress - religion - nationality - religion - homeAddress - nationality - legalResidence + biologicalChildren(childNationalId: $childNationalId) { + details { + nationalId + fullName + baseId + housing { + address { + streetAddress + } + } + } + } + } +} + +query NationalRegistryChildCustody($childNationalId: String) { + nationalRegistryPerson { + nationalId + fullName + childCustody(childNationalId: $childNationalId) { + details { + nationalId + fullName + baseId + gender + religion + housing { + address { + streetAddress + postalCode + city + } + } + birthplace { + location + } + citizenship { + code + name + } + } + } + } +} + +query NationalRegistrySpouse { + nationalRegistryPerson { + nationalId + spouse { + maritalStatus + nationalId + fullName + } } } diff --git a/apps/native/app/src/screens/family/family-details.tsx b/apps/native/app/src/screens/family/family-details.tsx index 154a056f051e..e4cf541c22cd 100644 --- a/apps/native/app/src/screens/family/family-details.tsx +++ b/apps/native/app/src/screens/family/family-details.tsx @@ -6,10 +6,40 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' -import { useNationalRegistryChildrenQuery } from '../../graphql/types/schema' +import { + NationalRegistryBioChildQuery, + NationalRegistryChildCustodyQuery, + NationalRegistrySpouseQuery, + useNationalRegistryBioChildQuery, + useNationalRegistryChildCustodyQuery, + useNationalRegistrySpouseQuery, +} from '../../graphql/types/schema' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { formatNationalId } from '../../lib/format-national-id' import { testIDs } from '../../utils/test-ids' +import { useTheme } from 'styled-components' + +type ChildCustodyDetails = NonNullable< + NonNullable< + NonNullable< + NonNullable + >['childCustody'] + >[number] +>['details'] + +type BioChildDetails = NonNullable< + NonNullable< + NonNullable< + NonNullable + >['biologicalChildren'] + >[number] +>['details'] + +type SpouseDetails = NonNullable< + NonNullable +>['spouse'] + +type Person = ChildCustodyDetails | BioChildDetails | SpouseDetails const { getNavigationOptions, useNavigationOptions } = createNavigationOptionHooks(() => ({ @@ -20,25 +50,39 @@ const { getNavigationOptions, useNavigationOptions } = export const FamilyDetailScreen: NavigationFunctionComponent<{ id: string - type: string + type: 'bioChild' | 'custodyChild' | 'spouse' }> = ({ componentId, id, type }) => { useNavigationOptions(componentId) const intl = useIntl() + const theme = useTheme() - const { data, loading, error } = useNationalRegistryChildrenQuery({ - fetchPolicy: 'cache-first', + const bioChildRes = useNationalRegistryBioChildQuery({ + variables: { childNationalId: id }, + skip: type !== 'bioChild', }) - const { nationalRegistryUser, nationalRegistryChildren = [] } = data || {} - const listOfPeople = [ - { ...(nationalRegistryUser?.spouse ?? {}), type: 'spouse' }, - ...(nationalRegistryChildren ?? []).map((item: any) => ({ - ...item, - type: 'child', - })), - ].filter((item) => item.nationalId) + const custodyChildRes = useNationalRegistryChildCustodyQuery({ + variables: { childNationalId: id }, + skip: type !== 'custodyChild', + }) + + const spouseRes = useNationalRegistrySpouseQuery({ + skip: type !== 'spouse', + }) - const person = listOfPeople?.find((x) => x.nationalId === id) || null + const person: Person = + bioChildRes.data?.nationalRegistryPerson?.biologicalChildren?.find( + (child) => child.details?.nationalId === id, + )?.details || + custodyChildRes?.data?.nationalRegistryPerson?.childCustody?.find( + (child) => child.details?.nationalId === id, + )?.details || + spouseRes.data?.nationalRegistryPerson?.spouse || + null + + const loading = + bioChildRes.loading || custodyChildRes.loading || spouseRes.loading + const error = bioChildRes.error || custodyChildRes.error || spouseRes.error if (!person) return null @@ -53,7 +97,11 @@ export const FamilyDetailScreen: NavigationFunctionComponent<{ {intl.formatMessage({ id: 'familyDetail.description' })} @@ -64,13 +112,12 @@ export const FamilyDetailScreen: NavigationFunctionComponent<{ label={intl.formatMessage({ id: 'familyDetail.natreg.displayName', })} - value={person?.name || person?.displayName} + value={person?.fullName} loading={loading} error={!!error} size="big" /> - - {person?.nationality ? ( + {'citizenship' in person && person?.citizenship ? ( ) : null} - {person?.legalResidence ? ( + {'housing' in person && + person?.housing && + 'address' in person.housing && + person.housing.address ? ( @@ -118,20 +178,23 @@ export const FamilyDetailScreen: NavigationFunctionComponent<{ ) : null} - {person?.genderDisplay ? ( + {'gender' in person && person.gender ? ( ) : null} - {person?.birthplace ? ( + {'birthplace' in person && person?.birthplace ? ( diff --git a/apps/native/app/src/screens/family/family-overview.tsx b/apps/native/app/src/screens/family/family-overview.tsx index 91dd32bf52fb..f2bb50879b2a 100644 --- a/apps/native/app/src/screens/family/family-overview.tsx +++ b/apps/native/app/src/screens/family/family-overview.tsx @@ -1,4 +1,4 @@ -import { EmptyList, FamilyMemberCard, Skeleton, TopLine } from '@ui' +import { EmptyList, FamilyMemberCard, Problem, Skeleton, TopLine } from '@ui' import React, { useCallback, useRef, useState } from 'react' import { useIntl } from 'react-intl' import { @@ -14,13 +14,29 @@ import { NavigationFunctionComponent } from 'react-native-navigation' import { useTheme } from 'styled-components/native' import illustrationSrc from '../../assets/illustrations/hero_spring.png' import { BottomTabsIndicator } from '../../components/bottom-tabs-indicator/bottom-tabs-indicator' -import { useNationalRegistryChildrenQuery } from '../../graphql/types/schema' +import { + NationalRegistryChildCustody, + NationalRegistrySpouse, + useNationalRegistryPersonQuery, +} from '../../graphql/types/schema' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { useConnectivityIndicator } from '../../hooks/use-connectivity-indicator' import { navigateTo } from '../../lib/deep-linking' import { formatNationalId } from '../../lib/format-national-id' import { testIDs } from '../../utils/test-ids' +type ChildItem = NationalRegistryChildCustody & { + type: 'custodyChild' | 'bioChild' +} + +type SpouseItem = NationalRegistrySpouse & { type: 'spouse' } + +type FamilyListItem = + | ChildItem + | SpouseItem + | { type: 'skeleton'; id: string } + | { type: 'empty'; id: string } + const { useNavigationOptions, getNavigationOptions } = createNavigationOptionHooks((theme, intl) => ({ topBar: { @@ -30,32 +46,34 @@ const { useNavigationOptions, getNavigationOptions } = }, })) -const FamilyMember = React.memo(({ item }: { item: any }) => { - const theme = useTheme() - - return ( - - { - navigateTo(`/family/${item.type}/${item.nationalId}`, { - id: item?.nationalId, - }) - }} - > - - - - - - ) -}) +const FamilyMember = React.memo( + ({ item }: { item: ChildItem | SpouseItem }) => { + const theme = useTheme() + + return ( + + { + navigateTo(`/family/${item.type}/${item.nationalId}`, { + id: item?.nationalId, + }) + }} + > + + + + + + ) + }, +) export const FamilyOverviewScreen: NavigationFunctionComponent = ({ componentId, @@ -68,7 +86,7 @@ export const FamilyOverviewScreen: NavigationFunctionComponent = ({ const theme = useTheme() const scrollY = useRef(new Animated.Value(0)).current const loadingTimeout = useRef() - const familyRes = useNationalRegistryChildrenQuery() + const familyRes = useNationalRegistryPersonQuery() useConnectivityIndicator({ componentId, @@ -76,14 +94,23 @@ export const FamilyOverviewScreen: NavigationFunctionComponent = ({ refetching, }) - const { nationalRegistryUser, nationalRegistryChildren = [] } = - familyRes?.data || {} + const { biologicalChildren, spouse, childCustody } = + familyRes.data?.nationalRegistryPerson || {} + + // Filter out bio children with custody so we don't show them twice + const bioChildren = biologicalChildren?.filter( + (child) => !childCustody?.some((c) => c.nationalId === child.nationalId), + ) const listOfPeople = [ - { ...(nationalRegistryUser?.spouse ?? {}), type: 'spouse' }, - ...(nationalRegistryChildren ?? []).map((item: any) => ({ + { ...(spouse ?? {}), type: 'spouse' }, + ...(childCustody ?? []).map((item: NationalRegistryChildCustody) => ({ ...item, - type: 'child', + type: 'custodyChild', + })), + ...(bioChildren ?? []).map((item: NationalRegistryChildCustody) => ({ + ...item, + type: 'bioChild', })), ].filter((item) => item.nationalId) @@ -110,10 +137,10 @@ export const FamilyOverviewScreen: NavigationFunctionComponent = ({ } }, []) - const renderItem = ({ item }: { item: any }) => { + const renderItem = ({ item }: { item: FamilyListItem }) => { if (item.type === 'skeleton') { return ( - + @@ -137,7 +164,7 @@ export const FamilyOverviewScreen: NavigationFunctionComponent = ({ if (item.type === 'empty') { return ( - + - - } - scrollEventThrottle={16} - scrollToOverflowEnabled={true} - onScroll={Animated.event( - [{ nativeEvent: { contentOffset: { y: scrollY } } }], - { - useNativeDriver: true, - }, - )} - data={isSkeleton ? skeletonItems : isEmpty ? emptyItems : listOfPeople} - keyExtractor={keyExtractor} - renderItem={renderItem} - /> + {(familyRes.data || familyRes.loading) && ( + + } + scrollEventThrottle={16} + scrollToOverflowEnabled={true} + onScroll={Animated.event( + [{ nativeEvent: { contentOffset: { y: scrollY } } }], + { + useNativeDriver: true, + }, + )} + data={ + isSkeleton ? skeletonItems : isEmpty ? emptyItems : listOfPeople + } + keyExtractor={keyExtractor} + renderItem={renderItem} + /> + )} + {familyRes.error && !familyRes.data && } > diff --git a/apps/native/app/src/screens/more/personal-info.tsx b/apps/native/app/src/screens/more/personal-info.tsx index f0b7eec2784e..87417710ad0c 100644 --- a/apps/native/app/src/screens/more/personal-info.tsx +++ b/apps/native/app/src/screens/more/personal-info.tsx @@ -27,7 +27,7 @@ export const PersonalInfoScreen: NavigationFunctionComponent = ({ const intl = useIntl() const { dismiss, dismissed } = usePreferencesStore() const natRegRes = useNationalRegistryUserQuery() - const natRegData = natRegRes?.data?.nationalRegistryUser + const natRegData = natRegRes?.data?.nationalRegistryPerson const errorNatReg = !!natRegRes.error && !natRegData const loadingNatReg = natRegRes.loading && !natRegData diff --git a/apps/native/app/src/ui/lib/avatar/avatar.tsx b/apps/native/app/src/ui/lib/avatar/avatar.tsx index 0debd370fd8a..d3a77094e7a7 100644 --- a/apps/native/app/src/ui/lib/avatar/avatar.tsx +++ b/apps/native/app/src/ui/lib/avatar/avatar.tsx @@ -38,13 +38,15 @@ interface AvatarProps { export function Avatar({ name, isSmall }: AvatarProps) { function getFirstLetters(str: string) { - const firstLetters = str - .split(' ') - .slice(0, 2) - .map((word) => word[0]) - .join('') + const names = str.split(' ') - return firstLetters + let initials = names[0].substring(0, 1).toUpperCase() + + if (names.length > 1) { + initials += names[names.length - 1].substring(0, 1).toUpperCase() + } + + return initials } return ( From 2e1b8a0c4c909e47ab1fa1638f8fbc6d8fb29612 Mon Sep 17 00:00:00 2001 From: valurefugl <65780958+valurefugl@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:33:19 +0000 Subject: [PATCH 30/57] fix(ids-api): Use name field instead of fullname. (#17164) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../auth/ids-api/src/app/user-profile/user-profile.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/services/auth/ids-api/src/app/user-profile/user-profile.service.ts b/apps/services/auth/ids-api/src/app/user-profile/user-profile.service.ts index 9e310e1513c9..626b782bb437 100644 --- a/apps/services/auth/ids-api/src/app/user-profile/user-profile.service.ts +++ b/apps/services/auth/ids-api/src/app/user-profile/user-profile.service.ts @@ -130,7 +130,7 @@ export class UserProfileService { } return { - name: individual.fulltNafn?.fulltNafn ?? individual.nafn ?? undefined, + name: individual.nafn ?? undefined, givenName: individual.fulltNafn?.eiginNafn ?? undefined, familyName: individual.fulltNafn?.kenniNafn ?? undefined, middleName: individual.fulltNafn?.milliNafn ?? undefined, From f6eea1c7ff8226b04dfae0af98063547c20326c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rey=20J=C3=B3na?= Date: Mon, 9 Dec 2024 10:20:51 +0000 Subject: [PATCH 31/57] feat(native-app): support type: 'Table' in field render - do not merge (#16908) * feat: support type: 'Table' in field render * fix: use theme when relevant --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../wallet-pass/components/field-render.tsx | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/apps/native/app/src/screens/wallet-pass/components/field-render.tsx b/apps/native/app/src/screens/wallet-pass/components/field-render.tsx index 7355b4f118b6..7e1eebbb79e1 100644 --- a/apps/native/app/src/screens/wallet-pass/components/field-render.tsx +++ b/apps/native/app/src/screens/wallet-pass/components/field-render.tsx @@ -1,6 +1,7 @@ import { Field, FieldCard, FieldGroup, FieldLabel, FieldRow } from '@ui' import React from 'react' import { View } from 'react-native' +import { useTheme } from 'styled-components' import { GenericLicenseDataField, GenericLicenseType, @@ -15,6 +16,7 @@ export const FieldRender = ({ level?: number licenseType?: GenericLicenseType }) => { + const theme = useTheme() return ( <> {(data || []).map( @@ -45,7 +47,44 @@ export const FieldRender = ({ case 'Group': if (label) { return ( - + + {label} + {FieldRender({ + data: fields as GenericLicenseDataField[], + level: 2, + licenseType: licenseType, + })} + + ) + } + return ( + + + {FieldRender({ + data: fields as GenericLicenseDataField[], + level: 2, + licenseType: licenseType, + })} + + + ) + + case 'Table': + if (label) { + return ( + {label} {FieldRender({ data: fields as GenericLicenseDataField[], From e87eb22bed6b8eb70cadb3b1b983baacc6885f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sn=C3=A6r=20Seljan=20=C3=9E=C3=B3roddsson?= Date: Mon, 9 Dec 2024 11:04:59 +0000 Subject: [PATCH 32/57] fix(services-bff): Disable PAR until ids resolves error (#17176) * fix(services-bff): Disable PAR until ids resolves error * fix tests * chore: charts update dirty files --------- Co-authored-by: andes-it --- charts/islandis/values.dev.yaml | 4 ++-- charts/islandis/values.prod.yaml | 4 ++-- charts/islandis/values.staging.yaml | 4 ++-- charts/services/services-bff-portals-admin/values.dev.yaml | 2 +- charts/services/services-bff-portals-admin/values.prod.yaml | 2 +- .../services/services-bff-portals-admin/values.staging.yaml | 2 +- charts/services/services-bff-portals-my-pages/values.dev.yaml | 2 +- .../services/services-bff-portals-my-pages/values.prod.yaml | 2 +- .../services-bff-portals-my-pages/values.staging.yaml | 2 +- infra/src/dsl/bff.ts | 2 +- infra/src/dsl/feature-values.spec.ts | 2 +- infra/src/dsl/portal-env.spec.ts | 2 +- 12 files changed, 15 insertions(+), 15 deletions(-) diff --git a/charts/islandis/values.dev.yaml b/charts/islandis/values.dev.yaml index 0dd1634ad8ad..3ff1c6643bbd 100644 --- a/charts/islandis/values.dev.yaml +++ b/charts/islandis/values.dev.yaml @@ -2313,7 +2313,7 @@ services-bff-portals-admin: BFF_LOGIN_ATTEMPT_TTL_MS: '604800000' BFF_LOGOUT_REDIRECT_URI: 'https://beta.dev01.devland.is' BFF_NAME: 'stjornbord' - BFF_PAR_SUPPORT_ENABLED: 'true' + BFF_PAR_SUPPORT_ENABLED: 'false' BFF_PROXY_API_ENDPOINT: 'http://web-api.islandis.svc.cluster.local/api/graphql' CODE_OWNER: 'core' IDENTITY_SERVER_CLIENT_ID: '@admin.island.is/bff-stjornbord' @@ -2400,7 +2400,7 @@ services-bff-portals-my-pages: BFF_LOGIN_ATTEMPT_TTL_MS: '604800000' BFF_LOGOUT_REDIRECT_URI: 'https://beta.dev01.devland.is' BFF_NAME: 'minarsidur' - BFF_PAR_SUPPORT_ENABLED: 'true' + BFF_PAR_SUPPORT_ENABLED: 'false' BFF_PROXY_API_ENDPOINT: 'http://web-api.islandis.svc.cluster.local/api/graphql' IDENTITY_SERVER_CLIENT_ID: '@island.is/web' IDENTITY_SERVER_CLIENT_SCOPES: '["api_resource.scope","@island.is/applications:read","@island.is/applications:write","@island.is/user-profile:read","@island.is/user-profile:write","@island.is/auth/actor-delegations","@island.is/auth/delegations:write","@island.is/auth/consents","@skra.is/individuals","@island.is/documents","@island.is/endorsements","@admin.island.is/petitions","@island.is/assets/ip","@island.is/assets","@island.is/education","@island.is/education-license","@island.is/finance:overview","@island.is/finance/salary","@island.is/finance/schedule:read","@island.is/finance/loans","@island.is/internal","@island.is/internal:procuring","@island.is/me:details","@island.is/law-and-order","@island.is/licenses","@island.is/licenses:verify","@island.is/company","@island.is/vehicles","@island.is/work-machines","@island.is/health/payments","@island.is/health/medicines","@island.is/health/assistive-devices-and-nutrition","@island.is/health/therapies","@island.is/health/healthcare","@island.is/health/rights-status","@island.is/health/dentists","@island.is/health/organ-donation","@island.is/health/vaccinations","@island.is/signature-collection","@island.is/applications/urvinnslusjodur","@island.is/applications/orkusjodur","@island.is/fishing-license","@island.is/applications/samgongustofa-vehicles","@island.is/applications/ver","@samband.is/financial-aid/applicant","@samband.is/financial-aid:read","@samband.is/financial-aid:write"]' diff --git a/charts/islandis/values.prod.yaml b/charts/islandis/values.prod.yaml index 04619e6f6b0a..3d4924f04ccd 100644 --- a/charts/islandis/values.prod.yaml +++ b/charts/islandis/values.prod.yaml @@ -2182,7 +2182,7 @@ services-bff-portals-admin: BFF_LOGIN_ATTEMPT_TTL_MS: '604800000' BFF_LOGOUT_REDIRECT_URI: 'https://island.is' BFF_NAME: 'stjornbord' - BFF_PAR_SUPPORT_ENABLED: 'true' + BFF_PAR_SUPPORT_ENABLED: 'false' BFF_PROXY_API_ENDPOINT: 'http://web-api.islandis.svc.cluster.local/api/graphql' CODE_OWNER: 'core' IDENTITY_SERVER_CLIENT_ID: '@admin.island.is/bff-stjornbord' @@ -2271,7 +2271,7 @@ services-bff-portals-my-pages: BFF_LOGIN_ATTEMPT_TTL_MS: '604800000' BFF_LOGOUT_REDIRECT_URI: 'https://island.is' BFF_NAME: 'minarsidur' - BFF_PAR_SUPPORT_ENABLED: 'true' + BFF_PAR_SUPPORT_ENABLED: 'false' BFF_PROXY_API_ENDPOINT: 'http://web-api.islandis.svc.cluster.local/api/graphql' IDENTITY_SERVER_CLIENT_ID: '@island.is/web' IDENTITY_SERVER_CLIENT_SCOPES: '["api_resource.scope","@island.is/applications:read","@island.is/applications:write","@island.is/user-profile:read","@island.is/user-profile:write","@island.is/auth/actor-delegations","@island.is/auth/delegations:write","@island.is/auth/consents","@skra.is/individuals","@island.is/documents","@island.is/endorsements","@admin.island.is/petitions","@island.is/assets/ip","@island.is/assets","@island.is/education","@island.is/education-license","@island.is/finance:overview","@island.is/finance/salary","@island.is/finance/schedule:read","@island.is/finance/loans","@island.is/internal","@island.is/internal:procuring","@island.is/me:details","@island.is/law-and-order","@island.is/licenses","@island.is/licenses:verify","@island.is/company","@island.is/vehicles","@island.is/work-machines","@island.is/health/payments","@island.is/health/medicines","@island.is/health/assistive-devices-and-nutrition","@island.is/health/therapies","@island.is/health/healthcare","@island.is/health/rights-status","@island.is/health/dentists","@island.is/health/organ-donation","@island.is/health/vaccinations","@island.is/signature-collection","@island.is/applications/urvinnslusjodur","@island.is/applications/orkusjodur","@island.is/fishing-license","@island.is/applications/samgongustofa-vehicles","@island.is/applications/ver","@samband.is/financial-aid/applicant","@samband.is/financial-aid:read","@samband.is/financial-aid:write"]' diff --git a/charts/islandis/values.staging.yaml b/charts/islandis/values.staging.yaml index a2192f932e41..311d39020959 100644 --- a/charts/islandis/values.staging.yaml +++ b/charts/islandis/values.staging.yaml @@ -2051,7 +2051,7 @@ services-bff-portals-admin: BFF_LOGIN_ATTEMPT_TTL_MS: '604800000' BFF_LOGOUT_REDIRECT_URI: 'https://beta.staging01.devland.is' BFF_NAME: 'stjornbord' - BFF_PAR_SUPPORT_ENABLED: 'true' + BFF_PAR_SUPPORT_ENABLED: 'false' BFF_PROXY_API_ENDPOINT: 'http://web-api.islandis.svc.cluster.local/api/graphql' CODE_OWNER: 'core' IDENTITY_SERVER_CLIENT_ID: '@admin.island.is/bff-stjornbord' @@ -2138,7 +2138,7 @@ services-bff-portals-my-pages: BFF_LOGIN_ATTEMPT_TTL_MS: '604800000' BFF_LOGOUT_REDIRECT_URI: 'https://beta.staging01.devland.is' BFF_NAME: 'minarsidur' - BFF_PAR_SUPPORT_ENABLED: 'true' + BFF_PAR_SUPPORT_ENABLED: 'false' BFF_PROXY_API_ENDPOINT: 'http://web-api.islandis.svc.cluster.local/api/graphql' IDENTITY_SERVER_CLIENT_ID: '@island.is/web' IDENTITY_SERVER_CLIENT_SCOPES: '["api_resource.scope","@island.is/applications:read","@island.is/applications:write","@island.is/user-profile:read","@island.is/user-profile:write","@island.is/auth/actor-delegations","@island.is/auth/delegations:write","@island.is/auth/consents","@skra.is/individuals","@island.is/documents","@island.is/endorsements","@admin.island.is/petitions","@island.is/assets/ip","@island.is/assets","@island.is/education","@island.is/education-license","@island.is/finance:overview","@island.is/finance/salary","@island.is/finance/schedule:read","@island.is/finance/loans","@island.is/internal","@island.is/internal:procuring","@island.is/me:details","@island.is/law-and-order","@island.is/licenses","@island.is/licenses:verify","@island.is/company","@island.is/vehicles","@island.is/work-machines","@island.is/health/payments","@island.is/health/medicines","@island.is/health/assistive-devices-and-nutrition","@island.is/health/therapies","@island.is/health/healthcare","@island.is/health/rights-status","@island.is/health/dentists","@island.is/health/organ-donation","@island.is/health/vaccinations","@island.is/signature-collection","@island.is/applications/urvinnslusjodur","@island.is/applications/orkusjodur","@island.is/fishing-license","@island.is/applications/samgongustofa-vehicles","@island.is/applications/ver","@samband.is/financial-aid/applicant","@samband.is/financial-aid:read","@samband.is/financial-aid:write"]' diff --git a/charts/services/services-bff-portals-admin/values.dev.yaml b/charts/services/services-bff-portals-admin/values.dev.yaml index ad41b2594e0f..6d2cde1b6507 100644 --- a/charts/services/services-bff-portals-admin/values.dev.yaml +++ b/charts/services/services-bff-portals-admin/values.dev.yaml @@ -29,7 +29,7 @@ env: BFF_LOGIN_ATTEMPT_TTL_MS: '604800000' BFF_LOGOUT_REDIRECT_URI: 'https://beta.dev01.devland.is' BFF_NAME: 'stjornbord' - BFF_PAR_SUPPORT_ENABLED: 'true' + BFF_PAR_SUPPORT_ENABLED: 'false' BFF_PROXY_API_ENDPOINT: 'http://web-api.islandis.svc.cluster.local/api/graphql' CODE_OWNER: 'core' IDENTITY_SERVER_CLIENT_ID: '@admin.island.is/bff-stjornbord' diff --git a/charts/services/services-bff-portals-admin/values.prod.yaml b/charts/services/services-bff-portals-admin/values.prod.yaml index 85573c54b3f8..0ac30cab3700 100644 --- a/charts/services/services-bff-portals-admin/values.prod.yaml +++ b/charts/services/services-bff-portals-admin/values.prod.yaml @@ -29,7 +29,7 @@ env: BFF_LOGIN_ATTEMPT_TTL_MS: '604800000' BFF_LOGOUT_REDIRECT_URI: 'https://island.is' BFF_NAME: 'stjornbord' - BFF_PAR_SUPPORT_ENABLED: 'true' + BFF_PAR_SUPPORT_ENABLED: 'false' BFF_PROXY_API_ENDPOINT: 'http://web-api.islandis.svc.cluster.local/api/graphql' CODE_OWNER: 'core' IDENTITY_SERVER_CLIENT_ID: '@admin.island.is/bff-stjornbord' diff --git a/charts/services/services-bff-portals-admin/values.staging.yaml b/charts/services/services-bff-portals-admin/values.staging.yaml index 5244c4b5efc5..a8b5553f847b 100644 --- a/charts/services/services-bff-portals-admin/values.staging.yaml +++ b/charts/services/services-bff-portals-admin/values.staging.yaml @@ -29,7 +29,7 @@ env: BFF_LOGIN_ATTEMPT_TTL_MS: '604800000' BFF_LOGOUT_REDIRECT_URI: 'https://beta.staging01.devland.is' BFF_NAME: 'stjornbord' - BFF_PAR_SUPPORT_ENABLED: 'true' + BFF_PAR_SUPPORT_ENABLED: 'false' BFF_PROXY_API_ENDPOINT: 'http://web-api.islandis.svc.cluster.local/api/graphql' CODE_OWNER: 'core' IDENTITY_SERVER_CLIENT_ID: '@admin.island.is/bff-stjornbord' diff --git a/charts/services/services-bff-portals-my-pages/values.dev.yaml b/charts/services/services-bff-portals-my-pages/values.dev.yaml index e8e6dfa8330e..3c9074dd156a 100644 --- a/charts/services/services-bff-portals-my-pages/values.dev.yaml +++ b/charts/services/services-bff-portals-my-pages/values.dev.yaml @@ -29,7 +29,7 @@ env: BFF_LOGIN_ATTEMPT_TTL_MS: '604800000' BFF_LOGOUT_REDIRECT_URI: 'https://beta.dev01.devland.is' BFF_NAME: 'minarsidur' - BFF_PAR_SUPPORT_ENABLED: 'true' + BFF_PAR_SUPPORT_ENABLED: 'false' BFF_PROXY_API_ENDPOINT: 'http://web-api.islandis.svc.cluster.local/api/graphql' IDENTITY_SERVER_CLIENT_ID: '@island.is/web' IDENTITY_SERVER_CLIENT_SCOPES: '["api_resource.scope","@island.is/applications:read","@island.is/applications:write","@island.is/user-profile:read","@island.is/user-profile:write","@island.is/auth/actor-delegations","@island.is/auth/delegations:write","@island.is/auth/consents","@skra.is/individuals","@island.is/documents","@island.is/endorsements","@admin.island.is/petitions","@island.is/assets/ip","@island.is/assets","@island.is/education","@island.is/education-license","@island.is/finance:overview","@island.is/finance/salary","@island.is/finance/schedule:read","@island.is/finance/loans","@island.is/internal","@island.is/internal:procuring","@island.is/me:details","@island.is/law-and-order","@island.is/licenses","@island.is/licenses:verify","@island.is/company","@island.is/vehicles","@island.is/work-machines","@island.is/health/payments","@island.is/health/medicines","@island.is/health/assistive-devices-and-nutrition","@island.is/health/therapies","@island.is/health/healthcare","@island.is/health/rights-status","@island.is/health/dentists","@island.is/health/organ-donation","@island.is/health/vaccinations","@island.is/signature-collection","@island.is/applications/urvinnslusjodur","@island.is/applications/orkusjodur","@island.is/fishing-license","@island.is/applications/samgongustofa-vehicles","@island.is/applications/ver","@samband.is/financial-aid/applicant","@samband.is/financial-aid:read","@samband.is/financial-aid:write"]' diff --git a/charts/services/services-bff-portals-my-pages/values.prod.yaml b/charts/services/services-bff-portals-my-pages/values.prod.yaml index 2d2a43d9a7eb..930594666907 100644 --- a/charts/services/services-bff-portals-my-pages/values.prod.yaml +++ b/charts/services/services-bff-portals-my-pages/values.prod.yaml @@ -29,7 +29,7 @@ env: BFF_LOGIN_ATTEMPT_TTL_MS: '604800000' BFF_LOGOUT_REDIRECT_URI: 'https://island.is' BFF_NAME: 'minarsidur' - BFF_PAR_SUPPORT_ENABLED: 'true' + BFF_PAR_SUPPORT_ENABLED: 'false' BFF_PROXY_API_ENDPOINT: 'http://web-api.islandis.svc.cluster.local/api/graphql' IDENTITY_SERVER_CLIENT_ID: '@island.is/web' IDENTITY_SERVER_CLIENT_SCOPES: '["api_resource.scope","@island.is/applications:read","@island.is/applications:write","@island.is/user-profile:read","@island.is/user-profile:write","@island.is/auth/actor-delegations","@island.is/auth/delegations:write","@island.is/auth/consents","@skra.is/individuals","@island.is/documents","@island.is/endorsements","@admin.island.is/petitions","@island.is/assets/ip","@island.is/assets","@island.is/education","@island.is/education-license","@island.is/finance:overview","@island.is/finance/salary","@island.is/finance/schedule:read","@island.is/finance/loans","@island.is/internal","@island.is/internal:procuring","@island.is/me:details","@island.is/law-and-order","@island.is/licenses","@island.is/licenses:verify","@island.is/company","@island.is/vehicles","@island.is/work-machines","@island.is/health/payments","@island.is/health/medicines","@island.is/health/assistive-devices-and-nutrition","@island.is/health/therapies","@island.is/health/healthcare","@island.is/health/rights-status","@island.is/health/dentists","@island.is/health/organ-donation","@island.is/health/vaccinations","@island.is/signature-collection","@island.is/applications/urvinnslusjodur","@island.is/applications/orkusjodur","@island.is/fishing-license","@island.is/applications/samgongustofa-vehicles","@island.is/applications/ver","@samband.is/financial-aid/applicant","@samband.is/financial-aid:read","@samband.is/financial-aid:write"]' diff --git a/charts/services/services-bff-portals-my-pages/values.staging.yaml b/charts/services/services-bff-portals-my-pages/values.staging.yaml index f0e31ccc9d58..e4ce58391d55 100644 --- a/charts/services/services-bff-portals-my-pages/values.staging.yaml +++ b/charts/services/services-bff-portals-my-pages/values.staging.yaml @@ -29,7 +29,7 @@ env: BFF_LOGIN_ATTEMPT_TTL_MS: '604800000' BFF_LOGOUT_REDIRECT_URI: 'https://beta.staging01.devland.is' BFF_NAME: 'minarsidur' - BFF_PAR_SUPPORT_ENABLED: 'true' + BFF_PAR_SUPPORT_ENABLED: 'false' BFF_PROXY_API_ENDPOINT: 'http://web-api.islandis.svc.cluster.local/api/graphql' IDENTITY_SERVER_CLIENT_ID: '@island.is/web' IDENTITY_SERVER_CLIENT_SCOPES: '["api_resource.scope","@island.is/applications:read","@island.is/applications:write","@island.is/user-profile:read","@island.is/user-profile:write","@island.is/auth/actor-delegations","@island.is/auth/delegations:write","@island.is/auth/consents","@skra.is/individuals","@island.is/documents","@island.is/endorsements","@admin.island.is/petitions","@island.is/assets/ip","@island.is/assets","@island.is/education","@island.is/education-license","@island.is/finance:overview","@island.is/finance/salary","@island.is/finance/schedule:read","@island.is/finance/loans","@island.is/internal","@island.is/internal:procuring","@island.is/me:details","@island.is/law-and-order","@island.is/licenses","@island.is/licenses:verify","@island.is/company","@island.is/vehicles","@island.is/work-machines","@island.is/health/payments","@island.is/health/medicines","@island.is/health/assistive-devices-and-nutrition","@island.is/health/therapies","@island.is/health/healthcare","@island.is/health/rights-status","@island.is/health/dentists","@island.is/health/organ-donation","@island.is/health/vaccinations","@island.is/signature-collection","@island.is/applications/urvinnslusjodur","@island.is/applications/orkusjodur","@island.is/fishing-license","@island.is/applications/samgongustofa-vehicles","@island.is/applications/ver","@samband.is/financial-aid/applicant","@samband.is/financial-aid:read","@samband.is/financial-aid:write"]' diff --git a/infra/src/dsl/bff.ts b/infra/src/dsl/bff.ts index 76d9aa87a345..f37b23b36e08 100644 --- a/infra/src/dsl/bff.ts +++ b/infra/src/dsl/bff.ts @@ -78,7 +78,7 @@ export const bffConfig = ({ }, BFF_GLOBAL_PREFIX: globalPrefix, BFF_CLIENT_BASE_PATH: `/${key}`, - BFF_PAR_SUPPORT_ENABLED: 'true', + BFF_PAR_SUPPORT_ENABLED: 'false', BFF_CLIENT_BASE_URL: { local: 'http://localhost:4200', dev: ref((ctx) => ctx.svc(getBaseUrl(ctx))), diff --git a/infra/src/dsl/feature-values.spec.ts b/infra/src/dsl/feature-values.spec.ts index 2fbc7bca3530..f3c53c101e09 100644 --- a/infra/src/dsl/feature-values.spec.ts +++ b/infra/src/dsl/feature-values.spec.ts @@ -125,7 +125,7 @@ describe('Feature-deployment support', () => { BFF_NAME: 'stjornbord', BFF_CLIENT_BASE_PATH: '/stjornbord', BFF_GLOBAL_PREFIX: `/stjornbord/bff`, - BFF_PAR_SUPPORT_ENABLED: 'true', + BFF_PAR_SUPPORT_ENABLED: 'false', BFF_ALLOWED_REDIRECT_URIS: json([ 'https://feature-A-beta.dev01.devland.is/stjornbord', ]), diff --git a/infra/src/dsl/portal-env.spec.ts b/infra/src/dsl/portal-env.spec.ts index df39ad3e13bc..9864c0a28c8d 100644 --- a/infra/src/dsl/portal-env.spec.ts +++ b/infra/src/dsl/portal-env.spec.ts @@ -158,7 +158,7 @@ describe('BFF PortalEnv serialization', () => { // BFF BFF_NAME: 'stjornbord', BFF_GLOBAL_PREFIX: `/${key}/bff`, - BFF_PAR_SUPPORT_ENABLED: 'true', + BFF_PAR_SUPPORT_ENABLED: 'false', BFF_ALLOWED_REDIRECT_URIS: json([`https://beta.dev01.devland.is/${key}`]), BFF_CLIENT_BASE_PATH: `/${key}`, BFF_CLIENT_BASE_URL: 'https://beta.dev01.devland.is', From 083feb4b1b79772ceb2410c6c9609e5d07e96d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:10:05 +0000 Subject: [PATCH 33/57] feat(web): Display logos in login button if there is a different top item (#17175) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/web/components/Header/LoginButton.css.ts | 5 + apps/web/components/Header/LoginButton.tsx | 94 +++++++++++-------- 2 files changed, 60 insertions(+), 39 deletions(-) create mode 100644 apps/web/components/Header/LoginButton.css.ts diff --git a/apps/web/components/Header/LoginButton.css.ts b/apps/web/components/Header/LoginButton.css.ts new file mode 100644 index 000000000000..7a7a92b0078b --- /dev/null +++ b/apps/web/components/Header/LoginButton.css.ts @@ -0,0 +1,5 @@ +import { style } from '@vanilla-extract/css' + +export const dropdownMenu = style({ + width: 193, +}) diff --git a/apps/web/components/Header/LoginButton.tsx b/apps/web/components/Header/LoginButton.tsx index 668563c25b76..a7e696641095 100644 --- a/apps/web/components/Header/LoginButton.tsx +++ b/apps/web/components/Header/LoginButton.tsx @@ -1,17 +1,22 @@ import React, { MouseEvent } from 'react' +import { useWindowSize } from 'react-use' +import cn from 'classnames' import { useRouter } from 'next/router' import { Button, ButtonTypes, DropdownMenu, - Hidden, Inline, + Logo, } from '@island.is/island-ui/core' +import { theme } from '@island.is/island-ui/theme' import { webLoginButtonSelect } from '@island.is/plausible' import { useI18n } from '@island.is/web/i18n' import { LayoutProps } from '@island.is/web/layouts/main' +import * as styles from './LoginButton.css' + const minarsidurLink = '/minarsidur/' const minarsidurDelegationsLink = '/minarsidur/login?prompt=select_account' @@ -21,6 +26,7 @@ export function LoginButton(props: { }) { const { t } = useI18n() const router = useRouter() + const { width } = useWindowSize() function trackAndNavigate( buttonType: 'Dropdown - Individuals' | 'Dropdown - Companies' | string, @@ -49,12 +55,38 @@ export function LoginButton(props: { const items = [ { href: minarsidurLink, - title: t.loginIndividuals, + title: ( + + {props.topItem && ( + + )} + {t.loginIndividuals} + + ), onClick: trackAndNavigate.bind(null, 'Dropdown - Individuals'), }, { href: minarsidurDelegationsLink, - title: t.loginDelegations, + title: ( + + {props.topItem && ( + + )} + {t.loginDelegations} + + ), onClick: trackAndNavigate.bind(null, 'Dropdown - Companies'), }, ] @@ -79,42 +111,26 @@ export function LoginButton(props: { }) } + const isMobile = width < theme.breakpoints.md + return ( - <> - - - } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore make web strict - items={items} - /> - - - - {t.login} - - } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore make web strict - items={items} - openOnHover - /> - - > + + {!isMobile && t.login} + + } + openOnHover={!isMobile} + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore make web strict + items={items} + /> ) } From fb234b4a19fb2bdc1bb2fb467a0ed9fb25756797 Mon Sep 17 00:00:00 2001 From: mannipje <135017126+mannipje@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:42:49 +0000 Subject: [PATCH 34/57] feat(contentful-apps): Add organization parent subpage to link group link field (#17143) * Add organizationParentPage as option for organizationPage menu links * Add organizationParentSubpage to link mapping * Add generated contentful types * Fix subpage type when generating organization subpage link * Minor fix after review --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../pages/fields/link-group-link-field.tsx | 41 +++++++++++-------- .../src/lib/generated/contentfulTypes.d.ts | 15 ++++++- libs/cms/src/lib/models/linkGroup.model.ts | 21 +++++++--- 3 files changed, 54 insertions(+), 23 deletions(-) diff --git a/apps/contentful-apps/pages/fields/link-group-link-field.tsx b/apps/contentful-apps/pages/fields/link-group-link-field.tsx index 635d3eacfc8c..17c2bed45b92 100644 --- a/apps/contentful-apps/pages/fields/link-group-link-field.tsx +++ b/apps/contentful-apps/pages/fields/link-group-link-field.tsx @@ -14,15 +14,18 @@ import { DEFAULT_LOCALE } from '../../constants' const RETRY_COUNT = 5 -const getSubpageContentTypeId = (pageContentTypeId?: string) => { - if (pageContentTypeId === 'organizationPage') return 'organizationSubpage' - if (pageContentTypeId === 'projectPage') return 'projectSubpage' +const getSubpageContentTypeIds = ( + pageContentTypeId?: string, +): string[] | null => { + if (pageContentTypeId === 'organizationPage') + return ['organizationParentSubpage', 'organizationSubpage'] + if (pageContentTypeId === 'projectPage') return ['projectSubpage'] return null } interface SubpageData { pageAbove: EntryProps | null - subpageContentType: ContentTypeProps | null + subpageContentType: ContentTypeProps[] | null loading: boolean } @@ -48,20 +51,25 @@ const useSubpageData = (): SubpageData => { if (pageAboveResponse.items.length > 0) { const pageAboveEntry = pageAboveResponse.items[0] - - const subpageContentTypeId = getSubpageContentTypeId( + const subpageContentTypeIds = getSubpageContentTypeIds( pageAboveEntry.sys.contentType.sys.id, ) + const subpageContentTypes = subpageContentTypeIds + ? await Promise.all( + subpageContentTypeIds.map((id) => + cma.contentType.get({ contentTypeId: id }), + ), + ) + : [] + setData({ loading: false, pageAbove: pageAboveEntry, - subpageContentType: subpageContentTypeId - ? await cma.contentType.get({ - contentTypeId: subpageContentTypeId, - }) - : null, + subpageContentType: + subpageContentTypes.length > 0 ? subpageContentTypes : null, }) + return true } return false @@ -154,7 +162,8 @@ const LinkGroupLinkField = () => { let fields = {} if ( - contentTypeId === 'organizationSubpage' && + (contentTypeId === 'organizationSubpage' || + contentTypeId === 'organizationParentSubpage') && pageAbove?.sys?.contentType?.sys?.id === 'organizationPage' ) { fields = { @@ -209,7 +218,7 @@ const LinkGroupLinkField = () => { } const handleLinkExisting = (props: LinkActionsProps) => { - const contentTypeToSelect = getSubpageContentTypeId( + const contentTypesToSelect = getSubpageContentTypeIds( pageAbove?.sys?.contentType?.sys?.id, ) @@ -219,8 +228,8 @@ const LinkGroupLinkField = () => { : sdk.dialogs.selectMultipleEntries selectEntriesFunction({ - contentTypes: contentTypeToSelect - ? [contentTypeToSelect, 'link'] + contentTypes: contentTypesToSelect + ? [...contentTypesToSelect, 'link'] : ['link'], }).then((entries: EntryProps[]) => { entries = Array.isArray(entries) ? entries : [entries] @@ -238,7 +247,7 @@ const LinkGroupLinkField = () => { } const selectableContentTypes = subpageContentType - ? [subpageContentType, linkContentType] + ? [...subpageContentType, linkContentType] : [linkContentType] if (sdk.ids.field === 'primaryLink') { diff --git a/libs/cms/src/lib/generated/contentfulTypes.d.ts b/libs/cms/src/lib/generated/contentfulTypes.d.ts index 368f8fc63417..a03f5f97171d 100644 --- a/libs/cms/src/lib/generated/contentfulTypes.d.ts +++ b/libs/cms/src/lib/generated/contentfulTypes.d.ts @@ -2348,10 +2348,21 @@ export interface ILinkGroupFields { name: string /** Primary Link */ - primaryLink: ILink | IOrganizationSubpage | IProjectSubpage + primaryLink: + | ILink + | IOrganizationSubpage + | IProjectSubpage + | IOrganizationParentSubpage /** Children Links */ - childrenLinks?: (ILink | IProjectSubpage | IOrganizationSubpage)[] | undefined + childrenLinks?: + | ( + | ILink + | IProjectSubpage + | IOrganizationSubpage + | IOrganizationParentSubpage + )[] + | undefined } export interface ILinkGroup extends Entry { diff --git a/libs/cms/src/lib/models/linkGroup.model.ts b/libs/cms/src/lib/models/linkGroup.model.ts index 8d3ee650dd09..c7483d54e92a 100644 --- a/libs/cms/src/lib/models/linkGroup.model.ts +++ b/libs/cms/src/lib/models/linkGroup.model.ts @@ -8,6 +8,7 @@ import { ILink, ILinkGroup, ILinkGroupFields, + IOrganizationParentSubpage, IOrganizationSubpage, IProjectPage, IProjectSubpage, @@ -32,7 +33,7 @@ export class LinkGroup { type PageAbove = IProjectPage type LinkType = Omit< - ILink | IOrganizationSubpage | IProjectSubpage, + ILink | IOrganizationSubpage | IProjectSubpage | IOrganizationParentSubpage, 'update' | 'toPlainObject' > @@ -63,8 +64,14 @@ export const mapLinkGroup = ({ const mapLinkWrapper = (link: LinkType, pageAbove: PageAbove | undefined) => { const contentTypeId = link?.sys?.contentType?.sys?.id - if (contentTypeId === 'organizationSubpage') { - return generateOrganizationSubpageLink(link as IOrganizationSubpage) + + if ( + contentTypeId === 'organizationSubpage' || + contentTypeId === 'organizationParentSubpage' + ) { + return generateOrganizationSubpageLink( + link as IOrganizationSubpage | IOrganizationParentSubpage, + ) } else if (contentTypeId === 'projectSubpage') { return generateProjectSubpageLink(link as IProjectSubpage, pageAbove) } else if (contentTypeId === 'link') { @@ -73,7 +80,9 @@ const mapLinkWrapper = (link: LinkType, pageAbove: PageAbove | undefined) => { return null } -const generateOrganizationSubpageLink = (subpage: IOrganizationSubpage) => { +const generateOrganizationSubpageLink = ( + subpage: IOrganizationSubpage | IOrganizationParentSubpage, +) => { const prefix = getOrganizationPageUrlPrefix(subpage.sys.locale) return mapLink({ @@ -88,7 +97,9 @@ const generateOrganizationSubpageLink = (subpage: IOrganizationSubpage) => { }, }, fields: { - text: subpage.fields.shortTitle || subpage.fields.title, + text: + (subpage as IOrganizationSubpage).fields.shortTitle || + subpage.fields.title, url: `/${prefix}/${subpage.fields.organizationPage.fields.slug}/${subpage.fields.slug}`, }, }) From 31b2fdbf901de85a6b73f94bbf32a7eb5a4402ec Mon Sep 17 00:00:00 2001 From: albinagu <47886428+albinagu@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:59:00 +0000 Subject: [PATCH 35/57] fix(inheritance-report): adding service to mappers (#17177) * fix(inheritance-report): adding service to mappers * prepaid text --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../templates/inheritance-report/utils/mappers.ts | 1 + .../src/forms/sections/prepaidInheritance/heirs.ts | 2 +- .../templates/inheritance-report/src/lib/dataSchema.ts | 1 + .../templates/inheritance-report/src/lib/messages.ts | 10 ++++++++-- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/mappers.ts b/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/mappers.ts index b248ea6462c9..b4e0e2ab0f8e 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/mappers.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/inheritance-report/utils/mappers.ts @@ -265,6 +265,7 @@ export const expandAnswers = ( rent: answers?.funeralCost?.rent ?? '', food: answers?.funeralCost?.food ?? '', tombstone: answers?.funeralCost?.tombstone ?? '', + service: answers?.funeralCost?.service ?? '', hasOther: answers?.funeralCost?.hasOther ?? [], other: answers?.funeralCost?.other ?? '', otherDetails: answers?.funeralCost?.otherDetails ?? '', diff --git a/libs/application/templates/inheritance-report/src/forms/sections/prepaidInheritance/heirs.ts b/libs/application/templates/inheritance-report/src/forms/sections/prepaidInheritance/heirs.ts index 8403cdfb24e6..fc80ed76ab7a 100644 --- a/libs/application/templates/inheritance-report/src/forms/sections/prepaidInheritance/heirs.ts +++ b/libs/application/templates/inheritance-report/src/forms/sections/prepaidInheritance/heirs.ts @@ -84,7 +84,7 @@ export const prePaidHeirs = buildSection({ buildMultiField({ id: 'prePaidHeirsAdditionalInfo', title: m.heirAdditionalInfo, - description: m.heirAdditionalInfoDescription, + description: m.heirAdditionalInfoPrePaidDescription, children: [ buildTextField({ id: 'heirsAdditionalInfo', diff --git a/libs/application/templates/inheritance-report/src/lib/dataSchema.ts b/libs/application/templates/inheritance-report/src/lib/dataSchema.ts index 70b60e815a72..3e437681836f 100644 --- a/libs/application/templates/inheritance-report/src/lib/dataSchema.ts +++ b/libs/application/templates/inheritance-report/src/lib/dataSchema.ts @@ -498,6 +498,7 @@ export const inheritanceReportSchema = z.object({ rent: z.string().optional(), food: z.string().optional(), tombstone: z.string().optional(), + service: z.string().optional(), hasOther: z.array(z.enum([YES])).optional(), other: z.string().optional(), otherDetails: z.string().optional(), diff --git a/libs/application/templates/inheritance-report/src/lib/messages.ts b/libs/application/templates/inheritance-report/src/lib/messages.ts index 39003d0532a1..16822da0fc70 100644 --- a/libs/application/templates/inheritance-report/src/lib/messages.ts +++ b/libs/application/templates/inheritance-report/src/lib/messages.ts @@ -1199,7 +1199,7 @@ export const m = defineMessages({ // Assets to share assetsToShareDescription: { - id: 'ir.application:assetsToShareDescription', + id: 'ir.application:assetsToShareDescription#markdown', defaultMessage: 'Frá dregst búshluti eftirlifandi maka samkvæmt reglum hjúskaparlaga nr. 31/1993.', description: '', @@ -1382,7 +1382,7 @@ export const m = defineMessages({ description: '', }, heirsAndPartitionDescription: { - id: 'ir.application:heirsAndPartitionDescription', + id: 'ir.application:heirsAndPartitionDescription#markdown', defaultMessage: 'Skrá skal netfang erfingja vegna tilkynninga skattstjóra skv. 9. og 10. gr. laga nr. 14/2004.', description: '', @@ -1664,6 +1664,12 @@ export const m = defineMessages({ 'Skýringar og athugasemdir erfingja og/eða þess sem skilar inn erfðafjárskýrslu.', description: '', }, + heirAdditionalInfoPrePaidDescription: { + id: 'ir.application:heirAdditionalInfoPrePaidDescription', + defaultMessage: + 'Skýringar og athugasemdir erfingja og/eða þess sem skilar inn erfðafjárskýrslu.', + description: '', + }, info: { id: 'ir.application:info', defaultMessage: 'Athugasemdir', From c33dc8068cf8fed457a8d0f3a5a6d170b5dc8c44 Mon Sep 17 00:00:00 2001 From: mannipje <135017126+mannipje@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:31:01 +0000 Subject: [PATCH 36/57] feat(web): Change vinnueftirlitid service web footer (#17179) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../Wrapper/OrganizationWrapper.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx b/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx index 44e38cb37d59..9c68d0398745 100644 --- a/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx +++ b/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx @@ -1,6 +1,7 @@ import React, { PropsWithChildren, ReactNode, + useContext, useEffect, useMemo, useState, @@ -44,6 +45,7 @@ import { } from '@island.is/web/components' import { DefaultHeader, WatsonChatPanel } from '@island.is/web/components' import { SLICE_SPACING, STICKY_NAV_MAX_WIDTH } from '@island.is/web/constants' +import { GlobalContext } from '@island.is/web/context' import { Image, Organization, @@ -712,6 +714,8 @@ export const OrganizationFooter: React.FC< let OrganizationFooterComponent = null + const { isServiceWeb } = useContext(GlobalContext) + switch (organization?.slug) { case 'syslumenn': case 'district-commissioner': @@ -910,6 +914,29 @@ export const OrganizationFooter: React.FC< > ) break + case 'vinnueftirlitid': + case 'aosh': + { + const footerItems = organization?.footerItems ?? [] + if (footerItems.length === 0) break + OrganizationFooterComponent = ( + + ) + } + break default: { const footerItems = organization?.footerItems ?? [] if (footerItems.length === 0) break From 556dc1bc157a4229e55fe04d79ae760ee833c91c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sn=C3=A6r=20Seljan=20=C3=9E=C3=B3roddsson?= Date: Mon, 9 Dec 2024 16:02:39 +0000 Subject: [PATCH 37/57] fix(service-portal): Fixes for third-party paths to old login (#17118) * Fix thirdparty paths and add fallback redirect in bff provider * Update comment * revert notification worker changes * simplify bff startup script * Add backwards compatibility for old login path and add targetLInkUri param if it exists to be included in the login flow * Update links * default to href if param is not found * Update target link uri to support fallback to /login urls * Add support for existing params * Update comment * Add comment for clarity * UseCallback instead of useMemo * Fix notifications third party url * remove console.log * chore: charts update dirty files * update urls * chore: charts update dirty files --------- Co-authored-by: snaerseljan Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/native/app/src/lib/passkeys/helpers.ts | 18 +++---- apps/portals/admin/project.json | 4 +- apps/portals/my-pages/project.json | 4 +- .../infra/user-notification.ts | 19 ++++--- .../notificationsWorker/mocks.ts | 1 + .../notificationsWorker.service.spec.ts | 32 +++++------ .../notificationsWorker.service.ts | 25 +++++---- apps/services/user-notification/src/config.ts | 6 ++- .../user-notification/test/environment.ts | 1 + apps/web/components/Header/LoginButton.tsx | 2 +- charts/islandis/values.dev.yaml | 2 + charts/islandis/values.prod.yaml | 2 + charts/islandis/values.staging.yaml | 2 + .../user-notification-worker/values.dev.yaml | 1 + .../user-notification-worker/values.prod.yaml | 1 + .../values.staging.yaml | 1 + .../user-notification/values.dev.yaml | 1 + .../user-notification/values.prod.yaml | 1 + .../user-notification/values.staging.yaml | 1 + libs/react-spa/bff/src/lib/BffProvider.tsx | 54 ++++++++++++++++--- 20 files changed, 119 insertions(+), 59 deletions(-) diff --git a/apps/native/app/src/lib/passkeys/helpers.ts b/apps/native/app/src/lib/passkeys/helpers.ts index 594f65f393ba..eea55f73f3c3 100644 --- a/apps/native/app/src/lib/passkeys/helpers.ts +++ b/apps/native/app/src/lib/passkeys/helpers.ts @@ -1,6 +1,6 @@ import { - PasskeyRegistrationResult, PasskeyAuthenticationResult, + PasskeyRegistrationResult, } from 'react-native-passkey' import { AuthPasskeyAuthenticationOptions, @@ -106,6 +106,10 @@ export const convertBase64UrlToBase64String = (base64Url: string) => { return base64Url.replace(/-/g, '+').replace(/_/g, '/') } +const MY_PAGES_PATH = '/minarsidur' +const APPLICATIONS_PATH = '/umsoknir' +const allowedPaths = [MY_PAGES_PATH, APPLICATIONS_PATH] + export const addPasskeyAsLoginHint = ( url: string, authenticationResponse: string, @@ -120,14 +124,10 @@ export const addPasskeyAsLoginHint = ( return false } - if (url.includes('/minarsidur')) { - return `${origin}/minarsidur/login?login_hint=passkey:${authenticationResponse}&target_link_uri=${encodeURIComponent( - url, - )}` - } + const matchedPath = allowedPaths.find((path) => url.includes(path)) - if (url.includes('/umsoknir')) { - return `${origin}/umsoknir/login?login_hint=passkey:${authenticationResponse}&target_link_uri=${encodeURIComponent( + if (matchedPath) { + return `${origin}/bff/login?login_hint=passkey:${authenticationResponse}&target_link_uri=${encodeURIComponent( url, )}` } @@ -139,7 +139,7 @@ export const doesUrlSupportPasskey = (url: string): boolean => { (url.startsWith('https://beta.dev01.devland.is') || url.startsWith('https://beta.staging01.devland.is') || url.startsWith('https://island.is')) && - (url.includes('/minarsidur') || url.includes('/umsoknir')) + (url.includes(MY_PAGES_PATH) || url.includes(APPLICATIONS_PATH)) ) { return true } diff --git a/apps/portals/admin/project.json b/apps/portals/admin/project.json index ae909c4cae73..4cbb46369d9e 100644 --- a/apps/portals/admin/project.json +++ b/apps/portals/admin/project.json @@ -96,9 +96,7 @@ "start-bff": { "executor": "nx:run-commands", "options": { - "commands": [ - "node -r esbuild-register src/cli/cli.ts run-local-env services-bff-portals-admin" - ], + "commands": ["yarn infra run-local-env services-bff-portals-admin"], "cwd": "infra" } }, diff --git a/apps/portals/my-pages/project.json b/apps/portals/my-pages/project.json index 285c3d1a352e..592d65f6910d 100644 --- a/apps/portals/my-pages/project.json +++ b/apps/portals/my-pages/project.json @@ -105,9 +105,7 @@ "start-bff": { "executor": "nx:run-commands", "options": { - "commands": [ - "node -r esbuild-register src/cli/cli.ts run-local-env services-bff-portals-my-pages" - ], + "commands": ["yarn infra run-local-env services-bff-portals-my-pages"], "cwd": "infra" } }, diff --git a/apps/services/user-notification/infra/user-notification.ts b/apps/services/user-notification/infra/user-notification.ts index 96c8788bf22a..4cd6e621c5c1 100644 --- a/apps/services/user-notification/infra/user-notification.ts +++ b/apps/services/user-notification/infra/user-notification.ts @@ -1,16 +1,16 @@ -import { - Base, - Client, - NationalRegistryB2C, - RskCompanyInfo, -} from '../../../../infra/src/dsl/xroad' import { CodeOwners, + ServiceBuilder, json, ref, service, - ServiceBuilder, } from '../../../../infra/src/dsl/dsl' +import { + Base, + Client, + NationalRegistryB2C, + RskCompanyInfo, +} from '../../../../infra/src/dsl/xroad' const serviceName = 'user-notification' const serviceWorkerName = `${serviceName}-worker` @@ -42,6 +42,11 @@ const getEnv = (services: { '@island.is/auth/delegations/index:system', ]), SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur', + SERVICE_PORTAL_BFF_LOGIN_URL: { + dev: 'https://beta.dev01.devland.is/bff/login', + staging: 'https://beta.staging01.devland.is/bff/login', + prod: 'https://island.is/bff/login', + }, EMAIL_FROM_ADDRESS: { dev: 'development@island.is', staging: 'development@island.is', diff --git a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/mocks.ts b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/mocks.ts index 02638dd0f0f3..62af04a27bb1 100644 --- a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/mocks.ts +++ b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/mocks.ts @@ -233,6 +233,7 @@ export const MockUserNotificationsConfig: ConfigType< emailFromAddress: 'development@island.is', isConfigured: true, servicePortalClickActionUrl: 'https://island.is/minarsidur', + servicePortalBffLoginUrl: 'https://island.is/bff/login', redis: { nodes: ['node'], ssl: false, diff --git a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.spec.ts b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.spec.ts index df1a95d6b828..ab618464b2d7 100644 --- a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.spec.ts +++ b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.spec.ts @@ -1,29 +1,28 @@ -import { getConnectionToken, getModelToken } from '@nestjs/sequelize' import { INestApplication, Type } from '@nestjs/common' +import { getConnectionToken, getModelToken } from '@nestjs/sequelize' import { TestingModuleBuilder } from '@nestjs/testing' import { Sequelize } from 'sequelize-typescript' -import { testServer, truncate, useDatabase } from '@island.is/testing/nest' -import { UserProfileDto, V2UsersApi } from '@island.is/clients/user-profile' -import { getQueueServiceToken, QueueService } from '@island.is/message-queue' -import { FeatureFlagService } from '@island.is/nest/feature-flags' -import { NationalRegistryV3ClientService } from '@island.is/clients/national-registry-v3' -import { EmailService } from '@island.is/email-service' import { DelegationsApi } from '@island.is/clients/auth/delegation-api' import { CmsService } from '@island.is/clients/cms' +import { NationalRegistryV3ClientService } from '@island.is/clients/national-registry-v3' import { CompanyExtendedInfo, CompanyRegistryClientService, } from '@island.is/clients/rsk/company-registry' +import { UserProfileDto, V2UsersApi } from '@island.is/clients/user-profile' +import { EmailService } from '@island.is/email-service' +import { QueueService, getQueueServiceToken } from '@island.is/message-queue' +import { FeatureFlagService } from '@island.is/nest/feature-flags' +import { testServer, truncate, useDatabase } from '@island.is/testing/nest' import { UserNotificationsConfig } from '../../../../config' import { FIREBASE_PROVIDER } from '../../../../constants' import { AppModule } from '../../../app.module' import { SequelizeConfigService } from '../../../sequelizeConfig.service' -import { NotificationDispatchService } from '../notificationDispatch.service' import { Notification } from '../notification.model' +import { NotificationDispatchService } from '../notificationDispatch.service' import { NotificationsService } from '../notifications.service' -import { NotificationsWorkerService } from './notificationsWorker.service' import { wait } from './helpers' import { MockDelegationsService, @@ -31,19 +30,20 @@ import { MockNationalRegistryV3ClientService, MockUserNotificationsConfig, companyUser, - userWithNoEmail, + delegationSubjectId, + getMockHnippTemplate, + mockTemplateId, + userProfiles, userWithDelegations, userWithDelegations2, userWithDocumentNotificationsDisabled, userWithEmailNotificationsDisabled, userWithFeatureFlagDisabled, - userWithSendToDelegationsFeatureFlagDisabled, - getMockHnippTemplate, - mockTemplateId, - delegationSubjectId, userWithNoDelegations, - userProfiles, + userWithNoEmail, + userWithSendToDelegationsFeatureFlagDisabled, } from './mocks' +import { NotificationsWorkerService } from './notificationsWorker.service' const workingHoursDelta = 1000 * 60 * 60 // 1 hour const insideWorkingHours = new Date(2021, 1, 1, 9, 0, 0) @@ -303,7 +303,7 @@ describe('NotificationsWorkerService', () => { expect.objectContaining({ component: 'ImageWithLink', context: expect.objectContaining({ - href: `https://island.is/minarsidur/login?login_hint=${delegationSubjectId}&target_link_uri=https://island.is/minarsidur/postholf`, + href: `https://island.is/bff/login?login_hint=${delegationSubjectId}&target_link_uri=https://island.is/minarsidur/postholf`, }), }), ]), diff --git a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.ts b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.ts index fd353149f63d..d0d9ae9368fd 100644 --- a/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.ts +++ b/apps/services/user-notification/src/app/modules/notifications/notificationsWorker/notificationsWorker.service.ts @@ -1,21 +1,20 @@ import { Inject, Injectable, OnApplicationBootstrap } from '@nestjs/common' -import { join } from 'path' import { InjectModel } from '@nestjs/sequelize' import { isCompany } from 'kennitala' +import { join } from 'path' import { User } from '@island.is/auth-nest-tools' import { DocumentsScope } from '@island.is/auth/scopes' +import { DelegationsApi } from '@island.is/clients/auth/delegation-api' import { - EinstaklingurDTONafnAllt, EinstaklingurDTONafnItar, NationalRegistryV3ClientService, } from '@island.is/clients/national-registry-v3' import { + ActorProfileDto, UserProfileDto, V2UsersApi, - ActorProfileDto, } from '@island.is/clients/user-profile' -import { DelegationsApi } from '@island.is/clients/auth/delegation-api' import { Body, EmailService, Message } from '@island.is/email-service' import type { Logger } from '@island.is/logging' import { LOGGER_PROVIDER } from '@island.is/logging' @@ -29,17 +28,17 @@ import { type ConfigType } from '@island.is/nest/config' import { FeatureFlagService, Features } from '@island.is/nest/feature-flags' import type { Locale } from '@island.is/shared/types' -import { UserNotificationsConfig } from '../../../../config' -import { MessageProcessorService } from '../messageProcessor.service' -import { NotificationDispatchService } from '../notificationDispatch.service' -import { CreateHnippNotificationDto } from '../dto/createHnippNotification.dto' -import { NotificationsService } from '../notifications.service' -import { HnippTemplate } from '../dto/hnippTemplate.response' -import { Notification } from '../notification.model' import { CompanyExtendedInfo, CompanyRegistryClientService, } from '@island.is/clients/rsk/company-registry' +import { UserNotificationsConfig } from '../../../../config' +import { CreateHnippNotificationDto } from '../dto/createHnippNotification.dto' +import { HnippTemplate } from '../dto/hnippTemplate.response' +import { MessageProcessorService } from '../messageProcessor.service' +import { Notification } from '../notification.model' +import { NotificationDispatchService } from '../notificationDispatch.service' +import { NotificationsService } from '../notifications.service' type HandleNotification = { profile: { @@ -475,8 +474,8 @@ export class NotificationsWorkerService { return shouldUseThirdPartyLogin ? `${ - this.config.servicePortalClickActionUrl - }/login?login_hint=${subjectId}&target_link_uri=${encodeURI( + this.config.servicePortalBffLoginUrl + }?login_hint=${subjectId}&target_link_uri=${encodeURI( formattedTemplate.clickActionUrl, )}` : formattedTemplate.clickActionUrl diff --git a/apps/services/user-notification/src/config.ts b/apps/services/user-notification/src/config.ts index ee1134be1e34..0ba2d9017bcb 100644 --- a/apps/services/user-notification/src/config.ts +++ b/apps/services/user-notification/src/config.ts @@ -1,13 +1,14 @@ import { z } from 'zod' -import { defineConfig } from '@island.is/nest/config' import { processJob } from '@island.is/infra-nest-server' +import { defineConfig } from '@island.is/nest/config' // Exported for testing purposes export const schema = z.object({ isWorker: z.boolean(), firebaseCredentials: z.string(), servicePortalClickActionUrl: z.string(), + servicePortalBffLoginUrl: z.string(), contentfulAccessToken: z.string(), emailFromAddress: z.string(), redis: z.object({ @@ -27,6 +28,9 @@ export const UserNotificationsConfig = defineConfig({ servicePortalClickActionUrl: env.optional('SERVICE_PORTAL_CLICK_ACTION_URL') ?? 'https://island.is/minarsidur', + servicePortalBffLoginUrl: + env.optional('SERVICE_PORTAL_BFF_LOGIN_URL') ?? + 'https://island.is/bff/login', contentfulAccessToken: env.optional('CONTENTFUL_ACCESS_TOKEN', ''), emailFromAddress: env.required( 'EMAIL_FROM_ADDRESS', diff --git a/apps/services/user-notification/test/environment.ts b/apps/services/user-notification/test/environment.ts index 77956bffa36e..d7605cd63d56 100644 --- a/apps/services/user-notification/test/environment.ts +++ b/apps/services/user-notification/test/environment.ts @@ -5,6 +5,7 @@ export const environment = { SQS_ACCESS_KEY: 'testing', SQS_SECRET_ACCESS_KEY: 'testing', SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur', + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://island.is/bff/login', // Disable redis registration during testing REDIS_URL_NODE_01: '[]', } as const diff --git a/apps/web/components/Header/LoginButton.tsx b/apps/web/components/Header/LoginButton.tsx index a7e696641095..726843f7d262 100644 --- a/apps/web/components/Header/LoginButton.tsx +++ b/apps/web/components/Header/LoginButton.tsx @@ -18,7 +18,7 @@ import { LayoutProps } from '@island.is/web/layouts/main' import * as styles from './LoginButton.css' const minarsidurLink = '/minarsidur/' -const minarsidurDelegationsLink = '/minarsidur/login?prompt=select_account' +const minarsidurDelegationsLink = '/bff/login?prompt=select_account' export function LoginButton(props: { colorScheme: ButtonTypes['colorScheme'] diff --git a/charts/islandis/values.dev.yaml b/charts/islandis/values.dev.yaml index 3ff1c6643bbd..e0f5e81d1937 100644 --- a/charts/islandis/values.dev.yaml +++ b/charts/islandis/values.dev.yaml @@ -3184,6 +3184,7 @@ user-notification: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: '' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://beta.dev01.devland.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.dev01.devland.is' @@ -3387,6 +3388,7 @@ user-notification-worker: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: '' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://beta.dev01.devland.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.dev01.devland.is' diff --git a/charts/islandis/values.prod.yaml b/charts/islandis/values.prod.yaml index 3d4924f04ccd..d08668a62457 100644 --- a/charts/islandis/values.prod.yaml +++ b/charts/islandis/values.prod.yaml @@ -3063,6 +3063,7 @@ user-notification: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.whakos.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://island.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.island.is' @@ -3266,6 +3267,7 @@ user-notification-worker: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.whakos.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://island.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.island.is' diff --git a/charts/islandis/values.staging.yaml b/charts/islandis/values.staging.yaml index 311d39020959..2cd78ddb6714 100644 --- a/charts/islandis/values.staging.yaml +++ b/charts/islandis/values.staging.yaml @@ -2922,6 +2922,7 @@ user-notification: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: '' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://beta.staging01.devland.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.staging01.devland.is' @@ -3125,6 +3126,7 @@ user-notification-worker: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: '' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://beta.staging01.devland.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.staging01.devland.is' diff --git a/charts/services/user-notification-worker/values.dev.yaml b/charts/services/user-notification-worker/values.dev.yaml index 2a0ec43d43f3..ee126139b967 100644 --- a/charts/services/user-notification-worker/values.dev.yaml +++ b/charts/services/user-notification-worker/values.dev.yaml @@ -49,6 +49,7 @@ env: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: '' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://beta.dev01.devland.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.dev01.devland.is' diff --git a/charts/services/user-notification-worker/values.prod.yaml b/charts/services/user-notification-worker/values.prod.yaml index 6ab822a0aa50..dc8b5e5d2697 100644 --- a/charts/services/user-notification-worker/values.prod.yaml +++ b/charts/services/user-notification-worker/values.prod.yaml @@ -49,6 +49,7 @@ env: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.whakos.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://island.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.island.is' diff --git a/charts/services/user-notification-worker/values.staging.yaml b/charts/services/user-notification-worker/values.staging.yaml index 4d38939c4667..ce46946924d2 100644 --- a/charts/services/user-notification-worker/values.staging.yaml +++ b/charts/services/user-notification-worker/values.staging.yaml @@ -49,6 +49,7 @@ env: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: '' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://beta.staging01.devland.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.staging01.devland.is' diff --git a/charts/services/user-notification/values.dev.yaml b/charts/services/user-notification/values.dev.yaml index 2a4c2df02f1d..f6ba4ec5469a 100644 --- a/charts/services/user-notification/values.dev.yaml +++ b/charts/services/user-notification/values.dev.yaml @@ -46,6 +46,7 @@ env: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.5fzau3.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: '' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://beta.dev01.devland.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.dev01.devland.is' diff --git a/charts/services/user-notification/values.prod.yaml b/charts/services/user-notification/values.prod.yaml index 00cf09f12ae3..bb75838fa119 100644 --- a/charts/services/user-notification/values.prod.yaml +++ b/charts/services/user-notification/values.prod.yaml @@ -46,6 +46,7 @@ env: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.whakos.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://island.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.island.is' diff --git a/charts/services/user-notification/values.staging.yaml b/charts/services/user-notification/values.staging.yaml index 9a9c1025e3bc..843f3f779e41 100644 --- a/charts/services/user-notification/values.staging.yaml +++ b/charts/services/user-notification/values.staging.yaml @@ -46,6 +46,7 @@ env: REDIS_URL_NODE_01: '["clustercfg.general-redis-cluster-group.ab9ckb.euw1.cache.amazonaws.com:6379"]' REDIS_USE_SSL: 'true' SERVERSIDE_FEATURES_ON: '' + SERVICE_PORTAL_BFF_LOGIN_URL: 'https://beta.staging01.devland.is/bff/login' SERVICE_PORTAL_CLICK_ACTION_URL: 'https://island.is/minarsidur' USER_PROFILE_CLIENT_URL: 'http://web-service-portal-api.service-portal.svc.cluster.local' XROAD_BASE_PATH: 'http://securityserver.staging01.devland.is' diff --git a/libs/react-spa/bff/src/lib/BffProvider.tsx b/libs/react-spa/bff/src/lib/BffProvider.tsx index 7ca9502c62ff..f10534fc550e 100644 --- a/libs/react-spa/bff/src/lib/BffProvider.tsx +++ b/libs/react-spa/bff/src/lib/BffProvider.tsx @@ -42,6 +42,7 @@ export const BffProvider = ({ authState === 'switching' || authState === 'logging-out' const isLoggedIn = authState === 'logged-in' + const oldLoginPath = `${applicationBasePath}/login` const { postMessage } = useBffBroadcaster((event) => { if ( @@ -74,6 +75,39 @@ export const BffProvider = ({ } }, [postMessage, state.userInfo, isLoggedIn]) + /** + * Builds authentication query parameters for login redirection: + * - target_link_uri: Destination URL after successful login + * • Uses URL from query string if provided + * • Falls back to current URL, with '/login' stripped if on legacy login path + * - prompt: Optional authentication prompt type + * - login_hint: Optional suggested account identifier + */ + const getLoginQueryParams = useCallback(() => { + const urlParams = new URLSearchParams(window.location.search) + const targetLinkUri = urlParams.get('target_link_uri') + const prompt = urlParams.get('prompt') + const loginHint = urlParams.get('login_hint') + const url = window.location.href + + const params = { + target_link_uri: + targetLinkUri ?? + (window.location.pathname.startsWith(oldLoginPath) + ? // Remove `/login` from the path to prevent redirect loop + url.replace(oldLoginPath, applicationBasePath) + : url), + ...(prompt && { + prompt, + }), + ...(loginHint && { + login_hint: loginHint, + }), + } + + return params + }, [applicationBasePath, oldLoginPath]) + const checkLogin = async (noRefresh = false) => { dispatch({ type: ActionType.SIGNIN_START, @@ -100,6 +134,13 @@ export const BffProvider = ({ return } + // If user is logged in and on the old login path, then start the sign-in process + if (window.location.pathname.startsWith(oldLoginPath)) { + signIn() + + return + } + const user = await res.json() dispatch({ @@ -119,10 +160,8 @@ export const BffProvider = ({ type: ActionType.SIGNIN_START, }) - window.location.href = bffUrlGenerator('/login', { - target_link_uri: window.location.href, - }) - }, [bffUrlGenerator]) + window.location.href = bffUrlGenerator('/login', getLoginQueryParams()) + }, [bffUrlGenerator, getLoginQueryParams]) const signOut = useCallback(() => { if (!state.userInfo) { @@ -149,14 +188,17 @@ export const BffProvider = ({ type: ActionType.SWITCH_USER, }) + const loginQueryParams = getLoginQueryParams() + const targetLinkUri = loginQueryParams['target_link_uri'] + window.location.href = bffUrlGenerator('/login', { - target_link_uri: window.location.href, + target_link_uri: targetLinkUri, ...(nationalId ? { login_hint: nationalId } : { prompt: 'select_account' }), }) }, - [bffUrlGenerator], + [bffUrlGenerator, getLoginQueryParams], ) const checkQueryStringError = () => { From 24c792bf5f5600a6507b497f9dae2a4ba3904fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Mon, 9 Dec 2024 17:07:36 +0000 Subject: [PATCH 38/57] fix(web): Set DropdownMenu styles to use minWidth (#17185) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/web/components/Header/LoginButton.css.ts | 5 ----- apps/web/components/Header/LoginButton.tsx | 3 --- libs/island-ui/core/src/lib/DropdownMenu/DropdownMenu.css.ts | 3 ++- 3 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 apps/web/components/Header/LoginButton.css.ts diff --git a/apps/web/components/Header/LoginButton.css.ts b/apps/web/components/Header/LoginButton.css.ts deleted file mode 100644 index 7a7a92b0078b..000000000000 --- a/apps/web/components/Header/LoginButton.css.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { style } from '@vanilla-extract/css' - -export const dropdownMenu = style({ - width: 193, -}) diff --git a/apps/web/components/Header/LoginButton.tsx b/apps/web/components/Header/LoginButton.tsx index 726843f7d262..7dc22605409b 100644 --- a/apps/web/components/Header/LoginButton.tsx +++ b/apps/web/components/Header/LoginButton.tsx @@ -15,8 +15,6 @@ import { webLoginButtonSelect } from '@island.is/plausible' import { useI18n } from '@island.is/web/i18n' import { LayoutProps } from '@island.is/web/layouts/main' -import * as styles from './LoginButton.css' - const minarsidurLink = '/minarsidur/' const minarsidurDelegationsLink = '/bff/login?prompt=select_account' @@ -116,7 +114,6 @@ export function LoginButton(props: { return ( Date: Mon, 9 Dec 2024 18:28:32 +0000 Subject: [PATCH 39/57] feat(j-s): Add indictment count subtypes to IndictmentCount (#17129) * Add frontend changes for selecting indictment subtypes in indictment count * Create migration * Update DB on change * Refactor * Refactor * chore: nx format:write update dirty files * Add feature flag * Refactor * Refactor * Add featur flag --------- Co-authored-by: andes-it Co-authored-by: unakb Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../api/infra/judicial-system-api.ts | 4 +- .../dto/updateIndictmentCount.input.ts | 12 +++- .../models/indictmentCount.model.ts | 9 ++- .../20241204124025-update-indictment-count.js | 25 ++++++++ .../dto/updateIndictmentCount.dto.ts | 11 +++- .../models/indictmentCount.model.ts | 13 +++- .../src/components/FormProvider/case.graphql | 1 + .../IndictmentCaseFilesList.tsx | 8 ++- .../ServiceAnnouncement.tsx | 2 +- .../Indictments/CaseFiles/CaseFiles.tsx | 11 +++- .../Indictments/Indictment/Indictment.tsx | 2 +- .../Indictment/IndictmentCount.css.ts | 15 +++++ .../Indictment/IndictmentCount.strings.ts | 5 ++ .../Indictment/IndictmentCount.tsx | 62 ++++++++++++++++++- .../Indictments/Processing/Processing.tsx | 7 ++- .../web/src/utils/hooks/useCaseList/index.tsx | 7 ++- .../updateIndictmentCount.graphql | 1 + .../web/src/utils/hooks/useSections/index.ts | 8 ++- charts/judicial-system/values.prod.yaml | 2 +- charts/judicial-system/values.staging.yaml | 2 +- .../judicial-system-api/values.prod.yaml | 2 +- .../judicial-system-api/values.staging.yaml | 2 +- libs/judicial-system/types/src/lib/feature.ts | 1 + 23 files changed, 193 insertions(+), 19 deletions(-) create mode 100644 apps/judicial-system/backend/migrations/20241204124025-update-indictment-count.js create mode 100644 apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.css.ts diff --git a/apps/judicial-system/api/infra/judicial-system-api.ts b/apps/judicial-system/api/infra/judicial-system-api.ts index efe736fa005b..8f5cabe11e2c 100644 --- a/apps/judicial-system/api/infra/judicial-system-api.ts +++ b/apps/judicial-system/api/infra/judicial-system-api.ts @@ -47,8 +47,8 @@ export const serviceSetup = (services: { }, HIDDEN_FEATURES: { dev: '', - staging: '', - prod: '', + staging: 'MULTIPLE_INDICTMENT_SUBTYPES', + prod: 'MULTIPLE_INDICTMENT_SUBTYPES', }, }) .secrets({ diff --git a/apps/judicial-system/api/src/app/modules/indictment-count/dto/updateIndictmentCount.input.ts b/apps/judicial-system/api/src/app/modules/indictment-count/dto/updateIndictmentCount.input.ts index d4d05cedb578..7e26e223a6b6 100644 --- a/apps/judicial-system/api/src/app/modules/indictment-count/dto/updateIndictmentCount.input.ts +++ b/apps/judicial-system/api/src/app/modules/indictment-count/dto/updateIndictmentCount.input.ts @@ -4,7 +4,10 @@ import { GraphQLJSONObject } from 'graphql-type-json' import { Field, ID, InputType } from '@nestjs/graphql' import type { SubstanceMap } from '@island.is/judicial-system/types' -import { IndictmentCountOffense } from '@island.is/judicial-system/types' +import { + IndictmentCountOffense, + IndictmentSubtype, +} from '@island.is/judicial-system/types' @InputType() export class UpdateIndictmentCountInput { @@ -53,4 +56,11 @@ export class UpdateIndictmentCountInput { @IsOptional() @Field(() => String, { nullable: true }) readonly legalArguments?: string + + @Allow() + @IsOptional() + @IsArray() + @IsEnum(IndictmentSubtype, { each: true }) + @Field(() => [IndictmentSubtype], { nullable: true }) + readonly indictmentCountSubtypes?: IndictmentSubtype[] } diff --git a/apps/judicial-system/api/src/app/modules/indictment-count/models/indictmentCount.model.ts b/apps/judicial-system/api/src/app/modules/indictment-count/models/indictmentCount.model.ts index 3ea6a132945c..f3ef5aa954c6 100644 --- a/apps/judicial-system/api/src/app/modules/indictment-count/models/indictmentCount.model.ts +++ b/apps/judicial-system/api/src/app/modules/indictment-count/models/indictmentCount.model.ts @@ -3,9 +3,13 @@ import { GraphQLJSONObject } from 'graphql-type-json' import { Field, ID, ObjectType, registerEnumType } from '@nestjs/graphql' import type { SubstanceMap } from '@island.is/judicial-system/types' -import { IndictmentCountOffense } from '@island.is/judicial-system/types' +import { + IndictmentCountOffense, + IndictmentSubtype, +} from '@island.is/judicial-system/types' registerEnumType(IndictmentCountOffense, { name: 'IndictmentCountOffense' }) +registerEnumType(IndictmentSubtype, { name: 'IndictmentSubtype' }) @ObjectType() export class IndictmentCount { @@ -41,4 +45,7 @@ export class IndictmentCount { @Field(() => String, { nullable: true }) readonly legalArguments?: string + + @Field(() => [IndictmentSubtype], { nullable: true }) + readonly indictmentCountSubtypes?: IndictmentSubtype[] } diff --git a/apps/judicial-system/backend/migrations/20241204124025-update-indictment-count.js b/apps/judicial-system/backend/migrations/20241204124025-update-indictment-count.js new file mode 100644 index 000000000000..523e416ba23e --- /dev/null +++ b/apps/judicial-system/backend/migrations/20241204124025-update-indictment-count.js @@ -0,0 +1,25 @@ +'use strict' + +module.exports = { + async up(queryInterface, Sequelize) { + await queryInterface.sequelize.transaction((transaction) => + queryInterface.addColumn( + 'indictment_count', + 'indictment_count_subtypes', + { + type: Sequelize.ARRAY(Sequelize.STRING), + allowNull: true, + defaultValue: [], + }, + { transaction }, + ), + ) + }, + + async down(queryInterface) { + await queryInterface.removeColumn( + 'indictment_count', + 'indictment_count_subtypes', + ) + }, +} diff --git a/apps/judicial-system/backend/src/app/modules/indictment-count/dto/updateIndictmentCount.dto.ts b/apps/judicial-system/backend/src/app/modules/indictment-count/dto/updateIndictmentCount.dto.ts index 0559ffa35277..3b12997e7fed 100644 --- a/apps/judicial-system/backend/src/app/modules/indictment-count/dto/updateIndictmentCount.dto.ts +++ b/apps/judicial-system/backend/src/app/modules/indictment-count/dto/updateIndictmentCount.dto.ts @@ -10,7 +10,10 @@ import { import { ApiPropertyOptional } from '@nestjs/swagger' import type { SubstanceMap } from '@island.is/judicial-system/types' -import { IndictmentCountOffense } from '@island.is/judicial-system/types' +import { + IndictmentCountOffense, + IndictmentSubtype, +} from '@island.is/judicial-system/types' export class UpdateIndictmentCountDto { @IsOptional() @@ -50,4 +53,10 @@ export class UpdateIndictmentCountDto { @IsString() @ApiPropertyOptional({ type: String }) readonly legalArguments?: string + + @IsOptional() + @IsArray() + @IsEnum(IndictmentSubtype, { each: true }) + @ApiPropertyOptional({ enum: IndictmentSubtype, isArray: true }) + readonly indictmentCountSubtypes?: IndictmentSubtype[] } diff --git a/apps/judicial-system/backend/src/app/modules/indictment-count/models/indictmentCount.model.ts b/apps/judicial-system/backend/src/app/modules/indictment-count/models/indictmentCount.model.ts index ae7ec5c59316..03b9d511f2d7 100644 --- a/apps/judicial-system/backend/src/app/modules/indictment-count/models/indictmentCount.model.ts +++ b/apps/judicial-system/backend/src/app/modules/indictment-count/models/indictmentCount.model.ts @@ -11,7 +11,10 @@ import { import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' import type { SubstanceMap } from '@island.is/judicial-system/types' -import { IndictmentCountOffense } from '@island.is/judicial-system/types' +import { + IndictmentCountOffense, + IndictmentSubtype, +} from '@island.is/judicial-system/types' import { Case } from '../../case/models/case.model' @@ -69,4 +72,12 @@ export class IndictmentCount extends Model { @Column({ type: DataType.TEXT, allowNull: true }) @ApiPropertyOptional({ type: String }) legalArguments?: string + + @Column({ + type: DataType.ARRAY(DataType.ENUM), + allowNull: true, + values: Object.values(IndictmentSubtype), + }) + @ApiPropertyOptional({ enum: IndictmentSubtype, isArray: true }) + indictmentCountSubtypes?: IndictmentSubtype[] } diff --git a/apps/judicial-system/web/src/components/FormProvider/case.graphql b/apps/judicial-system/web/src/components/FormProvider/case.graphql index 341c18c355fc..00c390081d4a 100644 --- a/apps/judicial-system/web/src/components/FormProvider/case.graphql +++ b/apps/judicial-system/web/src/components/FormProvider/case.graphql @@ -216,6 +216,7 @@ query Case($input: CaseQueryInput!) { lawsBroken incidentDescription legalArguments + indictmentCountSubtypes } requestDriversLicenseSuspension appealState diff --git a/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.tsx b/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.tsx index 9dd7bb9cb3bd..b9646bc28671 100644 --- a/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.tsx +++ b/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.tsx @@ -5,6 +5,7 @@ import { AnimatePresence } from 'framer-motion' import { Box, Text } from '@island.is/island-ui/core' import { formatDate } from '@island.is/judicial-system/formatters' import { + Feature, isCompletedCase, isDefenceUser, isDistrictCourtUser, @@ -15,6 +16,7 @@ import { isTrafficViolationCase, } from '@island.is/judicial-system/types' import { + FeatureContext, FileNotFoundModal, PdfButton, SectionHeading, @@ -71,12 +73,16 @@ const IndictmentCaseFilesList: FC = ({ }) => { const { formatMessage } = useIntl() const { user, limitedAccess } = useContext(UserContext) + const { features } = useContext(FeatureContext) const { onOpen, fileNotFound, dismissFileNotFound } = useFileList({ caseId: workingCase.id, connectedCaseParentId, }) - const showTrafficViolationCaseFiles = isTrafficViolationCase(workingCase) + const showTrafficViolationCaseFiles = + features.includes(Feature.MULTIPLE_INDICTMENT_SUBTYPES) || + isTrafficViolationCase(workingCase) + const showSubpoenaPdf = displayGeneratedPDFs && workingCase.defendants?.some( diff --git a/apps/judicial-system/web/src/components/ServiceAnnouncement/ServiceAnnouncement.tsx b/apps/judicial-system/web/src/components/ServiceAnnouncement/ServiceAnnouncement.tsx index 8dac19a8c448..f70fe17e3e35 100644 --- a/apps/judicial-system/web/src/components/ServiceAnnouncement/ServiceAnnouncement.tsx +++ b/apps/judicial-system/web/src/components/ServiceAnnouncement/ServiceAnnouncement.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from 'react' +import { FC } from 'react' import { IntlShape, MessageDescriptor, useIntl } from 'react-intl' import { AlertMessage, Box, LoadingDots, Text } from '@island.is/island-ui/core' diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFiles/CaseFiles.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFiles/CaseFiles.tsx index 72f3b46677af..e10112fd8b4e 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFiles/CaseFiles.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/CaseFiles/CaseFiles.tsx @@ -5,9 +5,13 @@ import router from 'next/router' import { Box, InputFileUpload } from '@island.is/island-ui/core' import { fileExtensionWhitelist } from '@island.is/island-ui/core/types' import * as constants from '@island.is/judicial-system/consts' -import { isTrafficViolationCase } from '@island.is/judicial-system/types' +import { + Feature, + isTrafficViolationCase, +} from '@island.is/judicial-system/types' import { titles } from '@island.is/judicial-system-web/messages' import { + FeatureContext, FormContentContainer, FormContext, FormFooter, @@ -29,6 +33,7 @@ import * as strings from './CaseFiles.strings' const CaseFiles = () => { const { workingCase, isLoadingWorkingCase, caseNotFound } = useContext(FormContext) + const { features } = useContext(FeatureContext) const { formatMessage } = useIntl() const { uploadFiles, @@ -41,7 +46,9 @@ const CaseFiles = () => { workingCase.id, ) - const isTrafficViolationCaseCheck = isTrafficViolationCase(workingCase) + const isTrafficViolationCaseCheck = + features.includes(Feature.MULTIPLE_INDICTMENT_SUBTYPES) || + isTrafficViolationCase(workingCase) const stepIsValid = (isTrafficViolationCaseCheck || diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx index 135bb7a57348..e30f6bd64f9a 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx @@ -380,7 +380,7 @@ const Indictment = () => { onChange={handleUpdateIndictmentCount} setWorkingCase={setWorkingCase} updateIndictmentCountState={updateIndictmentCountState} - > + /> diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.css.ts b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.css.ts new file mode 100644 index 000000000000..7671970527a8 --- /dev/null +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.css.ts @@ -0,0 +1,15 @@ +import { style } from '@vanilla-extract/css' + +import { theme } from '@island.is/island-ui/theme' + +export const indictmentSubtypesContainter = style({ + display: 'flex', + flexWrap: 'wrap', + gap: theme.spacing[1], + marginBottom: theme.spacing[2], +}) + +export const indictmentSubtypesItem = style({ + flex: `1 1 calc(50% - ${theme.spacing[1]}px)`, + whiteSpace: 'nowrap', +}) diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.strings.ts b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.strings.ts index dcea457a39c5..d6b7abe87aff 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.strings.ts +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.strings.ts @@ -141,4 +141,9 @@ export const indictmentCount = defineMessages({ 'Telst háttsemi þessi varða við {articles} umferðarlaga nr. 77/2019.', description: 'Notaður sem sjálfgefinn texti í heimfærslu svæði.', }, + selectIndictmentSubtype: { + id: 'judicial.system.core:indictments_indictment.indictment_count.select_indictment_subtype', + defaultMessage: 'Veldu sakarefni', + description: 'Notaður sem titill fyrir "Veldu sakarefni" svæði.', + }, }) diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.tsx index 995eb38f5d4c..d1c964386e88 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.tsx @@ -5,14 +5,20 @@ import { IntlShape, useIntl } from 'react-intl' import { Box, Button, + Checkbox, Icon, Input, Select, Tag, } from '@island.is/island-ui/core' -import { formatDate } from '@island.is/judicial-system/formatters' +import { + capitalize, + formatDate, + indictmentSubtypes, +} from '@island.is/judicial-system/formatters' import { CrimeScene, + IndictmentSubtype, offenseSubstances, Substance, SubstanceMap, @@ -20,6 +26,7 @@ import { import { BlueBox, IndictmentInfo, + SectionHeading, } from '@island.is/judicial-system-web/src/components' import { IndictmentCountOffense } from '@island.is/judicial-system-web/src/graphql/schema' import { @@ -39,6 +46,7 @@ import { Substances as SubstanceChoices } from './Substances/Substances' import { indictmentCount as strings } from './IndictmentCount.strings' import { indictmentCountEnum as enumStrings } from './IndictmentCountEnum.strings' import { indictmentCountSubstanceEnum as substanceStrings } from './IndictmentCountSubstanceEnum.strings' +import * as styles from './IndictmentCount.css' interface Props { indictmentCount: TIndictmentCount @@ -338,6 +346,10 @@ export const IndictmentCount: FC = ({ const [legalArgumentsErrorMessage, setLegalArgumentsErrorMessage] = useState('') + const subtypes = indictmentCount.policeCaseNumber + ? workingCase.indictmentSubtypes[indictmentCount.policeCaseNumber] + : [] + const offensesOptions = useMemo( () => Object.values(IndictmentCountOffense).map((offense) => ({ @@ -400,6 +412,21 @@ export const IndictmentCount: FC = ({ }) } + const handleSubtypeChange = ( + subtype: IndictmentSubtype, + checked: boolean, + ) => { + const currentSubtypes = new Set( + indictmentCount.indictmentCountSubtypes ?? [], + ) + + checked ? currentSubtypes.add(subtype) : currentSubtypes.delete(subtype) + + handleIndictmentCountChanges({ + indictmentCountSubtypes: Array.from(currentSubtypes), + }) + } + return ( {onDelete && ( @@ -449,6 +476,39 @@ export const IndictmentCount: FC = ({ crimeScenes={workingCase.crimeScenes} /> + {subtypes.length > 1 && ( + + + + {subtypes.map((subtype: IndictmentSubtype) => ( + + { + handleSubtypeChange(subtype, evt.target.checked) + }} + backgroundColor="white" + large + filled + /> + + ))} + + + )} { isCaseUpToDate, refreshCase, } = useContext(FormContext) + const { features } = useContext(FeatureContext) const { updateCase, transitionCase, setAndSendCaseToServer } = useCase() const { formatMessage } = useIntl() const { updateDefendant, updateDefendantState } = useDefendants() @@ -73,7 +76,9 @@ const Processing: FC = () => { deleteCivilClaimant, } = useCivilClaimants() const router = useRouter() - const isTrafficViolationCaseCheck = isTrafficViolationCase(workingCase) + const isTrafficViolationCaseCheck = + features.includes(Feature.MULTIPLE_INDICTMENT_SUBTYPES) || + isTrafficViolationCase(workingCase) const [civilClaimantNationalIdUpdate, setCivilClaimantNationalIdUpdate] = useState<{ nationalId: string | null; civilClaimantId: string }>() const [hasCivilClaimantChoice, setHasCivilClaimantChoice] = diff --git a/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx b/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx index d184a5703fb9..60458c5e195c 100644 --- a/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx +++ b/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx @@ -10,6 +10,7 @@ import { DEFENDER_ROUTE, } from '@island.is/judicial-system/consts' import { + Feature, isCompletedCase, isCourtOfAppealsUser, isDefenceUser, @@ -23,6 +24,7 @@ import { } from '@island.is/judicial-system/types' import { errors } from '@island.is/judicial-system-web/messages' import { + FeatureContext, FormContext, UserContext, } from '@island.is/judicial-system-web/src/components' @@ -40,6 +42,7 @@ const useCaseList = () => { >([null, false]) const { user, limitedAccess } = useContext(UserContext) const { getCase } = useContext(FormContext) + const { features } = useContext(FeatureContext) const { formatMessage } = useIntl() const { isTransitioningCase, isSendingNotification } = useCase() const router = useRouter() @@ -47,7 +50,9 @@ const useCaseList = () => { const openCase = useCallback( (caseToOpen: Case, openCaseInNewTab?: boolean) => { let routeTo = null - const isTrafficViolation = isTrafficViolationCase(caseToOpen) + const isTrafficViolation = + features.includes(Feature.MULTIPLE_INDICTMENT_SUBTYPES) || + isTrafficViolationCase(caseToOpen) if (isDefenceUser(user)) { if (isRequestCase(caseToOpen.type)) { diff --git a/apps/judicial-system/web/src/utils/hooks/useIndictmentCounts/updateIndictmentCount.graphql b/apps/judicial-system/web/src/utils/hooks/useIndictmentCounts/updateIndictmentCount.graphql index f87f2173bf6f..f8aca1a9268d 100644 --- a/apps/judicial-system/web/src/utils/hooks/useIndictmentCounts/updateIndictmentCount.graphql +++ b/apps/judicial-system/web/src/utils/hooks/useIndictmentCounts/updateIndictmentCount.graphql @@ -11,5 +11,6 @@ mutation UpdateIndictmentCount($input: UpdateIndictmentCountInput!) { lawsBroken incidentDescription legalArguments + indictmentCountSubtypes } } diff --git a/apps/judicial-system/web/src/utils/hooks/useSections/index.ts b/apps/judicial-system/web/src/utils/hooks/useSections/index.ts index 5608d706ab7e..c0bc8bfbb355 100644 --- a/apps/judicial-system/web/src/utils/hooks/useSections/index.ts +++ b/apps/judicial-system/web/src/utils/hooks/useSections/index.ts @@ -1,3 +1,4 @@ +import { useContext } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' @@ -7,6 +8,7 @@ import { getAppealResultTextByValue, } from '@island.is/judicial-system/formatters' import { + Feature, isCompletedCase, isCourtOfAppealsUser, isDefenceUser, @@ -18,6 +20,7 @@ import { isTrafficViolationCase, } from '@island.is/judicial-system/types' import { core, sections } from '@island.is/judicial-system-web/messages' +import { FeatureContext } from '@island.is/judicial-system-web/src/components' import { RouteSection } from '@island.is/judicial-system-web/src/components/PageLayout/PageLayout' import { formatCaseResult } from '@island.is/judicial-system-web/src/components/PageLayout/utils' import { @@ -60,6 +63,7 @@ const useSections = ( ) => { const { formatMessage } = useIntl() const router = useRouter() + const { features } = useContext(FeatureContext) const isActive = (pathname: string) => router.pathname.replace(/\/\[\w+\]/g, '') === pathname @@ -402,7 +406,9 @@ const useSections = ( state === CaseState.RECEIVED || state === CaseState.WAITING_FOR_CANCELLATION || router.pathname === `${constants.INDICTMENTS_ADD_FILES_ROUTE}/[id]` - const isTrafficViolation = isTrafficViolationCase(workingCase) + const isTrafficViolation = + features.includes(Feature.MULTIPLE_INDICTMENT_SUBTYPES) || + isTrafficViolationCase(workingCase) return { name: formatMessage(sections.indictmentCaseProsecutorSection.title), diff --git a/charts/judicial-system/values.prod.yaml b/charts/judicial-system/values.prod.yaml index dc74c1e64d61..ffcef752f5ec 100644 --- a/charts/judicial-system/values.prod.yaml +++ b/charts/judicial-system/values.prod.yaml @@ -30,7 +30,7 @@ judicial-system-api: BACKEND_URL: 'http://web-judicial-system-backend' CONTENTFUL_ENVIRONMENT: 'master' CONTENTFUL_HOST: 'cdn.contentful.com' - HIDDEN_FEATURES: '' + HIDDEN_FEATURES: 'MULTIPLE_INDICTMENT_SUBTYPES' IDENTITY_SERVER_ISSUER_URL: 'https://innskra.island.is' LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' diff --git a/charts/judicial-system/values.staging.yaml b/charts/judicial-system/values.staging.yaml index 3dacb3f6fb4a..30afa9a56e74 100644 --- a/charts/judicial-system/values.staging.yaml +++ b/charts/judicial-system/values.staging.yaml @@ -30,7 +30,7 @@ judicial-system-api: BACKEND_URL: 'http://web-judicial-system-backend' CONTENTFUL_ENVIRONMENT: 'test' CONTENTFUL_HOST: 'cdn.contentful.com' - HIDDEN_FEATURES: '' + HIDDEN_FEATURES: 'MULTIPLE_INDICTMENT_SUBTYPES' IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.staging01.devland.is' LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' diff --git a/charts/services/judicial-system-api/values.prod.yaml b/charts/services/judicial-system-api/values.prod.yaml index 2143dcd8565b..05f04f9d7278 100644 --- a/charts/services/judicial-system-api/values.prod.yaml +++ b/charts/services/judicial-system-api/values.prod.yaml @@ -30,7 +30,7 @@ env: BACKEND_URL: 'http://web-judicial-system-backend' CONTENTFUL_ENVIRONMENT: 'master' CONTENTFUL_HOST: 'cdn.contentful.com' - HIDDEN_FEATURES: '' + HIDDEN_FEATURES: 'MULTIPLE_INDICTMENT_SUBTYPES' IDENTITY_SERVER_ISSUER_URL: 'https://innskra.island.is' LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' diff --git a/charts/services/judicial-system-api/values.staging.yaml b/charts/services/judicial-system-api/values.staging.yaml index 3d6002b0a864..7533d12090a6 100644 --- a/charts/services/judicial-system-api/values.staging.yaml +++ b/charts/services/judicial-system-api/values.staging.yaml @@ -30,7 +30,7 @@ env: BACKEND_URL: 'http://web-judicial-system-backend' CONTENTFUL_ENVIRONMENT: 'test' CONTENTFUL_HOST: 'cdn.contentful.com' - HIDDEN_FEATURES: '' + HIDDEN_FEATURES: 'MULTIPLE_INDICTMENT_SUBTYPES' IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.staging01.devland.is' LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' diff --git a/libs/judicial-system/types/src/lib/feature.ts b/libs/judicial-system/types/src/lib/feature.ts index 742e56c2f145..dd8f5e437a8f 100644 --- a/libs/judicial-system/types/src/lib/feature.ts +++ b/libs/judicial-system/types/src/lib/feature.ts @@ -1,3 +1,4 @@ export enum Feature { NONE = 'NONE', // must be at least one + MULTIPLE_INDICTMENT_SUBTYPES = 'MULTIPLE_INDICTMENT_SUBTYPES', } From aa1e2542f44afd8ce63f1fa4ffedb21c231bd5ba Mon Sep 17 00:00:00 2001 From: unakb Date: Mon, 9 Dec 2024 19:04:51 +0000 Subject: [PATCH 40/57] feat(j-s): Only registered judge can complete indictment case with ruling (#17052) * feat(j-s): Only registered judge can complete indictment case with ruling * chore: nx format:write update dirty files * feat(j-s): Guards for complex transition rules * Update caseTransition.guard.ts * Update rolesRules.ts * Update transitionGuards.spec.ts --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/app/modules/case/case.controller.ts | 9 +- .../case/guards/caseTransition.guard.ts | 37 +++++++ .../case/guards/caseTransitionRules.ts | 41 ++++++++ .../src/app/modules/case/guards/rolesRules.ts | 6 +- .../guards/test/caseTransitionGuard.spec.ts | 98 +++++++++++++++++++ .../caseController/transitionGuards.spec.ts | 16 ++- .../Indictments/Summary/Summary.strings.ts | 6 ++ .../Court/Indictments/Summary/Summary.tsx | 15 +++ 8 files changed, 222 insertions(+), 6 deletions(-) create mode 100644 apps/judicial-system/backend/src/app/modules/case/guards/caseTransition.guard.ts create mode 100644 apps/judicial-system/backend/src/app/modules/case/guards/caseTransitionRules.ts create mode 100644 apps/judicial-system/backend/src/app/modules/case/guards/test/caseTransitionGuard.spec.ts diff --git a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts index 9728dc420508..3f11857ac9c5 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts @@ -70,6 +70,7 @@ import { CurrentCase } from './guards/case.decorator' import { CaseCompletedGuard } from './guards/caseCompleted.guard' import { CaseExistsGuard } from './guards/caseExists.guard' import { CaseReadGuard } from './guards/caseRead.guard' +import { CaseTransitionGuard } from './guards/caseTransition.guard' import { CaseTypeGuard } from './guards/caseType.guard' import { CaseWriteGuard } from './guards/caseWrite.guard' import { MergedCaseExistsGuard } from './guards/mergedCaseExists.guard' @@ -273,7 +274,13 @@ export class CaseController { return this.caseService.update(theCase, update, user) as Promise // Never returns undefined } - @UseGuards(JwtAuthGuard, CaseExistsGuard, RolesGuard, CaseWriteGuard) + @UseGuards( + JwtAuthGuard, + CaseExistsGuard, + RolesGuard, + CaseWriteGuard, + CaseTransitionGuard, + ) @RolesRules( prosecutorTransitionRule, prosecutorRepresentativeTransitionRule, diff --git a/apps/judicial-system/backend/src/app/modules/case/guards/caseTransition.guard.ts b/apps/judicial-system/backend/src/app/modules/case/guards/caseTransition.guard.ts new file mode 100644 index 000000000000..80898c0fc5f2 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/guards/caseTransition.guard.ts @@ -0,0 +1,37 @@ +import { + CanActivate, + ExecutionContext, + ForbiddenException, + Injectable, + InternalServerErrorException, +} from '@nestjs/common' + +import { User } from '@island.is/judicial-system/types' + +import { getTransitionRule } from './caseTransitionRules' + +@Injectable() +// Used for more complex cases than just whether a role can perform a +// transition overall, which is handled in the transition roles rules +export class CaseTransitionGuard implements CanActivate { + canActivate(context: ExecutionContext): boolean { + const request = context.switchToHttp().getRequest() + + const { transition } = request.body + const theCase = request.case + const user: User = request.user + + // This shouldn't happen + if (!theCase || !user) { + throw new InternalServerErrorException('Missing case or user') + } + + const transitionRule = getTransitionRule(transition) + + if (!transitionRule(theCase, user)) { + throw new ForbiddenException('Forbidden transition') + } + + return true + } +} diff --git a/apps/judicial-system/backend/src/app/modules/case/guards/caseTransitionRules.ts b/apps/judicial-system/backend/src/app/modules/case/guards/caseTransitionRules.ts new file mode 100644 index 000000000000..605e877e3791 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/guards/caseTransitionRules.ts @@ -0,0 +1,41 @@ +import { ForbiddenException } from '@nestjs/common' + +import { + CaseIndictmentRulingDecision, + CaseTransition, + CaseType, + User, +} from '@island.is/judicial-system/types' + +import { Case } from '..' + +type TransitionRule = (theCase: Case, user: User) => boolean + +const defaultTransitionRule: TransitionRule = () => true + +const completeTransitionRule: TransitionRule = (theCase, user) => { + if (theCase.type !== CaseType.INDICTMENT) { + throw new ForbiddenException( + `Forbidden transition for ${theCase.type} cases`, + ) + } + + if ( + theCase.indictmentRulingDecision === CaseIndictmentRulingDecision.RULING || + theCase.indictmentRulingDecision === CaseIndictmentRulingDecision.DISMISSAL + ) { + return user.id === theCase.judgeId + } + + return true +} + +const transitionRuleMap: Partial> = { + [CaseTransition.COMPLETE]: completeTransitionRule, +} + +export const getTransitionRule = ( + transition: CaseTransition, +): TransitionRule => { + return transitionRuleMap[transition] || defaultTransitionRule +} diff --git a/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts b/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts index 2b95c2f46a57..1e4b10ef019f 100644 --- a/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts +++ b/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts @@ -211,7 +211,7 @@ export const prosecutorTransitionRule: RolesRule = { const dto: TransitionCaseDto = request.body const theCase: Case = request.case - // Deny if something is missing - shuould never happen + // Deny if something is missing - should never happen if (!user || !dto || !theCase) { return false } @@ -258,7 +258,7 @@ export const defenderTransitionRule: RolesRule = { const dto: TransitionCaseDto = request.body const theCase: Case = request.case - // Deny if something is missing - shuould never happen + // Deny if something is missing - should never happen if (!dto || !theCase) { return false } @@ -393,7 +393,7 @@ export const districtCourtJudgeSignRulingRule: RolesRule = { const user: User = request.user const theCase: Case = request.case - // Deny if something is missing - shuould never happen + // Deny if something is missing - should never happen if (!user || !theCase) { return false } diff --git a/apps/judicial-system/backend/src/app/modules/case/guards/test/caseTransitionGuard.spec.ts b/apps/judicial-system/backend/src/app/modules/case/guards/test/caseTransitionGuard.spec.ts new file mode 100644 index 000000000000..6c88ea5e5053 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/guards/test/caseTransitionGuard.spec.ts @@ -0,0 +1,98 @@ +import { + ExecutionContext, + ForbiddenException, + InternalServerErrorException, +} from '@nestjs/common' + +import { + CaseIndictmentRulingDecision, + CaseTransition, + CaseType, +} from '@island.is/judicial-system/types' + +import { CaseTransitionGuard } from '../caseTransition.guard' + +describe('CaseTransitionGuard', () => { + let guard: CaseTransitionGuard + // eslint-disable-next-line @typescript-eslint/no-unused-vars + let mockRequest: jest.Mock + + beforeEach(() => { + guard = new CaseTransitionGuard() + mockRequest = jest.fn() + }) + + const mockExecutionContext = (requestMock: unknown): ExecutionContext => + ({ + switchToHttp: () => ({ + getRequest: () => requestMock, + }), + } as unknown as ExecutionContext) + + const createMockCase = ( + type: CaseType, + rulingDecision: unknown, + judgeId: string, + ) => ({ + type, + indictmentRulingDecision: rulingDecision, + judgeId, + }) + + it('should activate when the judge is the assigned judge', () => { + const mockCase = createMockCase( + CaseType.INDICTMENT, + CaseIndictmentRulingDecision.RULING, + 'judgeId', + ) + const context = mockExecutionContext({ + body: { transition: CaseTransition.COMPLETE }, + case: mockCase, + user: { id: 'judgeId' }, + }) + + const result = guard.canActivate(context) + + expect(result).toBe(true) + }) + + it('should not activate when the user is not the assigned judge', () => { + const mockCase = createMockCase( + CaseType.INDICTMENT, + CaseIndictmentRulingDecision.RULING, + 'judgeId', + ) + const context = mockExecutionContext({ + body: { transition: CaseTransition.COMPLETE }, + case: mockCase, + user: { id: 'differentJudgeId' }, + }) + + expect(() => guard.canActivate(context)).toThrow(ForbiddenException) + }) + + it('should activate using the default rule for transitions not in the rule map', () => { + const mockCase = createMockCase(CaseType.CUSTODY, null, 'someId') + const context = mockExecutionContext({ + body: { transition: CaseTransition.SUBMIT }, + case: mockCase, + user: { id: 'someId' }, + }) + + const result = guard.canActivate(context) + + expect(result).toBe(true) + }) + + it('should throw InternalServerErrorException when case or user is missing', () => { + const context = mockExecutionContext({ + body: { transition: CaseTransition.COMPLETE }, + case: null, + user: null, + }) + + expect(() => guard.canActivate(context)).toThrow( + InternalServerErrorException, + ) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/transitionGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/transitionGuards.spec.ts index 2e7775081e91..1e275969093b 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/transitionGuards.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/transitionGuards.spec.ts @@ -4,6 +4,7 @@ import { JwtAuthGuard, RolesGuard } from '@island.is/judicial-system/auth' import { CaseController } from '../../case.controller' import { CaseExistsGuard } from '../../guards/caseExists.guard' +import { CaseTransitionGuard } from '../../guards/caseTransition.guard' import { CaseWriteGuard } from '../../guards/caseWrite.guard' describe('CaseController - Transition guards', () => { @@ -17,8 +18,8 @@ describe('CaseController - Transition guards', () => { ) }) - it('should have four guards', () => { - expect(guards).toHaveLength(4) + it('should have five guards', () => { + expect(guards).toHaveLength(5) }) describe('JwtAuthGuard', () => { @@ -68,4 +69,15 @@ describe('CaseController - Transition guards', () => { expect(guard).toBeInstanceOf(CaseWriteGuard) }) }) + describe('CaseTransitionGuard', () => { + let guard: CanActivate + + beforeEach(() => { + guard = new guards[4]() + }) + + it('should have CaseTransitionGuard as guard 5', () => { + expect(guard).toBeInstanceOf(CaseTransitionGuard) + }) + }) }) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts index e5c50b59327c..c9c5bdf3cdc2 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts @@ -52,4 +52,10 @@ export const strings = defineMessages({ description: 'Notaður sem texti á aukahnapp í staðfestingarglugga um hvort eigi að ljúka máli.', }, + onlyAssignedJudgeCanComplete: { + id: 'judicial.system.core:indictments.summary.only_assigned_judge_can_complete', + defaultMessage: 'Einungis skráður dómari getur lokið málinu', + description: + 'Notaður sem texti í stað "áfram" takkans á samantektarskjá ákæru þegar "áfram" takkinn er falinn', + }, }) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx index 51d109336b5e..2f14057d02bf 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx @@ -19,6 +19,7 @@ import { PageTitle, RenderFiles, SectionHeading, + UserContext, } from '@island.is/judicial-system-web/src/components' import { Defendants, @@ -43,6 +44,7 @@ const Summary: FC = () => { useContext(FormContext) const { transitionCase, isTransitioningCase } = useCase() const [modalVisible, setModalVisible] = useState<'CONFIRM_INDICTMENT'>() + const { user } = useContext(UserContext) const { onOpen } = useFileList({ caseId: workingCase.id, @@ -90,6 +92,13 @@ const Summary: FC = () => { workingCase.indictmentRulingDecision, ) + const canUserCompleteCase = + (workingCase.indictmentRulingDecision !== + CaseIndictmentRulingDecision.RULING && + workingCase.indictmentRulingDecision !== + CaseIndictmentRulingDecision.DISMISSAL) || + workingCase.judge?.id === user?.id + return ( { nextButtonIcon="checkmark" nextButtonText={formatMessage(strings.nextButtonText)} onNextButtonClick={() => setModalVisible('CONFIRM_INDICTMENT')} + hideNextButton={!canUserCompleteCase} + infoBoxText={ + canUserCompleteCase + ? '' + : formatMessage(strings.onlyAssignedJudgeCanComplete) + } /> {modalVisible === 'CONFIRM_INDICTMENT' && ( From 2036feb9967ca1a4b2f4a23b36086a1fe5bca656 Mon Sep 17 00:00:00 2001 From: albinagu <47886428+albinagu@users.noreply.github.com> Date: Mon, 9 Dec 2024 19:33:35 +0000 Subject: [PATCH 41/57] fix(inheritance-report): deceased share texts (#17182) * fix(inheritance-report): deceased share texts * tweaks - current.enabled * bakka --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/fields/CalculateShare/index.tsx | 12 ++++++------ .../src/fields/ReportFieldsRepeater/index.tsx | 4 +++- .../src/forms/sections/deceased.ts | 2 +- .../templates/inheritance-report/src/lib/messages.ts | 7 ++++++- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/libs/application/templates/inheritance-report/src/fields/CalculateShare/index.tsx b/libs/application/templates/inheritance-report/src/fields/CalculateShare/index.tsx index 3a2f0e735eff..c5737912c549 100644 --- a/libs/application/templates/inheritance-report/src/fields/CalculateShare/index.tsx +++ b/libs/application/templates/inheritance-report/src/fields/CalculateShare/index.tsx @@ -166,9 +166,9 @@ export const CalculateShare: FC> = ({ } }) - const inventory: CalcShared = ( - [(answers.assets as unknown as EstateAssets)?.inventory] ?? [] - ).map((item) => { + const inventory: CalcShared = [ + (answers.assets as unknown as EstateAssets)?.inventory ?? [], + ].map((item) => { const value = valueToNumber(item.value) const deceasedShare = valueToNumber(item.deceasedShare) const { shareValue, deceasedShareValue } = getShareValue( @@ -185,9 +185,9 @@ export const CalculateShare: FC> = ({ } }) - const money: CalcShared = ( - [(answers.assets as unknown as EstateAssets)?.money] ?? [] - ).map((item) => { + const money: CalcShared = [ + (answers.assets as unknown as EstateAssets)?.money ?? [], + ].map((item) => { const value = valueToNumber(item.value) const deceasedShare = valueToNumber(item.deceasedShare) const { shareValue, deceasedShareValue } = getShareValue( diff --git a/libs/application/templates/inheritance-report/src/fields/ReportFieldsRepeater/index.tsx b/libs/application/templates/inheritance-report/src/fields/ReportFieldsRepeater/index.tsx index 7529b650832a..08731a1c43ec 100644 --- a/libs/application/templates/inheritance-report/src/fields/ReportFieldsRepeater/index.tsx +++ b/libs/application/templates/inheritance-report/src/fields/ReportFieldsRepeater/index.tsx @@ -85,7 +85,9 @@ export const ReportFieldsRepeater: FC< } const total = values.reduce((acc: number, current: any, index: number) => { - const sumField2 = valueToNumber(current[props?.sumField2], ',') + const sumField2 = current.enabled + ? valueToNumber(current[props?.sumField2], ',') + : 0 let currentValue = valueToNumber( current.enabled ? current[props?.sumField] : 0, ',', diff --git a/libs/application/templates/inheritance-report/src/forms/sections/deceased.ts b/libs/application/templates/inheritance-report/src/forms/sections/deceased.ts index 501dfc9f920c..8cac5ea189ea 100644 --- a/libs/application/templates/inheritance-report/src/forms/sections/deceased.ts +++ b/libs/application/templates/inheritance-report/src/forms/sections/deceased.ts @@ -118,7 +118,7 @@ export const deceased = buildSection({ { name: 'customShare.customSpouseSharePercentage', placeholder: '50%', - label: m.deceasedShare.defaultMessage, + label: m.deceasedSharePercentage.defaultMessage, }, ), buildDescriptionField({ diff --git a/libs/application/templates/inheritance-report/src/lib/messages.ts b/libs/application/templates/inheritance-report/src/lib/messages.ts index 16822da0fc70..84f67d71a06a 100644 --- a/libs/application/templates/inheritance-report/src/lib/messages.ts +++ b/libs/application/templates/inheritance-report/src/lib/messages.ts @@ -1256,9 +1256,14 @@ export const m = defineMessages({ defaultMessage: 'Séreign', description: '', }, + deceasedSharePercentage: { + id: 'ir.application:deceasedSharePercentage', + defaultMessage: 'Búshluti maka', + description: '', + }, deceasedShare: { id: 'ir.application:deceasedShare', - defaultMessage: 'Búshluti maka', + defaultMessage: 'Hlutfall séreignar', description: '', }, spousesShareDescription: { From 5f9b084a2c00062c0c8c0743c768314f68fc9bf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93lafur=20Bj=C3=B6rn=20Magn=C3=BAsson?= Date: Mon, 9 Dec 2024 20:03:39 +0000 Subject: [PATCH 42/57] fix(application-system-api-worker): Get template error catch (#17186) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../application-lifecycle.service.ts | 73 +++++++++++-------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/apps/application-system/api/src/app/modules/application/lifecycle/application-lifecycle.service.ts b/apps/application-system/api/src/app/modules/application/lifecycle/application-lifecycle.service.ts index 332c163b2555..ab2dbab36e7d 100644 --- a/apps/application-system/api/src/app/modules/application/lifecycle/application-lifecycle.service.ts +++ b/apps/application-system/api/src/app/modules/application/lifecycle/application-lifecycle.service.ts @@ -151,39 +151,52 @@ export class ApplicationLifeCycleService { private async preparePrunedNotification( application: PruningApplication, ): Promise { - const template = await getApplicationTemplateByTypeId(application.typeId) - const stateConfig = template.stateMachineConfig.states[application.state] - const lifeCycle = stateConfig.meta?.lifecycle - if (lifeCycle && lifeCycle.shouldBePruned && lifeCycle.pruneMessage) { - try { - const pruneMessage = - typeof lifeCycle.pruneMessage === 'function' - ? lifeCycle.pruneMessage(application as ApplicationWithAttachments) - : lifeCycle.pruneMessage - const notification = { - recipient: application.applicant, - templateId: pruneMessage.notificationTemplateId, - args: [ - { - key: 'externalBody', - value: pruneMessage.externalBody || '', - }, - { - key: 'internalBody', - value: pruneMessage.internalBody || '', - }, - ], - } - return notification - } catch (error) { - this.logger.error( - `Failed to prepare pruning notification for application ${application.id}`, - error, - ) + try { + const template = await getApplicationTemplateByTypeId(application.typeId) + if (!template) { return null } + const stateConfig = template.stateMachineConfig.states[application.state] + const lifeCycle = stateConfig.meta?.lifecycle + if (lifeCycle && lifeCycle.shouldBePruned && lifeCycle.pruneMessage) { + try { + const pruneMessage = + typeof lifeCycle.pruneMessage === 'function' + ? lifeCycle.pruneMessage( + application as ApplicationWithAttachments, + ) + : lifeCycle.pruneMessage + const notification = { + recipient: application.applicant, + templateId: pruneMessage.notificationTemplateId, + args: [ + { + key: 'externalBody', + value: pruneMessage.externalBody || '', + }, + { + key: 'internalBody', + value: pruneMessage.internalBody || '', + }, + ], + } + return notification + } catch (error) { + this.logger.error( + `Failed to prepare pruning notification for application ${application.id}`, + error, + ) + return null + } + } + return null + } catch (error) { + this.logger.error( + `Failed to get application template for application typeId ${application.typeId}`, + error, + ) + return null } - return null } private async sendPrunedNotification( From 02d4080788199f29e3eca8c25aa6caaeb21a7d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Mon, 9 Dec 2024 21:08:03 +0000 Subject: [PATCH 43/57] feat(j-s): Add modal when revoking send to prison admin (#17110) * Add modal when revoking send to prison admin * Update CF ids * Fix lint --- .../web/messages/Core/index.ts | 5 ++++ .../BlueBoxWithDate.strings.ts | 16 ++++++++++++ .../BlueBoxWithIcon/BlueBoxWithDate.tsx | 26 ++++++++++++++++--- .../SendToPrisonAdmin.strings.ts | 2 +- .../web/src/utils/hooks/useCaseList/index.tsx | 2 +- 5 files changed, 46 insertions(+), 5 deletions(-) diff --git a/apps/judicial-system/web/messages/Core/index.ts b/apps/judicial-system/web/messages/Core/index.ts index f783a0114759..efb61f12781d 100644 --- a/apps/judicial-system/web/messages/Core/index.ts +++ b/apps/judicial-system/web/messages/Core/index.ts @@ -209,6 +209,11 @@ export const core = defineMessages({ defaultMessage: 'Halda áfram', description: 'Notað fyrir "Halda áfram" takka í öllum flæðum.', }, + cancel: { + id: 'judicial.system.core:cancel', + defaultMessage: 'Hætta við', + description: 'Notað fyrir "Hætta við" takka í öllum flæðum.', + }, createCase: { id: 'judicial.system.core:create_case', defaultMessage: 'Stofna mál', diff --git a/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.strings.ts b/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.strings.ts index a3f57cbebd4c..282e98b4bb14 100644 --- a/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.strings.ts +++ b/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.strings.ts @@ -83,4 +83,20 @@ export const strings = defineMessages({ description: 'Notaður sem titill í svæði þar sem kærufrestur viðurlagaákvörðunar er tekinn fram', }, + revokeSendToPrisonAdminModalTitle: { + id: 'judicial.system.core:blue_box_with_date.revoke_send_to_prison_admin_modal_title', + defaultMessage: 'Afturkalla úr fullnustu', + description: 'Notaður sem titill í "Afturkalla úr fullnustu" modal glugga.', + }, + revokeSendToPrisonAdminModalText: { + id: 'judicial.system.core:blue_box_with_date.revoke_send_to_prison_admin_modal_text', + defaultMessage: + 'Mál {courtCaseNumber} verður afturkallað.\nÁkærði: {defendant}.', + description: 'Notaður sem texti í "Afturkalla úr fullnustu" modal glugga.', + }, + revoke: { + id: 'judicial.system.core:blue_box_with_date.revoke', + defaultMessage: 'Afturkalla', + description: 'Notaður sem texti fyrir aðgerðina að afturkalla mál', + }, }) diff --git a/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.tsx b/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.tsx index 323fe5cf4469..24e179704afe 100644 --- a/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.tsx +++ b/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.tsx @@ -15,7 +15,7 @@ import { import { PUBLIC_PROSECUTOR_STAFF_INDICTMENT_SEND_TO_PRISON_ADMIN_ROUTE } from '@island.is/judicial-system/consts' import { formatDate } from '@island.is/judicial-system/formatters' import { VERDICT_APPEAL_WINDOW_DAYS } from '@island.is/judicial-system/types' -import { errors } from '@island.is/judicial-system-web/messages' +import { core, errors } from '@island.is/judicial-system-web/messages' import { CaseIndictmentRulingDecision, @@ -26,6 +26,7 @@ import { formatDateForServer, useDefendants } from '../../utils/hooks' import DateTime from '../DateTime/DateTime' import { FormContext } from '../FormProvider/FormProvider' import { getAppealExpirationInfo } from '../InfoCard/DefendantInfo/DefendantInfo' +import Modal from '../Modal/Modal' import SectionHeading from '../SectionHeading/SectionHeading' import { strings } from './BlueBoxWithDate.strings' import * as styles from './BlueBoxWithIcon.css' @@ -35,6 +36,8 @@ interface Props { icon?: IconMapIcon } +type VisibleModal = 'REVOKE_SEND_TO_PRISON_ADMIN' + const BlueBoxWithDate: FC = (props) => { const { defendant, icon } = props const { formatMessage } = useIntl() @@ -47,7 +50,8 @@ const BlueBoxWithDate: FC = (props) => { }) const [triggerAnimation, setTriggerAnimation] = useState(false) const [triggerAnimation2, setTriggerAnimation2] = useState(false) - const { setAndSendDefendantToServer } = useDefendants() + const [modalVisible, setModalVisible] = useState() + const { setAndSendDefendantToServer, isUpdatingDefendant } = useDefendants() const { workingCase, setWorkingCase } = useContext(FormContext) const router = useRouter() @@ -111,6 +115,8 @@ const BlueBoxWithDate: FC = (props) => { }, setWorkingCase, ) + + setModalVisible(undefined) } const appealExpirationInfo = useMemo(() => { @@ -335,7 +341,7 @@ const BlueBoxWithDate: FC = (props) => { {defendant.isSentToPrisonAdmin ? ( setModalVisible('REVOKE_SEND_TO_PRISON_ADMIN')} size="small" colorScheme="destructive" > @@ -355,6 +361,20 @@ const BlueBoxWithDate: FC = (props) => { )} + {modalVisible === 'REVOKE_SEND_TO_PRISON_ADMIN' && ( + setModalVisible(undefined)} + /> + )} > ) } diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/SendToPrisonAdmin/SendToPrisonAdmin.strings.ts b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/SendToPrisonAdmin/SendToPrisonAdmin.strings.ts index 6a513f738833..44201a0341f3 100644 --- a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/SendToPrisonAdmin/SendToPrisonAdmin.strings.ts +++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/SendToPrisonAdmin/SendToPrisonAdmin.strings.ts @@ -29,7 +29,7 @@ export const strings = defineMessages({ 'Notaður sem titill á tilkynningarglugga um að senda til fullnustu.', }, modalText: { - id: 'judicial.system.core:send_to_prison_admin.modal_text', + id: 'judicial.system.core:send_to_prison_admin.modal_text_v1', defaultMessage: 'Mál {courtCaseNumber} verður sent til Fangelsismálastofnunar til fullnustu.\nÁkærði: {defendant}.', description: diff --git a/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx b/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx index 60458c5e195c..6629594f8d5f 100644 --- a/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx +++ b/apps/judicial-system/web/src/utils/hooks/useCaseList/index.tsx @@ -142,7 +142,7 @@ const useCaseList = () => { router.push(`${routeTo}/${caseToOpen.id}`) } }, - [router, user], + [router, user, features], ) const handleOpenCase = useCallback( From 93800d2e08a31f2bb3b7fbe949d1901b54e95448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Mon, 9 Dec 2024 21:15:43 +0000 Subject: [PATCH 44/57] chore(j-s): Switch indictment confirmation request (#17153) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../Prosecutor/Indictments/Overview/Overview.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx index 2640b512c74d..a7bbf3b8cfa5 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx @@ -280,20 +280,20 @@ const Overview: FC = () => { setIndictmentConfirmationDecision('confirm')} + label={formatMessage(strings.denyIndictment)} + checked={indictmentConfirmationDecision === 'deny'} + onChange={() => setIndictmentConfirmationDecision('deny')} /> setIndictmentConfirmationDecision('deny')} + label={formatMessage(strings.confirmIndictment)} + checked={indictmentConfirmationDecision === 'confirm'} + onChange={() => setIndictmentConfirmationDecision('confirm')} /> From a043d4cd882da3736e610157bcea72b2380fce0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B3nas=20G=2E=20Sigur=C3=B0sson?= Date: Mon, 9 Dec 2024 21:32:16 +0000 Subject: [PATCH 45/57] chore(inao-cem): Refactor out custom components (#17161) * chore: start of custom refactor * chore: remove custom components * chore: refactor out custom components * chore: remove console.log * fix: better id names for descriptions * chore: remove dev condition * chore: added text to readme * fix: typo * fix: wrong id on description * chore: use getValueVia string in the overview component * fix: message key * chore: use foreash instead of map * chore: rename and refactor misleading function name * chore: safer handling of conversion to number * fix: typo * chore: make helper function logic better --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../financial-statement-cemetery/README.md | 4 +- .../src/dataProviders/index.ts | 2 +- .../src/fields/CemeteryEquities/index.tsx | 326 ------------------ .../src/fields/CemeteryIncomeLimit/index.tsx | 4 +- .../CemeteryOperation/CemeteryExpenses.tsx | 168 --------- .../CemeteryOperation/CemeteryIncome.tsx | 147 -------- .../src/fields/CemeteryOperation/index.tsx | 95 ----- .../src/fields/CemeteryOverview/BottomBar.tsx | 37 -- .../src/fields/CemeteryOverview/index.tsx | 286 +++++++-------- .../src/fields/KeyNumbers/index.tsx | 34 -- .../src/fields/KeyNumbersCapital/index.tsx | 94 ----- .../src/fields/OperatingYear/index.tsx | 84 ----- .../src/fields/OperatingYear/styles.css.ts | 5 - .../src/fields/PowerOfAttorney/index.tsx | 2 - .../src/fields/Success/index.tsx | 73 ---- .../src/fields/index.ts | 6 +- .../capitalNumberSubSection.ts | 29 +- .../equityAndLiabilitySubSection.ts | 147 +++++++- .../opperatingCostSubSection.ts | 135 +++++++- .../clientInfoSection/index.ts | 24 +- .../conclusionSection/conclusionSection.ts | 19 + .../src/forms/applicationForm/index.ts | 4 +- .../applicationForm/overviewSection/index.ts | 48 ++- .../overviewSection/oveviewMultiField.ts | 16 - .../src/forms/done/conclusionSection.ts | 24 -- .../src/forms/done/index.ts | 4 +- .../src/forms/notAllowed/index.ts | 22 +- .../src/forms/notAllowed/notAllowedSection.ts | 17 - .../src/forms/prerequisites/index.ts | 62 +++- .../prerequisites/prerequsitesSection.ts | 56 --- .../src/lib/dataSchema.ts | 243 +++++++------ .../lib/financialStatementCemeteryTemplate.ts | 4 +- .../src/lib/messages.ts | 8 +- .../src/types/types.ts | 10 + .../src/utils/constants.ts | 10 +- .../src/utils/helpers.ts | 299 ++++++++++++++-- .../tsconfig.json | 9 +- .../tsconfig.lib.json | 14 +- .../src/dataProviders/index.ts | 2 +- .../prerequisites/prerequisitesSection.ts | 4 +- ...financial-statement-individual-election.ts | 4 +- .../README.md | 4 +- .../src/dataProviders/index.ts | 2 +- .../forms/prerequsites/prerequsitesSection.ts | 4 +- ...inancialStatementPoliticalPartyTemplate.ts | 6 +- 45 files changed, 1052 insertions(+), 1545 deletions(-) delete mode 100644 libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryEquities/index.tsx delete mode 100644 libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/CemeteryExpenses.tsx delete mode 100644 libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/CemeteryIncome.tsx delete mode 100644 libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/index.tsx delete mode 100644 libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOverview/BottomBar.tsx delete mode 100644 libs/application/templates/inao/financial-statement-cemetery/src/fields/KeyNumbers/index.tsx delete mode 100644 libs/application/templates/inao/financial-statement-cemetery/src/fields/KeyNumbersCapital/index.tsx delete mode 100644 libs/application/templates/inao/financial-statement-cemetery/src/fields/OperatingYear/index.tsx delete mode 100644 libs/application/templates/inao/financial-statement-cemetery/src/fields/OperatingYear/styles.css.ts delete mode 100644 libs/application/templates/inao/financial-statement-cemetery/src/fields/Success/index.tsx create mode 100644 libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/conclusionSection/conclusionSection.ts delete mode 100644 libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/overviewSection/oveviewMultiField.ts delete mode 100644 libs/application/templates/inao/financial-statement-cemetery/src/forms/done/conclusionSection.ts delete mode 100644 libs/application/templates/inao/financial-statement-cemetery/src/forms/notAllowed/notAllowedSection.ts delete mode 100644 libs/application/templates/inao/financial-statement-cemetery/src/forms/prerequisites/prerequsitesSection.ts diff --git a/libs/application/templates/inao/financial-statement-cemetery/README.md b/libs/application/templates/inao/financial-statement-cemetery/README.md index 759b46c0bb58..d8c8a06c518e 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/README.md +++ b/libs/application/templates/inao/financial-statement-cemetery/README.md @@ -1,6 +1,8 @@ # financial-statement-cemetery -This application turns in financial statements for cemeteries to the The Icelandic National Audit Office (Ríkisendurskoðun, inao for short). +This application turns in financial statements for cemeteries to the The Icelandic National Audit Office (Ríkisendurskoðun, INAO for short). Cemeteries are required to turn in their financial statements before the 1. of June each year. + +To test this application in the dev environment, you can log in as Gervimaður Færeyjar (010-2399) and swap to the procure of Fjárfestingafélagið Blámi. Blámi is registered as a cemetery with the INAO dev service and should be able to enter and go through the application. ## Running unit tests diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/dataProviders/index.ts b/libs/application/templates/inao/financial-statement-cemetery/src/dataProviders/index.ts index 91d817999f92..dd52c79ad7f2 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/dataProviders/index.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/dataProviders/index.ts @@ -3,7 +3,7 @@ import { UserProfileApi } from '@island.is/application/types' export { NationalRegistryUserApi, - IdentityApi as IndentityApiProvider, + IdentityApi as IdentityApiProvider, } from '@island.is/application/types' export const CurrentUserTypeProvider = defineTemplateApi({ diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryEquities/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryEquities/index.tsx deleted file mode 100644 index e16606e47af3..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryEquities/index.tsx +++ /dev/null @@ -1,326 +0,0 @@ -import { useState, useEffect } from 'react' -import { - AlertBanner, - Box, - GridColumn, - GridContainer, - GridRow, - Text, -} from '@island.is/island-ui/core' -import debounce from 'lodash/debounce' -import { useFormContext } from 'react-hook-form' -import { useLocale } from '@island.is/localization' -import { FieldBaseProps } from '@island.is/application/types' -import { InputController } from '@island.is/shared/form-fields' -import { m } from '../../lib/messages' -import { Total } from '../KeyNumbers' -import { getErrorViaPath, getValueViaPath } from '@island.is/application/core' -import { - CAPITALNUMBERS, - CEMETERYEQUITIESANDLIABILITIESIDS, - INPUTCHANGEINTERVAL, - OPERATINGCOST, - VALIDATOR, -} from '../../utils/constants' -import { useTotals } from '../../hooks/useTotals' -import { getTotal } from '../../utils/helpers' - -export const CemeteryEquities = ({ - application, - setBeforeSubmitCallback, -}: FieldBaseProps) => { - const answers = application.answers - const { formatMessage } = useLocale() - const { - clearErrors, - formState: { errors }, - setValue, - getValues, - setError, - } = useFormContext() - - const operatingCostTotal = Number( - getValueViaPath(answers, OPERATINGCOST.total), - ) - - const capitalTotal = Number(getValueViaPath(answers, CAPITALNUMBERS.total)) - - useEffect(() => { - const total = operatingCostTotal + capitalTotal - setValue(CEMETERYEQUITIESANDLIABILITIESIDS.operationResult, total) - setTotalOperatingCost(total) - }, [operatingCostTotal, capitalTotal, setValue]) - - const [totalOperatingCost, setTotalOperatingCost] = useState(0) - const [equityTotal, setEquityTotal] = useState(0) - const [equityAndDebts, setEquityAndDebts] = useState(0) - - const [getTotalAssets, totalAssets] = useTotals( - CEMETERYEQUITIESANDLIABILITIESIDS.assetPrefix, - ) - const [getTotalLiabilities, totalLiabilities] = useTotals( - CEMETERYEQUITIESANDLIABILITIESIDS.liabilityPrefix, - ) - const [getTotalEquity, totalEquity] = useTotals( - CEMETERYEQUITIESANDLIABILITIESIDS.equityPrefix, - ) - - useEffect(() => { - setEquityTotal(totalEquity) - }, [totalEquity, totalOperatingCost]) - - useEffect(() => { - const total = totalEquity + totalLiabilities - setEquityAndDebts(total) - }, [totalEquity, totalLiabilities]) - - useEffect(() => { - clearErrors(VALIDATOR) - }, [totalEquity, totalLiabilities, totalAssets, clearErrors]) - - // we need to validate some info before allowing submission of the current screen data - // since we're comparing values from different objects, doing it via zod is not an option - setBeforeSubmitCallback && - setBeforeSubmitCallback(async () => { - const values = getValues() - const assets = getTotal(values, 'cemetryAsset') - const liabilties = getTotal(values, 'cemetryLiability') - const equities = getTotal(values, 'cemetryEquity') - - // assets should equal liabilties + equities - const isValid = liabilties + equities === assets - if (!isValid) { - setError(VALIDATOR, { - type: 'custom', - message: formatMessage(m.equityDebtsAssetsValidatorError), - }) - return [false, formatMessage(m.equityDebtsAssetsValidatorError)] - } - return [true, null] - }) - - return ( - - - - - {formatMessage(m.properties)} - - - { - getTotalAssets() - clearErrors(CEMETERYEQUITIESANDLIABILITIESIDS.fixedAssetsTotal) - }, INPUTCHANGEINTERVAL)} - label={formatMessage(m.fixedAssetsTotal)} - backgroundColor="blue" - rightAlign - currency - /> - - - { - getTotalAssets() - clearErrors(CEMETERYEQUITIESANDLIABILITIESIDS.currentAssets) - }, INPUTCHANGEINTERVAL)} - label={formatMessage(m.currentAssets)} - error={ - errors && - getErrorViaPath( - errors, - CEMETERYEQUITIESANDLIABILITIESIDS.currentAssets, - ) - } - backgroundColor="blue" - rightAlign - currency - /> - - - - - - {formatMessage(m.debtsAndEquity)} - - - { - getTotalLiabilities() - clearErrors(CEMETERYEQUITIESANDLIABILITIESIDS.longTerm) - }, INPUTCHANGEINTERVAL)} - error={ - errors && - getErrorViaPath( - errors, - CEMETERYEQUITIESANDLIABILITIESIDS.longTerm, - ) - } - label={formatMessage(m.longTerm)} - backgroundColor="blue" - rightAlign - currency - /> - - - { - getTotalLiabilities() - clearErrors(CEMETERYEQUITIESANDLIABILITIESIDS.shortTerm) - }, INPUTCHANGEINTERVAL)} - error={ - errors && - getErrorViaPath( - errors, - CEMETERYEQUITIESANDLIABILITIESIDS.shortTerm, - ) - } - label={formatMessage(m.shortTerm)} - backgroundColor="blue" - rightAlign - currency - /> - - - - { - getTotalEquity() - clearErrors( - CEMETERYEQUITIESANDLIABILITIESIDS.equityAtTheBeginningOfTheYear, - ) - }, INPUTCHANGEINTERVAL)} - error={ - errors && - getErrorViaPath( - errors, - CEMETERYEQUITIESANDLIABILITIESIDS.equityAtTheBeginningOfTheYear, - ) - } - label={formatMessage(m.equityAtTheBeginningOfTheYear)} - backgroundColor="blue" - rightAlign - currency - /> - - - { - getTotalEquity() - clearErrors( - CEMETERYEQUITIESANDLIABILITIESIDS.revaluationDueToPriceChanges, - ) - }, INPUTCHANGEINTERVAL)} - error={ - errors && - getErrorViaPath( - errors, - CEMETERYEQUITIESANDLIABILITIESIDS.revaluationDueToPriceChanges, - ) - } - label={formatMessage(m.revaluationDueToPriceChanges)} - backgroundColor="blue" - rightAlign - currency - /> - - - { - getTotalEquity() - clearErrors(CEMETERYEQUITIESANDLIABILITIESIDS.reevaluateOther) - }, INPUTCHANGEINTERVAL)} - error={ - errors && - getErrorViaPath( - errors, - CEMETERYEQUITIESANDLIABILITIESIDS.reevaluateOther, - ) - } - label={formatMessage(m.reevaluateOther)} - backgroundColor="blue" - rightAlign - currency - /> - - - - - - - - - - - {errors && errors.validator ? ( - - - - ) : null} - - ) -} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryIncomeLimit/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryIncomeLimit/index.tsx index a76d1ed2d9e0..3e78d43e4af1 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryIncomeLimit/index.tsx +++ b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryIncomeLimit/index.tsx @@ -20,8 +20,8 @@ export const CemeteryIncomeLimit = () => { }) useEffect(() => { - const limit = - data?.financialStatementCemeteryClientFinancialLimit?.toString() + const limit = data?.financialStatementsInaoClientFinancialLimit?.toString() + if (limit) { setValue(CEMETERYOPERATIONIDS.incomeLimit, limit) } diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/CemeteryExpenses.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/CemeteryExpenses.tsx deleted file mode 100644 index ef7aa2b9a900..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/CemeteryExpenses.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { useEffect } from 'react' -import { useLocale } from '@island.is/localization' -import { Box } from '@island.is/island-ui/core' -import { InputController } from '@island.is/shared/form-fields' -import { getErrorViaPath, getValueViaPath } from '@island.is/application/core' -import { RecordObject } from '@island.is/application/types' -import debounce from 'lodash/debounce' -import { useFormContext } from 'react-hook-form' -import { m } from '../../lib/messages' - -import { FinancialStatementsInaoTaxInfo } from '@island.is/api/schema' -import { - CEMETERYOPERATIONIDS, - INPUTCHANGEINTERVAL, -} from '../../utils/constants' -type Props = { - data?: { - financialStatementsInaoTaxInfo: FinancialStatementsInaoTaxInfo[] - } | null - loading: boolean - getSum: () => void - errors: RecordObject | undefined -} - -export const CemeteryExpenses = ({ data, loading, errors, getSum }: Props) => { - const { formatMessage } = useLocale() - const { clearErrors, setValue, getValues } = useFormContext() - const values = getValues() - - const donationsToCemeteryFund = getValueViaPath( - values, - CEMETERYOPERATIONIDS.donationsToCemeteryFund, - ) - - useEffect(() => { - if (data?.financialStatementsInaoTaxInfo) { - if (!donationsToCemeteryFund) { - setValue( - CEMETERYOPERATIONIDS.donationsToCemeteryFund, - data.financialStatementsInaoTaxInfo - ?.find((x) => x.key === 334) - ?.value?.toString() ?? '', - ) - } - } - getSum() - }, [data, getSum, setValue]) - - const onInputChange = debounce((fieldId: string) => { - getSum() - clearErrors(fieldId) - }, INPUTCHANGEINTERVAL) - - return ( - <> - - onInputChange(CEMETERYOPERATIONIDS.payroll)} - error={ - errors && getErrorViaPath(errors, CEMETERYOPERATIONIDS.payroll) - } - backgroundColor="blue" - rightAlign - currency - /> - - - onInputChange(CEMETERYOPERATIONIDS.funeralCost)} - error={ - errors && getErrorViaPath(errors, CEMETERYOPERATIONIDS.funeralCost) - } - backgroundColor="blue" - rightAlign - currency - /> - - - onInputChange(CEMETERYOPERATIONIDS.chapelExpense)} - error={ - errors && - getErrorViaPath(errors, CEMETERYOPERATIONIDS.chapelExpense) - } - backgroundColor="blue" - rightAlign - currency - /> - - - - onInputChange(CEMETERYOPERATIONIDS.donationsToCemeteryFund) - } - error={ - errors && - getErrorViaPath( - errors, - CEMETERYOPERATIONIDS.donationsToCemeteryFund, - ) - } - backgroundColor="blue" - rightAlign - currency - /> - - - onInputChange(CEMETERYOPERATIONIDS.donationsToOther)} - error={ - errors && - getErrorViaPath(errors, CEMETERYOPERATIONIDS.donationsToOther) - } - backgroundColor="blue" - rightAlign - currency - /> - - - - onInputChange(CEMETERYOPERATIONIDS.otherOperationCost) - } - error={ - errors && - getErrorViaPath(errors, CEMETERYOPERATIONIDS.otherOperationCost) - } - backgroundColor="blue" - rightAlign - currency - /> - - - onInputChange(CEMETERYOPERATIONIDS.depreciation)} - error={ - errors && getErrorViaPath(errors, CEMETERYOPERATIONIDS.depreciation) - } - backgroundColor="blue" - rightAlign - currency - /> - - > - ) -} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/CemeteryIncome.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/CemeteryIncome.tsx deleted file mode 100644 index 46179f7371da..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/CemeteryIncome.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { useEffect } from 'react' -import { Box } from '@island.is/island-ui/core' -import { RecordObject } from '@island.is/application/types' -import debounce from 'lodash/debounce' -import { InputController } from '@island.is/shared/form-fields' -import { useLocale } from '@island.is/localization' -import { useFormContext } from 'react-hook-form' -import { getErrorViaPath, getValueViaPath } from '@island.is/application/core' -import { m } from '../../lib/messages' -import { FinancialStatementsInaoTaxInfo } from '@island.is/api/schema' -import { - CEMETERYOPERATIONIDS, - INPUTCHANGEINTERVAL, - TaxInfoTypes, -} from '../../utils/constants' - -type Props = { - data?: { - financialStatementsInaoTaxInfo: FinancialStatementsInaoTaxInfo[] - } | null - loading: boolean - getSum: () => void - errors: RecordObject | undefined -} - -export const CemetryIncome = ({ data, loading, errors, getSum }: Props) => { - const { formatMessage } = useLocale() - const { clearErrors, getValues, setValue } = useFormContext() - - useEffect(() => { - const values = getValues() - - const careIncome = getValueViaPath(values, CEMETERYOPERATIONIDS.careIncome) - const burialRevenue = getValueViaPath( - values, - CEMETERYOPERATIONIDS.burialRevenue, - ) - const grantFromTheCemeteryFund = getValueViaPath( - values, - CEMETERYOPERATIONIDS.grantFromTheCemeteryFund, - ) - - if (data?.financialStatementsInaoTaxInfo) { - if (!careIncome) { - setValue( - CEMETERYOPERATIONIDS.careIncome, - data.financialStatementsInaoTaxInfo - ?.find((x) => x.key === TaxInfoTypes.CARE_INCOME) - ?.value?.toString() ?? '', - ) - } - if (!burialRevenue) { - setValue( - CEMETERYOPERATIONIDS.burialRevenue, - data.financialStatementsInaoTaxInfo - ?.find((x) => x.key === TaxInfoTypes.BURIAL_REVENUE) - ?.value?.toString() ?? '', - ) - } - if (!grantFromTheCemeteryFund) { - setValue( - CEMETERYOPERATIONIDS.grantFromTheCemeteryFund, - data.financialStatementsInaoTaxInfo - ?.find((x) => x.key === TaxInfoTypes.GRANT_FROM_THE_CEMETERY_FUND) - ?.value?.toString() ?? '', - ) - } - getSum() - } - }, [data, getSum, setValue]) - - const onInputChange = debounce((fieldId: string) => { - getSum() - clearErrors(fieldId) - }, INPUTCHANGEINTERVAL) - - return ( - <> - - onInputChange(CEMETERYOPERATIONIDS.careIncome)} - backgroundColor="blue" - currency - rightAlign - error={ - errors && getErrorViaPath(errors, CEMETERYOPERATIONIDS.careIncome) - } - /> - - - onInputChange(CEMETERYOPERATIONIDS.burialRevenue)} - backgroundColor="blue" - currency - rightAlign - error={ - errors && - getErrorViaPath(errors, CEMETERYOPERATIONIDS.burialRevenue) - } - /> - - - - onInputChange(CEMETERYOPERATIONIDS.grantFromTheCemeteryFund) - } - backgroundColor="blue" - currency - rightAlign - error={ - errors && - getErrorViaPath( - errors, - CEMETERYOPERATIONIDS.grantFromTheCemeteryFund, - ) - } - /> - - - onInputChange(CEMETERYOPERATIONIDS.otherIncome)} - backgroundColor="blue" - currency - rightAlign - error={ - errors && getErrorViaPath(errors, CEMETERYOPERATIONIDS.otherIncome) - } - /> - - > - ) -} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/index.tsx deleted file mode 100644 index 424169cccdfa..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOperation/index.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { useFormContext } from 'react-hook-form' -import { - GridColumn, - GridContainer, - GridRow, - Text, -} from '@island.is/island-ui/core' -import { Application } from '@island.is/application/types' -import { useLocale } from '@island.is/localization' -import { m } from '../../lib/messages' -import { Total } from '../KeyNumbers' -import { CemetryIncome } from './CemeteryIncome' -import { CemeteryExpenses } from './CemeteryExpenses' -import { CemeteryIncomeLimit } from '../CemeteryIncomeLimit/index' -import { useQuery } from '@apollo/client' -import { getValueViaPath } from '@island.is/application/core' -import { taxInfoQuery } from '../../graphql' -import { CEMETERYOPERATIONIDS, OPERATINGCOST } from '../../utils/constants' -import { useTotals } from '../../hooks/useTotals' - -export const CemeteryOperation = ({ - application, -}: { - application: Application -}) => { - const { answers } = application - - const operatingYear = - getValueViaPath(answers, 'conditionalAbout.operatingYear') ?? '' - - const { data, loading } = useQuery(taxInfoQuery, { - variables: { year: operatingYear }, - }) - - const { - formState: { errors }, - } = useFormContext() - const { formatMessage } = useLocale() - const [getTotalIncome, totalIncome] = useTotals( - CEMETERYOPERATIONIDS.prefixIncome, - ) - const [getTotalExpense, totalExpense] = useTotals( - CEMETERYOPERATIONIDS.prefixExpense, - ) - - return ( - - - - - - {formatMessage(m.income)} - - - - - - - {formatMessage(m.expenses)} - - - - - - - - - - - - ) -} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOverview/BottomBar.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOverview/BottomBar.tsx deleted file mode 100644 index a014701fc18c..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOverview/BottomBar.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { Box, Button, Divider } from '@island.is/island-ui/core' -import { useLocale } from '@island.is/localization' -import { m } from '../../lib/messages' - -type Props = { - onBackButtonClick: () => void - onSendButtonClick: () => void - loading?: boolean - sendText?: string -} - -const BottomBar = ({ - onBackButtonClick, - onSendButtonClick, - loading = false, - sendText, -}: Props) => { - const { formatMessage } = useLocale() - - return ( - <> - - - - - - {formatMessage(m.goBack)} - - - {sendText ? sendText : formatMessage(m.send)} - - - > - ) -} - -export default BottomBar diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOverview/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOverview/index.tsx index 57040fecba5d..0f8c89f2c9f0 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOverview/index.tsx +++ b/libs/application/templates/inao/financial-statement-cemetery/src/fields/CemeteryOverview/index.tsx @@ -1,86 +1,134 @@ -import { useState } from 'react' -import { DefaultEvents, FieldBaseProps } from '@island.is/application/types' -import { getErrorViaPath, getValueViaPath } from '@island.is/application/core' - +import { Fragment } from 'react' +import { FieldBaseProps } from '@island.is/application/types' +import { getValueViaPath } from '@island.is/application/core' import { AlertBanner, Box, - Checkbox, Divider, GridColumn, GridRow, - InputError, Text, } from '@island.is/island-ui/core' -import { Controller, useFormContext } from 'react-hook-form' import { useLocale } from '@island.is/localization' import { format as formatNationalId } from 'kennitala' -import { useSubmitApplication } from '../../hooks/useSubmitApplication' import { m } from '../../lib/messages' import { FinancialStatementCemetery } from '../../lib/dataSchema' -import { currencyStringToNumber, formatCurrency } from '../../utils/helpers' +import { formatCurrency } from '../../utils/helpers' import { AboutOverview } from './AboutOverview' import { ValueLine } from './ValueLine' import { CapitalNumberOverview } from './CapitalNumbersOverview' import { BOARDMEMEBER } from '../../utils/constants' import { FileValueLine } from './FileValueLine' -import BottomBar from './BottomBar' import { columnStyle, sectionColumn, starterColumnStyle, } from './overviewStyles.css' -export const CemeteryOverview = ({ - application, - goToScreen, - refetch, -}: FieldBaseProps) => { - const { - formState: { errors }, - setError, - setValue, - } = useFormContext() +export const CemeteryOverview = ({ application }: FieldBaseProps) => { const { formatMessage } = useLocale() - const [approveOverview, setApproveOverview] = useState(false) - - const [submitApplication, { error: submitError, loading }] = - useSubmitApplication({ - application, - refetch, - event: DefaultEvents.SUBMIT, - }) const answers = application.answers as FinancialStatementCemetery - const fileName = answers.attachments?.file?.[0]?.name - const careTakerLimit = answers.cemeteryOperation?.incomeLimit ?? '0' - const cemeteryIncome = currencyStringToNumber(answers.cemeteryIncome?.total) - const fixedAssetsTotal = answers.cemeteryAsset?.fixedAssetsTotal - const longTermDebt = answers.cemeteryLiability?.longTerm - const email = getValueViaPath(answers, 'about.email') + const file = getValueViaPath>(answers, 'attachments.file') + const fileName = file?.[0]?.name + const incomeLimit = + getValueViaPath(answers, 'cemeteryOperations.incomeLimit') ?? '0' + const email = getValueViaPath(answers, 'about.email') const cemeteryCaretakers = answers.cemeteryCaretaker - const onBackButtonClick = () => { - if ( - Number(cemeteryIncome) < Number(careTakerLimit) && - fixedAssetsTotal === '0' && - longTermDebt === '0' - ) { - goToScreen && goToScreen('caretakers') - } else { - goToScreen && goToScreen('attachments.file') - } - } + const careIncome = getValueViaPath( + answers, + 'cemeteryIncome.careIncome', + ) + const burialRevenue = getValueViaPath( + answers, + 'cemeteryIncome.burialRevenue', + ) + const grantFromTheCemeteryFund = getValueViaPath( + answers, + 'cemeteryIncome.grantFromTheCemeteryFund', + ) + const otherIncome = getValueViaPath( + answers, + 'cemeteryIncome.otherIncome', + ) + const totalIncome = getValueViaPath(answers, 'cemeteryIncome.total') + + const payroll = getValueViaPath(answers, 'cemeteryExpense.payroll') + const funeralCost = getValueViaPath( + answers, + 'cemeteryExpense.funeralCost', + ) + const chapelExpense = getValueViaPath( + answers, + 'cemeteryExpense.chapelExpense', + ) + const donationsToCemeteryFund = getValueViaPath( + answers, + 'cemeteryExpense.cemeteryFundExpense', + ) + const donationsToOther = getValueViaPath( + answers, + 'cemeteryExpense.donationsToOther', + ) + const otherOperationCost = getValueViaPath( + answers, + 'cemeteryExpense.otherOperationCost', + ) + const depreciation = getValueViaPath( + answers, + 'cemeteryExpense.depreciation', + ) + const totalExpenses = getValueViaPath( + answers, + 'cemeteryExpense.total', + ) - const onSendButtonClick = () => { - if (approveOverview) { - submitApplication() - } else { - setError('applicationApprove', { - type: 'error', - }) - } - } + const fixedAssetsTotal = getValueViaPath( + answers, + 'cemeteryAsset.fixedAssetsTotal', + ) + const currentAssets = getValueViaPath( + answers, + 'cemeteryAsset.currentAssets', + ) + const totalAssets = getValueViaPath(answers, 'assetsTotal') + + const longTerm = getValueViaPath( + answers, + 'cemeteryLiability.longTerm', + ) + const shortTerm = getValueViaPath( + answers, + 'cemeteryLiability.shortTerm', + ) + const totalLiabilities = getValueViaPath( + answers, + 'equityAndLiabilitiesTotals.liabilitiesTotal', + ) + + const equityAtTheBeginningOfTheYear = getValueViaPath( + answers, + 'cemeteryEquity.equityAtTheBeginningOfTheYear', + ) + const revaluationDueToPriceChanges = getValueViaPath( + answers, + 'cemeteryEquity.revaluationDueToPriceChanges', + ) + const reevaluateOther = getValueViaPath( + answers, + 'cemeteryEquity.reevaluateOther', + ) + const operationResult = getValueViaPath( + answers, + 'cemeteryEquity.operationResult', + ) + const totalEquity = getValueViaPath(answers, 'cemeteryEquity.total') + + const debtsAndCash = getValueViaPath( + answers, + 'equityAndLiabilitiesTotals.equityAndLiabilitiesTotal', + ) return ( @@ -104,26 +152,24 @@ export const CemeteryOverview = ({ @@ -132,42 +178,35 @@ export const CemeteryOverview = ({ {formatMessage(m.expenses)} - + @@ -193,16 +232,16 @@ export const CemeteryOverview = ({ @@ -213,18 +252,12 @@ export const CemeteryOverview = ({ {formatMessage(m.debts)} - - + + @@ -233,39 +266,35 @@ export const CemeteryOverview = ({ - {parseInt(answers.cemeteryIncome?.total, 10) < Number(careTakerLimit) && + {Number(totalIncome) < Number(incomeLimit) && cemeteryCaretakers?.length > 0 ? ( <> @@ -273,8 +302,8 @@ export const CemeteryOverview = ({ {formatMessage(m.cemeteryBoardmembers)} - {cemeteryCaretakers.map((careTaker) => ( - <> + {cemeteryCaretakers.map((careTaker, i) => ( + @@ -303,48 +332,19 @@ export const CemeteryOverview = ({ - > + ))} > ) : null} {fileName ? ( <> - + > ) : null} - - - {formatMessage(m.overview)} - - - - - { - return ( - { - onChange(e.target.checked) - setApproveOverview(e.target.checked) - setValue('applicationApprove' as string, e.target.checked) - }} - checked={value} - name="applicationApprove" - id="applicationApprove" - label={formatMessage(m.overviewCorrect)} - large - /> - ) - }} - /> - - {Number(cemeteryIncome) < Number(careTakerLimit) && + {Number(totalIncome) < Number(incomeLimit) && fixedAssetsTotal === '0' && - longTermDebt === '0' ? ( + longTerm === '0' ? ( ) : null} - {errors && getErrorViaPath(errors, 'applicationApprove') ? ( - - ) : null} - {submitError ? ( - - - - ) : null} - ) } diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/KeyNumbers/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/KeyNumbers/index.tsx deleted file mode 100644 index d565106e5e7b..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/KeyNumbers/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useEffect } from 'react' -import { Box, Text } from '@island.is/island-ui/core' -import { InputController } from '@island.is/shared/form-fields' -import { useFormContext } from 'react-hook-form' - -type PropTypes = { name: string; total: number; label: string; title?: string } - -export const Total = ({ name, total, label, title }: PropTypes) => { - const { setValue } = useFormContext() - - useEffect(() => { - setValue(name, total.toString()) - }, [total]) - - return ( - - {title ? ( - - {title} - - ) : null} - - - - ) -} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/KeyNumbersCapital/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/KeyNumbersCapital/index.tsx deleted file mode 100644 index 23ce3e66e903..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/KeyNumbersCapital/index.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React, { useState, useEffect } from 'react' -import { - Box, - GridColumn, - GridContainer, - GridRow, -} from '@island.is/island-ui/core' -import { getValueViaPath } from '@island.is/application/core' -import debounce from 'lodash/debounce' -import { useFormContext } from 'react-hook-form' -import { useLocale } from '@island.is/localization' -import { InputController } from '@island.is/shared/form-fields' -import { m } from '../../lib/messages' -import { Total } from '../KeyNumbers' - -import { getErrorViaPath } from '@island.is/application/core' -import { CAPITALNUMBERS, INPUTCHANGEINTERVAL } from '../../utils/constants' - -export const KeyNumbersCapital = () => { - const { formatMessage } = useLocale() - const [totalCapital, setTotalCapital] = useState(0) - const { - clearErrors, - formState: { errors }, - getValues, - } = useFormContext() - - const getTotalCapital = () => { - const values = getValues() - - const income = getValueViaPath(values, CAPITALNUMBERS.capitalIncome) || '0' - const expense = getValueViaPath(values, CAPITALNUMBERS.capitalCost) || '0' - const total = Number(income) - Number(expense) - setTotalCapital(total) - } - - useEffect(() => { - getTotalCapital() - }, [getTotalCapital]) - - return ( - - - - - { - getTotalCapital() - clearErrors(CAPITALNUMBERS.capitalIncome) - }, INPUTCHANGEINTERVAL)} - label={formatMessage(m.capitalIncome)} - backgroundColor="blue" - rightAlign - currency - /> - - - - - { - getTotalCapital() - clearErrors(CAPITALNUMBERS.capitalCost) - }, INPUTCHANGEINTERVAL)} - label={formatMessage(m.capitalCost)} - error={ - errors && getErrorViaPath(errors, CAPITALNUMBERS.capitalCost) - } - backgroundColor="blue" - rightAlign - currency - /> - - - - - - - - - - ) -} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/OperatingYear/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/OperatingYear/index.tsx deleted file mode 100644 index 87dfb926490b..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/OperatingYear/index.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { - AlertMessage, - Box, - ContentBlock, - SkeletonLoader, -} from '@island.is/island-ui/core' -import { useLocale } from '@island.is/localization' -import { m } from '../../lib/messages' - -import * as styles from './styles.css' -import { useFormContext } from 'react-hook-form' -import { getErrorViaPath } from '@island.is/application/core' -import { SelectController } from '@island.is/shared/form-fields' -import { useQuery } from '@apollo/client' - -import { getAuditConfig } from '../../graphql' -import { - getConfigInfoForKey, - possibleOperatingYears, -} from '../../utils/helpers' -import { - ABOUTIDS, - CemeteriesBackwardLimit, - CemeteriesYearAllowed, -} from '../../utils/constants' - -export const OperatingYear = () => { - const { data, loading, error } = useQuery(getAuditConfig) - const { formatMessage } = useLocale() - const { - formState: { errors }, - } = useFormContext() - - if (loading) { - return ( - - - - ) - } - - if (error || data?.financialStatementsInaoConfig?.length <= 0) { - return ( - - - - ) - } - - const { financialStatementsInaoConfig } = data - - const backwardsYearLimit = getConfigInfoForKey( - financialStatementsInaoConfig, - CemeteriesBackwardLimit, - ) - - const countYearBackwardsFrom = getConfigInfoForKey( - financialStatementsInaoConfig, - CemeteriesYearAllowed, - ) - - const operatingYear = possibleOperatingYears( - backwardsYearLimit, - countYearBackwardsFrom, - ) - - return ( - - - - ) -} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/OperatingYear/styles.css.ts b/libs/application/templates/inao/financial-statement-cemetery/src/fields/OperatingYear/styles.css.ts deleted file mode 100644 index c5dd8724f088..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/OperatingYear/styles.css.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { style } from '@vanilla-extract/css' - -export const selectSpace = style({ - paddingRight: '10px', -}) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/PowerOfAttorney/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/PowerOfAttorney/index.tsx index cdf31e9ad78f..f4dcf6f77248 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/PowerOfAttorney/index.tsx +++ b/libs/application/templates/inao/financial-statement-cemetery/src/fields/PowerOfAttorney/index.tsx @@ -11,9 +11,7 @@ import { import { InputController } from '@island.is/shared/form-fields' import { useLocale } from '@island.is/localization' import { IdentityInput, Query } from '@island.is/api/schema' - import { m } from '../../lib/messages' - import { FieldBaseProps } from '@island.is/application/types' import { getErrorViaPath } from '@island.is/application/core' import { IdentityQuery } from '../../graphql' diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/Success/index.tsx b/libs/application/templates/inao/financial-statement-cemetery/src/fields/Success/index.tsx deleted file mode 100644 index e8a6adc55f21..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/Success/index.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { - Box, - ContentBlock, - ActionCard, - AlertMessage, -} from '@island.is/island-ui/core' -import { useLocale } from '@island.is/localization' -import { CustomField, FieldBaseProps } from '@island.is/application/types' -import format from 'date-fns/format' -import { m } from '../../lib/messages' -import { FinancialStatementCemetery } from '../../lib/dataSchema' -import { - getCurrentUserType, - isCemetryUnderFinancialLimit, -} from '../../utils/helpers' - -interface PropTypes extends FieldBaseProps { - field: CustomField -} - -export const Success = ({ application }: PropTypes) => { - const { answers, externalData } = application - const applicationAnswers = application.answers as FinancialStatementCemetery - const { formatMessage } = useLocale() - - const getDescriptionText = () => { - const currentDate = format(new Date(), "dd.MM.yyyy 'kl.' kk:mm") - return `${formatMessage(m.operatingYearMsgFirst)} ${ - applicationAnswers.conditionalAbout.operatingYear - } - ${formatMessage(m.individualReceivedMsgSecond)} ${currentDate}` - } - - const shouldShowDigitalSigningMessage = () => { - return isCemetryUnderFinancialLimit(answers, externalData) - } - - return ( - - - - - - {shouldShowDigitalSigningMessage() && ( - - - - )} - - window.open('/minarsidur/postholf', '_blank'), - }} - backgroundColor="blue" - /> - - - - ) -} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/fields/index.ts b/libs/application/templates/inao/financial-statement-cemetery/src/fields/index.ts index 38e7219bec44..9903bafb3734 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/fields/index.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/fields/index.ts @@ -1,8 +1,4 @@ export { CemeteryCaretaker } from './CemeteryCareteker' -export { CemeteryOperation } from './CemeteryOperation' -export { KeyNumbersCapital } from './KeyNumbersCapital' -export { CemeteryEquities } from './CemeteryEquities' -export { OperatingYear } from './OperatingYear' export { PowerOfAttorneyFields } from './PowerOfAttorney' export { CemeteryOverview } from './CemeteryOverview' -export { Success } from './Success' +export { CemeteryIncomeLimit } from './CemeteryIncomeLimit' diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/capitalNumberSubSection.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/capitalNumberSubSection.ts index 549bf3b78658..86bc4ae3eaa0 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/capitalNumberSubSection.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/capitalNumberSubSection.ts @@ -1,10 +1,12 @@ import { - buildCustomField, + buildDisplayField, buildMultiField, buildSubSection, + buildTextField, } from '@island.is/application/core' import { m } from '../../../lib/messages' import { CAPITALNUMBERS } from '../../../utils/constants' +import { sumCapitalNumbers } from '../../../utils/helpers' export const capitalNumberSubSection = buildSubSection({ id: 'keynumbers.capitalNumbers', @@ -15,12 +17,27 @@ export const capitalNumberSubSection = buildSubSection({ title: m.capitalNumbersSectionTitle, description: m.fillOutAppopriate, children: [ - buildCustomField({ - id: 'capitalNumberField', + buildTextField({ + id: CAPITALNUMBERS.capitalIncome, + title: m.capitalIncome, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CAPITALNUMBERS.capitalCost, + title: m.capitalCost, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: CAPITALNUMBERS.total, title: '', - description: '', - component: 'KeyNumbersCapital', - childInputIds: Object.values(CAPITALNUMBERS), + label: m.totalCapital, + value: sumCapitalNumbers, + variant: 'currency', + rightAlign: true, }), ], }), diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/equityAndLiabilitySubSection.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/equityAndLiabilitySubSection.ts index f5cb6fdf8f6e..1e0f6a2987f2 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/equityAndLiabilitySubSection.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/equityAndLiabilitySubSection.ts @@ -1,10 +1,24 @@ import { - buildCustomField, + buildAlertMessageField, + buildDescriptionField, + buildDisplayField, buildMultiField, buildSubSection, + buildTextField, } from '@island.is/application/core' import { m } from '../../../lib/messages' -import { CEMETERYEQUITIESANDLIABILITIESIDS } from '../../../utils/constants' +import { + CEMETERYEQUITIESANDLIABILITIESIDS, + EQUITYANDLIABILITIESTOTALS, +} from '../../../utils/constants' +import { + operationResult, + showEquitiesAndLiabilitiesAlert, + sumAssets, + sumLiabilities, + sumTotalEquity, + sumTotalEquityAndLiabilities, +} from '../../../utils/helpers' export const equityAndLiabilitiesSubSection = buildSubSection({ id: 'keyNumbers.cemetryEquitiesAndLiabilities', @@ -15,12 +29,129 @@ export const equityAndLiabilitiesSubSection = buildSubSection({ title: m.keyNumbersDebt, description: m.fillOutAppopriate, children: [ - buildCustomField({ - id: 'cemeteryEquitiesAndLiabilities', - title: m.keyNumbersDebt, - description: m.fillOutAppopriate, - component: 'CemeteryEquities', - childInputIds: Object.values(CEMETERYEQUITIESANDLIABILITIESIDS), + // Assets + buildDescriptionField({ + id: 'assetsDescription', + title: m.properties, + titleVariant: 'h3', + }), + buildTextField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.fixedAssetsTotal, + title: m.fixedAssetsTotal, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.currentAssets, + title: m.currentAssets, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: EQUITYANDLIABILITIESTOTALS.assetsTotal, + title: '', + label: m.totalAssets, + value: sumAssets, + variant: 'currency', + rightAlign: true, + }), + + // Debt + buildDescriptionField({ + id: 'debtsDescription', + title: m.debts, + titleVariant: 'h3', + }), + buildTextField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.longTerm, + title: m.longTerm, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.shortTerm, + title: m.shortTerm, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: EQUITYANDLIABILITIESTOTALS.liabilitiesTotal, + title: '', + label: m.totalLiabilities, + value: sumLiabilities, + variant: 'currency', + rightAlign: true, + }), + + // Equity + buildDescriptionField({ + id: 'equityDescription', + title: m.equity, + titleVariant: 'h3', + }), + buildTextField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.equityAtTheBeginningOfTheYear, + title: m.equityAtTheBeginningOfTheYear, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.revaluationDueToPriceChanges, + title: m.revaluationDueToPriceChanges, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.reevaluateOther, + title: m.reevaluateOther, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.operationResult, + title: '', + label: m.operationResult, + value: operationResult, + variant: 'currency', + rightAlign: true, + width: 'half', + }), + buildDisplayField({ + id: CEMETERYEQUITIESANDLIABILITIESIDS.equityTotal, + title: '', + label: m.totalEquity, + value: sumTotalEquity, + variant: 'currency', + rightAlign: true, + }), + + // Debts and Equity + buildDescriptionField({ + id: 'debtsAndEquityDescription', + title: m.debtsAndCash, + titleVariant: 'h3', + }), + buildDisplayField({ + id: EQUITYANDLIABILITIESTOTALS.equityAndLiabilitiesTotal, + title: '', + value: sumTotalEquityAndLiabilities, + variant: 'currency', + rightAlign: true, + }), + + buildAlertMessageField({ + condition: showEquitiesAndLiabilitiesAlert, + id: 'equityAndLiabilityError', + title: m.equityErrorTitle, + message: m.equityDebtsAssetsValidatorError, + alertType: 'error', }), ], }), diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/opperatingCostSubSection.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/opperatingCostSubSection.ts index 66ef7c08bc52..34329fa8ee15 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/opperatingCostSubSection.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/cemeteryKeyNumbersSection/opperatingCostSubSection.ts @@ -1,10 +1,18 @@ import { buildCustomField, + buildDescriptionField, + buildDisplayField, buildMultiField, buildSubSection, + buildTextField, } from '@island.is/application/core' -import { CEMETERYOPERATIONIDS } from '../../../utils/constants' +import { CEMETERYOPERATIONIDS, OPERATINGCOST } from '../../../utils/constants' import { m } from '../../../lib/messages' +import { + sumExpenses, + sumIncome, + sumOperatingResults, +} from '../../../utils/helpers' export const opperatingCostSubSection = buildSubSection({ id: 'operatingCost', @@ -16,10 +24,129 @@ export const opperatingCostSubSection = buildSubSection({ description: m.fillOutAppopriate, children: [ buildCustomField({ - id: 'cemetryKeyNumbers', + id: 'cemetryIncomeLimit', + title: '', + component: 'CemeteryIncomeLimit', + }), + // Income + buildDescriptionField({ + id: 'incomeDescription', + title: m.income, + titleVariant: 'h3', + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.careIncome, + title: m.careIncome, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.burialRevenue, + title: m.burialRevenue, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.grantFromTheCemeteryFund, + title: m.grantFromTheCemeteryFund, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.otherIncome, + title: m.otherIncome, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: CEMETERYOPERATIONIDS.totalIncome, + title: '', + label: m.totalIncome, + value: sumIncome, + variant: 'currency', + rightAlign: true, + }), + + // Expenses + buildDescriptionField({ + id: 'expensesDescription', + title: m.expenses, + titleVariant: 'h3', + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.payroll, + title: m.payroll, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.funeralCost, + title: m.funeralCost, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.chapelExpense, + title: m.chapelExpense, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.donationsToCemeteryFund, + title: m.donationsToCemeteryFund, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.donationsToOther, + title: m.donationsToOther, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.otherOperationCost, + title: m.otherOperationCost, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildTextField({ + id: CEMETERYOPERATIONIDS.depreciation, + title: m.depreciation, + width: 'half', + variant: 'currency', + rightAlign: true, + }), + buildDisplayField({ + id: CEMETERYOPERATIONIDS.totalExpense, + title: '', + label: m.totalExpenses, + value: sumExpenses, + variant: 'currency', + rightAlign: true, + }), + + // Operating results + buildDescriptionField({ + id: 'operatingResultsDescription', + title: m.operatingCost, + titleVariant: 'h3', + }), + buildDisplayField({ + id: OPERATINGCOST.total, title: '', - component: 'CemeteryOperation', - childInputIds: Object.values(CEMETERYOPERATIONIDS), + value: sumOperatingResults, + variant: 'currency', + rightAlign: true, }), ], }), diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/clientInfoSection/index.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/clientInfoSection/index.ts index 387a2072098f..9916366fd934 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/clientInfoSection/index.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/clientInfoSection/index.ts @@ -1,4 +1,5 @@ import { + buildAsyncSelectField, buildCustomField, buildDescriptionField, buildMultiField, @@ -9,6 +10,9 @@ import { Application, UserProfile } from '@island.is/application/types' import { m } from '../../../lib/messages' import { ABOUTIDS } from '../../../utils/constants' import { Identity } from '@island.is/api/schema' +import { getAuditConfig } from '../../../graphql' +import { AuditConfig } from '../../../types/types' +import { getYearOptions } from '../../../utils/helpers' export const clientInfoSection = buildSection({ id: 'info', @@ -19,15 +23,21 @@ export const clientInfoSection = buildSection({ title: m.info, description: m.reviewContact, children: [ - buildDescriptionField({ + buildAsyncSelectField({ id: ABOUTIDS.operatingYear, - title: '', + title: m.operatingYear, + placeholder: m.selectOperatingYear, + width: 'half', + loadOptions: async ({ apolloClient }) => { + const { data } = await apolloClient.query({ + query: getAuditConfig, + }) + return getYearOptions(data) + }, }), - buildCustomField({ - id: 'OperatingYear', - childInputIds: [ABOUTIDS.operatingYear], + buildDescriptionField({ + id: 'about.description', title: '', - component: 'OperatingYear', }), buildTextField({ id: 'about.nationalId', @@ -49,7 +59,7 @@ export const clientInfoSection = buildSection({ }, }), buildDescriptionField({ - id: ABOUTIDS.powerOfAttorneyName, + id: 'about.description2', title: '', }), buildCustomField({ diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/conclusionSection/conclusionSection.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/conclusionSection/conclusionSection.ts new file mode 100644 index 000000000000..1ca0e1529209 --- /dev/null +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/conclusionSection/conclusionSection.ts @@ -0,0 +1,19 @@ +import { buildFormConclusionSection } from '@island.is/application/ui-forms' +import { m } from '../../../lib/messages' +import { getValueViaPath } from '@island.is/application/core' + +export const conclusionSection = buildFormConclusionSection({ + multiFieldTitle: m.received, + alertTitle: m.returned, + alertMessage: (application) => { + const year = getValueViaPath( + application.answers, + 'conditionalAbout.operatingYear', + ) + return { + ...m.conclusionAlertMessage, + values: { value1: year }, + } + }, + accordion: false, +}) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/index.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/index.ts index bf4875117557..736e879139ee 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/index.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/index.ts @@ -7,12 +7,13 @@ import { cemeteryFinancialStatementSection } from './cemeteryFinancialStatementS import Logo from '../../components/Logo' import { clientInfoSection } from './clientInfoSection' import { overviewSection } from './overviewSection' +import { conclusionSection } from './conclusionSection/conclusionSection' export const FinancialStatementCemeteryForm: Form = buildForm({ id: 'FinancialStatementCemeteryForm', title: m.applicationTitle, mode: FormModes.DRAFT, - renderLastScreenButton: false, + renderLastScreenButton: true, logo: Logo, children: [ clientInfoSection, @@ -20,5 +21,6 @@ export const FinancialStatementCemeteryForm: Form = buildForm({ cemeteryCaretekerSection, cemeteryFinancialStatementSection, overviewSection, + conclusionSection, ], }) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/overviewSection/index.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/overviewSection/index.ts index 3d1cb64989b8..1bd658de4cfc 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/overviewSection/index.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/overviewSection/index.ts @@ -1,9 +1,51 @@ -import { buildSection } from '@island.is/application/core' +import { + buildCheckboxField, + buildCustomField, + buildMultiField, + buildSection, + buildSubmitField, + YES, +} from '@island.is/application/core' import { m } from '../../../lib/messages' -import { overviewMultiField } from './oveviewMultiField' +import { DefaultEvents } from '@island.is/application/types' export const overviewSection = buildSection({ id: 'overviewSection', title: m.overviewSectionTitle, - children: [overviewMultiField], + children: [ + buildMultiField({ + id: 'overview', + title: m.yearlyOverview, + description: m.review, + children: [ + buildCustomField({ + id: 'overviewCemetryField', + title: '', + doesNotRequireAnswer: true, + component: 'CemeteryOverview', + }), + buildCheckboxField({ + id: 'approveOverview', + title: '', + options: [ + { + label: m.overviewCorrect, + value: YES, + }, + ], + }), + buildSubmitField({ + id: 'overview.submit', + title: '', + actions: [ + { + event: DefaultEvents.SUBMIT, + name: m.send, + type: 'primary', + }, + ], + }), + ], + }), + ], }) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/overviewSection/oveviewMultiField.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/overviewSection/oveviewMultiField.ts deleted file mode 100644 index 4f93c70dd20a..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/applicationForm/overviewSection/oveviewMultiField.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { buildCustomField, buildMultiField } from '@island.is/application/core' -import { m } from '../../../lib/messages' - -export const overviewMultiField = buildMultiField({ - id: 'overview', - title: m.yearlyOverview, - description: m.review, - children: [ - buildCustomField({ - id: 'overviewCemetryField', - title: '', - doesNotRequireAnswer: true, - component: 'CemeteryOverview', - }), - ], -}) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/done/conclusionSection.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/done/conclusionSection.ts deleted file mode 100644 index 532136f509bc..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/done/conclusionSection.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { - buildCustomField, - buildMultiField, - buildSection, -} from '@island.is/application/core' -import { m } from '../../lib/messages' - -export const conclusionSection = buildSection({ - id: 'conclusionSection', - title: '', - children: [ - buildMultiField({ - id: 'conclusion', - title: m.received, - children: [ - buildCustomField({ - id: 'overview', - component: 'Success', - title: m.applicationAccept, - }), - ], - }), - ], -}) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/done/index.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/done/index.ts index e4ea0c21bc77..5c2dd8c0f237 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/done/index.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/done/index.ts @@ -1,11 +1,13 @@ import { buildForm } from '@island.is/application/core' import { Form, FormModes } from '@island.is/application/types' -import { conclusionSection } from './conclusionSection' import { m } from '../../lib/messages' +import { conclusionSection } from '../applicationForm/conclusionSection/conclusionSection' +import Logo from '../../components/Logo' export const done: Form = buildForm({ id: 'done', title: m.applicationAccept, mode: FormModes.COMPLETED, + logo: Logo, children: [conclusionSection], }) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/notAllowed/index.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/notAllowed/index.ts index 64b658b27a8e..bc8aa7c1ae11 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/notAllowed/index.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/notAllowed/index.ts @@ -1,10 +1,26 @@ -import { buildForm } from '@island.is/application/core' -import { notAllowedSection } from './notAllowedSection' +import { + buildDescriptionField, + buildForm, + buildSection, +} from '@island.is/application/core' import Logo from '../../components/Logo' +import { m } from '../../lib/messages' export const notAllowedForm = buildForm({ id: 'notAllowedForm', title: '', logo: Logo, - children: [notAllowedSection], + children: [ + buildSection({ + id: 'notAllowedSection', + title: '', + children: [ + buildDescriptionField({ + id: 'notAllowedDescription', + title: m.notAllowedTitle, + description: m.notAllowedDescription, + }), + ], + }), + ], }) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/notAllowed/notAllowedSection.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/notAllowed/notAllowedSection.ts deleted file mode 100644 index 5c8359dd2a4b..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/notAllowed/notAllowedSection.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - buildDescriptionField, - buildSection, -} from '@island.is/application/core' -import { m } from '../../lib/messages' - -export const notAllowedSection = buildSection({ - id: 'notAllowedSection', - title: '', - children: [ - buildDescriptionField({ - id: 'notAllowedDescription', - title: m.notAllowedTitle, - description: m.notAllowedDescription, - }), - ], -}) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/prerequisites/index.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/prerequisites/index.ts index e8f8d9631b70..4e69a430c00e 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/prerequisites/index.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/forms/prerequisites/index.ts @@ -1,7 +1,19 @@ -import { buildForm } from '@island.is/application/core' -import { Form, FormModes } from '@island.is/application/types' +import { + buildDataProviderItem, + buildExternalDataProvider, + buildForm, + buildSection, + buildSubmitField, + coreMessages, +} from '@island.is/application/core' +import { DefaultEvents, Form, FormModes } from '@island.is/application/types' import Logo from '../../components/Logo' -import { prerequisitesSection } from './prerequsitesSection' +import { + CurrentUserTypeProvider, + IdentityApiProvider, + UserInfoApi, +} from '../../dataProviders' +import { m } from '../../lib/messages' export const PrerequisitesForm: Form = buildForm({ id: 'PrerequisitesForm', @@ -9,5 +21,47 @@ export const PrerequisitesForm: Form = buildForm({ mode: FormModes.NOT_STARTED, renderLastScreenButton: true, logo: Logo, - children: [prerequisitesSection], + children: [ + buildSection({ + id: 'ExternalDataSection', + title: '', + children: [ + buildExternalDataProvider({ + id: 'approveExternalData', + title: m.dataCollectionTitleUserCemetery, + checkboxLabel: m.dataCollectionCheckboxLabel, + dataProviders: [ + buildDataProviderItem({ + provider: IdentityApiProvider, + title: m.dataCollectionNationalRegistryTitle, + subTitle: m.dataCollectionNationalRegistrySubtitle, + }), + buildDataProviderItem({ + provider: UserInfoApi, + title: m.dataCollectionUserProfileTitle, + subTitle: m.dataCollectionUserProfileSubtitle, + }), + buildDataProviderItem({ + provider: CurrentUserTypeProvider, + title: m.dataCollectionUserFinancialInfoTitle, + subTitle: m.dataCollectionUserFinancialInfo, + }), + ], + submitField: buildSubmitField({ + id: 'submit', + placement: 'footer', + title: '', + refetchApplicationAfterSubmit: true, + actions: [ + { + event: DefaultEvents.SUBMIT, + name: coreMessages.buttonNext, + type: 'primary', + }, + ], + }), + }), + ], + }), + ], }) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/forms/prerequisites/prerequsitesSection.ts b/libs/application/templates/inao/financial-statement-cemetery/src/forms/prerequisites/prerequsitesSection.ts deleted file mode 100644 index 6b9bed52110a..000000000000 --- a/libs/application/templates/inao/financial-statement-cemetery/src/forms/prerequisites/prerequsitesSection.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - buildDataProviderItem, - buildExternalDataProvider, - buildSection, - buildSubmitField, - coreMessages, -} from '@island.is/application/core' -import { m } from '../../lib/messages' -import { - CurrentUserTypeProvider, - IndentityApiProvider, - UserInfoApi, -} from '../../dataProviders' -import { DefaultEvents } from '@island.is/application/types' - -export const prerequisitesSection = buildSection({ - id: 'ExternalDataSection', - title: '', - children: [ - buildExternalDataProvider({ - id: 'approveExternalData', - title: m.dataCollectionTitleUserCemetry, - checkboxLabel: m.dataCollectionCheckboxLabel, - dataProviders: [ - buildDataProviderItem({ - provider: IndentityApiProvider, - title: m.dataCollectionNationalRegistryTitle, - subTitle: m.dataCollectionNationalRegistrySubtitle, - }), - buildDataProviderItem({ - provider: UserInfoApi, - title: m.dataCollectionUserProfileTitle, - subTitle: m.dataCollectionUserProfileSubtitle, - }), - buildDataProviderItem({ - provider: CurrentUserTypeProvider, - title: m.dataCollectionUserFinancialInfoTitle, - subTitle: m.dataCollectionUserFinancialInfo, - }), - ], - submitField: buildSubmitField({ - id: 'submit', - placement: 'footer', - title: '', - refetchApplicationAfterSubmit: true, - actions: [ - { - event: DefaultEvents.SUBMIT, - name: coreMessages.buttonNext, - type: 'primary', - }, - ], - }), - }), - ], -}) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/lib/dataSchema.ts b/libs/application/templates/inao/financial-statement-cemetery/src/lib/dataSchema.ts index b177ee8daac2..97379678b103 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/lib/dataSchema.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/lib/dataSchema.ts @@ -3,7 +3,11 @@ import { m } from './messages' import * as kennitala from 'kennitala' import { parsePhoneNumberFromString } from 'libphonenumber-js' import { BOARDMEMEBER, CARETAKER } from '../utils/constants' -import { getBoardmembersAndCaretakers } from '../utils/helpers' +import { + isPositiveNumberInString, + getBoardmembersAndCaretakers, +} from '../utils/helpers' +import { YES } from '@island.is/application/types' const FileSchema = z.object({ name: z.string(), @@ -11,117 +15,191 @@ const FileSchema = z.object({ url: z.string().optional(), }) -const checkIfNegative = (inputNumber: string) => { - if (Number(inputNumber) < 0) { - return false - } else { - return true - } -} - -const conditionalAbout = z.object({ - operatingYear: z.string().refine((x) => !!x, { params: m.required }), -}) - const cemeteryOperation = z.object({ incomeLimit: z.string().optional(), }) -const cemeteryLiability = z.object({ - longTerm: z - .string() - .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), - shortTerm: z - .string() - .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), - total: z.string().refine((x) => !!x, { params: m.required }), +const conditionalAbout = z.object({ + operatingYear: z.string().refine((x) => !!x, { params: m.required }), }) -const cemeteryAsset = z.object({ - currentAssets: z - .string() - .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), - fixedAssetsTotal: z +const about = z.object({ + nationalId: z .string() - .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), - total: z.string(), + .refine((val) => (val ? kennitala.isValid(val) : false), { + params: m.nationalIdError, + }), + fullName: z.string().refine((x) => !!x, { params: m.required }), + powerOfAttorneyNationalId: z.string().optional(), + powerOfAttorneyName: z.string().optional(), + phoneNumber: z.string().refine( + (p) => { + const phoneNumber = parsePhoneNumberFromString(p, 'IS') + return phoneNumber && phoneNumber.isValid() + }, + { params: m.dataSchemePhoneNumber }, + ), + email: z.string().email(), }) +// Key numbers - Income and Expenses - Income const cemeteryIncome = z.object({ careIncome: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), burialRevenue: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), grantFromTheCemeteryFund: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), otherIncome: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), total: z.string(), }) +// Key numbers - Income and Expenses - Expenses const cemeteryExpense = z.object({ payroll: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), funeralCost: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), chapelExpense: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), donationsToOther: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), cemeteryFundExpense: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), otherOperationCost: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), depreciation: z .string() .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), total: z.string(), }) -const about = z.object({ - nationalId: z +// Key numbers - Capital numbers +const capitalNumbers = z.object({ + capitalIncome: z .string() - .refine((val) => (val ? kennitala.isValid(val) : false), { - params: m.nationalIdError, + .refine((x) => !!x, { params: m.required }) + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, }), - fullName: z.string().refine((x) => !!x, { params: m.required }), - powerOfAttorneyNationalId: z.string().optional(), - powerOfAttorneyName: z.string().optional(), - phoneNumber: z.string().refine( - (p) => { - const phoneNumber = parsePhoneNumberFromString(p, 'IS') - return phoneNumber && phoneNumber.isValid() - }, - { params: m.dataSchemePhoneNumber }, - ), - email: z.string().email(), + capitalCost: z + .string() + .refine((x) => !!x, { params: m.required }) + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), + total: z.string(), }) +// Key numbers - Equity and Liability - Assets +const cemeteryAsset = z.object({ + currentAssets: z + .string() + .refine((x) => !!x, { params: m.required }) + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), + fixedAssetsTotal: z + .string() + .refine((x) => !!x, { params: m.required }) + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), +}) + +// Key numbers - Equity and Liability - Liabilities +const cemeteryLiability = z.object({ + longTerm: z + .string() + .refine((x) => !!x, { params: m.required }) + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), + shortTerm: z + .string() + .refine((x) => !!x, { params: m.required }) + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), +}) + +// Key numbers - Equity and Liability - Equity +const cemeteryEquity = z.object({ + equityAtTheBeginningOfTheYear: z + .string() + .refine((x) => !!x, { params: m.required }), + operationResult: z.string(), + revaluationDueToPriceChanges: z + .string() + .refine((x) => !!x, { params: m.required }) + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), + reevaluateOther: z + .string() + .refine((x) => !!x, { params: m.required }) + .refine((x) => isPositiveNumberInString(x), { + params: m.negativeNumberError, + }), + total: z.string(), +}) + +// Key numbers - Equity and Liability - Equity and Liabilities totals +const equityAndLiabilitiesTotals = z + .object({ + assetsTotal: z.string(), + liabilitiesTotal: z.string(), + equityAndLiabilitiesTotal: z.string(), + }) + .refine((x) => x.assetsTotal === x.equityAndLiabilitiesTotal, { + message: 'equityAndLiabilities.total must match assets.total', + path: ['equityAndLiabilitiesTotals', 'equityAndLiabilitiesTotal'], + }) + const cemeteryCaretaker = z .array( z.object({ @@ -178,54 +256,23 @@ const cemeteryCaretaker = z { params: m.errormemberNotUnique }, ) -const cemeteryEquity = z.object({ - equityAtTheBeginningOfTheYear: z - .string() - .refine((x) => !!x, { params: m.required }), - operationResult: z.string(), - revaluationDueToPriceChanges: z - .string() - .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), - reevaluateOther: z - .string() - .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), - total: z.string(), -}) - -const equityAndLiabilities = z.object({ - total: z.string(), -}) - -const capitalNumbers = z.object({ - capitalIncome: z - .string() - .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), - capitalCost: z - .string() - .refine((x) => !!x, { params: m.required }) - .refine((x) => checkIfNegative(x), { params: m.negativeNumberError }), - total: z.string(), -}) - export const dataSchema = z.object({ - conditionalAbout, - about, approveExternalData: z.boolean().refine((v) => v), cemeteryOperation, - cemeteryAsset, - cemeteryLiability, + conditionalAbout, + about, cemeteryIncome, cemeteryExpense, + capitalNumbers, + cemeteryAsset, + cemeteryLiability, + cemeteryEquity, + equityAndLiabilitiesTotals, + cemeteryCaretaker, attachments: z.object({ file: z.array(FileSchema).nonempty(), }), - cemeteryCaretaker, - cemeteryEquity, - equityAndLiabilities, - capitalNumbers, + approveOverview: z.array(z.literal(YES)).length(1), }) export type FinancialStatementCemetery = z.TypeOf diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/lib/financialStatementCemeteryTemplate.ts b/libs/application/templates/inao/financial-statement-cemetery/src/lib/financialStatementCemeteryTemplate.ts index a92fbc786cb5..0c851e4c2dd1 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/lib/financialStatementCemeteryTemplate.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/lib/financialStatementCemeteryTemplate.ts @@ -19,7 +19,7 @@ import { AuthDelegationType } from '@island.is/shared/types' import { dataSchema } from './dataSchema' import { CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, NationalRegistryUserApi, UserInfoApi, } from '../dataProviders' @@ -79,7 +79,7 @@ const FinancialStatementCemeteryTemplate: ApplicationTemplate< delete: true, api: [ CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, NationalRegistryUserApi, UserInfoApi, ], diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/lib/messages.ts b/libs/application/templates/inao/financial-statement-cemetery/src/lib/messages.ts index a78cd7e4296a..7bce9e4fffe1 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/lib/messages.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/lib/messages.ts @@ -375,7 +375,7 @@ export const m = defineMessages({ defaultMessage: 'Gagnaöflun', description: 'Title for data collection section', }, - dataCollectionTitleUserCemetry: { + dataCollectionTitleUserCemetery: { id: 'fsc.application:applicationDataCollectionTitleUserCemetry', defaultMessage: 'Gagnaöflun vegna skila ársreiknings kirkjugarðs', description: 'Title for data collection section', @@ -682,4 +682,10 @@ export const m = defineMessages({ 'Ef þú telur að þessi kennitala ætti að vera skráð sem kirkjugarður þá bendum við þér á að hafa samband við Ríkisendurskoðun í síma 448 8800', description: 'Descriptionwhen user is not allowed to apply', }, + conclusionAlertMessage: { + id: 'fsc.application:conclusionAlertMessage', + defaultMessage: + 'Ársreikning fyrir rekstrarárið {value1} hefur verið skilað', + description: 'Conclusion alert message', + }, }) diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/types/types.ts b/libs/application/templates/inao/financial-statement-cemetery/src/types/types.ts index 274aeaf840e3..53e29233513f 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/types/types.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/types/types.ts @@ -36,3 +36,13 @@ export enum Roles { APPLICANT = 'applicant', NOTALLOWED = 'notAllowed', } + +type InaoConfigItem = { + __typename: string + key: string + value: string +} + +export type AuditConfig = { + financialStatementsInaoConfig: Array +} diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/utils/constants.ts b/libs/application/templates/inao/financial-statement-cemetery/src/utils/constants.ts index a7981b212bbc..2c0cb3bb6afe 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/utils/constants.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/utils/constants.ts @@ -63,16 +63,20 @@ export const CEMETERYEQUITIESANDLIABILITIESIDS = { equityPrefix: 'cemeteryEquity', currentAssets: 'cemeteryAsset.currentAssets', fixedAssetsTotal: 'cemeteryAsset.fixedAssetsTotal', - assetTotal: 'cemeteryAsset.total', longTerm: 'cemeteryLiability.longTerm', shortTerm: 'cemeteryLiability.shortTerm', - liabilityTotal: 'cemeteryLiability.total', equityAtTheBeginningOfTheYear: 'cemeteryEquity.equityAtTheBeginningOfTheYear', revaluationDueToPriceChanges: 'cemeteryEquity.revaluationDueToPriceChanges', reevaluateOther: 'cemeteryEquity.reevaluateOther', operationResult: 'cemeteryEquity.operationResult', equityTotal: 'cemeteryEquity.total', - totalEquityAndLiabilities: 'equityAndLiabilities.total', +} + +export const EQUITYANDLIABILITIESTOTALS = { + assetsTotal: 'equityAndLiabilitiesTotals.assetsTotal', + liabilitiesTotal: 'equityAndLiabilitiesTotals.liabilitiesTotal', + equityAndLiabilitiesTotal: + 'equityAndLiabilitiesTotals.equityAndLiabilitiesTotal', } export const OPERATINGCOST = { diff --git a/libs/application/templates/inao/financial-statement-cemetery/src/utils/helpers.ts b/libs/application/templates/inao/financial-statement-cemetery/src/utils/helpers.ts index c7b382ede33f..aec943d790e8 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/src/utils/helpers.ts +++ b/libs/application/templates/inao/financial-statement-cemetery/src/utils/helpers.ts @@ -1,10 +1,21 @@ import { ExternalData, FormValue } from '@island.is/application/types' import { getValueViaPath } from '@island.is/application/core' -import { BOARDMEMEBER, CARETAKER, TOTAL } from './constants' +import { + BOARDMEMEBER, + CAPITALNUMBERS, + CARETAKER, + CemeteriesBackwardLimit, + CemeteriesYearAllowed, + CEMETERYEQUITIESANDLIABILITIESIDS, + CEMETERYOPERATIONIDS, + EQUITYANDLIABILITIESTOTALS, + OPERATINGCOST, + TOTAL, +} from './constants' import { FinancialStatementCemetery } from '../lib/dataSchema' import getYear from 'date-fns/getYear' import subYears from 'date-fns/subYears' -import { BoardMember, Config, FSIUSERTYPE } from '../types/types' +import { AuditConfig, BoardMember, Config, FSIUSERTYPE } from '../types/types' export const getTotal = (values: Record, key: string) => { if (!values[key]) { @@ -27,23 +38,6 @@ export const currencyStringToNumber = (str: string) => { return parseInt(cleanString, 10) } -export const getCurrentUserType = ( - answers: FormValue, - externalData: ExternalData, -) => { - const fakeUserType: FSIUSERTYPE | undefined = getValueViaPath( - answers, - 'fakeData.options', - ) - - const currentUserType: FSIUSERTYPE | undefined = getValueViaPath( - externalData, - 'getUserType.data.value', - ) - - return fakeUserType ? fakeUserType : currentUserType -} - export const getBoardmembersAndCaretakers = (members: Array) => { const careTakers = members ?.filter((member) => member.role === CARETAKER) @@ -55,30 +49,44 @@ export const getBoardmembersAndCaretakers = (members: Array) => { return { careTakers, boardMembers } } -export const isCemetryUnderFinancialLimit = ( - answers: FormValue, - externalData: ExternalData, -) => { - const userType = getCurrentUserType(answers, externalData) - const applicationAnswers = answers as FinancialStatementCemetery - const careTakerLimit = - applicationAnswers.cemeteryOperation?.incomeLimit ?? '0' - const fixedAssetsTotal = applicationAnswers.cemeteryAsset?.fixedAssetsTotal - const isCemetry = userType === FSIUSERTYPE.CEMETRY - const totalIncome = isCemetry ? applicationAnswers.cemeteryIncome?.total : '0' - const longTermDebt = applicationAnswers.cemeteryLiability?.longTerm - const isUnderLimit = currencyStringToNumber(totalIncome) < careTakerLimit - if ( - isCemetry && - isUnderLimit && - fixedAssetsTotal === '0' && - longTermDebt === '0' - ) { +export const isCemetryUnderFinancialLimit = (answers: FormValue) => { + const totalIncome = + getValueViaPath(answers, 'cemeteryIncome.total') || '0' + const incomeLimit = + getValueViaPath(answers, 'cemeteryOperation.incomeLimit') || '0' + const fixedAssetsTotal = + getValueViaPath(answers, 'cemeteryAsset.fixedAssetsTotal') || '0' + const longTermDebt = + getValueViaPath(answers, 'cemeteryLiability.longTerm') || '0' + const isUnderLimit = Number(totalIncome) < Number(incomeLimit) + + if (isUnderLimit && fixedAssetsTotal === '0' && longTermDebt === '0') { return true } + return false } +export const getYearOptions = (data: AuditConfig) => { + let yearLimit: string | undefined + let countYearBackwardsFrom: string | undefined + data.financialStatementsInaoConfig.forEach((item) => { + if (item.key === CemeteriesBackwardLimit) { + yearLimit = item.value + } + + if (item.key === CemeteriesYearAllowed) { + countYearBackwardsFrom = item.value + } + }) + + if (!countYearBackwardsFrom) { + return [] + } + + return possibleOperatingYears(yearLimit || '1', countYearBackwardsFrom) +} + export const possibleOperatingYears = ( yearLimit: string, countYearBackwardsFrom: string, @@ -103,3 +111,218 @@ export const formatCurrency = (answer?: string) => { if (!answer) return '0. kr' return answer.replace(/\B(?=(\d{3})+(?!\d))/g, '.') + ' kr.' } + +export const isPositiveNumberInString = (input: string) => { + return Number(input) > 0 +} + +export const sumIncome = (answers: FormValue) => { + const careIncome = + getValueViaPath(answers, CEMETERYOPERATIONIDS.careIncome) || '0' + const burialRevenue = + getValueViaPath(answers, CEMETERYOPERATIONIDS.burialRevenue) || '0' + const grantFromTheCemeteryFund = + getValueViaPath( + answers, + CEMETERYOPERATIONIDS.grantFromTheCemeteryFund, + ) || '0' + const otherIncome = + getValueViaPath(answers, CEMETERYOPERATIONIDS.otherIncome) || '0' + + return `${ + Number(careIncome) + + Number(burialRevenue) + + Number(grantFromTheCemeteryFund) + + Number(otherIncome) + }` +} + +export const sumExpenses = (answers: FormValue) => { + const payroll = getValueViaPath(answers, CEMETERYOPERATIONIDS.payroll) + const funeralCost = + getValueViaPath(answers, CEMETERYOPERATIONIDS.funeralCost) || '0' + const chapelExpense = + getValueViaPath(answers, CEMETERYOPERATIONIDS.chapelExpense) || '0' + const donationsToCemeteryFund = + getValueViaPath( + answers, + CEMETERYOPERATIONIDS.donationsToCemeteryFund, + ) || '0' + const donationsToOther = + getValueViaPath(answers, CEMETERYOPERATIONIDS.donationsToOther) || + '0' + const otherOperationCost = + getValueViaPath(answers, CEMETERYOPERATIONIDS.otherOperationCost) || + '0' + const depreciation = + getValueViaPath(answers, CEMETERYOPERATIONIDS.depreciation) || '0' + + return `${ + Number(payroll) + + Number(funeralCost) + + Number(chapelExpense) + + Number(donationsToCemeteryFund) + + Number(donationsToOther) + + Number(otherOperationCost) + + Number(depreciation) + }` +} + +export const sumOperatingResults = (answers: FormValue) => { + const income = + getValueViaPath(answers, CEMETERYOPERATIONIDS.totalIncome) || '0' + const expenses = + getValueViaPath(answers, CEMETERYOPERATIONIDS.totalExpense) || '0' + + return `${Number(income) - Number(expenses)}` +} + +export const sumCapitalNumbers = (answers: FormValue) => { + const capitalIncome = + getValueViaPath(answers, CAPITALNUMBERS.capitalIncome) || '0' + const capitalCost = + getValueViaPath(answers, CAPITALNUMBERS.capitalCost) || '0' + return `${Number(capitalIncome) - Number(capitalCost)}` +} + +export const sumAssets = (answers: FormValue) => { + const fixedAssetsTotal = + getValueViaPath( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.fixedAssetsTotal, + ) || '0' + const currentAssets = + getValueViaPath( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.currentAssets, + ) || '0' + return `${Number(fixedAssetsTotal) + Number(currentAssets)}` +} + +export const sumLiabilities = (answers: FormValue) => { + const longTerm = + getValueViaPath( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.longTerm, + ) || '0' + const shortTerm = + getValueViaPath( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.shortTerm, + ) || '0' + return `${Number(longTerm) + Number(shortTerm)}` +} + +export const operationResult = (answers: FormValue) => { + const operatingTotalCost = + getValueViaPath(answers, OPERATINGCOST.total) || '0' + const capitalTotal = + getValueViaPath(answers, CAPITALNUMBERS.total) || '0' + return `${Number(operatingTotalCost) + Number(capitalTotal)}` +} + +export const sumTotalEquity = (answers: FormValue) => { + const equityAtTheBeginningOfTheYear = + getValueViaPath( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.equityAtTheBeginningOfTheYear, + ) || '0' + const revaluationDueToPriceChanges = + getValueViaPath( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.revaluationDueToPriceChanges, + ) || '0' + const reevaluateOther = + getValueViaPath( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.reevaluateOther, + ) || '0' + const operationResult = + getValueViaPath( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.operationResult, + ) || '0' + + return `${ + Number(equityAtTheBeginningOfTheYear) + + Number(revaluationDueToPriceChanges) + + Number(reevaluateOther) + + Number(operationResult) + }` +} + +export const sumTotalEquityAndLiabilities = (answers: FormValue) => { + const liabilityTotal = + getValueViaPath( + answers, + EQUITYANDLIABILITIESTOTALS.liabilitiesTotal, + ) || '0' + const totalEquity = + getValueViaPath( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.equityTotal, + ) || '0' + + return `${Number(totalEquity) + Number(liabilityTotal)}` +} + +export const showEquitiesAndLiabilitiesAlert = (answers: FormValue) => { + const fixedAssetsTotal = + getValueViaPath( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.fixedAssetsTotal, + ) || '0' + const currentAssets = + getValueViaPath( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.currentAssets, + ) || '0' + const longTerm = + getValueViaPath( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.longTerm, + ) || '0' + const shortTerm = + getValueViaPath( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.shortTerm, + ) || '0' + const equityAtTheBeginningOfTheYear = + getValueViaPath( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.equityAtTheBeginningOfTheYear, + ) || '0' + const revaluationDueToPriceChanges = + getValueViaPath( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.revaluationDueToPriceChanges, + ) || '0' + const reevaluateOther = + getValueViaPath( + answers, + CEMETERYEQUITIESANDLIABILITIESIDS.reevaluateOther, + ) || '0' + + const totalAssets = + getValueViaPath(answers, EQUITYANDLIABILITIESTOTALS.assetsTotal) || + '0' + const totalEquityAndLiabilities = + getValueViaPath( + answers, + EQUITYANDLIABILITIESTOTALS.equityAndLiabilitiesTotal, + ) || '0' + + if ( + !fixedAssetsTotal || + !currentAssets || + !longTerm || + !shortTerm || + !equityAtTheBeginningOfTheYear || + !revaluationDueToPriceChanges || + !reevaluateOther || + !operationResult + ) { + return false + } + return totalAssets !== totalEquityAndLiabilities +} diff --git a/libs/application/templates/inao/financial-statement-cemetery/tsconfig.json b/libs/application/templates/inao/financial-statement-cemetery/tsconfig.json index 52f7c83105fc..04538292f729 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/tsconfig.json +++ b/libs/application/templates/inao/financial-statement-cemetery/tsconfig.json @@ -1,8 +1,10 @@ { - "extends": "../../../../tsconfig.base.json", "compilerOptions": { "jsx": "react-jsx", - "allowJs": false + "allowJs": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true }, "files": [], "include": [], @@ -13,5 +15,6 @@ { "path": "./tsconfig.spec.json" } - ] + ], + "extends": "../../../../../tsconfig.base.json" } diff --git a/libs/application/templates/inao/financial-statement-cemetery/tsconfig.lib.json b/libs/application/templates/inao/financial-statement-cemetery/tsconfig.lib.json index 4362bec84a9a..d17804e8805a 100644 --- a/libs/application/templates/inao/financial-statement-cemetery/tsconfig.lib.json +++ b/libs/application/templates/inao/financial-statement-cemetery/tsconfig.lib.json @@ -1,23 +1,13 @@ { "extends": "./tsconfig.json", "compilerOptions": { - "outDir": "../../../../dist/out-tsc", + "outDir": "../../../../../dist/out-tsc", "types": ["node"] }, "files": [ "../../../../../node_modules/@nx/react/typings/cssmodule.d.ts", "../../../../../node_modules/@nx/react/typings/image.d.ts" ], - "exclude": [ - "jest.config.ts", - "src/**/*.spec.ts", - "src/**/*.test.ts", - "src/**/*.spec.tsx", - "src/**/*.test.tsx", - "src/**/*.spec.js", - "src/**/*.test.js", - "src/**/*.spec.jsx", - "src/**/*.test.jsx" - ], + "exclude": ["/**/*.spec.ts", "/**/*.spec.tsx"], "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] } diff --git a/libs/application/templates/inao/financial-statement-individual-election/src/dataProviders/index.ts b/libs/application/templates/inao/financial-statement-individual-election/src/dataProviders/index.ts index 36303255e230..d537ed3dfa93 100644 --- a/libs/application/templates/inao/financial-statement-individual-election/src/dataProviders/index.ts +++ b/libs/application/templates/inao/financial-statement-individual-election/src/dataProviders/index.ts @@ -3,7 +3,7 @@ import { UserProfileApi } from '@island.is/application/types' export { NationalRegistryUserApi, - IdentityApi as IndentityApiProvider, + IdentityApi as IdentityApiProvider, } from '@island.is/application/types' export const CurrentUserTypeProvider = defineTemplateApi({ action: 'getUserType', diff --git a/libs/application/templates/inao/financial-statement-individual-election/src/forms/prerequisites/prerequisitesSection.ts b/libs/application/templates/inao/financial-statement-individual-election/src/forms/prerequisites/prerequisitesSection.ts index e9820806c3d6..eb7e898460aa 100644 --- a/libs/application/templates/inao/financial-statement-individual-election/src/forms/prerequisites/prerequisitesSection.ts +++ b/libs/application/templates/inao/financial-statement-individual-election/src/forms/prerequisites/prerequisitesSection.ts @@ -8,7 +8,7 @@ import { import { m } from '../../lib/utils/messages' import { CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, UserInfoApi, } from '../../dataProviders' import { DefaultEvents } from '@island.is/application/types' @@ -23,7 +23,7 @@ export const prerequisitesSection = buildSection({ checkboxLabel: m.dataCollectionCheckboxLabel, dataProviders: [ buildDataProviderItem({ - provider: IndentityApiProvider, + provider: IdentityApiProvider, title: m.dataCollectionNationalRegistryTitle, subTitle: m.dataCollectionNationalRegistrySubtitle, }), diff --git a/libs/application/templates/inao/financial-statement-individual-election/src/lib/financial-statement-individual-election.ts b/libs/application/templates/inao/financial-statement-individual-election/src/lib/financial-statement-individual-election.ts index 83e1e625a391..db241a94b205 100644 --- a/libs/application/templates/inao/financial-statement-individual-election/src/lib/financial-statement-individual-election.ts +++ b/libs/application/templates/inao/financial-statement-individual-election/src/lib/financial-statement-individual-election.ts @@ -17,7 +17,7 @@ import { import { CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, NationalRegistryUserApi, UserInfoApi, } from '../dataProviders' @@ -77,7 +77,7 @@ const FinancialStatementIndividualElectionTemplate: ApplicationTemplate< delete: true, api: [ CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, NationalRegistryUserApi, UserInfoApi, ], diff --git a/libs/application/templates/inao/financial-statement-political-party/README.md b/libs/application/templates/inao/financial-statement-political-party/README.md index 3961bb865298..e962ad62bf8e 100644 --- a/libs/application/templates/inao/financial-statement-political-party/README.md +++ b/libs/application/templates/inao/financial-statement-political-party/README.md @@ -1,6 +1,8 @@ # application-templates-inao-financial-statement-political-party -This library was generated with [Nx](https://nx.dev). +This application turns in financial statements for political parties to the The Icelandic National Audit Office (Ríkisendurskoðun, INAO for short). Political parties are required to turn in their financial statements before the 31. of October each year. + +To test this application in the dev environment, you can log in as Gervimaður Færeyjar (010-2399) and swap to the procure of 65° Arctic. Arctic is registered as a political party with the INAO dev service and should be able to enter and go through the application. ## Running unit tests diff --git a/libs/application/templates/inao/financial-statement-political-party/src/dataProviders/index.ts b/libs/application/templates/inao/financial-statement-political-party/src/dataProviders/index.ts index 36303255e230..d537ed3dfa93 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/dataProviders/index.ts +++ b/libs/application/templates/inao/financial-statement-political-party/src/dataProviders/index.ts @@ -3,7 +3,7 @@ import { UserProfileApi } from '@island.is/application/types' export { NationalRegistryUserApi, - IdentityApi as IndentityApiProvider, + IdentityApi as IdentityApiProvider, } from '@island.is/application/types' export const CurrentUserTypeProvider = defineTemplateApi({ action: 'getUserType', diff --git a/libs/application/templates/inao/financial-statement-political-party/src/forms/prerequsites/prerequsitesSection.ts b/libs/application/templates/inao/financial-statement-political-party/src/forms/prerequsites/prerequsitesSection.ts index 40dca0ccb508..c10d83771cc3 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/forms/prerequsites/prerequsitesSection.ts +++ b/libs/application/templates/inao/financial-statement-political-party/src/forms/prerequsites/prerequsitesSection.ts @@ -8,7 +8,7 @@ import { import { m } from '../../lib/messages' import { CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, UserInfoApi, } from '../../dataProviders' import { DefaultEvents } from '@island.is/application/types' @@ -23,7 +23,7 @@ export const prerequisitesSection = buildSection({ checkboxLabel: m.dataCollectionCheckboxLabel, dataProviders: [ buildDataProviderItem({ - provider: IndentityApiProvider, + provider: IdentityApiProvider, title: m.dataCollectionNationalRegistryTitle, subTitle: m.dataCollectionNationalRegistrySubtitle, }), diff --git a/libs/application/templates/inao/financial-statement-political-party/src/lib/financialStatementPoliticalPartyTemplate.ts b/libs/application/templates/inao/financial-statement-political-party/src/lib/financialStatementPoliticalPartyTemplate.ts index 627860da4841..3025138a3546 100644 --- a/libs/application/templates/inao/financial-statement-political-party/src/lib/financialStatementPoliticalPartyTemplate.ts +++ b/libs/application/templates/inao/financial-statement-political-party/src/lib/financialStatementPoliticalPartyTemplate.ts @@ -19,7 +19,7 @@ import { } from '@island.is/application/core' import { CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, NationalRegistryUserApi, UserInfoApi, } from '../dataProviders' @@ -81,7 +81,7 @@ const FinancialStatementPoliticalPartyTemplate: ApplicationTemplate< delete: true, api: [ CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, NationalRegistryUserApi, UserInfoApi, ], @@ -122,7 +122,7 @@ const FinancialStatementPoliticalPartyTemplate: ApplicationTemplate< delete: true, api: [ CurrentUserTypeProvider, - IndentityApiProvider, + IdentityApiProvider, NationalRegistryUserApi, UserInfoApi, ], From 1073e70c28f1bdca94f308f2f26fd660716b14b2 Mon Sep 17 00:00:00 2001 From: mannipje <135017126+mannipje@users.noreply.github.com> Date: Tue, 10 Dec 2024 09:16:51 +0000 Subject: [PATCH 46/57] fix(web): Fix query for generic and team member lists (#17115) * Fix query for generic and team member lists * Add tags to be searchable in gengeric list search * Add title to be searchable in team member list search --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- libs/cms/src/lib/cms.elasticsearch.service.ts | 12 ++++-------- .../search/importers/genericListItem.service.ts | 4 ++++ .../src/lib/search/importers/teamList.service.ts | 15 ++++++++++++--- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/libs/cms/src/lib/cms.elasticsearch.service.ts b/libs/cms/src/lib/cms.elasticsearch.service.ts index 6b2b5c5d96a3..e35685d85964 100644 --- a/libs/cms/src/lib/cms.elasticsearch.service.ts +++ b/libs/cms/src/lib/cms.elasticsearch.service.ts @@ -509,7 +509,9 @@ export class CmsElasticsearchService { }, ] - let queryString = input.queryString ? input.queryString.toLowerCase() : '' + let queryString = input.queryString + ? input.queryString.trim().toLowerCase() + : '' if (input.lang === 'is') { queryString = queryString.replace('`', '') @@ -520,6 +522,7 @@ export class CmsElasticsearchService { query: queryString + '*', fields: ['title^100', 'content'], analyze_wildcard: true, + default_operator: 'and', }, }) @@ -531,15 +534,8 @@ export class CmsElasticsearchService { order: SortDirection.DESC, }, }, - // Sort items with equal values by ascending title order - { 'title.sort': { order: SortDirection.ASC } }, ] - // Order by score first in case there is a query string - if (queryString.length > 0 && queryString !== '*') { - sort.unshift('_score') - } - if (input.tags && input.tags.length > 0 && input.tagGroups) { must = must.concat( generateGenericTagGroupQueries(input.tags, input.tagGroups), diff --git a/libs/cms/src/lib/search/importers/genericListItem.service.ts b/libs/cms/src/lib/search/importers/genericListItem.service.ts index a55d8e21905f..adf56bed139e 100644 --- a/libs/cms/src/lib/search/importers/genericListItem.service.ts +++ b/libs/cms/src/lib/search/importers/genericListItem.service.ts @@ -51,6 +51,10 @@ export class GenericListItemSyncService ) } + for (const tag of mapped.filterTags ?? []) { + contentSections.push(tag.title) + } + const content = contentSections.join(' ') const tags: MappedData['tags'] = diff --git a/libs/cms/src/lib/search/importers/teamList.service.ts b/libs/cms/src/lib/search/importers/teamList.service.ts index 86fc4699cf9b..57cf25f54878 100644 --- a/libs/cms/src/lib/search/importers/teamList.service.ts +++ b/libs/cms/src/lib/search/importers/teamList.service.ts @@ -31,9 +31,18 @@ export class TeamListSyncService implements CmsSyncProvider { const memberEntry = teamListEntry.fields.teamMembers?.find( (m) => m.sys.id === member.id, ) - const content = memberEntry?.fields?.intro - ? documentToPlainTextString(memberEntry.fields.intro) - : '' + const contentSection: string[] = [] + + contentSection.push( + memberEntry?.fields?.intro + ? documentToPlainTextString(memberEntry.fields.intro) + : '', + ) + if (member.title) { + contentSection.push(member.title) + } + + const content = contentSection.join(' ') teamMembers.push({ _id: member.id, title: member.name, From 5ddb6629704a86e82cce10106a0ba8a62d5217e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81sd=C3=ADs=20Erna=20Gu=C3=B0mundsd=C3=B3ttir?= Date: Tue, 10 Dec 2024 09:47:18 +0000 Subject: [PATCH 47/57] fix(my-pages): small tweaks for vaccinations (#17138) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../clients/vaccinations/clientConfig.json | 14 +++++++++- .../SortableTable/SortableTable.tsx | 18 +++++++----- .../screens/HealthOverview/HealthOverview.tsx | 28 +++++++++---------- .../Vaccinations/VaccinationsWrapper.tsx | 25 ++++++++--------- .../tables/SortedVaccinationsTable.tsx | 8 +++--- 5 files changed, 53 insertions(+), 40 deletions(-) diff --git a/libs/clients/health-directorate/src/lib/clients/vaccinations/clientConfig.json b/libs/clients/health-directorate/src/lib/clients/vaccinations/clientConfig.json index a17038288d7a..37ea7918d8d7 100644 --- a/libs/clients/health-directorate/src/lib/clients/vaccinations/clientConfig.json +++ b/libs/clients/health-directorate/src/lib/clients/vaccinations/clientConfig.json @@ -597,7 +597,7 @@ "schema": { "type": "string" } }, { - "name": "age", + "name": "agePatient", "required": true, "in": "query", "schema": { "type": "number" } @@ -613,6 +613,12 @@ "required": true, "in": "query", "schema": { "type": "array", "items": { "type": "string" } } + }, + { + "name": "vaccineCodes", + "required": true, + "in": "query", + "schema": { "type": "array", "items": { "type": "string" } } } ], "responses": { @@ -820,6 +826,7 @@ "diseaseId": { "type": "string" }, "order": { "type": "number" }, "type": { "type": "object" }, + "vaccineCodes": { "type": "string" }, "cond1Type": { "type": "string" }, "cond1Min": { "type": "number" }, "cond1Max": { "type": "number" }, @@ -859,6 +866,7 @@ "description": { "type": "string" }, "isFeatured": { "type": "boolean" }, "isVisible": { "type": "boolean" }, + "hideIfNoVaccinations": { "type": "boolean" }, "vaccines": { "type": "array", "items": { "$ref": "#/components/schemas/VaccineDiseaseDto" } @@ -877,6 +885,7 @@ "name", "isFeatured", "isVisible", + "hideIfNoVaccinations", "vaccines", "rules", "translations" @@ -890,6 +899,7 @@ "description": { "type": "string" }, "isFeatured": { "type": "boolean" }, "isVisible": { "type": "boolean" }, + "hideIfNoVaccinations": { "type": "boolean" }, "vaccines": { "type": "array", "items": { "$ref": "#/components/schemas/VaccineDiseaseDto" } @@ -909,6 +919,7 @@ "properties": { "order": { "type": "number" }, "type": { "type": "object" }, + "vaccineCodes": { "type": "string" }, "cond1Type": { "type": "string" }, "cond1Min": { "type": "number" }, "cond1Max": { "type": "number" }, @@ -938,6 +949,7 @@ "diseaseId": { "type": "string" }, "order": { "type": "number" }, "type": { "type": "object" }, + "vaccineCodes": { "type": "string" }, "cond1Type": { "type": "string" }, "cond1Min": { "type": "number" }, "cond1Max": { "type": "number" }, diff --git a/libs/portals/my-pages/core/src/components/SortableTable/SortableTable.tsx b/libs/portals/my-pages/core/src/components/SortableTable/SortableTable.tsx index 1ae2abb073c9..3d9ad89bf3eb 100644 --- a/libs/portals/my-pages/core/src/components/SortableTable/SortableTable.tsx +++ b/libs/portals/my-pages/core/src/components/SortableTable/SortableTable.tsx @@ -1,13 +1,13 @@ -import React, { useMemo, useState } from 'react' import { - Text, - Table as T, Icon, - TagVariant, + Table as T, Tag, + TagVariant, + Text, } from '@island.is/island-ui/core' -import * as styles from './SortableTable.css' +import React, { useMemo, useState } from 'react' import { ExpandHeader, ExpandRow } from '../ExpandableTable' +import * as styles from './SortableTable.css' type ConfigType = { direction: 'ascending' | 'descending'; key: string } @@ -166,7 +166,7 @@ export const SortableTable = (props: SortableTableProps) => { data={valueItems.map((valueItem, i) => ({ value: valueItems.length - 1 === i && tag ? ( - + {valueItem} ) : ( @@ -187,7 +187,11 @@ export const SortableTable = (props: SortableTableProps) => { return ( {lastItem && tag ? ( - + {valueItem} ) : ( diff --git a/libs/portals/my-pages/health/src/screens/HealthOverview/HealthOverview.tsx b/libs/portals/my-pages/health/src/screens/HealthOverview/HealthOverview.tsx index 990a8e0e11e4..b7e5ae865dbe 100644 --- a/libs/portals/my-pages/health/src/screens/HealthOverview/HealthOverview.tsx +++ b/libs/portals/my-pages/health/src/screens/HealthOverview/HealthOverview.tsx @@ -1,20 +1,18 @@ -import { useUserInfo } from '@island.is/react-spa/bff' import { AlertMessage, Box, - Text, Button, GridColumn, GridRow, + Icon, SkeletonLoader, Stack, + Text, toast, - Icon, } from '@island.is/island-ui/core' import { useLocale, useNamespaces } from '@island.is/localization' -import { Problem } from '@island.is/react-spa/shared' import { - IntroHeader, + IntroWrapper, SJUKRATRYGGINGAR_SLUG, StackWithBottomDivider, UserInfoLine, @@ -24,6 +22,8 @@ import { isDateAfterToday, m, } from '@island.is/portals/my-pages/core' +import { useUserInfo } from '@island.is/react-spa/bff' +import { Problem } from '@island.is/react-spa/shared' import { useEffect, useState } from 'react' import { messages } from '../../lib/messages' import { HealthPaths } from '../../lib/paths' @@ -85,15 +85,13 @@ export const HealthOverview = () => { ) return ( - - - - + {error ? ( ) : loading ? ( @@ -209,7 +207,7 @@ export const HealthOverview = () => { )} )} - + ) } diff --git a/libs/portals/my-pages/health/src/screens/Vaccinations/VaccinationsWrapper.tsx b/libs/portals/my-pages/health/src/screens/Vaccinations/VaccinationsWrapper.tsx index 82aa962b2be4..67c57e727ff6 100644 --- a/libs/portals/my-pages/health/src/screens/Vaccinations/VaccinationsWrapper.tsx +++ b/libs/portals/my-pages/health/src/screens/Vaccinations/VaccinationsWrapper.tsx @@ -1,17 +1,17 @@ -import { useLocale, useNamespaces } from '@island.is/localization' import { Box, SkeletonLoader, Tabs } from '@island.is/island-ui/core' +import { useLocale, useNamespaces } from '@island.is/localization' import { + EmptyTable, HEALTH_DIRECTORATE_SLUG, - IntroHeader, + IntroWrapper, LinkButton, - EmptyTable, } from '@island.is/portals/my-pages/core' +import { Problem } from '@island.is/react-spa/shared' +import { isDefined } from '@island.is/shared/utils' import { messages as m } from '../../lib/messages' import { SECTION_GAP } from '../../utils/constants' import { useGetVaccinationsQuery } from './Vaccinations.generated' import { SortedVaccinationsTable } from './tables/SortedVaccinationsTable' -import { isDefined } from '@island.is/shared/utils' -import { Problem } from '@island.is/react-spa/shared' export const VaccinationsWrapper = () => { useNamespaces('sp.health') @@ -39,13 +39,12 @@ export const VaccinationsWrapper = () => { ].filter(isDefined) return ( - - + {/* Buttons */} { /> )} - + ) } export default VaccinationsWrapper diff --git a/libs/portals/my-pages/health/src/screens/Vaccinations/tables/SortedVaccinationsTable.tsx b/libs/portals/my-pages/health/src/screens/Vaccinations/tables/SortedVaccinationsTable.tsx index b0918f667bdd..ad380413cff9 100644 --- a/libs/portals/my-pages/health/src/screens/Vaccinations/tables/SortedVaccinationsTable.tsx +++ b/libs/portals/my-pages/health/src/screens/Vaccinations/tables/SortedVaccinationsTable.tsx @@ -1,3 +1,5 @@ +import { HealthDirectorateVaccination } from '@island.is/api/schema' +import { Box } from '@island.is/island-ui/core' import { useLocale, useNamespaces } from '@island.is/localization' import { EmptyTable, @@ -6,10 +8,8 @@ import { } from '@island.is/portals/my-pages/core' import { messages } from '../../../lib/messages' import { tagSelector } from '../../../utils/tagSelector' -import { VaccinationsDetailTable } from './VaccinationsDetailTable' import { DetailHeader, DetailRow } from '../../../utils/types' -import { HealthDirectorateVaccination } from '@island.is/api/schema' -import { Box } from '@island.is/island-ui/core' +import { VaccinationsDetailTable } from './VaccinationsDetailTable' interface Props { data?: Array @@ -48,7 +48,7 @@ export const SortedVaccinationsTable = ({ data }: Props) => { }} tagOutlined expandable - defaultSortByKey="vaccine" + defaultSortByKey="status" items={ data.map((item, i) => ({ id: item?.id ?? `${i}`, From fcadfe5dc7e799f4cc683ef829b6de25cd7dbfa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9tur=20Neisti=20Erlingsson?= Date: Tue, 10 Dec 2024 09:57:16 +0000 Subject: [PATCH 48/57] chore: Helm template support for ArgoCD (#17000) * chore: Helm template support for ArgoCD * stuff * Fix helm template name for deployment * Fix helm template name for pdb and secrets * Put namespaces in same mono template * Fix namespace template to work without spinnaker --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../libs/api-template/templates/deployment.yaml | 16 ++++++++-------- infra/helm/libs/api-template/templates/hpa.yaml | 7 ++++--- .../libs/api-template/templates/ingress.yaml | 5 +++-- .../libs/api-template/templates/namespaces.yaml | 8 ++++++++ infra/helm/libs/api-template/templates/pdb.yaml | 2 +- .../libs/api-template/templates/secrets.yaml | 4 ++-- .../libs/api-template/templates/service.yaml | 2 +- .../libs/cronjob-template/templates/cronjob.yaml | 12 +++++++----- 8 files changed, 34 insertions(+), 22 deletions(-) create mode 100644 infra/helm/libs/api-template/templates/namespaces.yaml diff --git a/infra/helm/libs/api-template/templates/deployment.yaml b/infra/helm/libs/api-template/templates/deployment.yaml index e1ff2f6676a3..dd7d9fe970f0 100644 --- a/infra/helm/libs/api-template/templates/deployment.yaml +++ b/infra/helm/libs/api-template/templates/deployment.yaml @@ -1,9 +1,9 @@ {{- if .Values.enabled }} -{{- $fullName := include "api-template.fullname" . -}} +{{- $fullName := include "api-template.name" . -}} apiVersion: apps/v1 kind: Deployment metadata: - name: {{ include "api-template.fullname" . }} + name: {{ include "api-template.name" . }} {{- if .Values.namespace }} namespace: {{ .Values.namespace }} {{- end }} @@ -11,7 +11,7 @@ metadata: {{- include "api-template.labels" . | nindent 4 }} tags.datadoghq.com/env: {{ .Values.global.env.name }} tags.datadoghq.com/service: {{ include "api-template.name" . }} - tags.datadoghq.com/version: {{ .Values.image.tag | default .Values.global.image.tag }} + tags.datadoghq.com/version: {{ .Values.image.tag }} spec: replicas: {{ .Values.replicaCount.default }} strategy: @@ -25,7 +25,7 @@ spec: {{- include "api-template.selectorLabels" . | nindent 8 }} tags.datadoghq.com/env: {{ .Values.global.env.name }} tags.datadoghq.com/service: {{ include "api-template.name" . }} - tags.datadoghq.com/version: {{ .Values.image.tag | default .Values.global.image.tag }} + tags.datadoghq.com/version: {{ .Values.image.tag }} annotations: prometheus.io/scrape: "true" prometheus.io/path: /metrics @@ -78,7 +78,7 @@ spec: - name: {{ .name | default "migration" }} securityContext: {{- toYaml $.Values.securityContext | nindent 12 }} - image: "{{ if .image }}{{ .image }}{{ else }}{{ $.Values.image.repository }}:{{ $.Values.image.tag | default $.Values.global.image.tag }}{{ end }}" + image: "{{ if .image }}{{ .image }}{{ else }}{{ $.Values.image.repository }}:{{ $.Values.image.tag }}{{ end }}" command: {{ .command | toJson }} args: {{ .args | toJson }} env: @@ -98,7 +98,7 @@ spec: key: {{ $key }} {{- end }} - name: APP_VERSION - value: {{ $.Values.image.tag | default $.Values.global.image.tag }} + value: {{ $.Values.image.tag }} - name: DD_ENV valueFrom: fieldRef: @@ -132,7 +132,7 @@ spec: - name: {{ .Chart.Name }} securityContext: {{- toYaml .Values.securityContext | nindent 12 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Values.global.image.tag }}" + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" imagePullPolicy: {{ .Values.image.pullPolicy }} {{- if .Values.command }} command: {{ .Values.command | toJson }} @@ -169,7 +169,7 @@ spec: key: {{ $key }} {{- end }} - name: APP_VERSION - value: {{ .Values.image.tag | default .Values.global.image.tag }} + value: {{ .Values.image.tag }} - name: DD_ENV valueFrom: fieldRef: diff --git a/infra/helm/libs/api-template/templates/hpa.yaml b/infra/helm/libs/api-template/templates/hpa.yaml index a8e0b5ebfffb..ec3fabece354 100644 --- a/infra/helm/libs/api-template/templates/hpa.yaml +++ b/infra/helm/libs/api-template/templates/hpa.yaml @@ -1,9 +1,10 @@ {{- if .Values.enabled }} {{- $fullName := include "api-template.fullname" . -}} +{{- $appName := include "api-template.name" . -}} apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: - name: {{ include "api-template.fullname" . }} + name: {{ $appName }} {{- if .Values.namespace }} namespace: {{ .Values.namespace }} {{- end }} @@ -13,7 +14,7 @@ spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment - name: {{ include "api-template.fullname" . }} + name: {{ $appName }} minReplicas: {{ .Values.hpa.scaling.replicas.min }} maxReplicas: {{ .Values.hpa.scaling.replicas.max }} behavior: @@ -37,7 +38,7 @@ spec: name: nginx_ingress_controller_requests_rate selector: matchLabels: - ingress: "{{ $fullName }}-{{ $name }}" + ingress: "{{ $name }}" target: type: AverageValue averageValue: {{ $.Values.hpa.scaling.metric.nginxRequestsIrate }} diff --git a/infra/helm/libs/api-template/templates/ingress.yaml b/infra/helm/libs/api-template/templates/ingress.yaml index 3b53d924d9b7..366293c1948d 100644 --- a/infra/helm/libs/api-template/templates/ingress.yaml +++ b/infra/helm/libs/api-template/templates/ingress.yaml @@ -1,5 +1,6 @@ {{- if .Values.enabled }} {{- $fullName := include "api-template.fullname" . -}} +{{- $appName := include "api-template.name" . -}} {{- $labels := include "api-template.labels" . -}} {{- $svcPort := $.Values.service.port -}} {{- range $name, $ingress := omit .Values.ingress "behindCloudfront" }} @@ -10,7 +11,7 @@ apiVersion: extensions/v1beta1 {{- end }} kind: Ingress metadata: - name: "{{ $fullName }}-{{ $name }}" + name: "{{ $appName }}-{{ $name }}" {{- if $.Values.namespace }} namespace: {{ $.Values.namespace }} {{- end }} @@ -38,7 +39,7 @@ spec: - pathType: Prefix backend: service: - name: {{ $fullName }} + name: {{ $appName }} port: number: {{ $svcPort }} {{- if eq (kindOf .) "map" }} diff --git a/infra/helm/libs/api-template/templates/namespaces.yaml b/infra/helm/libs/api-template/templates/namespaces.yaml new file mode 100644 index 000000000000..4a4755e99d09 --- /dev/null +++ b/infra/helm/libs/api-template/templates/namespaces.yaml @@ -0,0 +1,8 @@ +{{- if .Values.enabled }} +{{- if .Values.namespace}} +apiVersion: v1 +kind: Namespace +metadata: + name: {{ .Values.namespace }} +{{- end }} +{{- end }} diff --git a/infra/helm/libs/api-template/templates/pdb.yaml b/infra/helm/libs/api-template/templates/pdb.yaml index 3b3534fd8eb4..3204babe69f3 100644 --- a/infra/helm/libs/api-template/templates/pdb.yaml +++ b/infra/helm/libs/api-template/templates/pdb.yaml @@ -7,7 +7,7 @@ apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: {{ $serviceName }}-pdb - namespace: {{ $namespace }} + namespace: {{ $namespace }} spec: {{- if hasKey .Values.podDisruptionBudget "minAvailable" }} minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} diff --git a/infra/helm/libs/api-template/templates/secrets.yaml b/infra/helm/libs/api-template/templates/secrets.yaml index 602dc8afb508..2cb9fe9f680a 100644 --- a/infra/helm/libs/api-template/templates/secrets.yaml +++ b/infra/helm/libs/api-template/templates/secrets.yaml @@ -6,7 +6,7 @@ metadata: {{- if .Values.namespace }} namespace: {{ .Values.namespace }} {{- end }} - name: {{ include "api-template.fullname" . }} + name: {{ include "api-template.name" . }} spec: backendType: systemManager data: @@ -23,7 +23,7 @@ metadata: {{- if .Values.namespace }} namespace: {{ .Values.namespace }} {{- end }} - name: {{ include "api-template.fullname" . }}-init-container + name: {{ include "api-template.name" . }}-init-container spec: backendType: systemManager data: diff --git a/infra/helm/libs/api-template/templates/service.yaml b/infra/helm/libs/api-template/templates/service.yaml index 5b7e61223d1b..25bbde4f30c2 100644 --- a/infra/helm/libs/api-template/templates/service.yaml +++ b/infra/helm/libs/api-template/templates/service.yaml @@ -2,7 +2,7 @@ apiVersion: v1 kind: Service metadata: - name: {{ include "api-template.fullname" . }} + name: {{ include "api-template.name" . }} {{- if .Values.namespace }} namespace: {{ .Values.namespace }} {{- end }} diff --git a/infra/helm/libs/cronjob-template/templates/cronjob.yaml b/infra/helm/libs/cronjob-template/templates/cronjob.yaml index a3892dce80ce..9a10878ca56f 100644 --- a/infra/helm/libs/cronjob-template/templates/cronjob.yaml +++ b/infra/helm/libs/cronjob-template/templates/cronjob.yaml @@ -1,5 +1,6 @@ +{{- if .Values.schedule }} {{- if .Values.enabled }} -{{- $fullName := include "cronjob-template.fullname" . -}} +{{- $fullName := include "api-template.fullname" . -}} apiVersion: batch/v1 kind: CronJob metadata: @@ -12,7 +13,7 @@ metadata: chart: "{{ $.Chart.Name }}-{{ $.Chart.Version | replace "+" "_" }}" tags.datadoghq.com/env: {{ .Values.global.env.name }} tags.datadoghq.com/service: {{ include "cronjob-template.name" . }} - tags.datadoghq.com/version: {{ .Values.image.tag | default .Values.global.image.tag }} + tags.datadoghq.com/version: {{ .Values.image.tag }} spec: concurrencyPolicy: {{ .Values.concurrencyPolicy | default "Allow" }} failedJobsHistoryLimit: {{ .Values.failedJobsHistoryLimit | default 1 }} @@ -52,13 +53,13 @@ spec: chart: "{{ $.Chart.Name }}-{{ $.Chart.Version | replace "+" "_" }}" tags.datadoghq.com/env: {{ .Values.global.env.name }} tags.datadoghq.com/service: {{ include "cronjob-template.name" . }} - tags.datadoghq.com/version: {{ .Values.image.tag | default .Values.global.image.tag }} + tags.datadoghq.com/version: {{ .Values.image.tag }} spec: serviceAccountName: {{ include "cronjob-template.serviceAccountName" $ }} securityContext: {{- toYaml $.Values.podSecurityContext | nindent 12 }} containers: - - image: "{{ $.Values.image.repository }}:{{ $.Values.image.tag | default .Values.global.image.tag }}" + - image: "{{ $.Values.image.repository }}:{{ $.Values.image.tag }}" name: {{ .Chart.Name }} securityContext: {{- toYaml $.Values.securityContext | nindent 14 }} @@ -92,7 +93,7 @@ spec: key: {{ $key }} {{- end }} - name: APP_VERSION - value: {{ .Values.image.tag | default .Values.global.image.tag }} + value: {{ .Values.image.tag }} - name: DD_ENV valueFrom: fieldRef: @@ -145,3 +146,4 @@ spec: {{- end }} restartPolicy: {{ .Values.restartPolicy | default "Never" }} {{- end }} +{{- end }} From 1ebd6719163fca2ac571d7d7b68184f9a58119e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eir=C3=ADkur=20Hei=C3=B0ar=20Nilsson?= Date: Tue, 10 Dec 2024 11:27:11 +0000 Subject: [PATCH 49/57] feat: Add codeOwner to logging context (#17172) * feat: Add codeOwner to logging context * Fix tests * fix: Silence warning in development --- libs/infra-tracing/src/lib/code-owner.spec.ts | 48 +++++++++++++++--- libs/infra-tracing/src/lib/code-owner.ts | 16 +++--- libs/logging/src/lib/context.spec.ts | 49 +++++++++++++++++- libs/logging/src/lib/context.ts | 4 ++ .../code-owner/code-owner.interceptor.spec.ts | 50 +++++++++++++++---- .../lib/code-owner/code-owner.interceptor.ts | 5 +- 6 files changed, 143 insertions(+), 29 deletions(-) diff --git a/libs/infra-tracing/src/lib/code-owner.spec.ts b/libs/infra-tracing/src/lib/code-owner.spec.ts index ed860dbd7a97..3aed2bbb1d55 100644 --- a/libs/infra-tracing/src/lib/code-owner.spec.ts +++ b/libs/infra-tracing/src/lib/code-owner.spec.ts @@ -1,12 +1,12 @@ -import { setCodeOwner } from './code-owner' +import { withCodeOwner } from './code-owner' import { CodeOwners } from '@island.is/shared/constants' -import { logger } from '@island.is/logging' +import { logger, withLoggingContext } from '@island.is/logging' import tracer from 'dd-trace' jest.mock('dd-trace') jest.mock('@island.is/logging') -describe('setCodeOwner', () => { +describe('withCodeOwner', () => { let mockSpan: { setTag: jest.Mock } let mockScope: jest.Mock @@ -19,31 +19,63 @@ describe('setCodeOwner', () => { })) ;(tracer.scope as jest.Mock) = mockScope ;(logger.warn as jest.Mock).mockClear() + ;(withLoggingContext as jest.Mock).mockImplementation( + (_, callback, ...args) => callback(...args), + ) }) - it('should set code owner tag on active span', () => { + it('should set code owner tag on active span and call the callback', () => { + // Arrange + const mockCallback = jest.fn() + const mockArgs = ['arg1', 'arg2'] + // Act - setCodeOwner(CodeOwners.Core) + withCodeOwner(CodeOwners.Core, mockCallback, ...mockArgs) // Assert expect(mockSpan.setTag).toHaveBeenCalledWith('codeOwner', CodeOwners.Core) + expect(withLoggingContext).toHaveBeenCalledWith( + { codeOwner: CodeOwners.Core }, + mockCallback, + ...mockArgs, + ) + expect(mockCallback).toHaveBeenCalledWith(...mockArgs) expect(logger.warn).not.toHaveBeenCalled() }) - it('should log warning when no active span exists', () => { + it('should log warning when no active span exists and still call the callback', () => { // Arrange mockScope = jest.fn(() => ({ active: () => null, })) ;(tracer.scope as jest.Mock) = mockScope + const mockCallback = jest.fn() + const mockArgs = ['arg1', 'arg2'] // Act - setCodeOwner(CodeOwners.Core) + withCodeOwner(CodeOwners.Core, mockCallback, ...mockArgs) // Assert expect(logger.warn).toHaveBeenCalledWith( 'Setting code owner "core" with no active dd-trace span', - { stack: expect.any(String) }, ) + expect(withLoggingContext).toHaveBeenCalledWith( + { codeOwner: CodeOwners.Core }, + mockCallback, + ...mockArgs, + ) + expect(mockCallback).toHaveBeenCalledWith(...mockArgs) + }) + + it('should return the callback result', () => { + // Arrange + const expectedResult = { foo: 'bar' } + const mockCallback = jest.fn().mockReturnValue(expectedResult) + + // Act + const result = withCodeOwner(CodeOwners.Core, mockCallback) + + // Assert + expect(result).toBe(expectedResult) }) }) diff --git a/libs/infra-tracing/src/lib/code-owner.ts b/libs/infra-tracing/src/lib/code-owner.ts index 7d4d6352fe52..f0d085dbf4dc 100644 --- a/libs/infra-tracing/src/lib/code-owner.ts +++ b/libs/infra-tracing/src/lib/code-owner.ts @@ -1,22 +1,26 @@ -import { logger } from '@island.is/logging' +import { logger, withLoggingContext } from '@island.is/logging' import { CodeOwners } from '@island.is/shared/constants' import tracer from 'dd-trace' /** - * Sets a code owner for the current dd-trace span. + * Sets a code owner for the current dd-trace span and all nested log entries. * * The assumption here is that each trace / request has only one "dynamic" * code owner. This way we skip cluttering the trace with extra spans. */ -export const setCodeOwner = (codeOwner: CodeOwners) => { +export const withCodeOwner = ( + codeOwner: CodeOwners, + callback: (...args: TArgs) => R, + ...args: TArgs +) => { const span = tracer.scope().active() if (span) { span.setTag('codeOwner', codeOwner) - } else { - const stack = new Error().stack + } else if (process.env.NODE_ENV !== 'development') { logger.warn( `Setting code owner "${codeOwner}" with no active dd-trace span`, - { stack }, ) } + + return withLoggingContext({ codeOwner }, callback, ...args) } diff --git a/libs/logging/src/lib/context.spec.ts b/libs/logging/src/lib/context.spec.ts index ca371ba27376..28a44634868c 100644 --- a/libs/logging/src/lib/context.spec.ts +++ b/libs/logging/src/lib/context.spec.ts @@ -1,3 +1,4 @@ +import { CodeOwners } from '@island.is/shared/constants' import { includeContextFormatter, withLoggingContext } from './context' describe('Winston context', () => { @@ -11,6 +12,50 @@ describe('Winston context', () => { process.env = originalEnv }) + it('should add default CODE_OWNER when environment variable is set', () => { + // Arrange + process.env.CODE_OWNER = 'default-team' + const formatter = includeContextFormatter() + const logInfo = { + level: 'info', + message: 'Test message', + } + + // Act + const formattedLog = formatter.transform(logInfo) + + // Assert + expect(formattedLog).toEqual({ + level: 'info', + message: 'Test message', + codeOwner: 'default-team', + }) + }) + + it('should override default CODE_OWNER with context codeOwner', () => { + // Arrange + process.env.CODE_OWNER = 'default-team' + const formatter = includeContextFormatter() + const logInfo = { + level: 'info', + message: 'Test message', + } + const context = { codeOwner: 'context-team' as CodeOwners } + + // Act + let formattedLog: unknown + withLoggingContext(context, () => { + formattedLog = formatter.transform(logInfo) + }) + + // Assert + expect(formattedLog).toEqual({ + level: 'info', + message: 'Test message', + codeOwner: 'context-team', + }) + }) + it('should add context to log info object', () => { // Arrange const formatter = includeContextFormatter() @@ -35,7 +80,7 @@ describe('Winston context', () => { }) }) - it('should not modify log info when no context exists', () => { + it('should not modify log info when no context or CODE_OWNER exists', () => { // Arrange const formatter = includeContextFormatter() const logInfo = { @@ -52,6 +97,7 @@ describe('Winston context', () => { it('should preserve existing log info properties when adding context', () => { // Arrange + process.env.CODE_OWNER = 'default-team' const formatter = includeContextFormatter() const logInfo = { level: 'info', @@ -71,6 +117,7 @@ describe('Winston context', () => { level: 'info', message: 'Test message', existingProp: 'should remain', + codeOwner: 'default-team', requestId: '123', }) }) diff --git a/libs/logging/src/lib/context.ts b/libs/logging/src/lib/context.ts index 03034f67d79d..c27e9652305d 100644 --- a/libs/logging/src/lib/context.ts +++ b/libs/logging/src/lib/context.ts @@ -19,8 +19,12 @@ export const withLoggingContext = ( } export const includeContextFormatter = format((info) => { + const defaultCodeOwner = process.env.CODE_OWNER const context = loggingContextStorage.getStore() + if (defaultCodeOwner) { + info.codeOwner = defaultCodeOwner + } if (context) { Object.assign(info, context) } diff --git a/libs/nest/core/src/lib/code-owner/code-owner.interceptor.spec.ts b/libs/nest/core/src/lib/code-owner/code-owner.interceptor.spec.ts index 038802b02237..54ffa4260584 100644 --- a/libs/nest/core/src/lib/code-owner/code-owner.interceptor.spec.ts +++ b/libs/nest/core/src/lib/code-owner/code-owner.interceptor.spec.ts @@ -1,4 +1,4 @@ -import { setCodeOwner } from '@island.is/infra-tracing' +import { withCodeOwner } from '@island.is/infra-tracing' import { CodeOwners } from '@island.is/shared/constants' import { Controller, Get, INestApplication } from '@nestjs/common' import { APP_INTERCEPTOR } from '@nestjs/core' @@ -9,7 +9,7 @@ import { CodeOwnerInterceptor } from './code-owner.interceptor' // Mock the logging module jest.mock('@island.is/infra-tracing', () => ({ - setCodeOwner: jest.fn(), + withCodeOwner: jest.fn((codeOwner, callback) => callback()), })) // Test controller with decorated endpoints @@ -50,26 +50,29 @@ describe('CodeOwnerInterceptor', () => { jest.clearAllMocks() }) - it('should call setCodeOwner when CodeOwner decorator is present', async () => { + it('should call withCodeOwner when CodeOwner decorator is present', async () => { // Make request to endpoint with CodeOwner decorator await request(app.getHttpServer()) .get('/test/with-owner') .expect(200) .expect({ message: 'with owner' }) - // Verify that setCodeOwner was called with correct parameters - expect(setCodeOwner).toHaveBeenCalledWith(CodeOwners.Core) + // Verify that withCodeOwner was called with correct parameters + expect(withCodeOwner).toHaveBeenCalledWith( + CodeOwners.Core, + expect.any(Function), + ) }) - it('should not call setCodeOwner when CodeOwner decorator is not present', async () => { + it('should not call withCodeOwner when CodeOwner decorator is not present', async () => { // Make request to endpoint without CodeOwner decorator await request(app.getHttpServer()) .get('/test/without-owner') .expect(200) .expect({ message: 'without owner' }) - // Verify that setCodeOwner was not called - expect(setCodeOwner).not.toHaveBeenCalled() + // Verify that withCodeOwner was not called + expect(withCodeOwner).not.toHaveBeenCalled() }) it('should handle multiple requests correctly', async () => { @@ -80,8 +83,33 @@ describe('CodeOwnerInterceptor', () => { request(app.getHttpServer()).get('/test/with-owner'), ]) - // Verify that setCodeOwner was called exactly twice (for the two 'with-owner' requests) - expect(setCodeOwner).toHaveBeenCalledTimes(2) - expect(setCodeOwner).toHaveBeenCalledWith(CodeOwners.Core) + // Verify that withCodeOwner was called exactly twice (for the two 'with-owner' requests) + expect(withCodeOwner).toHaveBeenCalledTimes(2) + expect(withCodeOwner).toHaveBeenCalledWith( + CodeOwners.Core, + expect.any(Function), + ) + }) + + it('should properly wrap and execute the handler', async () => { + // Arrange + let handlerExecuted = false + ;(withCodeOwner as jest.Mock).mockImplementation((codeOwner, callback) => { + handlerExecuted = true + return callback() + }) + + // Act + await request(app.getHttpServer()) + .get('/test/with-owner') + .expect(200) + .expect({ message: 'with owner' }) + + // Assert + expect(handlerExecuted).toBe(true) + expect(withCodeOwner).toHaveBeenCalledWith( + CodeOwners.Core, + expect.any(Function), + ) }) }) diff --git a/libs/nest/core/src/lib/code-owner/code-owner.interceptor.ts b/libs/nest/core/src/lib/code-owner/code-owner.interceptor.ts index a9f992fccbc6..89134e97552c 100644 --- a/libs/nest/core/src/lib/code-owner/code-owner.interceptor.ts +++ b/libs/nest/core/src/lib/code-owner/code-owner.interceptor.ts @@ -1,4 +1,4 @@ -import { setCodeOwner } from '@island.is/infra-tracing' +import { withCodeOwner } from '@island.is/infra-tracing' import { CodeOwners } from '@island.is/shared/constants' import { Injectable, @@ -19,9 +19,8 @@ export class CodeOwnerInterceptor implements NestInterceptor { CODE_OWNER_KEY, [context.getHandler(), context.getClass()], ) - if (codeOwner) { - setCodeOwner(codeOwner) + return withCodeOwner(codeOwner, () => next.handle()) } return next.handle() } From 0309242375c9e9b9522fb7a3549a39d0a7dfca2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:59:26 +0000 Subject: [PATCH 50/57] feat(web): WHODAS calculator - Allow users to go to previous step using browser back (#17183) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../web/components/connected/WHODAS/Calculator.tsx | 14 ++++++++++++-- package.json | 2 +- yarn.lock | 12 ++++++------ 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/apps/web/components/connected/WHODAS/Calculator.tsx b/apps/web/components/connected/WHODAS/Calculator.tsx index fc96577ef4f3..cb2294fda8c5 100644 --- a/apps/web/components/connected/WHODAS/Calculator.tsx +++ b/apps/web/components/connected/WHODAS/Calculator.tsx @@ -7,6 +7,7 @@ import { } from 'react' import { useIntl } from 'react-intl' import round from 'lodash/round' +import { parseAsInteger, useQueryState } from 'next-usequerystate' import { Box, @@ -235,7 +236,15 @@ interface WHODASCalculatorProps { } export const WHODASCalculator = ({ slice }: WHODASCalculatorProps) => { - const [stepIndex, setStepIndex] = useState(0) + const [stepIndex, setStepIndex] = useQueryState( + 'stepIndex', + parseAsInteger + .withOptions({ + history: 'push', + clearOnDefault: true, + }) + .withDefault(0), + ) const formRef = useRef(null) const steps = (slice.json?.steps ?? []) as Step[] const initialRender = useRef(true) @@ -266,6 +275,7 @@ export const WHODASCalculator = ({ slice }: WHODASCalculatorProps) => { useEffect(() => { if (initialRender.current) { + setStepIndex(0) // Reset step index on initial render initialRender.current = false return } @@ -273,7 +283,7 @@ export const WHODASCalculator = ({ slice }: WHODASCalculatorProps) => { behavior: 'smooth', top: formRef.current?.offsetTop ?? 0, }) - }, [stepIndex]) + }, [setStepIndex, stepIndex]) if (showResults) { let totalScore = 0 diff --git a/package.json b/package.json index e78c508c8747..d27381ce9a8a 100644 --- a/package.json +++ b/package.json @@ -228,7 +228,7 @@ "next": "14.2.3", "next-auth": "3.29.10", "next-cookies": "2.0.3", - "next-usequerystate": "1.8.4", + "next-usequerystate": "1.20.0", "node-fetch": "2.6.7", "node-gyp": "9.1.0", "node-html-markdown": "1.3.0", diff --git a/yarn.lock b/yarn.lock index dc4f63250639..23df4b3c706e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38840,7 +38840,7 @@ __metadata: next-auth: 3.29.10 next-cookies: 2.0.3 next-secure-headers: 2.1.0 - next-usequerystate: 1.8.4 + next-usequerystate: 1.20.0 node-fetch: 2.6.7 node-gyp: 9.1.0 node-html-markdown: 1.3.0 @@ -44538,14 +44538,14 @@ __metadata: languageName: node linkType: hard -"next-usequerystate@npm:1.8.4": - version: 1.8.4 - resolution: "next-usequerystate@npm:1.8.4" +"next-usequerystate@npm:1.20.0": + version: 1.20.0 + resolution: "next-usequerystate@npm:1.20.0" dependencies: mitt: ^3.0.1 peerDependencies: - next: ^13.4 - checksum: 4ab19aa11fd32058246375bdcd7c76fdff357e15a460e80a30835b972b7458ab99e59e8ae38be26b32d27727b156b655c2a0105c200b994408fb26366133b496 + next: ">=13.4 <14.0.2 || ^14.0.3" + checksum: fbe915a99e2374eb43c4b1c7ab9b29f4bf46c4383ee66c61ecb13d768e5552f040a90b33a0df1675c20d0cab64d62035cea1b801512f936648af78726ec6860e languageName: node linkType: hard From 4a6defe9b969bfb01d7b624bdd5ec47638caf99a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Eorkell=20M=C3=A1ni=20=C3=9Eorkelsson?= Date: Tue, 10 Dec 2024 12:30:21 +0000 Subject: [PATCH 51/57] fix(social-insurance-maintenance): Disable payment plan with FF (#17181) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- libs/feature-flags/src/lib/features.ts | 3 + .../src/lib/messages.ts | 5 ++ .../src/screens/IncomePlan/IncomePlan.tsx | 32 --------- .../IncomePlanDetail/IncomePlanDetail.tsx | 65 +++++++++++++++---- 4 files changed, 60 insertions(+), 45 deletions(-) diff --git a/libs/feature-flags/src/lib/features.ts b/libs/feature-flags/src/lib/features.ts index fc77efa64398..5cc65a03a541 100644 --- a/libs/feature-flags/src/lib/features.ts +++ b/libs/feature-flags/src/lib/features.ts @@ -60,6 +60,9 @@ export enum Features { //New License service fetch enabled licensesV2 = 'isLicensesV2Enabled', + //Is social administration payment plan 2025 enabled? + isServicePortalPaymentPlan2025Enabled = 'isServicePortalPaymentPlan2025Enabled', + //Possible universities isUniversityOfAkureyriEnabled = 'isUniversityOfAkureyriEnabled', isAgriculturalUniversityOfIcelandEnabled = 'isAgriculturalUniversityOfIcelandEnabled', diff --git a/libs/portals/my-pages/social-insurance-maintenance/src/lib/messages.ts b/libs/portals/my-pages/social-insurance-maintenance/src/lib/messages.ts index 3cbf39c1838d..f90f69b54bab 100644 --- a/libs/portals/my-pages/social-insurance-maintenance/src/lib/messages.ts +++ b/libs/portals/my-pages/social-insurance-maintenance/src/lib/messages.ts @@ -122,4 +122,9 @@ export const m = defineMessages({ id: 'sp.social-insurance-maintenance:income-plan-link-text', defaultMessage: 'Hvað er tekjuáætlun?', }, + incomePlanTemporarilyUnavailable: { + id: 'sp.social-insurance-maintenance:income-plan-temporarily-unavailable', + defaultMessage: + 'Bráðabirgðatekjuáætlun 2025 verður tilbúin seinni part desember', + }, }) diff --git a/libs/portals/my-pages/social-insurance-maintenance/src/screens/IncomePlan/IncomePlan.tsx b/libs/portals/my-pages/social-insurance-maintenance/src/screens/IncomePlan/IncomePlan.tsx index 1197a088e110..7777a757b3bf 100644 --- a/libs/portals/my-pages/social-insurance-maintenance/src/screens/IncomePlan/IncomePlan.tsx +++ b/libs/portals/my-pages/social-insurance-maintenance/src/screens/IncomePlan/IncomePlan.tsx @@ -37,38 +37,6 @@ const parseSubtext = ( } } -const parseTag = ( - tag: SocialInsuranceIncomePlanStatus, - formatMessage: FormatMessage, -) => { - switch (tag) { - case SocialInsuranceIncomePlanStatus.ACCEPTED: - return { - label: formatMessage(coreMessages.processed), - variant: 'mint' as const, - outlined: false, - } - case SocialInsuranceIncomePlanStatus.IN_PROGRESS: - return { - label: formatMessage(coreMessages.inProgress), - variant: 'purple' as const, - outlined: false, - } - case SocialInsuranceIncomePlanStatus.CANCELLED: - return { - label: formatMessage(coreMessages.rejected), - variant: 'red' as const, - outlined: false, - } - default: - return { - label: formatMessage(coreMessages.unknown), - variant: 'red' as const, - outlined: false, - } - } -} - const IncomePlan = () => { useNamespaces('sp.social-insurance-maintenance') const { formatMessage } = useLocale() diff --git a/libs/portals/my-pages/social-insurance-maintenance/src/screens/IncomePlanDetail/IncomePlanDetail.tsx b/libs/portals/my-pages/social-insurance-maintenance/src/screens/IncomePlanDetail/IncomePlanDetail.tsx index 3f5b207c349d..51878ab260c9 100644 --- a/libs/portals/my-pages/social-insurance-maintenance/src/screens/IncomePlanDetail/IncomePlanDetail.tsx +++ b/libs/portals/my-pages/social-insurance-maintenance/src/screens/IncomePlanDetail/IncomePlanDetail.tsx @@ -1,5 +1,4 @@ import { - Box, Inline, Stack, Button, @@ -10,32 +9,75 @@ import { import { useLocale, useNamespaces } from '@island.is/localization' import { EmptyTable, - FootNote, - IntroHeader, + IntroWrapper, LinkButton, amountFormat, m as coreMessages, } from '@island.is/portals/my-pages/core' import { m } from '../../lib/messages' -import { useGetIncomePlanDetailQuery } from './IncomePlanDetail.generated' +import { + useGetIncomePlanDetailLazyQuery, + useGetIncomePlanDetailQuery, +} from './IncomePlanDetail.generated' import { Problem } from '@island.is/react-spa/shared' +import { useEffect, useState } from 'react' +import { useFeatureFlagClient } from '@island.is/react/feature-flags' const IncomePlanDetail = () => { useNamespaces('sp.social-insurance-maintenance') const { formatMessage } = useLocale() - const { data, loading, error } = useGetIncomePlanDetailQuery() - return ( - - { + const isFlagEnabled = async () => { + const ffEnabled = await featureFlagClient.getValue( + `isServicePortalPaymentPlan2025Enabled`, + false, + ) + if (ffEnabled) { + setDisplayPaymentPlan(ffEnabled as boolean) + } + } + isFlagEnabled() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + useEffect(() => { + if (displayPaymentPlan) { + query() + } + }, [displayPaymentPlan]) + + if (!displayPaymentPlan) { + return ( + + > + + + ) + } + return ( + {error && !loading ? ( ) : !error && !loading && !data?.socialInsuranceIncomePlan ? ( @@ -117,10 +159,7 @@ const IncomePlanDetail = () => { )} )} - - - - + ) } From b3e08784d2ad650f9b3716f81441335cc73f26fc Mon Sep 17 00:00:00 2001 From: HjorturJ <34068269+HjorturJ@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:13:12 +0000 Subject: [PATCH 52/57] fix(core): S3 copy object with foreign characters (#17151) * Testing overwriting the metadata * chore: nx format:write update dirty files * Found a cleaner work around --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- libs/nest/aws/src/lib/s3.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/nest/aws/src/lib/s3.service.ts b/libs/nest/aws/src/lib/s3.service.ts index 0e534dcd33c1..12e5c1439e9e 100644 --- a/libs/nest/aws/src/lib/s3.service.ts +++ b/libs/nest/aws/src/lib/s3.service.ts @@ -63,7 +63,7 @@ export class S3Service { const input: CopyObjectRequest = { Bucket: bucket, Key: key, - CopySource: copySource, + CopySource: encodeURIComponent(copySource), } try { return await this.s3Client.send(new CopyObjectCommand(input)) From 22c5b520743358118ed9b429b3370bb4ca49d678 Mon Sep 17 00:00:00 2001 From: unakb Date: Tue, 10 Dec 2024 13:44:53 +0000 Subject: [PATCH 53/57] feat(j-s): Notifications to prosecutors office in completed indictments (#17174) * fix(j-s): Display confirmation modal before completing case * feat(j-s): Notifications for prosecutor * chore: charts update dirty files * chore: nx format:write update dirty files * fix(j-s): Only send verdict info if there was a ruling * Update eventLog.service.ts --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../backend/infra/judicial-system-backend.ts | 2 + .../backend/src/app/messages/notifications.ts | 2 +- .../app/modules/event-log/eventLog.module.ts | 9 +- .../app/modules/event-log/eventLog.service.ts | 37 ++++- .../dto/indictmentCaseNotification.dto.ts | 12 ++ .../dto/notificationDispatch.dto.ts | 12 +- .../internalNotification.controller.ts | 84 ++++++++-- .../notification/notification.config.ts | 6 + .../notification/notification.module.ts | 2 + .../notificationDispatch.service.ts | 43 +++++ .../indictmentCaseNotification.service.ts | 155 ++++++++++++++++++ .../indictmentCaseNotification.strings.ts | 19 +++ .../test/createTestingNotificationModule.ts | 5 + .../eventNotificationDispatch.spec.ts | 77 +++++++++ ...dIndictmentVerdictInfoNotification.spec.ts | 146 +++++++++++++++++ ...aitingForConfirmationNotifications.spec.ts | 2 +- charts/judicial-system/values.dev.yaml | 1 + charts/judicial-system/values.prod.yaml | 1 + charts/judicial-system/values.staging.yaml | 1 + .../judicial-system-backend/values.dev.yaml | 1 + .../judicial-system-backend/values.prod.yaml | 1 + .../values.staging.yaml | 1 + .../message/src/lib/message.ts | 4 + libs/judicial-system/types/src/index.ts | 2 + .../types/src/lib/notification.ts | 42 +++-- 25 files changed, 630 insertions(+), 37 deletions(-) create mode 100644 apps/judicial-system/backend/src/app/modules/notification/dto/indictmentCaseNotification.dto.ts create mode 100644 apps/judicial-system/backend/src/app/modules/notification/services/indictmentCaseNotification/indictmentCaseNotification.service.ts create mode 100644 apps/judicial-system/backend/src/app/modules/notification/services/indictmentCaseNotification/indictmentCaseNotification.strings.ts create mode 100644 apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/eventNotificationDispatch/eventNotificationDispatch.spec.ts create mode 100644 apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/indictmentCaseNotification/sendIndictmentVerdictInfoNotification.spec.ts diff --git a/apps/judicial-system/backend/infra/judicial-system-backend.ts b/apps/judicial-system/backend/infra/judicial-system-backend.ts index bab01682da23..8d8fb88fcd95 100644 --- a/apps/judicial-system/backend/infra/judicial-system-backend.ts +++ b/apps/judicial-system/backend/infra/judicial-system-backend.ts @@ -68,6 +68,8 @@ export const serviceSetup = (): ServiceBuilder<'judicial-system-backend'> => EMAIL_FROM_NAME: '/k8s/judicial-system/EMAIL_FROM_NAME', EMAIL_REPLY_TO: '/k8s/judicial-system/EMAIL_REPLY_TO', EMAIL_REPLY_TO_NAME: '/k8s/judicial-system/EMAIL_REPLY_TO_NAME', + POLICE_INSTITUTIONS_EMAILS: + '/k8s/judicial-system/POLICE_INSTITUTIONS_EMAILS', PRISON_EMAIL: '/k8s/judicial-system/PRISON_EMAIL', PRISON_ADMIN_EMAIL: '/k8s/judicial-system/PRISON_ADMIN_EMAIL', PRISON_ADMIN_INDICTMENT_EMAILS: diff --git a/apps/judicial-system/backend/src/app/messages/notifications.ts b/apps/judicial-system/backend/src/app/messages/notifications.ts index a7bf8f898acf..43bda5af9fb3 100644 --- a/apps/judicial-system/backend/src/app/messages/notifications.ts +++ b/apps/judicial-system/backend/src/app/messages/notifications.ts @@ -52,7 +52,7 @@ export const notifications = { }), emailWhitelistDomains: defineMessage({ id: 'judicial.system.backend:notifications.email_whitelist_domains', - defaultMessage: 'omnitrix.is,kolibri.is', + defaultMessage: 'omnitrix.is,kolibri.is,dummy.dd', description: 'Notað til að tilgreina hvort póstfang sé í hvítlista', }), readyForCourt: defineMessages({ diff --git a/apps/judicial-system/backend/src/app/modules/event-log/eventLog.module.ts b/apps/judicial-system/backend/src/app/modules/event-log/eventLog.module.ts index d208d6b47f62..2e23e6aacfa5 100644 --- a/apps/judicial-system/backend/src/app/modules/event-log/eventLog.module.ts +++ b/apps/judicial-system/backend/src/app/modules/event-log/eventLog.module.ts @@ -1,12 +1,17 @@ -import { Module } from '@nestjs/common' +import { forwardRef, Module } from '@nestjs/common' import { SequelizeModule } from '@nestjs/sequelize' +import { MessageModule } from '@island.is/judicial-system/message' + import { EventLog } from './models/eventLog.model' import { EventLogController } from './eventLog.controller' import { EventLogService } from './eventLog.service' @Module({ - imports: [SequelizeModule.forFeature([EventLog])], + imports: [ + forwardRef(() => MessageModule), + SequelizeModule.forFeature([EventLog]), + ], providers: [EventLogService], exports: [EventLogService], controllers: [EventLogController], diff --git a/apps/judicial-system/backend/src/app/modules/event-log/eventLog.service.ts b/apps/judicial-system/backend/src/app/modules/event-log/eventLog.service.ts index c1ee4450f943..52806927363b 100644 --- a/apps/judicial-system/backend/src/app/modules/event-log/eventLog.service.ts +++ b/apps/judicial-system/backend/src/app/modules/event-log/eventLog.service.ts @@ -7,7 +7,11 @@ import { InjectModel } from '@nestjs/sequelize' import type { Logger } from '@island.is/logging' import { LOGGER_PROVIDER } from '@island.is/logging' -import { EventType } from '@island.is/judicial-system/types' +import { MessageService, MessageType } from '@island.is/judicial-system/message' +import { + EventNotificationType, + EventType, +} from '@island.is/judicial-system/types' import { CreateEventLogDto } from './dto/createEventLog.dto' import { EventLog } from './models/eventLog.model' @@ -20,11 +24,19 @@ const allowMultiple: EventType[] = [ EventType.INDICTMENT_CONFIRMED, ] +const eventToNotificationMap: Partial< + Record +> = { + INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR: + EventNotificationType.INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR, +} + @Injectable() export class EventLogService { constructor( @InjectModel(EventLog) private readonly eventLogModel: typeof EventLog, + private readonly messageService: MessageService, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} @@ -58,6 +70,10 @@ export class EventLogService { // Tolerate failure but log error this.logger.error('Failed to create event log', error) } + + if (caseId) { + this.addEventNotificationToQueue(eventType, caseId) + } } async loginMap( @@ -86,4 +102,23 @@ export class EventLogService { ), ) } + + // Sends events to queue for notification dispatch + private addEventNotificationToQueue(eventType: EventType, caseId: string) { + const notificationType = eventToNotificationMap[eventType] + + if (notificationType) { + try { + this.messageService.sendMessagesToQueue([ + { + type: MessageType.EVENT_NOTIFICATION_DISPATCH, + caseId: caseId, + body: { type: notificationType }, + }, + ]) + } catch (error) { + this.logger.error('Failed to send event notification to queue', error) + } + } + } } diff --git a/apps/judicial-system/backend/src/app/modules/notification/dto/indictmentCaseNotification.dto.ts b/apps/judicial-system/backend/src/app/modules/notification/dto/indictmentCaseNotification.dto.ts new file mode 100644 index 000000000000..201f04c208b5 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/notification/dto/indictmentCaseNotification.dto.ts @@ -0,0 +1,12 @@ +import { IsEnum, IsNotEmpty } from 'class-validator' + +import { ApiProperty } from '@nestjs/swagger' + +import { IndictmentCaseNotificationType } from '@island.is/judicial-system/types' + +export class IndictmentCaseNotificationDto { + @IsNotEmpty() + @IsEnum(IndictmentCaseNotificationType) + @ApiProperty({ enum: IndictmentCaseNotificationType }) + readonly type!: IndictmentCaseNotificationType +} diff --git a/apps/judicial-system/backend/src/app/modules/notification/dto/notificationDispatch.dto.ts b/apps/judicial-system/backend/src/app/modules/notification/dto/notificationDispatch.dto.ts index 017f7a7fde0d..984e356a627a 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/dto/notificationDispatch.dto.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/dto/notificationDispatch.dto.ts @@ -2,7 +2,10 @@ import { IsEnum, IsNotEmpty } from 'class-validator' import { ApiProperty } from '@nestjs/swagger' -import { NotificationDispatchType } from '@island.is/judicial-system/types' +import { + EventNotificationType, + NotificationDispatchType, +} from '@island.is/judicial-system/types' export class NotificationDispatchDto { @IsNotEmpty() @@ -10,3 +13,10 @@ export class NotificationDispatchDto { @ApiProperty({ enum: NotificationDispatchType }) readonly type!: NotificationDispatchType } + +export class EventNotificationDispatchDto { + @IsNotEmpty() + @IsEnum(EventNotificationType) + @ApiProperty({ enum: EventNotificationType }) + readonly type!: EventNotificationType +} diff --git a/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts b/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts index 68131739f280..af4c5a235c61 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts @@ -16,8 +16,9 @@ import { messageEndpoint, MessageType, } from '@island.is/judicial-system/message' +import { indictmentCases } from '@island.is/judicial-system/types' -import { Case, CaseHasExistedGuard, CurrentCase } from '../case' +import { Case, CaseHasExistedGuard, CaseTypeGuard, CurrentCase } from '../case' import { CivilClaimant, CivilClaimantExistsGuard, @@ -30,13 +31,18 @@ import { SubpoenaExistsGuard } from '../subpoena' import { CaseNotificationDto } from './dto/caseNotification.dto' import { CivilClaimantNotificationDto } from './dto/civilClaimantNotification.dto' import { DefendantNotificationDto } from './dto/defendantNotification.dto' +import { IndictmentCaseNotificationDto } from './dto/indictmentCaseNotification.dto' import { InstitutionNotificationDto } from './dto/institutionNotification.dto' -import { NotificationDispatchDto } from './dto/notificationDispatch.dto' +import { + EventNotificationDispatchDto, + NotificationDispatchDto, +} from './dto/notificationDispatch.dto' import { SubpoenaNotificationDto } from './dto/subpoenaNotification.dto' import { DeliverResponse } from './models/deliver.response' import { CaseNotificationService } from './services/caseNotification/caseNotification.service' import { CivilClaimantNotificationService } from './services/civilClaimantNotification/civilClaimantNotification.service' import { DefendantNotificationService } from './services/defendantNotification/defendantNotification.service' +import { IndictmentCaseNotificationService } from './services/indictmentCaseNotification/indictmentCaseNotification.service' import { InstitutionNotificationService } from './services/institutionNotification/institutionNotification.service' import { SubpoenaNotificationService } from './services/subpoenaNotification/subpoenaNotification.service' import { NotificationDispatchService } from './notificationDispatch.service' @@ -52,6 +58,7 @@ export class InternalNotificationController { private readonly subpoenaNotificationService: SubpoenaNotificationService, private readonly defendantNotificationService: DefendantNotificationService, private readonly civilClaimantNotificationService: CivilClaimantNotificationService, + private readonly indictmentCaseNotificationService: IndictmentCaseNotificationService, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} @@ -77,6 +84,29 @@ export class InternalNotificationController { ) } + @Post( + `case/:caseId/${messageEndpoint[MessageType.INDICTMENT_CASE_NOTIFICATION]}`, + ) + @UseGuards(CaseHasExistedGuard, new CaseTypeGuard(indictmentCases)) + @ApiCreatedResponse({ + type: DeliverResponse, + description: 'Sends a case notification for an existing indictment case', + }) + sendIndictmentCaseNotification( + @Param('caseId') caseId: string, + @CurrentCase() theCase: Case, + @Body() notificationDto: IndictmentCaseNotificationDto, + ): Promise { + this.logger.debug( + `Sending ${notificationDto.type} indictment case notification for case ${caseId}`, + ) + + return this.indictmentCaseNotificationService.sendIndictmentCaseNotification( + notificationDto.type, + theCase, + ) + } + @Post( `case/:caseId/${ messageEndpoint[MessageType.SUBPOENA_NOTIFICATION] @@ -161,34 +191,58 @@ export class InternalNotificationController { ) } - @Post(messageEndpoint[MessageType.NOTIFICATION_DISPATCH]) + @Post(messageEndpoint[MessageType.INSTITUTION_NOTIFICATION]) @ApiCreatedResponse({ type: DeliverResponse, - description: 'Dispatches notifications', + description: 'Sends an institution notification', }) - dispatchNotification( - @Body() notificationDto: NotificationDispatchDto, + sendInstitutionNotification( + @Body() notificationDto: InstitutionNotificationDto, ): Promise { - this.logger.debug(`Dispatching ${notificationDto.type} notification`) + this.logger.debug(`Sending ${notificationDto.type} notification`) - return this.notificationDispatchService.dispatchNotification( + return this.institutionNotificationService.sendNotification( notificationDto.type, + notificationDto.prosecutorsOfficeId, ) } - @Post(messageEndpoint[MessageType.INSTITUTION_NOTIFICATION]) + @Post( + `case/:caseId/${messageEndpoint[MessageType.EVENT_NOTIFICATION_DISPATCH]}`, + ) + @UseGuards(CaseHasExistedGuard) @ApiCreatedResponse({ type: DeliverResponse, - description: 'Sends an institution notification', + description: + 'Dispatches notifications in response to events logged in event log', }) - sendNotification( - @Body() notificationDto: InstitutionNotificationDto, + dispatchEventNotification( + @Param('caseId') caseId: string, + @CurrentCase() theCase: Case, + @Body() notificationDto: EventNotificationDispatchDto, ): Promise { - this.logger.debug(`Sending ${notificationDto.type} notification`) + this.logger.debug( + `Dispatching ${notificationDto.type} event notification for case ${caseId}`, + ) - return this.institutionNotificationService.sendNotification( + return this.notificationDispatchService.dispatchEventNotification( + notificationDto.type, + theCase, + ) + } + + @Post(messageEndpoint[MessageType.NOTIFICATION_DISPATCH]) + @ApiCreatedResponse({ + type: DeliverResponse, + description: 'Dispatches notifications', + }) + dispatchNotification( + @Body() notificationDto: NotificationDispatchDto, + ): Promise { + this.logger.debug(`Dispatching ${notificationDto.type} notification`) + + return this.notificationDispatchService.dispatchNotification( notificationDto.type, - notificationDto.prosecutorsOfficeId, ) } } diff --git a/apps/judicial-system/backend/src/app/modules/notification/notification.config.ts b/apps/judicial-system/backend/src/app/modules/notification/notification.config.ts index aecd5dabb6c9..e91cc08bd354 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/notification.config.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/notification.config.ts @@ -21,6 +21,12 @@ export const notificationModuleConfig = defineConfig({ courtsEmails: env.requiredJSON('COURTS_EMAILS', {}) as { [key: string]: string }, + policeInstitutionEmails: env.requiredJSON( + 'POLICE_INSTITUTIONS_EMAILS', + {}, + ) as { + [key: string]: string + }, }, sms: { courtsMobileNumbers: env.requiredJSON('COURTS_MOBILE_NUMBERS', {}) as { diff --git a/apps/judicial-system/backend/src/app/modules/notification/notification.module.ts b/apps/judicial-system/backend/src/app/modules/notification/notification.module.ts index 101978e3d3ba..47331de9c1c8 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/notification.module.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/notification.module.ts @@ -20,6 +20,7 @@ import { Notification } from './models/notification.model' import { CaseNotificationService } from './services/caseNotification/caseNotification.service' import { CivilClaimantNotificationService } from './services/civilClaimantNotification/civilClaimantNotification.service' import { DefendantNotificationService } from './services/defendantNotification/defendantNotification.service' +import { IndictmentCaseNotificationService } from './services/indictmentCaseNotification/indictmentCaseNotification.service' import { InstitutionNotificationService } from './services/institutionNotification/institutionNotification.service' import { SubpoenaNotificationService } from './services/subpoenaNotification/subpoenaNotification.service' import { InternalNotificationController } from './internalNotification.controller' @@ -47,6 +48,7 @@ import { NotificationDispatchService } from './notificationDispatch.service' CaseNotificationService, CivilClaimantNotificationService, DefendantNotificationService, + IndictmentCaseNotificationService, InstitutionNotificationService, NotificationService, NotificationDispatchService, diff --git a/apps/judicial-system/backend/src/app/modules/notification/notificationDispatch.service.ts b/apps/judicial-system/backend/src/app/modules/notification/notificationDispatch.service.ts index 13884be1e9e0..dbaff1fd065d 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/notificationDispatch.service.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/notificationDispatch.service.ts @@ -3,16 +3,20 @@ import { Injectable, InternalServerErrorException, } from '@nestjs/common' +import { ConfigType } from '@nestjs/config' import { type Logger, LOGGER_PROVIDER } from '@island.is/logging' import { MessageService, MessageType } from '@island.is/judicial-system/message' import { + EventNotificationType, + IndictmentCaseNotificationType, InstitutionNotificationType, InstitutionType, NotificationDispatchType, } from '@island.is/judicial-system/types' +import { Case } from '../case' import { Institution, InstitutionService } from '../institution' import { DeliverResponse } from './models/deliver.response' @@ -63,4 +67,43 @@ export class NotificationDispatchService { return { delivered: true } } + + private async dispatchIndictmentSentToPublicProsecutorNotifications( + theCase: Case, + ): Promise { + return this.messageService.sendMessagesToQueue([ + { + type: MessageType.INDICTMENT_CASE_NOTIFICATION, + caseId: theCase.id, + body: { + type: IndictmentCaseNotificationType.INDICTMENT_VERDICT_INFO, + }, + }, + ]) + } + + async dispatchEventNotification( + type: EventNotificationType, + theCase: Case, + ): Promise { + try { + switch (type) { + case EventNotificationType.INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR: + await this.dispatchIndictmentSentToPublicProsecutorNotifications( + theCase, + ) + break + default: + throw new InternalServerErrorException( + `Invalid notification type ${type}`, + ) + } + } catch (error) { + this.logger.error('Failed to dispatch event notification', error) + + return { delivered: false } + } + + return { delivered: true } + } } diff --git a/apps/judicial-system/backend/src/app/modules/notification/services/indictmentCaseNotification/indictmentCaseNotification.service.ts b/apps/judicial-system/backend/src/app/modules/notification/services/indictmentCaseNotification/indictmentCaseNotification.service.ts new file mode 100644 index 000000000000..821662fa0ca0 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/notification/services/indictmentCaseNotification/indictmentCaseNotification.service.ts @@ -0,0 +1,155 @@ +import { + Inject, + Injectable, + InternalServerErrorException, +} from '@nestjs/common' +import { InjectModel } from '@nestjs/sequelize' + +import { IntlService } from '@island.is/cms-translations' +import { EmailService } from '@island.is/email-service' +import { type Logger, LOGGER_PROVIDER } from '@island.is/logging' +import { type ConfigType } from '@island.is/nest/config' + +import { + CaseIndictmentRulingDecision, + IndictmentCaseNotificationType, + IndictmentDecision, +} from '@island.is/judicial-system/types' + +import { Case } from '../../../case' +import { EventService } from '../../../event' +import { BaseNotificationService } from '../../baseNotification.service' +import { DeliverResponse } from '../../models/deliver.response' +import { Notification, Recipient } from '../../models/notification.model' +import { notificationModuleConfig } from '../../notification.config' +import { strings } from './indictmentCaseNotification.strings' + +@Injectable() +export class IndictmentCaseNotificationService extends BaseNotificationService { + constructor( + @InjectModel(Notification) + notificationModel: typeof Notification, + @Inject(notificationModuleConfig.KEY) + config: ConfigType, + @Inject(LOGGER_PROVIDER) logger: Logger, + intlService: IntlService, + emailService: EmailService, + eventService: EventService, + ) { + super( + notificationModel, + emailService, + intlService, + config, + eventService, + logger, + ) + } + + private async sendEmails( + theCase: Case, + notificationType: IndictmentCaseNotificationType, + subject: string, + body: string, + to: { name?: string; email?: string }[], + ) { + const promises: Promise[] = [] + + for (const recipient of to) { + if (recipient.email && recipient.name) { + promises.push( + this.sendEmail( + subject, + body, + recipient.name, + recipient.email, + undefined, + true, + ), + ) + } + } + + const recipients = await Promise.all(promises) + + return this.recordNotification(theCase.id, notificationType, recipients) + } + + private async sendVerdictInfoNotification( + theCase: Case, + ): Promise { + const institutionId = theCase.prosecutor?.institution?.id + const institutionEmail = + (institutionId && + this.config.email.policeInstitutionEmails[institutionId]) ?? + undefined + + const hasRuling = + theCase.indictmentRulingDecision === CaseIndictmentRulingDecision.RULING + + if (!institutionEmail || !hasRuling) { + // institution does not want to receive these emails or the case does not have a ruling + return { delivered: true } + } + + const formattedSubject = this.formatMessage( + strings.indictmentCompletedWithRuling.subject, + { + courtCaseNumber: theCase.courtCaseNumber, + }, + ) + + const formattedBody = this.formatMessage( + strings.indictmentCompletedWithRuling.body, + { + courtCaseNumber: theCase.courtCaseNumber, + courtName: theCase.court?.name, + serviceRequirement: + theCase.defendants && theCase.defendants[0].serviceRequirement, + caseOrigin: theCase.origin, + }, + ) + + return this.sendEmails( + theCase, + IndictmentCaseNotificationType.INDICTMENT_VERDICT_INFO, + formattedSubject, + formattedBody, + [ + { + name: theCase.prosecutor?.institution?.name, + email: institutionEmail, + }, + ], + ) + } + + private sendNotification( + notificationType: IndictmentCaseNotificationType, + theCase: Case, + ): Promise { + switch (notificationType) { + case IndictmentCaseNotificationType.INDICTMENT_VERDICT_INFO: + return this.sendVerdictInfoNotification(theCase) + + default: + throw new InternalServerErrorException( + `Invalid indictment notification type: ${notificationType}`, + ) + } + } + + async sendIndictmentCaseNotification( + type: IndictmentCaseNotificationType, + theCase: Case, + ): Promise { + await this.refreshFormatMessage() + try { + return await this.sendNotification(type, theCase) + } catch (error) { + this.logger.error('Failed to send indictment case notification', error) + + return { delivered: false } + } + } +} diff --git a/apps/judicial-system/backend/src/app/modules/notification/services/indictmentCaseNotification/indictmentCaseNotification.strings.ts b/apps/judicial-system/backend/src/app/modules/notification/services/indictmentCaseNotification/indictmentCaseNotification.strings.ts new file mode 100644 index 000000000000..c9ed5ec52513 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/notification/services/indictmentCaseNotification/indictmentCaseNotification.strings.ts @@ -0,0 +1,19 @@ +import { defineMessages } from '@formatjs/intl' + +export const strings = { + indictmentCompletedWithRuling: defineMessages({ + subject: { + id: 'judicial.system.backend:indictment_case_notifications.verdict_service.subject', + defaultMessage: 'Máli lokið {courtCaseNumber}', + description: + 'Notað sem titill í tilkynningu um stöðu birtingar dóms í lokinni ákæru', + }, + body: { + id: 'judicial.system.backend:indictment_case_notifications.verdict_service.body', + defaultMessage: + 'Máli {courtCaseNumber} hjá {courtName} hefur verið lokið.\n\nNiðurstaða: Dómur\n\n{serviceRequirement, select, REQUIRED {Birta skal dómfellda dóminn} NOT_REQUIRED {Birting dóms ekki þörf} NOT_APPLICABLE {Dómfelldi var viðstaddur dómsuppkvaðningu} other {}}\n\n{caseOrigin, select, LOKE {Dómur er aðgengilegur í LÖKE.} other {}}', + description: + 'Notað sem body í tilkynningu um stöðu birtingar dóms í lokinni ákæru', + }, + }), +} diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/createTestingNotificationModule.ts b/apps/judicial-system/backend/src/app/modules/notification/test/createTestingNotificationModule.ts index c5ea4ac34521..fa41ef517272 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/createTestingNotificationModule.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/createTestingNotificationModule.ts @@ -33,6 +33,7 @@ import { NotificationDispatchService } from '../notificationDispatch.service' import { CaseNotificationService } from '../services/caseNotification/caseNotification.service' import { CivilClaimantNotificationService } from '../services/civilClaimantNotification/civilClaimantNotification.service' import { DefendantNotificationService } from '../services/defendantNotification/defendantNotification.service' +import { IndictmentCaseNotificationService } from '../services/indictmentCaseNotification/indictmentCaseNotification.service' import { InstitutionNotificationService } from '../services/institutionNotification/institutionNotification.service' jest.mock('@island.is/judicial-system/message') @@ -130,6 +131,7 @@ export const createTestingNotificationModule = async () => { InstitutionNotificationService, DefendantNotificationService, CivilClaimantNotificationService, + IndictmentCaseNotificationService, ], }) .useMocker((token) => { @@ -158,6 +160,9 @@ export const createTestingNotificationModule = async () => { internalNotificationController: notificationModule.get( InternalNotificationController, ), + indictmentCaseNotificationService: notificationModule.get( + IndictmentCaseNotificationService, + ), } notificationModule.close() diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/eventNotificationDispatch/eventNotificationDispatch.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/eventNotificationDispatch/eventNotificationDispatch.spec.ts new file mode 100644 index 000000000000..490b135e8429 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/eventNotificationDispatch/eventNotificationDispatch.spec.ts @@ -0,0 +1,77 @@ +import { uuid } from 'uuidv4' + +import { MessageService, MessageType } from '@island.is/judicial-system/message' +import { + EventNotificationType, + IndictmentCaseNotificationType, +} from '@island.is/judicial-system/types' + +import { createTestingNotificationModule } from '../../createTestingNotificationModule' + +import { Case } from '../../../../case' +import { InternalNotificationController } from '../../../internalNotification.controller' + +describe('InternalNotificationController - Dispatch event notifications', () => { + const theCase = { id: uuid() } as Case + let mockMessageService: MessageService + let internalNotificationController: InternalNotificationController + + beforeEach(async () => { + const { messageService, internalNotificationController: controller } = + await createTestingNotificationModule() + + mockMessageService = messageService + internalNotificationController = controller + + const mockSendMessagesToQueue = + messageService.sendMessagesToQueue as jest.Mock + mockSendMessagesToQueue.mockResolvedValueOnce(undefined) + }) + + const notificationScenarios = [ + { + notificationType: + EventNotificationType.INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR, + expectedMessage: { + type: MessageType.INDICTMENT_CASE_NOTIFICATION, + caseId: theCase.id, + body: { + type: IndictmentCaseNotificationType.INDICTMENT_VERDICT_INFO, + }, + }, + }, + ] + + it.each( + notificationScenarios.map(({ notificationType, expectedMessage }) => ({ + notificationType, + expectedMessage, + description: `should send message to queue for notification type ${notificationType}`, + })), + )('$description', async ({ notificationType, expectedMessage }) => { + const result = + await internalNotificationController.dispatchEventNotification( + theCase.id, + theCase, + { type: notificationType }, + ) + + expect(mockMessageService.sendMessagesToQueue).toHaveBeenCalledWith([ + expectedMessage, + ]) + expect(result).toEqual({ delivered: true }) + }) + + it('will fail if a new EventNotificationType is missing from the tests', () => { + const allNotificationTypes = Object.values(EventNotificationType) + const testedNotificationTypes = notificationScenarios.map( + (scenario) => scenario.notificationType, + ) + + const missingNotificationTypes = allNotificationTypes.filter( + (type) => !testedNotificationTypes.includes(type), + ) + + expect(missingNotificationTypes).toEqual([]) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/indictmentCaseNotification/sendIndictmentVerdictInfoNotification.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/indictmentCaseNotification/sendIndictmentVerdictInfoNotification.spec.ts new file mode 100644 index 000000000000..3c4af29a137b --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/indictmentCaseNotification/sendIndictmentVerdictInfoNotification.spec.ts @@ -0,0 +1,146 @@ +import { uuid } from 'uuidv4' + +import { EmailService } from '@island.is/email-service' + +import { + CaseIndictmentRulingDecision, + CaseOrigin, + IndictmentCaseNotificationType, + ServiceRequirement, +} from '@island.is/judicial-system/types' + +import { + createTestingNotificationModule, + createTestUsers, +} from '../../createTestingNotificationModule' + +import { Case } from '../../../../case' +import { DeliverResponse } from '../../../models/deliver.response' + +interface Then { + result: DeliverResponse + error: Error +} + +type GivenWhenThen = ( + theCase: Case, + notificationType: IndictmentCaseNotificationType, +) => Promise + +describe('IndictmentCaseService', () => { + const { prosecutorsOffice, defender } = createTestUsers([ + 'prosecutorsOffice', + 'defender', + ]) + const caseId = uuid() + const courtName = uuid() + const prosecutorsOfficeName = prosecutorsOffice.name + const prosecutorsOfficeEmail = prosecutorsOffice.email + const prosecutorInstitutionId = uuid() + const courtCaseNumber = uuid() + let theCase = { + id: caseId, + court: { name: courtName }, + origin: CaseOrigin.LOKE, + defendants: [ + { + defenderNationalId: defender.nationalId, + defenderName: defender.name, + defenderEmail: defender.email, + serviceRequirement: ServiceRequirement.REQUIRED, + }, + ], + prosecutor: { + institution: { name: prosecutorsOfficeName, id: prosecutorInstitutionId }, + }, + + courtCaseNumber, + } as Case + + let mockEmailService: EmailService + let givenWhenThen: GivenWhenThen + + process.env.POLICE_INSTITUTIONS_EMAILS = `{"${prosecutorInstitutionId}": "${prosecutorsOfficeEmail}"}` + + beforeEach(async () => { + const { emailService, indictmentCaseNotificationService } = + await createTestingNotificationModule() + + mockEmailService = emailService + + givenWhenThen = async ( + theCase: Case, + notificationType: IndictmentCaseNotificationType, + ) => { + const then = {} as Then + + await indictmentCaseNotificationService + .sendIndictmentCaseNotification(notificationType, theCase) + .then((result) => (then.result = result)) + .catch((error) => (then.error = error)) + + return then + } + }) + + describe('notifications sent to institution with registered e-mail', () => { + it('should not send a notification if indictment ruling decision is not RULING', async () => { + const then = await givenWhenThen( + theCase, + IndictmentCaseNotificationType.INDICTMENT_VERDICT_INFO, + ) + + expect(mockEmailService.sendEmail).toBeCalledTimes(0) + expect(then.result.delivered).toEqual(true) + }) + + it('should send a notification when indictment ruling decision is RULING', async () => { + const caseWithRulingDecision = { + ...theCase, + indictmentRulingDecision: CaseIndictmentRulingDecision.RULING, + } as Case + + const then = await givenWhenThen( + caseWithRulingDecision, + IndictmentCaseNotificationType.INDICTMENT_VERDICT_INFO, + ) + + expect(mockEmailService.sendEmail).toBeCalledWith( + expect.objectContaining({ + to: [ + { address: prosecutorsOfficeEmail, name: prosecutorsOfficeName }, + ], + subject: expect.stringContaining(`Máli lokið ${courtCaseNumber}`), + html: expect.stringContaining( + `Máli ${courtCaseNumber} hjá ${courtName} hefur verið lokið.`, + ), + }), + ) + + expect(then.result).toEqual({ delivered: true }) + }) + }) + + describe('notifications sent to institution without registered e-mail', () => { + it('should not send a notification', async () => { + const invalidProsecutorInstitutionId = uuid() + theCase = { + ...theCase, + prosecutor: { + ...theCase.prosecutor, + institution: { + ...theCase.prosecutor?.institution, + id: invalidProsecutorInstitutionId, + }, + }, + } as Case + + await givenWhenThen( + theCase, + IndictmentCaseNotificationType.INDICTMENT_VERDICT_INFO, + ) + + expect(mockEmailService.sendEmail).not.toBeCalled() + }) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendIndictmentsWaitingForConfirmationNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendIndictmentsWaitingForConfirmationNotifications.spec.ts index 3c20d0d96a26..86c82c0c7275 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendIndictmentsWaitingForConfirmationNotifications.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendIndictmentsWaitingForConfirmationNotifications.spec.ts @@ -60,7 +60,7 @@ describe('InternalNotificationController - Send indictments waiting for confirma const then = {} as Then await internalNotificationController - .sendNotification({ + .sendInstitutionNotification({ type: InstitutionNotificationType.INDICTMENTS_WAITING_FOR_CONFIRMATION, prosecutorsOfficeId, }) diff --git a/charts/judicial-system/values.dev.yaml b/charts/judicial-system/values.dev.yaml index dd58423affdc..c5d0f700d69b 100644 --- a/charts/judicial-system/values.dev.yaml +++ b/charts/judicial-system/values.dev.yaml @@ -225,6 +225,7 @@ judicial-system-backend: NOVA_PASSWORD: '/k8s/judicial-system/NOVA_PASSWORD' NOVA_URL: '/k8s/judicial-system/NOVA_URL' NOVA_USERNAME: '/k8s/judicial-system/NOVA_USERNAME' + POLICE_INSTITUTIONS_EMAILS: '/k8s/judicial-system/POLICE_INSTITUTIONS_EMAILS' PRISON_ADMIN_EMAIL: '/k8s/judicial-system/PRISON_ADMIN_EMAIL' PRISON_ADMIN_INDICTMENT_EMAILS: '/k8s/judicial-system/PRISON_ADMIN_INDICTMENT_EMAILS' PRISON_EMAIL: '/k8s/judicial-system/PRISON_EMAIL' diff --git a/charts/judicial-system/values.prod.yaml b/charts/judicial-system/values.prod.yaml index ffcef752f5ec..e0747b1254c4 100644 --- a/charts/judicial-system/values.prod.yaml +++ b/charts/judicial-system/values.prod.yaml @@ -225,6 +225,7 @@ judicial-system-backend: NOVA_PASSWORD: '/k8s/judicial-system/NOVA_PASSWORD' NOVA_URL: '/k8s/judicial-system/NOVA_URL' NOVA_USERNAME: '/k8s/judicial-system/NOVA_USERNAME' + POLICE_INSTITUTIONS_EMAILS: '/k8s/judicial-system/POLICE_INSTITUTIONS_EMAILS' PRISON_ADMIN_EMAIL: '/k8s/judicial-system/PRISON_ADMIN_EMAIL' PRISON_ADMIN_INDICTMENT_EMAILS: '/k8s/judicial-system/PRISON_ADMIN_INDICTMENT_EMAILS' PRISON_EMAIL: '/k8s/judicial-system/PRISON_EMAIL' diff --git a/charts/judicial-system/values.staging.yaml b/charts/judicial-system/values.staging.yaml index 30afa9a56e74..e6b543a61ce6 100644 --- a/charts/judicial-system/values.staging.yaml +++ b/charts/judicial-system/values.staging.yaml @@ -225,6 +225,7 @@ judicial-system-backend: NOVA_PASSWORD: '/k8s/judicial-system/NOVA_PASSWORD' NOVA_URL: '/k8s/judicial-system/NOVA_URL' NOVA_USERNAME: '/k8s/judicial-system/NOVA_USERNAME' + POLICE_INSTITUTIONS_EMAILS: '/k8s/judicial-system/POLICE_INSTITUTIONS_EMAILS' PRISON_ADMIN_EMAIL: '/k8s/judicial-system/PRISON_ADMIN_EMAIL' PRISON_ADMIN_INDICTMENT_EMAILS: '/k8s/judicial-system/PRISON_ADMIN_INDICTMENT_EMAILS' PRISON_EMAIL: '/k8s/judicial-system/PRISON_EMAIL' diff --git a/charts/services/judicial-system-backend/values.dev.yaml b/charts/services/judicial-system-backend/values.dev.yaml index 8aeb2656041e..df2ab994c0bc 100644 --- a/charts/services/judicial-system-backend/values.dev.yaml +++ b/charts/services/judicial-system-backend/values.dev.yaml @@ -138,6 +138,7 @@ secrets: NOVA_PASSWORD: '/k8s/judicial-system/NOVA_PASSWORD' NOVA_URL: '/k8s/judicial-system/NOVA_URL' NOVA_USERNAME: '/k8s/judicial-system/NOVA_USERNAME' + POLICE_INSTITUTIONS_EMAILS: '/k8s/judicial-system/POLICE_INSTITUTIONS_EMAILS' PRISON_ADMIN_EMAIL: '/k8s/judicial-system/PRISON_ADMIN_EMAIL' PRISON_ADMIN_INDICTMENT_EMAILS: '/k8s/judicial-system/PRISON_ADMIN_INDICTMENT_EMAILS' PRISON_EMAIL: '/k8s/judicial-system/PRISON_EMAIL' diff --git a/charts/services/judicial-system-backend/values.prod.yaml b/charts/services/judicial-system-backend/values.prod.yaml index 2807d821b9d9..d0047fef3de8 100644 --- a/charts/services/judicial-system-backend/values.prod.yaml +++ b/charts/services/judicial-system-backend/values.prod.yaml @@ -138,6 +138,7 @@ secrets: NOVA_PASSWORD: '/k8s/judicial-system/NOVA_PASSWORD' NOVA_URL: '/k8s/judicial-system/NOVA_URL' NOVA_USERNAME: '/k8s/judicial-system/NOVA_USERNAME' + POLICE_INSTITUTIONS_EMAILS: '/k8s/judicial-system/POLICE_INSTITUTIONS_EMAILS' PRISON_ADMIN_EMAIL: '/k8s/judicial-system/PRISON_ADMIN_EMAIL' PRISON_ADMIN_INDICTMENT_EMAILS: '/k8s/judicial-system/PRISON_ADMIN_INDICTMENT_EMAILS' PRISON_EMAIL: '/k8s/judicial-system/PRISON_EMAIL' diff --git a/charts/services/judicial-system-backend/values.staging.yaml b/charts/services/judicial-system-backend/values.staging.yaml index 7221f2acfd51..30787a83c52d 100644 --- a/charts/services/judicial-system-backend/values.staging.yaml +++ b/charts/services/judicial-system-backend/values.staging.yaml @@ -138,6 +138,7 @@ secrets: NOVA_PASSWORD: '/k8s/judicial-system/NOVA_PASSWORD' NOVA_URL: '/k8s/judicial-system/NOVA_URL' NOVA_USERNAME: '/k8s/judicial-system/NOVA_USERNAME' + POLICE_INSTITUTIONS_EMAILS: '/k8s/judicial-system/POLICE_INSTITUTIONS_EMAILS' PRISON_ADMIN_EMAIL: '/k8s/judicial-system/PRISON_ADMIN_EMAIL' PRISON_ADMIN_INDICTMENT_EMAILS: '/k8s/judicial-system/PRISON_ADMIN_INDICTMENT_EMAILS' PRISON_EMAIL: '/k8s/judicial-system/PRISON_EMAIL' diff --git a/libs/judicial-system/message/src/lib/message.ts b/libs/judicial-system/message/src/lib/message.ts index c8650bc09256..c93854bab4e6 100644 --- a/libs/judicial-system/message/src/lib/message.ts +++ b/libs/judicial-system/message/src/lib/message.ts @@ -31,6 +31,8 @@ export enum MessageType { NOTIFICATION_DISPATCH = 'NOTIFICATION_DISPATCH', DEFENDANT_NOTIFICATION = 'DEFENDANT_NOTIFICATION', CIVIL_CLAIMANT_NOTIFICATION = 'CIVIL_CLAIMANT_NOTIFICATION', + INDICTMENT_CASE_NOTIFICATION = 'INDICTMENT_CASE_NOTIFICATION', + EVENT_NOTIFICATION_DISPATCH = 'EVENT_NOTIFICATION_DISPATCH', } export const messageEndpoint: { [key in MessageType]: string } = { @@ -68,6 +70,8 @@ export const messageEndpoint: { [key in MessageType]: string } = { NOTIFICATION_DISPATCH: 'notification/dispatch', DEFENDANT_NOTIFICATION: 'defendantNotification', CIVIL_CLAIMANT_NOTIFICATION: 'civilClaimantNotification', + INDICTMENT_CASE_NOTIFICATION: 'indictmentCaseNotification', + EVENT_NOTIFICATION_DISPATCH: 'eventNotification/dispatch', } export type Message = { diff --git a/libs/judicial-system/types/src/index.ts b/libs/judicial-system/types/src/index.ts index 140f6c9bc92a..82a11272ee06 100644 --- a/libs/judicial-system/types/src/index.ts +++ b/libs/judicial-system/types/src/index.ts @@ -19,6 +19,8 @@ export { NotificationDispatchType, DefendantNotificationType, CivilClaimantNotificationType, + IndictmentCaseNotificationType, + EventNotificationType, notificationTypes, } from './lib/notification' export type { Institution } from './lib/institution' diff --git a/libs/judicial-system/types/src/lib/notification.ts b/libs/judicial-system/types/src/lib/notification.ts index e9b8df060e63..347cef7b5852 100644 --- a/libs/judicial-system/types/src/lib/notification.ts +++ b/libs/judicial-system/types/src/lib/notification.ts @@ -20,6 +20,10 @@ export enum CaseNotificationType { CASE_FILES_UPDATED = 'CASE_FILES_UPDATED', } +export enum IndictmentCaseNotificationType { + INDICTMENT_VERDICT_INFO = 'INDICTMENT_VERDICT_INFO', +} + export enum DefendantNotificationType { DEFENDANT_SELECTED_DEFENDER = 'DEFENDANT_SELECTED_DEFENDER', DEFENDER_ASSIGNED = 'DEFENDER_ASSIGNED', @@ -40,34 +44,40 @@ export enum InstitutionNotificationType { INDICTMENTS_WAITING_FOR_CONFIRMATION = 'INDICTMENTS_WAITING_FOR_CONFIRMATION', } +export enum EventNotificationType { + INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR = 'INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR', +} + export enum NotificationType { - HEADS_UP = CaseNotificationType.HEADS_UP, - READY_FOR_COURT = CaseNotificationType.READY_FOR_COURT, - RECEIVED_BY_COURT = CaseNotificationType.RECEIVED_BY_COURT, - COURT_DATE = CaseNotificationType.COURT_DATE, - RULING = CaseNotificationType.RULING, - MODIFIED = CaseNotificationType.MODIFIED, - REVOKED = CaseNotificationType.REVOKED, ADVOCATE_ASSIGNED = CaseNotificationType.ADVOCATE_ASSIGNED, - DEFENDANTS_NOT_UPDATED_AT_COURT = CaseNotificationType.DEFENDANTS_NOT_UPDATED_AT_COURT, - APPEAL_TO_COURT_OF_APPEALS = CaseNotificationType.APPEAL_TO_COURT_OF_APPEALS, - APPEAL_RECEIVED_BY_COURT = CaseNotificationType.APPEAL_RECEIVED_BY_COURT, - APPEAL_STATEMENT = CaseNotificationType.APPEAL_STATEMENT, + APPEAL_CASE_FILES_UPDATED = CaseNotificationType.APPEAL_CASE_FILES_UPDATED, APPEAL_COMPLETED = CaseNotificationType.APPEAL_COMPLETED, APPEAL_JUDGES_ASSIGNED = CaseNotificationType.APPEAL_JUDGES_ASSIGNED, - APPEAL_CASE_FILES_UPDATED = CaseNotificationType.APPEAL_CASE_FILES_UPDATED, + APPEAL_RECEIVED_BY_COURT = CaseNotificationType.APPEAL_RECEIVED_BY_COURT, + APPEAL_STATEMENT = CaseNotificationType.APPEAL_STATEMENT, + APPEAL_TO_COURT_OF_APPEALS = CaseNotificationType.APPEAL_TO_COURT_OF_APPEALS, APPEAL_WITHDRAWN = CaseNotificationType.APPEAL_WITHDRAWN, - INDICTMENT_DENIED = CaseNotificationType.INDICTMENT_DENIED, - INDICTMENT_RETURNED = CaseNotificationType.INDICTMENT_RETURNED, CASE_FILES_UPDATED = CaseNotificationType.CASE_FILES_UPDATED, + COURT_DATE = CaseNotificationType.COURT_DATE, DEFENDANT_SELECTED_DEFENDER = DefendantNotificationType.DEFENDANT_SELECTED_DEFENDER, + DEFENDANTS_NOT_UPDATED_AT_COURT = CaseNotificationType.DEFENDANTS_NOT_UPDATED_AT_COURT, DEFENDER_ASSIGNED = DefendantNotificationType.DEFENDER_ASSIGNED, + HEADS_UP = CaseNotificationType.HEADS_UP, + INDICTMENT_DENIED = CaseNotificationType.INDICTMENT_DENIED, + INDICTMENT_RETURNED = CaseNotificationType.INDICTMENT_RETURNED, INDICTMENT_SENT_TO_PRISON_ADMIN = DefendantNotificationType.INDICTMENT_SENT_TO_PRISON_ADMIN, + INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR = EventNotificationType.INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR, + INDICTMENT_VERDICT_INFO = IndictmentCaseNotificationType.INDICTMENT_VERDICT_INFO, INDICTMENT_WITHDRAWN_FROM_PRISON_ADMIN = DefendantNotificationType.INDICTMENT_WITHDRAWN_FROM_PRISON_ADMIN, - SERVICE_SUCCESSFUL = SubpoenaNotificationType.SERVICE_SUCCESSFUL, + INDICTMENTS_WAITING_FOR_CONFIRMATION = InstitutionNotificationType.INDICTMENTS_WAITING_FOR_CONFIRMATION, + MODIFIED = CaseNotificationType.MODIFIED, + READY_FOR_COURT = CaseNotificationType.READY_FOR_COURT, + RECEIVED_BY_COURT = CaseNotificationType.RECEIVED_BY_COURT, + REVOKED = CaseNotificationType.REVOKED, + RULING = CaseNotificationType.RULING, SERVICE_FAILED = SubpoenaNotificationType.SERVICE_FAILED, + SERVICE_SUCCESSFUL = SubpoenaNotificationType.SERVICE_SUCCESSFUL, SPOKESPERSON_ASSIGNED = CivilClaimantNotificationType.SPOKESPERSON_ASSIGNED, - INDICTMENTS_WAITING_FOR_CONFIRMATION = InstitutionNotificationType.INDICTMENTS_WAITING_FOR_CONFIRMATION, } export const notificationTypes = Object.values(NotificationType) From 818f74ecfd20365bf41b3414430e4b328f5d4582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Tue, 10 Dec 2024 14:12:51 +0000 Subject: [PATCH 54/57] feat(web): Generic List - Stop date from taking up space if it is not present (#17188) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../components/GenericList/GenericList.tsx | 67 +++++++++++++------ 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/apps/web/components/GenericList/GenericList.tsx b/apps/web/components/GenericList/GenericList.tsx index bbc7592e5172..3bc8dfc8ba48 100644 --- a/apps/web/components/GenericList/GenericList.tsx +++ b/apps/web/components/GenericList/GenericList.tsx @@ -15,6 +15,7 @@ import { GridColumn, GridContainer, GridRow, + Hyphen, Icon, type IconProps, Inline, @@ -85,9 +86,11 @@ export const NonClickableItem = ({ item }: ItemProps) => { - - {item.date && format(new Date(item.date), 'dd.MM.yyyy')} - + {item.date && ( + + {format(new Date(item.date), 'dd.MM.yyyy')} + + )} {item.title} @@ -153,24 +156,48 @@ export const ClickableItem = ({ item, baseUrl }: ClickableItemProps) => { - - - - {item.date && format(new Date(item.date), 'dd.MM.yyyy')} + {item.date && ( + + + + {format(new Date(item.date), 'dd.MM.yyyy')} + + {icon && ( + + )} + + + )} + + + + {item.title} - {icon && ( - - )} - - - - {item.title} - + + {!item.date && icon && ( + + + + + + )} + {item.cardIntro?.length > 0 && ( {webRichText(item.cardIntro ?? [])} From 83ba8fac56aa2cf176515b0736f5c02b75ffffb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rey=20J=C3=B3na?= Date: Tue, 10 Dec 2024 15:07:29 +0000 Subject: [PATCH 55/57] feat(native-app): Move app to nx (#17098) * feat: init nx create app stuff * feat: copy src folder from app project * feat: add dependencies * feat: updating podFile * feat copy ios folder from older project * feat: copy android folder * feat: final fixes for ios app working locally * feat: remove old src folder * feat: add readme * feat: add storybook folder * feat: add back test stuff in * feat: add more stuff from old project * feat: add all scripts to package.json * feat: update packages that still have * to proper version * feat: final additions * feat: renaming app folder and removing old native folder * fix: remove mobile from workspaces * fix: remove extra extension * feat: add back root level read me * feat: add prettier and bundle config files * remove google-services.json file * feat: update gitignore file * fix: path for e2e project.json * chore: bump version to 1.4.8 * remove cache: true from nx.json * add tags to project.json * feat: fix yarn.lock * fix: valid json in nx.json * fix: remove app-e2e folder * fix: update package.json * feat: add license to package.json in app * remove private: true to fix license check * chore: nx format:write update dirty files * fix: remove dom from lib in tsconfig.json * update entryFilein project.json * fix: update settings.gradle after android build failing * fix: update import from build.gradle * feat: use relative import for ui * fix: remove duplicates from gitignore * fix: remove from tsconfig things that are already inherited * chore: remove babel-plugin-module-resolver * remove webpack.config.js * fix: update package.json to have same version as package.json in app * fix: update yarn.lock * fix: linting * chore: update react version to match root project * chore: update Podfile * chore: remove patch for old version of rn --------- Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .gitignore | 68 +- .../react-native-npm-0.71.1-f5d237f240.patch | Bin 7111 -> 0 bytes apps/native/app/.babelrc.js | 14 + apps/native/app/.bundle/config | 2 +- apps/native/app/.eslintrc.json | 11 +- apps/native/app/.gitignore | 81 -- apps/native/app/Gemfile | 2 +- apps/native/app/android/app/build.gradle | 4 +- apps/native/app/android/settings.gradle | 4 +- apps/native/app/babel.config.js | 17 - .../ios/IslandApp.xcodeproj/project.pbxproj | 10 +- apps/native/app/ios/IslandApp/Info.plist | 2 +- apps/native/app/ios/Podfile.lock | 416 +++--- apps/native/app/jest.config.js | 3 - apps/native/app/jest.config.ts | 22 + apps/native/app/metro.config.js | 39 +- apps/native/app/package.json | 11 +- apps/native/app/project.json | 76 +- .../bottom-tabs-indicator.tsx | 2 +- .../src/components/offline/offline-banner.tsx | 2 +- .../src/components/pin-keypad/pin-keypad.tsx | 2 +- .../visualized-pin-code.tsx | 2 +- .../src/hooks/use-connectivity-indicator.ts | 3 +- apps/native/app/src/lib/show-picker.ts | 2 +- .../src/screens/air-discount/air-discount.tsx | 22 +- .../air-discount/airfares-usage-table.tsx | 2 +- .../app/src/screens/app-lock/app-lock.tsx | 3 +- .../src/screens/applications/applications.tsx | 2 +- .../components/applications-list.tsx | 19 +- .../components/applications-preview.tsx | 10 +- .../app/src/screens/assets/assets-detail.tsx | 3 +- .../src/screens/assets/assets-overview.tsx | 3 +- .../src/screens/cognito-auth/cognito-auth.tsx | 3 +- .../document-detail/document-detail.tsx | 4 +- .../utils/get-buttons-for-actions.tsx | 2 +- .../app/src/screens/family/family-details.tsx | 3 +- .../src/screens/family/family-overview.tsx | 9 +- .../components/finance-status-card.tsx | 11 +- .../screens/finance/finance-status-detail.tsx | 3 +- .../app/src/screens/finance/finance.tsx | 3 +- .../src/screens/health/health-overview.tsx | 18 +- .../src/screens/home/air-discount-module.tsx | 17 +- .../src/screens/home/applications-module.tsx | 2 +- .../app/src/screens/home/hello-module.tsx | 3 +- .../app/src/screens/home/home-options.tsx | 2 +- apps/native/app/src/screens/home/home.tsx | 2 +- .../app/src/screens/home/inbox-module.tsx | 17 +- .../app/src/screens/home/licenses-module.tsx | 15 +- .../src/screens/home/onboarding-module.tsx | 6 +- .../app/src/screens/home/vehicles-module.tsx | 15 +- .../app/src/screens/inbox/inbox-filter.tsx | 11 +- apps/native/app/src/screens/inbox/inbox.tsx | 21 +- .../license-scanner/license-scan-detail.tsx | 3 +- .../license-scanner/license-scanner.tsx | 3 +- apps/native/app/src/screens/login/login.tsx | 3 +- .../app/src/screens/login/testing-login.tsx | 3 +- apps/native/app/src/screens/more/more.tsx | 3 +- .../app/src/screens/more/personal-info.tsx | 3 +- .../screens/notifications/notifications.tsx | 19 +- .../onboarding/onboarding-biometrics.tsx | 3 +- .../onboarding/onboarding-notifications.tsx | 3 +- .../onboarding/onboarding-pin-code.tsx | 3 +- .../app/src/screens/passkey/passkey.tsx | 3 +- .../src/screens/settings/edit-bank-info.tsx | 3 +- .../app/src/screens/settings/edit-confirm.tsx | 15 +- .../app/src/screens/settings/edit-email.tsx | 3 +- .../app/src/screens/settings/edit-phone.tsx | 3 +- .../app/src/screens/settings/settings.tsx | 15 +- .../app/src/screens/update-app/update-app.tsx | 3 +- .../components/vaccination-card.tsx | 14 +- .../src/screens/vaccinations/vaccinations.tsx | 14 +- .../vehicles/components/mileage-cell.tsx | 3 +- .../vehicles/components/vehicle-item.tsx | 3 +- .../vehicles/vehicle-mileage.screen.tsx | 16 +- .../src/screens/vehicles/vehicles-detail.tsx | 3 +- .../app/src/screens/vehicles/vehicles.tsx | 3 +- .../wallet-pass/components/field-render.tsx | 3 +- .../src/screens/wallet-pass/wallet-pass.tsx | 12 +- .../wallet-passport/wallet-passport.tsx | 25 +- .../screens/wallet/components/wallet-item.tsx | 3 +- apps/native/app/src/screens/wallet/wallet.tsx | 3 +- apps/native/app/src/test-setup.ts | 1 + apps/native/app/src/types/react-native.d.ts | 4 - .../app/src/types/styled-components.d.ts | 2 +- apps/native/app/src/ui/index.ts | 2 + .../app/src/ui/lib/card/license-card.tsx | 5 +- .../src/ui/lib/date-picker/date-picker.tsx | 2 +- apps/native/app/src/ui/lib/detail/header.tsx | 2 +- .../app/src/ui/lib/empty-state/empty-list.tsx | 2 +- .../lib/empty-state/empty-state.stories.tsx | 3 +- apps/native/app/src/ui/lib/input/input.tsx | 3 +- apps/native/app/src/ui/lib/label/label.tsx | 3 +- apps/native/app/src/ui/lib/link/link-text.tsx | 2 +- apps/native/app/src/ui/lib/list/list-item.tsx | 3 +- .../src/ui/lib/problem/problem-template.tsx | 4 +- .../app/src/ui/lib/search-bar/search-bar.tsx | 3 +- .../app/src/utils/applications-utils.ts | 2 +- .../src/utils/get-theme-with-preferences.ts | 3 +- apps/native/app/tsconfig.app.json | 18 + apps/native/app/tsconfig.json | 28 +- apps/native/app/tsconfig.spec.json | 21 + package.json | 14 +- tsconfig.base.json | 1 - yarn.lock | 1138 +++++++++++++++-- 104 files changed, 1774 insertions(+), 711 deletions(-) delete mode 100644 .yarn/patches/react-native-npm-0.71.1-f5d237f240.patch create mode 100644 apps/native/app/.babelrc.js delete mode 100644 apps/native/app/.gitignore delete mode 100644 apps/native/app/babel.config.js delete mode 100644 apps/native/app/jest.config.js create mode 100644 apps/native/app/jest.config.ts create mode 100644 apps/native/app/src/test-setup.ts create mode 100644 apps/native/app/tsconfig.app.json create mode 100644 apps/native/app/tsconfig.spec.json diff --git a/.gitignore b/.gitignore index 9ad6dbd3146e..46c71ffd1796 100644 --- a/.gitignore +++ b/.gitignore @@ -102,4 +102,70 @@ apps/**/index.html # E2E outputs test-results/ playwright-report/ -tmp-sessions/ \ No newline at end of file +tmp-sessions/ + +# React Native + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.dSYM.zip +*.xcuserstate +**/.xcode.env.local +ios/*.cer +ios/*.certSigningRequest +ios/*.mobileprovision +ios/*.p12 + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml +*.hprof +.cxx/ +*.keystore +!debug.keystore +google-services.json +service-account.json + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ + +**/fastlane/report.xml +**/fastlane/Preview.html +**/fastlane/screenshots +**/fastlane/test_output + +# Bundle artifact +*.jsbundle + +# Ruby / CocoaPods +**/Pods/ +**/vendor/bundle/ + +# Temporary files created by Metro to check the health of the file watcher +.metro-health-check* + +# testing +/coverage diff --git a/.yarn/patches/react-native-npm-0.71.1-f5d237f240.patch b/.yarn/patches/react-native-npm-0.71.1-f5d237f240.patch deleted file mode 100644 index 0c49e9c60fe66c17c545ab9549636c66191766f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7111 zcmeHMQES^U5SF*XinoV8?oot2B&4w&+jWvYr6nEorG;kfY0xRsDfQ-AL3Y}8r9W-# zC+#onWIL>D+Ooo6u<1bhq|@C=`a~j&6RDK6*PEv$Wxh9=o=!`pfzE!m-5f5cOf#UL zlmOZff+UXJG#Buh2EQquH{iiYhC4I4BlnPqOb+x=xN<1y_8+X3zN>3HmR2_eq@QuGPg!saFxgXH_SuLjn|SeUXVPa2Iu zr;4ITN`xqQHdAZXcPotVGr~wt1kvP%zsiim3vxmLhdC+9-d#pGuNBLHWxz6E8L$jk z1|Gpcb9Zh&e}s=UwG3DW{tpJs{{zKhaNJ=1N(U>W$Q8E`fx)5$(=vAJE>HfwHMise0)m7r~9j#YIT zBUYC&cFbk`@`fEXZ5$srXg>!G5mqb%f0+UI9X(PMQx;j817#(JTu>#e1cobaCOb}m z@p3O0TBX9DEz?YRbIkjWbOWoQ~sEa&POzbOFlx+bEWUx5ScOQzd zP0Py#?9xf&+rf}Mic)CaZk?6oqS*EQnTe?M-1{K4ntQ48nJ!a_$Ds73|B0o!uOS0g zfdA@h|M+9Q2w6#rJUaNEM7iy_F)Im>g*!d+9?B!Q0NpP25;|_JJ_miF(-PW}WyMM>0NFOFw%3?y TYp)K$920K1TIv0p?6^Mw@M2M9 diff --git a/apps/native/app/.babelrc.js b/apps/native/app/.babelrc.js new file mode 100644 index 000000000000..01d571a26229 --- /dev/null +++ b/apps/native/app/.babelrc.js @@ -0,0 +1,14 @@ +module.exports = function (api) { + api.cache(true) + + return { + presets: [ + ['module:@react-native/babel-preset', { useTransformReactJSX: true }], + ], + plugins: [ + // react-native-reanimated/plugin has to be listed last. + // Reason: In short, the Reanimated babel plugin automatically converts special JavaScript functions (called worklets) to allow them to be passed and run on the UI thread. + 'react-native-reanimated/plugin', + ], + } +} diff --git a/apps/native/app/.bundle/config b/apps/native/app/.bundle/config index 848943bb5274..d137d242ed74 100644 --- a/apps/native/app/.bundle/config +++ b/apps/native/app/.bundle/config @@ -1,2 +1,2 @@ BUNDLE_PATH: "vendor/bundle" -BUNDLE_FORCE_RUBY_PLATFORM: 1 +BUNDLE_FORCE_RUBY_PLATFORM: 1 \ No newline at end of file diff --git a/apps/native/app/.eslintrc.json b/apps/native/app/.eslintrc.json index d5e992022c7c..d6ea918430f5 100644 --- a/apps/native/app/.eslintrc.json +++ b/apps/native/app/.eslintrc.json @@ -6,7 +6,6 @@ "**/*.stories.tsx", "lib/pdf417/*-min.js" ], - "rules": {}, "overrides": [ { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], @@ -20,7 +19,13 @@ "func-style": "off" } }, - { "files": ["*.ts", "*.tsx"], "rules": {} }, - { "files": ["*.js", "*.jsx"], "rules": {} } + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } ] } diff --git a/apps/native/app/.gitignore b/apps/native/app/.gitignore deleted file mode 100644 index edf1e8ff9ab0..000000000000 --- a/apps/native/app/.gitignore +++ /dev/null @@ -1,81 +0,0 @@ -# OSX -# -.DS_Store - -# Xcode -# -build/ -*.pbxuser -!default.pbxuser -*.mode1v3 -!default.mode1v3 -*.mode2v3 -!default.mode2v3 -*.perspectivev3 -!default.perspectivev3 -xcuserdata -*.xccheckout -*.moved-aside -DerivedData -*.hmap -*.ipa -*.dSYM.zip -*.xcuserstate -**/.xcode.env.local -ios/*.cer -ios/*.certSigningRequest -ios/*.mobileprovision -ios/*.p12 - -# Android/IntelliJ -# -build/ -.idea -.gradle -local.properties -*.iml -*.hprof -.cxx/ -*.keystore -!debug.keystore -google-services.json -service-account.json - -# node.js -# -node_modules/ -npm-debug.log -yarn-error.log - -# fastlane -# -# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the -# screenshots whenever they are needed. -# For more information about the recommended setup visit: -# https://docs.fastlane.tools/best-practices/source-control/ - -**/fastlane/report.xml -**/fastlane/Preview.html -**/fastlane/screenshots -**/fastlane/test_output - -# Bundle artifact -*.jsbundle - -# Ruby / CocoaPods -**/Pods/ -/vendor/bundle/ - -# Temporary files created by Metro to check the health of the file watcher -.metro-health-check* - -# testing -/coverage - -# Yarn -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/sdks -!.yarn/versions diff --git a/apps/native/app/Gemfile b/apps/native/app/Gemfile index 8d72c37a80c3..ec21e32c9a69 100644 --- a/apps/native/app/Gemfile +++ b/apps/native/app/Gemfile @@ -6,4 +6,4 @@ ruby ">= 2.6.10" # Cocoapods 1.15 introduced a bug which break the build. We will remove the upper # bound in the template on Cocoapods with next React Native release. gem 'cocoapods', '>= 1.13', '< 1.15' -gem 'activesupport', '>= 6.1.7.5', '< 7.1.0' +gem 'activesupport', '>= 6.1.7.5', '< 7.1.0' \ No newline at end of file diff --git a/apps/native/app/android/app/build.gradle b/apps/native/app/android/app/build.gradle index 98b5f346f132..a32464e07a8f 100644 --- a/apps/native/app/android/app/build.gradle +++ b/apps/native/app/android/app/build.gradle @@ -103,7 +103,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode getMyVersionCode(143) - versionName "1.4.7" + versionName "1.4.8" manifestPlaceholders = [ appAuthRedirectScheme: "is.island.app" // project.config.get("BUNDLE_ID_ANDROID") ] @@ -171,4 +171,4 @@ dependencies { } } -apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) +apply from: file("../../../../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) diff --git a/apps/native/app/android/settings.gradle b/apps/native/app/android/settings.gradle index 7ed793eca85f..12979ce6cda2 100644 --- a/apps/native/app/android/settings.gradle +++ b/apps/native/app/android/settings.gradle @@ -1,10 +1,10 @@ rootProject.name = 'IslandApp' -apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); +apply from: file("../../../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) include ':app', ':react-native-code-push' -project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app') +project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../../../../node_modules/react-native-code-push/android/app') include ':react-native-clipboard' project(':react-native-clipboard').projectDir = new File(rootProject.projectDir, '../../node_modules/@react-native-clipboard/clipboard/android') diff --git a/apps/native/app/babel.config.js b/apps/native/app/babel.config.js deleted file mode 100644 index 24924e0cb1b1..000000000000 --- a/apps/native/app/babel.config.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - presets: ['module:@react-native/babel-preset'], - plugins: [ - [ - 'module-resolver', - { - alias: { - '@ui': './src/ui', - '@island.is/application/types': '../../../libs/application/types/src', - }, - }, - ], - // react-native-reanimated/plugin has to be listed last. - // Reason: In short, the Reanimated babel plugin automatically converts special JavaScript functions (called worklets) to allow them to be passed and run on the UI thread. - 'react-native-reanimated/plugin', - ], -} diff --git a/apps/native/app/ios/IslandApp.xcodeproj/project.pbxproj b/apps/native/app/ios/IslandApp.xcodeproj/project.pbxproj index a6c26ffd0c01..4bcd227127d6 100644 --- a/apps/native/app/ios/IslandApp.xcodeproj/project.pbxproj +++ b/apps/native/app/ios/IslandApp.xcodeproj/project.pbxproj @@ -301,7 +301,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; + shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../../../../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../../../../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; 1D5D14183630A5603D9A6D3E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -425,7 +425,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n"; + shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../../../../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../../../../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -554,7 +554,7 @@ "$(inherited)", " ", ); - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; VALIDATE_PRODUCT = YES; @@ -629,7 +629,7 @@ "$(inherited)", " ", ); - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; VALIDATE_PRODUCT = YES; @@ -752,7 +752,7 @@ "$(inherited)", " ", ); - REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; }; diff --git a/apps/native/app/ios/IslandApp/Info.plist b/apps/native/app/ios/IslandApp/Info.plist index 6d9a1a7cf5ac..42e798076119 100644 --- a/apps/native/app/ios/IslandApp/Info.plist +++ b/apps/native/app/ios/IslandApp/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.7 + 1.4.8 CFBundleSignature ???? CFBundleURLTypes diff --git a/apps/native/app/ios/Podfile.lock b/apps/native/app/ios/Podfile.lock index 3d5d52150dc7..4a489728f683 100644 --- a/apps/native/app/ios/Podfile.lock +++ b/apps/native/app/ios/Podfile.lock @@ -1,9 +1,9 @@ PODS: - - AppAuth (1.7.5): - - AppAuth/Core (= 1.7.5) - - AppAuth/ExternalUserAgent (= 1.7.5) - - AppAuth/Core (1.7.5) - - AppAuth/ExternalUserAgent (1.7.5): + - AppAuth (1.7.6): + - AppAuth/Core (= 1.7.6) + - AppAuth/ExternalUserAgent (= 1.7.6) + - AppAuth/Core (1.7.6) + - AppAuth/ExternalUserAgent (1.7.6): - AppAuth/Core - Base64 (1.1.2) - boost (1.83.0) @@ -141,7 +141,7 @@ PODS: - GoogleUtilities/ISASwizzler (~> 7.8) - GoogleUtilities/MethodSwizzler (~> 7.8) - nanopb (< 2.30910.0, >= 2.30908.0) - - FirebaseRemoteConfig (10.27.0): + - FirebaseRemoteConfig (10.29.0): - FirebaseABTesting (~> 10.0) - FirebaseCore (~> 10.0) - FirebaseInstallations (~> 10.0) @@ -149,8 +149,8 @@ PODS: - FirebaseSharedSwift (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseRemoteConfigInterop (10.27.0) - - FirebaseSharedSwift (10.27.0) + - FirebaseRemoteConfigInterop (10.29.0) + - FirebaseSharedSwift (10.29.0) - fmt (9.1.0) - glog (0.3.5) - GoogleAppMeasurement (10.3.0): @@ -1567,113 +1567,113 @@ PODS: - Yoga (0.0.0) DEPENDENCIES: - - boost (from `../node_modules/react-native/third-party-podspecs/boost.podspec`) - - CodePush (from `../node_modules/react-native-code-push`) - - "DatadogSDKReactNative (from `../node_modules/@datadog/mobile-react-native`)" - - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) - - EXApplication (from `../node_modules/expo-application/ios`) - - EXConstants (from `../node_modules/expo-constants/ios`) - - EXNotifications (from `../node_modules/expo-notifications/ios`) - - Expo (from `../node_modules/expo`) - - ExpoAsset (from `../node_modules/expo-asset/ios`) - - ExpoFileSystem (from `../node_modules/expo-file-system/ios`) - - ExpoFont (from `../node_modules/expo-font/ios`) - - ExpoHaptics (from `../node_modules/expo-haptics/ios`) - - ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`) - - ExpoLocalAuthentication (from `../node_modules/expo-local-authentication/ios`) - - ExpoModulesCore (from `../node_modules/expo-modules-core`) - - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) + - boost (from `../../../../node_modules/react-native/third-party-podspecs/boost.podspec`) + - CodePush (from `../../../../node_modules/react-native-code-push`) + - "DatadogSDKReactNative (from `../../../../node_modules/@datadog/mobile-react-native`)" + - DoubleConversion (from `../../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - EXApplication (from `../../../../node_modules/expo-application/ios`) + - EXConstants (from `../../../../node_modules/expo-constants/ios`) + - EXNotifications (from `../../../../node_modules/expo-notifications/ios`) + - Expo (from `../../../../node_modules/expo`) + - ExpoAsset (from `../../../../node_modules/expo-asset/ios`) + - ExpoFileSystem (from `../../../../node_modules/expo-file-system/ios`) + - ExpoFont (from `../../../../node_modules/expo-font/ios`) + - ExpoHaptics (from `../../../../node_modules/expo-haptics/ios`) + - ExpoKeepAwake (from `../../../../node_modules/expo-keep-awake/ios`) + - ExpoLocalAuthentication (from `../../../../node_modules/expo-local-authentication/ios`) + - ExpoModulesCore (from `../../../../node_modules/expo-modules-core`) + - FBLazyVector (from `../../../../node_modules/react-native/Libraries/FBLazyVector`) - Firebase - FirebaseABTesting - FirebaseCore - FirebaseCoreInternal - FirebaseInstallations - - fmt (from `../node_modules/react-native/third-party-podspecs/fmt.podspec`) - - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) + - fmt (from `../../../../node_modules/react-native/third-party-podspecs/fmt.podspec`) + - glog (from `../../../../node_modules/react-native/third-party-podspecs/glog.podspec`) - GoogleUtilities - - hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) - - Interactable (from `../node_modules/react-native-interactable`) - - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - - RCT-Folly/Fabric (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) - - RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) - - RCTRequired (from `../node_modules/react-native/Libraries/Required`) - - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) - - React (from `../node_modules/react-native/`) - - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) + - hermes-engine (from `../../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) + - Interactable (from `../../../../node_modules/react-native-interactable`) + - RCT-Folly (from `../../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) + - RCT-Folly/Fabric (from `../../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) + - RCTDeprecation (from `../../../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) + - RCTRequired (from `../../../../node_modules/react-native/Libraries/Required`) + - RCTTypeSafety (from `../../../../node_modules/react-native/Libraries/TypeSafety`) + - React (from `../../../../node_modules/react-native/`) + - React-callinvoker (from `../../../../node_modules/react-native/ReactCommon/callinvoker`) - React-Codegen (from `build/generated/ios`) - - React-Core (from `../node_modules/react-native/`) - - React-Core/RCTWebSocket (from `../node_modules/react-native/`) - - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) - - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) - - React-debug (from `../node_modules/react-native/ReactCommon/react/debug`) - - React-Fabric (from `../node_modules/react-native/ReactCommon`) - - React-FabricImage (from `../node_modules/react-native/ReactCommon`) - - React-featureflags (from `../node_modules/react-native/ReactCommon/react/featureflags`) - - React-graphics (from `../node_modules/react-native/ReactCommon/react/renderer/graphics`) - - React-hermes (from `../node_modules/react-native/ReactCommon/hermes`) - - React-ImageManager (from `../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) - - React-jserrorhandler (from `../node_modules/react-native/ReactCommon/jserrorhandler`) - - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) - - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector-modern`) - - React-jsitracing (from `../node_modules/react-native/ReactCommon/hermes/executor/`) - - React-logger (from `../node_modules/react-native/ReactCommon/logger`) - - React-Mapbuffer (from `../node_modules/react-native/ReactCommon`) - - react-native-app-auth (from `../node_modules/react-native-app-auth`) - - react-native-blob-util (from `../node_modules/react-native-blob-util`) - - "react-native-cookies (from `../node_modules/@react-native-cookies/cookies`)" - - react-native-date-picker (from `../node_modules/react-native-date-picker`) - - react-native-mmkv-storage (from `../node_modules/react-native-mmkv-storage`) - - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - - react-native-passkey (from `../node_modules/react-native-passkey`) - - react-native-passkit-wallet (from `../node_modules/react-native-passkit-wallet`) - - react-native-pdf (from `../node_modules/react-native-pdf`) - - "react-native-progress-bar-android (from `../node_modules/@react-native-community/progress-bar-android`)" - - "react-native-progress-view (from `../node_modules/@react-native-community/progress-view`)" - - react-native-quick-base64 (from `../node_modules/react-native-quick-base64`) - - react-native-spotlight-search (from `../node_modules/react-native-spotlight-search`) - - react-native-webview (from `../node_modules/react-native-webview`) - - React-nativeconfig (from `../node_modules/react-native/ReactCommon`) - - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) - - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) - - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) - - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) - - React-RCTAppDelegate (from `../node_modules/react-native/Libraries/AppDelegate`) - - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) - - React-RCTFabric (from `../node_modules/react-native/React`) - - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) - - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) - - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) - - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) - - React-RCTText (from `../node_modules/react-native/Libraries/Text`) - - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - - React-rendererdebug (from `../node_modules/react-native/ReactCommon/react/renderer/debug`) - - React-rncore (from `../node_modules/react-native/ReactCommon`) - - React-RuntimeApple (from `../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) - - React-RuntimeCore (from `../node_modules/react-native/ReactCommon/react/runtime`) - - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) - - React-RuntimeHermes (from `../node_modules/react-native/ReactCommon/react/runtime`) - - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) - - ReactNativeKeyboardManager (from `../node_modules/react-native-keyboard-manager`) - - ReactNativeNavigation (from `../node_modules/react-native-navigation`) - - "RNCAsyncStorage (from `../node_modules/@react-native-community/async-storage`)" - - "RNCClipboard (from `../node_modules/@react-native-clipboard/clipboard`)" - - RNDeviceInfo (from `../node_modules/react-native-device-info`) - - "RNFBAnalytics (from `../node_modules/@react-native-firebase/analytics`)" - - "RNFBApp (from `../node_modules/@react-native-firebase/app`)" - - "RNFBMessaging (from `../node_modules/@react-native-firebase/messaging`)" - - "RNFBPerf (from `../node_modules/@react-native-firebase/perf`)" - - RNGestureHandler (from `../node_modules/react-native-gesture-handler`) - - RNInAppBrowser (from `../node_modules/react-native-inappbrowser-reborn`) - - RNKeychain (from `../node_modules/react-native-keychain`) - - RNQuickAction (from `../node_modules/react-native-quick-actions`) + - React-Core (from `../../../../node_modules/react-native/`) + - React-Core/RCTWebSocket (from `../../../../node_modules/react-native/`) + - React-CoreModules (from `../../../../node_modules/react-native/React/CoreModules`) + - React-cxxreact (from `../../../../node_modules/react-native/ReactCommon/cxxreact`) + - React-debug (from `../../../../node_modules/react-native/ReactCommon/react/debug`) + - React-Fabric (from `../../../../node_modules/react-native/ReactCommon`) + - React-FabricImage (from `../../../../node_modules/react-native/ReactCommon`) + - React-featureflags (from `../../../../node_modules/react-native/ReactCommon/react/featureflags`) + - React-graphics (from `../../../../node_modules/react-native/ReactCommon/react/renderer/graphics`) + - React-hermes (from `../../../../node_modules/react-native/ReactCommon/hermes`) + - React-ImageManager (from `../../../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) + - React-jserrorhandler (from `../../../../node_modules/react-native/ReactCommon/jserrorhandler`) + - React-jsi (from `../../../../node_modules/react-native/ReactCommon/jsi`) + - React-jsiexecutor (from `../../../../node_modules/react-native/ReactCommon/jsiexecutor`) + - React-jsinspector (from `../../../../node_modules/react-native/ReactCommon/jsinspector-modern`) + - React-jsitracing (from `../../../../node_modules/react-native/ReactCommon/hermes/executor/`) + - React-logger (from `../../../../node_modules/react-native/ReactCommon/logger`) + - React-Mapbuffer (from `../../../../node_modules/react-native/ReactCommon`) + - react-native-app-auth (from `../../../../node_modules/react-native-app-auth`) + - react-native-blob-util (from `../../../../node_modules/react-native-blob-util`) + - "react-native-cookies (from `../../../../node_modules/@react-native-cookies/cookies`)" + - react-native-date-picker (from `../../../../node_modules/react-native-date-picker`) + - react-native-mmkv-storage (from `../../../../node_modules/react-native-mmkv-storage`) + - "react-native-netinfo (from `../../../../node_modules/@react-native-community/netinfo`)" + - react-native-passkey (from `../../../../node_modules/react-native-passkey`) + - react-native-passkit-wallet (from `../../../../node_modules/react-native-passkit-wallet`) + - react-native-pdf (from `../../../../node_modules/react-native-pdf`) + - "react-native-progress-bar-android (from `../../../../node_modules/@react-native-community/progress-bar-android`)" + - "react-native-progress-view (from `../../../../node_modules/@react-native-community/progress-view`)" + - react-native-quick-base64 (from `../../../../node_modules/react-native-quick-base64`) + - react-native-spotlight-search (from `../../../../node_modules/react-native-spotlight-search`) + - react-native-webview (from `../../../../node_modules/react-native-webview`) + - React-nativeconfig (from `../../../../node_modules/react-native/ReactCommon`) + - React-NativeModulesApple (from `../../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) + - React-perflogger (from `../../../../node_modules/react-native/ReactCommon/reactperflogger`) + - React-RCTActionSheet (from `../../../../node_modules/react-native/Libraries/ActionSheetIOS`) + - React-RCTAnimation (from `../../../../node_modules/react-native/Libraries/NativeAnimation`) + - React-RCTAppDelegate (from `../../../../node_modules/react-native/Libraries/AppDelegate`) + - React-RCTBlob (from `../../../../node_modules/react-native/Libraries/Blob`) + - React-RCTFabric (from `../../../../node_modules/react-native/React`) + - React-RCTImage (from `../../../../node_modules/react-native/Libraries/Image`) + - React-RCTLinking (from `../../../../node_modules/react-native/Libraries/LinkingIOS`) + - React-RCTNetwork (from `../../../../node_modules/react-native/Libraries/Network`) + - React-RCTSettings (from `../../../../node_modules/react-native/Libraries/Settings`) + - React-RCTText (from `../../../../node_modules/react-native/Libraries/Text`) + - React-RCTVibration (from `../../../../node_modules/react-native/Libraries/Vibration`) + - React-rendererdebug (from `../../../../node_modules/react-native/ReactCommon/react/renderer/debug`) + - React-rncore (from `../../../../node_modules/react-native/ReactCommon`) + - React-RuntimeApple (from `../../../../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) + - React-RuntimeCore (from `../../../../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimeexecutor (from `../../../../node_modules/react-native/ReactCommon/runtimeexecutor`) + - React-RuntimeHermes (from `../../../../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimescheduler (from `../../../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) + - React-utils (from `../../../../node_modules/react-native/ReactCommon/react/utils`) + - ReactCommon/turbomodule/core (from `../../../../node_modules/react-native/ReactCommon`) + - ReactNativeKeyboardManager (from `../../../../node_modules/react-native-keyboard-manager`) + - ReactNativeNavigation (from `../../../../node_modules/react-native-navigation`) + - "RNCAsyncStorage (from `../../../../node_modules/@react-native-community/async-storage`)" + - "RNCClipboard (from `../../../../node_modules/@react-native-clipboard/clipboard`)" + - RNDeviceInfo (from `../../../../node_modules/react-native-device-info`) + - "RNFBAnalytics (from `../../../../node_modules/@react-native-firebase/analytics`)" + - "RNFBApp (from `../../../../node_modules/@react-native-firebase/app`)" + - "RNFBMessaging (from `../../../../node_modules/@react-native-firebase/messaging`)" + - "RNFBPerf (from `../../../../node_modules/@react-native-firebase/perf`)" + - RNGestureHandler (from `../../../../node_modules/react-native-gesture-handler`) + - RNInAppBrowser (from `../../../../node_modules/react-native-inappbrowser-reborn`) + - RNKeychain (from `../../../../node_modules/react-native-keychain`) + - RNQuickAction (from `../../../../node_modules/react-native-quick-actions`) - RNReanimated (from `../node_modules/react-native-reanimated`) - - RNShare (from `../node_modules/react-native-share`) - - RNSVG (from `../node_modules/react-native-svg`) + - RNShare (from `../../../../node_modules/react-native-share`) + - RNSVG (from `../../../../node_modules/react-native-svg`) - VisionCamera (from `../node_modules/react-native-vision-camera`) - - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) + - Yoga (from `../../../../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: trunk: @@ -1709,207 +1709,207 @@ SPEC REPOS: EXTERNAL SOURCES: boost: - :podspec: "../node_modules/react-native/third-party-podspecs/boost.podspec" + :podspec: "../../../../node_modules/react-native/third-party-podspecs/boost.podspec" CodePush: - :path: "../node_modules/react-native-code-push" + :path: "../../../../node_modules/react-native-code-push" DatadogSDKReactNative: - :path: "../node_modules/@datadog/mobile-react-native" + :path: "../../../../node_modules/@datadog/mobile-react-native" DoubleConversion: - :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + :podspec: "../../../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" EXApplication: - :path: "../node_modules/expo-application/ios" + :path: "../../../../node_modules/expo-application/ios" EXConstants: - :path: "../node_modules/expo-constants/ios" + :path: "../../../../node_modules/expo-constants/ios" EXNotifications: - :path: "../node_modules/expo-notifications/ios" + :path: "../../../../node_modules/expo-notifications/ios" Expo: - :path: "../node_modules/expo" + :path: "../../../../node_modules/expo" ExpoAsset: - :path: "../node_modules/expo-asset/ios" + :path: "../../../../node_modules/expo-asset/ios" ExpoFileSystem: - :path: "../node_modules/expo-file-system/ios" + :path: "../../../../node_modules/expo-file-system/ios" ExpoFont: - :path: "../node_modules/expo-font/ios" + :path: "../../../../node_modules/expo-font/ios" ExpoHaptics: - :path: "../node_modules/expo-haptics/ios" + :path: "../../../../node_modules/expo-haptics/ios" ExpoKeepAwake: - :path: "../node_modules/expo-keep-awake/ios" + :path: "../../../../node_modules/expo-keep-awake/ios" ExpoLocalAuthentication: - :path: "../node_modules/expo-local-authentication/ios" + :path: "../../../../node_modules/expo-local-authentication/ios" ExpoModulesCore: - :path: "../node_modules/expo-modules-core" + :path: "../../../../node_modules/expo-modules-core" FBLazyVector: - :path: "../node_modules/react-native/Libraries/FBLazyVector" + :path: "../../../../node_modules/react-native/Libraries/FBLazyVector" fmt: - :podspec: "../node_modules/react-native/third-party-podspecs/fmt.podspec" + :podspec: "../../../../node_modules/react-native/third-party-podspecs/fmt.podspec" glog: - :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" + :podspec: "../../../../node_modules/react-native/third-party-podspecs/glog.podspec" hermes-engine: - :podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" + :podspec: "../../../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" :tag: hermes-2024-06-28-RNv0.74.3-7bda0c267e76d11b68a585f84cfdd65000babf85 Interactable: - :path: "../node_modules/react-native-interactable" + :path: "../../../../node_modules/react-native-interactable" RCT-Folly: - :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" + :podspec: "../../../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" RCTDeprecation: - :path: "../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" + :path: "../../../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" RCTRequired: - :path: "../node_modules/react-native/Libraries/Required" + :path: "../../../../node_modules/react-native/Libraries/Required" RCTTypeSafety: - :path: "../node_modules/react-native/Libraries/TypeSafety" + :path: "../../../../node_modules/react-native/Libraries/TypeSafety" React: - :path: "../node_modules/react-native/" + :path: "../../../../node_modules/react-native/" React-callinvoker: - :path: "../node_modules/react-native/ReactCommon/callinvoker" + :path: "../../../../node_modules/react-native/ReactCommon/callinvoker" React-Codegen: :path: build/generated/ios React-Core: - :path: "../node_modules/react-native/" + :path: "../../../../node_modules/react-native/" React-CoreModules: - :path: "../node_modules/react-native/React/CoreModules" + :path: "../../../../node_modules/react-native/React/CoreModules" React-cxxreact: - :path: "../node_modules/react-native/ReactCommon/cxxreact" + :path: "../../../../node_modules/react-native/ReactCommon/cxxreact" React-debug: - :path: "../node_modules/react-native/ReactCommon/react/debug" + :path: "../../../../node_modules/react-native/ReactCommon/react/debug" React-Fabric: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../../../node_modules/react-native/ReactCommon" React-FabricImage: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../../../node_modules/react-native/ReactCommon" React-featureflags: - :path: "../node_modules/react-native/ReactCommon/react/featureflags" + :path: "../../../../node_modules/react-native/ReactCommon/react/featureflags" React-graphics: - :path: "../node_modules/react-native/ReactCommon/react/renderer/graphics" + :path: "../../../../node_modules/react-native/ReactCommon/react/renderer/graphics" React-hermes: - :path: "../node_modules/react-native/ReactCommon/hermes" + :path: "../../../../node_modules/react-native/ReactCommon/hermes" React-ImageManager: - :path: "../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" + :path: "../../../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" React-jserrorhandler: - :path: "../node_modules/react-native/ReactCommon/jserrorhandler" + :path: "../../../../node_modules/react-native/ReactCommon/jserrorhandler" React-jsi: - :path: "../node_modules/react-native/ReactCommon/jsi" + :path: "../../../../node_modules/react-native/ReactCommon/jsi" React-jsiexecutor: - :path: "../node_modules/react-native/ReactCommon/jsiexecutor" + :path: "../../../../node_modules/react-native/ReactCommon/jsiexecutor" React-jsinspector: - :path: "../node_modules/react-native/ReactCommon/jsinspector-modern" + :path: "../../../../node_modules/react-native/ReactCommon/jsinspector-modern" React-jsitracing: - :path: "../node_modules/react-native/ReactCommon/hermes/executor/" + :path: "../../../../node_modules/react-native/ReactCommon/hermes/executor/" React-logger: - :path: "../node_modules/react-native/ReactCommon/logger" + :path: "../../../../node_modules/react-native/ReactCommon/logger" React-Mapbuffer: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../../../node_modules/react-native/ReactCommon" react-native-app-auth: - :path: "../node_modules/react-native-app-auth" + :path: "../../../../node_modules/react-native-app-auth" react-native-blob-util: - :path: "../node_modules/react-native-blob-util" + :path: "../../../../node_modules/react-native-blob-util" react-native-cookies: - :path: "../node_modules/@react-native-cookies/cookies" + :path: "../../../../node_modules/@react-native-cookies/cookies" react-native-date-picker: - :path: "../node_modules/react-native-date-picker" + :path: "../../../../node_modules/react-native-date-picker" react-native-mmkv-storage: - :path: "../node_modules/react-native-mmkv-storage" + :path: "../../../../node_modules/react-native-mmkv-storage" react-native-netinfo: - :path: "../node_modules/@react-native-community/netinfo" + :path: "../../../../node_modules/@react-native-community/netinfo" react-native-passkey: - :path: "../node_modules/react-native-passkey" + :path: "../../../../node_modules/react-native-passkey" react-native-passkit-wallet: - :path: "../node_modules/react-native-passkit-wallet" + :path: "../../../../node_modules/react-native-passkit-wallet" react-native-pdf: - :path: "../node_modules/react-native-pdf" + :path: "../../../../node_modules/react-native-pdf" react-native-progress-bar-android: - :path: "../node_modules/@react-native-community/progress-bar-android" + :path: "../../../../node_modules/@react-native-community/progress-bar-android" react-native-progress-view: - :path: "../node_modules/@react-native-community/progress-view" + :path: "../../../../node_modules/@react-native-community/progress-view" react-native-quick-base64: - :path: "../node_modules/react-native-quick-base64" + :path: "../../../../node_modules/react-native-quick-base64" react-native-spotlight-search: - :path: "../node_modules/react-native-spotlight-search" + :path: "../../../../node_modules/react-native-spotlight-search" react-native-webview: - :path: "../node_modules/react-native-webview" + :path: "../../../../node_modules/react-native-webview" React-nativeconfig: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../../../node_modules/react-native/ReactCommon" React-NativeModulesApple: - :path: "../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" + :path: "../../../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" React-perflogger: - :path: "../node_modules/react-native/ReactCommon/reactperflogger" + :path: "../../../../node_modules/react-native/ReactCommon/reactperflogger" React-RCTActionSheet: - :path: "../node_modules/react-native/Libraries/ActionSheetIOS" + :path: "../../../../node_modules/react-native/Libraries/ActionSheetIOS" React-RCTAnimation: - :path: "../node_modules/react-native/Libraries/NativeAnimation" + :path: "../../../../node_modules/react-native/Libraries/NativeAnimation" React-RCTAppDelegate: - :path: "../node_modules/react-native/Libraries/AppDelegate" + :path: "../../../../node_modules/react-native/Libraries/AppDelegate" React-RCTBlob: - :path: "../node_modules/react-native/Libraries/Blob" + :path: "../../../../node_modules/react-native/Libraries/Blob" React-RCTFabric: - :path: "../node_modules/react-native/React" + :path: "../../../../node_modules/react-native/React" React-RCTImage: - :path: "../node_modules/react-native/Libraries/Image" + :path: "../../../../node_modules/react-native/Libraries/Image" React-RCTLinking: - :path: "../node_modules/react-native/Libraries/LinkingIOS" + :path: "../../../../node_modules/react-native/Libraries/LinkingIOS" React-RCTNetwork: - :path: "../node_modules/react-native/Libraries/Network" + :path: "../../../../node_modules/react-native/Libraries/Network" React-RCTSettings: - :path: "../node_modules/react-native/Libraries/Settings" + :path: "../../../../node_modules/react-native/Libraries/Settings" React-RCTText: - :path: "../node_modules/react-native/Libraries/Text" + :path: "../../../../node_modules/react-native/Libraries/Text" React-RCTVibration: - :path: "../node_modules/react-native/Libraries/Vibration" + :path: "../../../../node_modules/react-native/Libraries/Vibration" React-rendererdebug: - :path: "../node_modules/react-native/ReactCommon/react/renderer/debug" + :path: "../../../../node_modules/react-native/ReactCommon/react/renderer/debug" React-rncore: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../../../node_modules/react-native/ReactCommon" React-RuntimeApple: - :path: "../node_modules/react-native/ReactCommon/react/runtime/platform/ios" + :path: "../../../../node_modules/react-native/ReactCommon/react/runtime/platform/ios" React-RuntimeCore: - :path: "../node_modules/react-native/ReactCommon/react/runtime" + :path: "../../../../node_modules/react-native/ReactCommon/react/runtime" React-runtimeexecutor: - :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" + :path: "../../../../node_modules/react-native/ReactCommon/runtimeexecutor" React-RuntimeHermes: - :path: "../node_modules/react-native/ReactCommon/react/runtime" + :path: "../../../../node_modules/react-native/ReactCommon/react/runtime" React-runtimescheduler: - :path: "../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" + :path: "../../../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" React-utils: - :path: "../node_modules/react-native/ReactCommon/react/utils" + :path: "../../../../node_modules/react-native/ReactCommon/react/utils" ReactCommon: - :path: "../node_modules/react-native/ReactCommon" + :path: "../../../../node_modules/react-native/ReactCommon" ReactNativeKeyboardManager: - :path: "../node_modules/react-native-keyboard-manager" + :path: "../../../../node_modules/react-native-keyboard-manager" ReactNativeNavigation: - :path: "../node_modules/react-native-navigation" + :path: "../../../../node_modules/react-native-navigation" RNCAsyncStorage: - :path: "../node_modules/@react-native-community/async-storage" + :path: "../../../../node_modules/@react-native-community/async-storage" RNCClipboard: - :path: "../node_modules/@react-native-clipboard/clipboard" + :path: "../../../../node_modules/@react-native-clipboard/clipboard" RNDeviceInfo: - :path: "../node_modules/react-native-device-info" + :path: "../../../../node_modules/react-native-device-info" RNFBAnalytics: - :path: "../node_modules/@react-native-firebase/analytics" + :path: "../../../../node_modules/@react-native-firebase/analytics" RNFBApp: - :path: "../node_modules/@react-native-firebase/app" + :path: "../../../../node_modules/@react-native-firebase/app" RNFBMessaging: - :path: "../node_modules/@react-native-firebase/messaging" + :path: "../../../../node_modules/@react-native-firebase/messaging" RNFBPerf: - :path: "../node_modules/@react-native-firebase/perf" + :path: "../../../../node_modules/@react-native-firebase/perf" RNGestureHandler: - :path: "../node_modules/react-native-gesture-handler" + :path: "../../../../node_modules/react-native-gesture-handler" RNInAppBrowser: - :path: "../node_modules/react-native-inappbrowser-reborn" + :path: "../../../../node_modules/react-native-inappbrowser-reborn" RNKeychain: - :path: "../node_modules/react-native-keychain" + :path: "../../../../node_modules/react-native-keychain" RNQuickAction: - :path: "../node_modules/react-native-quick-actions" + :path: "../../../../node_modules/react-native-quick-actions" RNReanimated: :path: "../node_modules/react-native-reanimated" RNShare: - :path: "../node_modules/react-native-share" + :path: "../../../../node_modules/react-native-share" RNSVG: - :path: "../node_modules/react-native-svg" + :path: "../../../../node_modules/react-native-svg" VisionCamera: :path: "../node_modules/react-native-vision-camera" Yoga: - :path: "../node_modules/react-native/ReactCommon/yoga" + :path: "../../../../node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: - AppAuth: 501c04eda8a8d11f179dbe8637b7a91bb7e5d2fa + AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73 Base64: cecfb41a004124895a7bcee567a89bae5a89d49b boost: d3f49c53809116a5d38da093a8aa78bf551aed09 CodePush: eaa66fc8dd9ff611304ecc48397ce8831ba79ac9 @@ -1938,9 +1938,9 @@ SPEC CHECKSUMS: FirebaseInstallations: 766dabca09fd94aef922538aaf144cc4a6fb6869 FirebaseMessaging: e345b219fd15d325f0cf2fef28cb8ce00d851b3f FirebasePerformance: 8f1c8e5a4fcc5a68400835518ee63a6d63dbff0c - FirebaseRemoteConfig: 37a2ba3c8c454be8553a41ba1a2f4a4f0b845670 - FirebaseRemoteConfigInterop: c55a739f5ab121792776e191d9fd437dc624a541 - FirebaseSharedSwift: a03fe7a59ee646fef71099a887f774fe25d745b8 + FirebaseRemoteConfig: 48ef3f243742a8d72422ccfc9f986e19d7de53fd + FirebaseRemoteConfigInterop: 6efda51fb5e2f15b16585197e26eaa09574e8a4d + FirebaseSharedSwift: 20530f495084b8d840f78a100d8c5ee613375f6e fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120 glog: fdfdfe5479092de0c4bdbebedd9056951f092c4f GoogleAppMeasurement: c7d6fff39bf2d829587d74088d582e32d75133c3 @@ -1962,7 +1962,7 @@ SPEC CHECKSUMS: RCTTypeSafety: f5ecbc86c5c5fa163c05acb7a1c5012e15b5f994 React: fc9fa7258eff606f44d58c5b233a82dc9cf09018 React-callinvoker: e3fab14d69607fb7e8e3a57e5a415aed863d3599 - React-Codegen: 6fa87b7c6b8efcd0cef4bfeaec8c8bc8a6abe75a + React-Codegen: df959b3e7962cad0125362bb448ae59d9c0c8dfe React-Core: 3a5fd9e781cecf87803e5b091496a606a3df774a React-CoreModules: cbf4707dafab8f9f826ac0c63a07d0bf5d01e256 React-cxxreact: 7b188556271e3c7fdf22a04819f6a6225045b9dd @@ -2030,7 +2030,7 @@ SPEC CHECKSUMS: RNInAppBrowser: e36d6935517101ccba0e875bac8ad7b0cb655364 RNKeychain: ff836453cba46938e0e9e4c22e43d43fa2c90333 RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93 - RNReanimated: f9192536fa8a312c737eaf15c905f78831193ef1 + RNReanimated: d093daf3973a7ee9f77c10a9337e10390b2969e2 RNShare: 0fad69ae2d71de9d1f7b9a43acf876886a6cb99c RNSVG: 43b64ed39c14ce830d840903774154ca0c1f27ec SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d diff --git a/apps/native/app/jest.config.js b/apps/native/app/jest.config.js deleted file mode 100644 index 1fbafc9cb781..000000000000 --- a/apps/native/app/jest.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - preset: 'react-native', -} diff --git a/apps/native/app/jest.config.ts b/apps/native/app/jest.config.ts new file mode 100644 index 000000000000..3e943d58cfc5 --- /dev/null +++ b/apps/native/app/jest.config.ts @@ -0,0 +1,22 @@ +module.exports = { + displayName: 'IslandApp', + preset: 'react-native', + resolver: '@nx/jest/plugins/resolver', + moduleFileExtensions: ['ts', 'js', 'html', 'tsx', 'jsx'], + setupFilesAfterEnv: ['/src/test-setup.ts'], + moduleNameMapper: { + '\\.svg$': '@nx/react-native/plugins/jest/svg-mock', + }, + transform: { + '^.+.(js|ts|tsx)$': [ + 'babel-jest', + { + configFile: __dirname + '/.babelrc.js', + }, + ], + '^.+.(bmp|gif|jpg|jpeg|mp4|png|psd|svg|webp)$': require.resolve( + 'react-native/jest/assetFileTransformer.js', + ), + }, + coverageDirectory: '../../../coverage/apps/native/app', +} diff --git a/apps/native/app/metro.config.js b/apps/native/app/metro.config.js index 491f6980daae..70fab7425451 100644 --- a/apps/native/app/metro.config.js +++ b/apps/native/app/metro.config.js @@ -1,18 +1,8 @@ -const path = require('path') +const { withNxMetro } = require('@nx/react-native') const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config') -const libs = ['application/types'] - -const extraNodeModules = libs.reduce((acc, lib) => { - acc[`@island.is/${lib}`] = path.resolve(__dirname, `../../../libs/${lib}/src`) - return acc -}, {}) - -const watchFolders = [ - ...libs.map((lib) => path.resolve(__dirname, `../../../libs/${lib}/src`)), -] - -const nodeModulesPaths = [path.resolve(path.join(__dirname, './node_modules'))] +const defaultConfig = getDefaultConfig(__dirname) +const { assetExts, sourceExts } = defaultConfig.resolver /** * Metro configuration @@ -20,15 +10,22 @@ const nodeModulesPaths = [path.resolve(path.join(__dirname, './node_modules'))] * * @type {import('metro-config').MetroConfig} */ -const config = { +const customConfig = { + transformer: { + babelTransformerPath: require.resolve('react-native-svg-transformer'), + }, resolver: { - extraNodeModules: { - '@ui': path.resolve(__dirname + '/src/ui'), - ...extraNodeModules, - }, - nodeModulesPaths, + assetExts: assetExts.filter((ext) => ext !== 'svg'), + sourceExts: [...sourceExts, 'cjs', 'mjs', 'svg'], }, - watchFolders, } -module.exports = mergeConfig(getDefaultConfig(__dirname), config) +module.exports = withNxMetro(mergeConfig(defaultConfig, customConfig), { + // Change this to true to see debugging info. + // Useful if you have issues resolving modules + debug: false, + // all the file extensions used for imports other than 'ts', 'tsx', 'js', 'jsx', 'json' + extensions: [], + // Specify folders to watch, in addition to Nx defaults (workspace libraries and node_modules) + watchFolders: [], +}) diff --git a/apps/native/app/package.json b/apps/native/app/package.json index 3e42a3ae7714..3ebb950c5cba 100644 --- a/apps/native/app/package.json +++ b/apps/native/app/package.json @@ -1,7 +1,7 @@ { "name": "@island.is/native-app", "version": "1.2.5", - "private": true, + "license": "MIT", "scripts": { "release:android": "cd android && ./gradlew bundleProdRelease bundleDevRelease", "android": "react-native run-android --mode devDebug", @@ -51,6 +51,8 @@ "@react-native/eslint-config": "0.74.87", "@react-native/metro-config": "0.74.87", "@react-native/typescript-config": "0.74.87", + "@testing-library/jest-native": "5.4.3", + "@testing-library/react-native": "12.9.0", "apollo3-cache-persist": "0.15.0", "compare-versions": "6.1.1", "configcat-js": "7.0.0", @@ -63,8 +65,9 @@ "expo-notifications": "0.28.9", "intl": "1.2.5", "lodash": "4.17.21", + "metro-config": "0.81.0", "path-to-regexp": "6.2.2", - "react": "18.2.0", + "react": "18.3.1", "react-intl": "5.20.12", "react-native": "0.74.5", "react-native-app-auth": "7.2.0", @@ -96,9 +99,6 @@ "styled-components": "6.1.11", "zustand": "3.5.12" }, - "installConfig": { - "hoistingLimits": "workspaces" - }, "devDependencies": { "@babel/core": "^7.20.0", "@babel/preset-env": "^7.20.0", @@ -112,7 +112,6 @@ "babel-jest": "^29.6.3", "babel-loader": "^8.3.0", "babel-plugin-formatjs": "10.3.9", - "babel-plugin-module-resolver": "5.0.2", "jest": "29.7.0", "react-test-renderer": "18.2.0", "typescript": "5.3.3" diff --git a/apps/native/app/project.json b/apps/native/app/project.json index 337b98afd2fe..a23ee071865a 100644 --- a/apps/native/app/project.json +++ b/apps/native/app/project.json @@ -6,30 +6,82 @@ "tags": ["scope:js"], "generators": {}, "targets": { - "serve": { - "executor": "nx:run-commands", + "start": { + "executor": "@nx/react-native:start", + "dependsOn": [], "options": { - "cwd": "apps/native/app", - "command": "yarn start" + "port": 8081 } }, - "build": { - "executor": "nx:run-commands", + "run-ios": { + "executor": "@nx/react-native:run-ios", + "dependsOn": [], + "options": {} + }, + "bundle-ios": { + "executor": "@nx/react-native:bundle", + "dependsOn": [], + "outputs": ["{options.bundleOutput}"], "options": { - "cwd": "apps/native/app", - "command": "yarn build-mock" + "entryFile": "index.js", + "platform": "ios", + "bundleOutput": "dist/apps/native/app/ios/main.jsbundle" } }, - "test": { - "executor": "nx:run-commands", + "run-android": { + "executor": "@nx/react-native:run-android", + "dependsOn": [], + "options": {} + }, + "build-android": { + "executor": "@nx/react-native:build-android", + "outputs": [ + "{projectRoot}/android/app/build/outputs/bundle", + "{projectRoot}/android/app/build/outputs/apk" + ], + "dependsOn": [], + "options": {} + }, + "build-ios": { + "executor": "@nx/react-native:build-ios", + "outputs": ["{projectRoot}/ios/build/Build"], + "dependsOn": [], + "options": {} + }, + "pod-install": { + "executor": "@nx/react-native:pod-install", + "dependsOn": ["sync-deps"], + "outputs": ["{projectRoot}/ios/Pods", "{projectRoot}/ios/Podfile.lock"], + "options": {} + }, + "upgrade": { + "executor": "@nx/react-native:upgrade", + "options": {} + }, + "bundle-android": { + "executor": "@nx/react-native:bundle", + "dependsOn": [], + "outputs": ["{options.bundleOutput}"], "options": { - "cwd": "apps/native/app", - "command": "yarn test-mock" + "entryFile": "index.js", + "platform": "android", + "bundleOutput": "dist/apps/native/app/android/main.jsbundle" } }, + "sync-deps": { + "executor": "@nx/react-native:sync-deps", + "options": {} + }, "lint": { "executor": "@nx/eslint:lint" }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "apps/native/app/jest.config.ts" + } + }, "codegen/frontend-client": { "executor": "nx:run-commands", "options": { diff --git a/apps/native/app/src/components/bottom-tabs-indicator/bottom-tabs-indicator.tsx b/apps/native/app/src/components/bottom-tabs-indicator/bottom-tabs-indicator.tsx index 27efe493886d..4967aaee8f59 100644 --- a/apps/native/app/src/components/bottom-tabs-indicator/bottom-tabs-indicator.tsx +++ b/apps/native/app/src/components/bottom-tabs-indicator/bottom-tabs-indicator.tsx @@ -1,9 +1,9 @@ -import { dynamicColor } from '@ui' import React, { useEffect, useRef, useState } from 'react' import { Animated, SafeAreaView, View, useWindowDimensions } from 'react-native' import { useTheme } from 'styled-components' import styled from 'styled-components/native' import { useUiStore } from '../../stores/ui-store' +import { dynamicColor } from '../../ui' const Host = styled.View` position: absolute; diff --git a/apps/native/app/src/components/offline/offline-banner.tsx b/apps/native/app/src/components/offline/offline-banner.tsx index 60f01b78f504..04c03fe828b6 100644 --- a/apps/native/app/src/components/offline/offline-banner.tsx +++ b/apps/native/app/src/components/offline/offline-banner.tsx @@ -1,8 +1,8 @@ -import { Alert, DARK_YELLOW_200, dynamicColor } from '@ui' import React, { useEffect, useRef } from 'react' import { Animated, Easing, SafeAreaView } from 'react-native' import { Navigation } from 'react-native-navigation' import styled from 'styled-components/native' +import { Alert, DARK_YELLOW_200, dynamicColor } from '../../ui' import { getIntl } from '../../contexts/i18n-provider' import { useOfflineActions } from '../../stores/offline-store' import { ComponentRegistry as CR } from '../../utils/component-registry' diff --git a/apps/native/app/src/components/pin-keypad/pin-keypad.tsx b/apps/native/app/src/components/pin-keypad/pin-keypad.tsx index c6bf5a106aca..3c2cebe2792f 100644 --- a/apps/native/app/src/components/pin-keypad/pin-keypad.tsx +++ b/apps/native/app/src/components/pin-keypad/pin-keypad.tsx @@ -1,4 +1,3 @@ -import { dynamicColor, font } from '@ui' import React, { useCallback, useEffect, useState } from 'react' import { useIntl } from 'react-intl' import { @@ -10,6 +9,7 @@ import { useWindowDimensions, } from 'react-native' import styled, { useTheme } from 'styled-components/native' +import { dynamicColor, font } from '../../ui' import { testIDs } from '../../utils/test-ids' interface PinKeypadProps { diff --git a/apps/native/app/src/components/visualized-pin-code/visualized-pin-code.tsx b/apps/native/app/src/components/visualized-pin-code/visualized-pin-code.tsx index c74a91d9a201..4385e265feb5 100644 --- a/apps/native/app/src/components/visualized-pin-code/visualized-pin-code.tsx +++ b/apps/native/app/src/components/visualized-pin-code/visualized-pin-code.tsx @@ -1,7 +1,7 @@ -import { dynamicColor } from '@ui' import React, { useCallback, useEffect, useRef } from 'react' import { Animated, ViewStyle } from 'react-native' import styled from 'styled-components/native' +import { dynamicColor } from '../../ui' interface VisualizedPinCodeProps { code: string diff --git a/apps/native/app/src/hooks/use-connectivity-indicator.ts b/apps/native/app/src/hooks/use-connectivity-indicator.ts index 3f06c4b4077e..3e324a320655 100644 --- a/apps/native/app/src/hooks/use-connectivity-indicator.ts +++ b/apps/native/app/src/hooks/use-connectivity-indicator.ts @@ -1,11 +1,10 @@ import { QueryResult } from '@apollo/client' - -import { theme } from '@ui' import isEqual from 'lodash/isEqual' import { useEffect, useRef } from 'react' import { Navigation, OptionsTopBar } from 'react-native-navigation' import { OptionsTopBarButton } from 'react-native-navigation/lib/src/interfaces/Options' +import { theme } from '../ui' import { useOfflineStore } from '../stores/offline-store' import { ButtonRegistry as BR } from '../utils/component-registry' import { isDefined } from '../utils/is-defined' diff --git a/apps/native/app/src/lib/show-picker.ts b/apps/native/app/src/lib/show-picker.ts index 20a41ca0167f..dbc2d73fe856 100644 --- a/apps/native/app/src/lib/show-picker.ts +++ b/apps/native/app/src/lib/show-picker.ts @@ -1,6 +1,6 @@ -import { dynamicColor } from '@ui' import { ActionSheetIOS } from 'react-native' import DialogAndroid from 'react-native-dialogs' +import { dynamicColor } from '../ui' import { uiStore } from '../stores/ui-store' import { isAndroid, isIos } from '../utils/devices' diff --git a/apps/native/app/src/screens/air-discount/air-discount.tsx b/apps/native/app/src/screens/air-discount/air-discount.tsx index 971b7b711686..f74b83dbd9d4 100644 --- a/apps/native/app/src/screens/air-discount/air-discount.tsx +++ b/apps/native/app/src/screens/air-discount/air-discount.tsx @@ -1,12 +1,3 @@ -import { - Alert, - Heading, - Link, - LinkText, - Problem, - Skeleton, - Typography, -} from '@ui' import React from 'react' import { Image, SafeAreaView, ScrollView, View } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' @@ -18,8 +9,17 @@ import { useGetAirDiscountFlightLegsQuery, useGetAirDiscountQuery, } from '../../graphql/types/schema' -import { AirDiscountCard } from '@ui/lib/card/air-discount-card' -import { Bullet } from '@ui/lib/bullet/bullet' +import { + Alert, + Heading, + Link, + LinkText, + Problem, + Skeleton, + Typography, + Bullet, + AirDiscountCard, +} from '../../ui' import { useConnectivityIndicator } from '../../hooks/use-connectivity-indicator' import { AirfaresUsageTable } from './airfares-usage-table' import externalLinkIcon from '../../assets/icons/external-link.png' diff --git a/apps/native/app/src/screens/air-discount/airfares-usage-table.tsx b/apps/native/app/src/screens/air-discount/airfares-usage-table.tsx index 22f60d265286..786660f7771b 100644 --- a/apps/native/app/src/screens/air-discount/airfares-usage-table.tsx +++ b/apps/native/app/src/screens/air-discount/airfares-usage-table.tsx @@ -2,7 +2,7 @@ import React from 'react' import { FormattedDate, FormattedTime } from 'react-intl' import styled from 'styled-components/native' -import { Typography, dynamicColor } from '@ui' +import { Typography, dynamicColor } from '../../ui' import { GetAirDiscountFlightLegsQuery } from '../../graphql/types/schema' import { useTheme } from 'styled-components' diff --git a/apps/native/app/src/screens/app-lock/app-lock.tsx b/apps/native/app/src/screens/app-lock/app-lock.tsx index 56ffc28596e2..28edb1dc7d16 100644 --- a/apps/native/app/src/screens/app-lock/app-lock.tsx +++ b/apps/native/app/src/screens/app-lock/app-lock.tsx @@ -1,4 +1,3 @@ -import { dynamicColor, font } from '@ui' import { selectionAsync } from 'expo-haptics' import { authenticateAsync, @@ -17,6 +16,8 @@ import { useNavigationComponentDidDisappear, } from 'react-native-navigation-hooks/dist' import styled from 'styled-components/native' + +import { dynamicColor, font } from '../../ui' import logo from '../../assets/logo/logo-64w.png' import { PinKeypad } from '../../components/pin-keypad/pin-keypad' import { VisualizedPinCode } from '../../components/visualized-pin-code/visualized-pin-code' diff --git a/apps/native/app/src/screens/applications/applications.tsx b/apps/native/app/src/screens/applications/applications.tsx index f999a0c124a9..b4951062324f 100644 --- a/apps/native/app/src/screens/applications/applications.tsx +++ b/apps/native/app/src/screens/applications/applications.tsx @@ -1,4 +1,3 @@ -import { EmptyList, StatusCardSkeleton } from '@ui' import { useCallback, useMemo, useState } from 'react' import { useIntl } from 'react-intl' import { Image, RefreshControl, ScrollView, View } from 'react-native' @@ -6,6 +5,7 @@ import { NavigationFunctionComponent } from 'react-native-navigation' import { useNavigationComponentDidAppear } from 'react-native-navigation-hooks' import { useTheme } from 'styled-components' +import { EmptyList, StatusCardSkeleton } from '../../ui' import illustrationSrc from '../../assets/illustrations/le-jobs-s3.png' import { Application, diff --git a/apps/native/app/src/screens/applications/components/applications-list.tsx b/apps/native/app/src/screens/applications/components/applications-list.tsx index 4d6234a1777b..e936fe82c504 100644 --- a/apps/native/app/src/screens/applications/components/applications-list.tsx +++ b/apps/native/app/src/screens/applications/components/applications-list.tsx @@ -1,12 +1,3 @@ -import { - Badge, - badgeColorSchemes, - EmptyList, - Problem, - StatusCard, - StatusCardSkeleton, - TopLine, -} from '@ui' import { useCallback, useMemo, useRef, useState } from 'react' import { useIntl } from 'react-intl' import { @@ -18,6 +9,16 @@ import { View, } from 'react-native' import { useTheme } from 'styled-components' + +import { + Badge, + badgeColorSchemes, + EmptyList, + Problem, + StatusCard, + StatusCardSkeleton, + TopLine, +} from '../../../ui' import illustrationSrc from '../../../assets/illustrations/le-jobs-s3.png' import { Application, diff --git a/apps/native/app/src/screens/applications/components/applications-preview.tsx b/apps/native/app/src/screens/applications/components/applications-preview.tsx index d9bcbcfd5c55..357c4f1a4f7d 100644 --- a/apps/native/app/src/screens/applications/components/applications-preview.tsx +++ b/apps/native/app/src/screens/applications/components/applications-preview.tsx @@ -1,3 +1,7 @@ +import { useIntl } from 'react-intl' +import { TouchableOpacity, View } from 'react-native' +import { useTheme } from 'styled-components' + import { Badge, badgeColorSchemes, @@ -6,11 +10,7 @@ import { StatusCard, Typography, ViewPager, -} from '@ui' -import { useIntl } from 'react-intl' -import { TouchableOpacity, View } from 'react-native' -import { useTheme } from 'styled-components' - +} from '../../../ui' import { Application } from '../../../graphql/types/schema' import { getApplicationType } from '../utils/getApplicationType' import { getBadgeVariant } from '../utils/getBadgeVariant' diff --git a/apps/native/app/src/screens/assets/assets-detail.tsx b/apps/native/app/src/screens/assets/assets-detail.tsx index 7c2f03da9c7c..2d39b01c4ac8 100644 --- a/apps/native/app/src/screens/assets/assets-detail.tsx +++ b/apps/native/app/src/screens/assets/assets-detail.tsx @@ -1,4 +1,3 @@ -import { Divider, Input, InputRow, NavigationBarSheet } from '@ui' import React from 'react' import { useIntl } from 'react-intl' import { ScrollView, View } from 'react-native' @@ -6,6 +5,8 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Divider, Input, InputRow, NavigationBarSheet } from '../../ui' import { useGetAssetQuery } from '../../graphql/types/schema' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { testIDs } from '../../utils/test-ids' diff --git a/apps/native/app/src/screens/assets/assets-overview.tsx b/apps/native/app/src/screens/assets/assets-overview.tsx index 80ae907a351b..508f83c60f5b 100644 --- a/apps/native/app/src/screens/assets/assets-overview.tsx +++ b/apps/native/app/src/screens/assets/assets-overview.tsx @@ -1,4 +1,3 @@ -import { AssetCard, EmptyList, Skeleton, TopLine } from '@ui' import React, { useCallback, useRef, useState } from 'react' import { useIntl } from 'react-intl' import { @@ -12,6 +11,8 @@ import { } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import { useTheme } from 'styled-components/native' + +import { AssetCard, EmptyList, Skeleton, TopLine } from '../../ui' import illustrationSrc from '../../assets/illustrations/le-moving-s1.png' import { BottomTabsIndicator } from '../../components/bottom-tabs-indicator/bottom-tabs-indicator' import { useListAssetsQuery } from '../../graphql/types/schema' diff --git a/apps/native/app/src/screens/cognito-auth/cognito-auth.tsx b/apps/native/app/src/screens/cognito-auth/cognito-auth.tsx index d64f21849c18..718232be294b 100644 --- a/apps/native/app/src/screens/cognito-auth/cognito-auth.tsx +++ b/apps/native/app/src/screens/cognito-auth/cognito-auth.tsx @@ -7,7 +7,6 @@ import { } from '@apollo/client/core' import { setContext } from '@apollo/client/link/context' import { useAsyncStorage } from '@react-native-community/async-storage' -import { Button } from '@ui' import { useEffect, useState } from 'react' import { ActionSheetIOS, Linking, Text, View } from 'react-native' import { @@ -19,6 +18,8 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Button } from '../../ui' import { config } from '../../config' import { openNativeBrowser } from '../../lib/rn-island' import { cognitoAuthUrl, configs } from './config-switcher' diff --git a/apps/native/app/src/screens/document-detail/document-detail.tsx b/apps/native/app/src/screens/document-detail/document-detail.tsx index 9914b0937f78..f12d411990f6 100644 --- a/apps/native/app/src/screens/document-detail/document-detail.tsx +++ b/apps/native/app/src/screens/document-detail/document-detail.tsx @@ -1,6 +1,4 @@ import { useApolloClient, useFragment_experimental } from '@apollo/client' -import { Alert, blue400, dynamicColor, Header, Loader } from '@ui' -import { Problem } from '@ui/lib/problem/problem' import React, { useEffect, useRef, useState } from 'react' import { FormattedDate, useIntl } from 'react-intl' import { @@ -22,6 +20,8 @@ import { import Pdf, { Source } from 'react-native-pdf' import WebView from 'react-native-webview' import styled, { useTheme } from 'styled-components/native' + +import { Alert, blue400, dynamicColor, Header, Loader, Problem } from '../../ui' import { DocumentV2, DocumentV2Action, diff --git a/apps/native/app/src/screens/document-detail/utils/get-buttons-for-actions.tsx b/apps/native/app/src/screens/document-detail/utils/get-buttons-for-actions.tsx index b5baae22f5ea..2dd2b9438144 100644 --- a/apps/native/app/src/screens/document-detail/utils/get-buttons-for-actions.tsx +++ b/apps/native/app/src/screens/document-detail/utils/get-buttons-for-actions.tsx @@ -1,8 +1,8 @@ -import { Button } from '@ui' import styled from 'styled-components' import { View } from 'react-native' import { isValidElement } from 'react' +import { Button } from '../../../ui' import openIcon from '../../../assets/icons/external-link.png' import downloadIcon from '../../../assets/icons/download.png' import { DocumentV2Action } from '../../../graphql/types/schema' diff --git a/apps/native/app/src/screens/family/family-details.tsx b/apps/native/app/src/screens/family/family-details.tsx index e4cf541c22cd..1733c663aea3 100644 --- a/apps/native/app/src/screens/family/family-details.tsx +++ b/apps/native/app/src/screens/family/family-details.tsx @@ -1,4 +1,3 @@ -import { Input, InputRow, NavigationBarSheet, Typography } from '@ui' import React from 'react' import { useIntl } from 'react-intl' import { SafeAreaView, ScrollView, View } from 'react-native' @@ -14,6 +13,8 @@ import { useNationalRegistryChildCustodyQuery, useNationalRegistrySpouseQuery, } from '../../graphql/types/schema' + +import { Input, InputRow, NavigationBarSheet, Typography } from '../../ui' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { formatNationalId } from '../../lib/format-national-id' import { testIDs } from '../../utils/test-ids' diff --git a/apps/native/app/src/screens/family/family-overview.tsx b/apps/native/app/src/screens/family/family-overview.tsx index f2bb50879b2a..13cd53270394 100644 --- a/apps/native/app/src/screens/family/family-overview.tsx +++ b/apps/native/app/src/screens/family/family-overview.tsx @@ -1,4 +1,3 @@ -import { EmptyList, FamilyMemberCard, Problem, Skeleton, TopLine } from '@ui' import React, { useCallback, useRef, useState } from 'react' import { useIntl } from 'react-intl' import { @@ -12,6 +11,14 @@ import { } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import { useTheme } from 'styled-components/native' + +import { + EmptyList, + FamilyMemberCard, + Skeleton, + TopLine, + Problem, +} from '../../ui' import illustrationSrc from '../../assets/illustrations/hero_spring.png' import { BottomTabsIndicator } from '../../components/bottom-tabs-indicator/bottom-tabs-indicator' import { diff --git a/apps/native/app/src/screens/finance/components/finance-status-card.tsx b/apps/native/app/src/screens/finance/components/finance-status-card.tsx index e8f884674ca8..2e9688994997 100644 --- a/apps/native/app/src/screens/finance/components/finance-status-card.tsx +++ b/apps/native/app/src/screens/finance/components/finance-status-card.tsx @@ -1,14 +1,15 @@ +import { useState } from 'react' +import { FormattedMessage, useIntl } from 'react-intl' +import { Image, Linking, Pressable, View } from 'react-native' +import styled, { useTheme } from 'styled-components/native' + import { Skeleton, Typography, blue400, dynamicColor, ExpandableCard, -} from '@ui' -import { useState } from 'react' -import { FormattedMessage, useIntl } from 'react-intl' -import { Image, Linking, Pressable, View } from 'react-native' -import styled, { useTheme } from 'styled-components/native' +} from '../../../ui' import chevronDown from '../../../assets/icons/chevron-down.png' import { ChargeType, diff --git a/apps/native/app/src/screens/finance/finance-status-detail.tsx b/apps/native/app/src/screens/finance/finance-status-detail.tsx index 5c8fbf6f166f..b3e89890e86a 100644 --- a/apps/native/app/src/screens/finance/finance-status-detail.tsx +++ b/apps/native/app/src/screens/finance/finance-status-detail.tsx @@ -1,10 +1,11 @@ -import { Input, InputRow, NavigationBarSheet } from '@ui' import { useIntl } from 'react-intl' import { ScrollView, View } from 'react-native' import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Input, InputRow, NavigationBarSheet } from '../../ui' import { useGetFinanceStatusDetailsQuery } from '../../graphql/types/schema' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { testIDs } from '../../utils/test-ids' diff --git a/apps/native/app/src/screens/finance/finance.tsx b/apps/native/app/src/screens/finance/finance.tsx index 095a70507600..1462b7a6bf2e 100644 --- a/apps/native/app/src/screens/finance/finance.tsx +++ b/apps/native/app/src/screens/finance/finance.tsx @@ -1,8 +1,9 @@ -import { Button, Heading, Skeleton, TableViewCell, Typography } from '@ui' import { FormattedMessage, useIntl } from 'react-intl' import { SafeAreaView, ScrollView } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import { useTheme } from 'styled-components/native' + +import { Button, Heading, Skeleton, TableViewCell, Typography } from '../../ui' import externalLink from '../../assets/icons/external-link.png' import { getConfig } from '../../config' import { GetFinanceStatus } from '../../graphql/types/finance.types' diff --git a/apps/native/app/src/screens/health/health-overview.tsx b/apps/native/app/src/screens/health/health-overview.tsx index 740cac1bbb45..2c25853fb0f5 100644 --- a/apps/native/app/src/screens/health/health-overview.tsx +++ b/apps/native/app/src/screens/health/health-overview.tsx @@ -1,12 +1,3 @@ -import { - Alert, - Button, - Heading, - Input, - InputRow, - Problem, - Typography, -} from '@ui' import React, { useCallback, useMemo, useState } from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { @@ -22,6 +13,15 @@ import { NavigationFunctionComponent } from 'react-native-navigation' import styled, { useTheme } from 'styled-components/native' import { ApolloError } from '@apollo/client' +import { + Alert, + Button, + Heading, + Input, + InputRow, + Problem, + Typography, +} from '../../ui' import { useGetHealthCenterQuery, useGetHealthInsuranceOverviewQuery, diff --git a/apps/native/app/src/screens/home/air-discount-module.tsx b/apps/native/app/src/screens/home/air-discount-module.tsx index bbabf15460d4..69fb3eefd079 100644 --- a/apps/native/app/src/screens/home/air-discount-module.tsx +++ b/apps/native/app/src/screens/home/air-discount-module.tsx @@ -1,3 +1,9 @@ +import React from 'react' +import { FormattedMessage, useIntl } from 'react-intl' +import { Image, SafeAreaView, TouchableOpacity } from 'react-native' +import styled, { useTheme } from 'styled-components/native' +import { ApolloError } from '@apollo/client' + import { Typography, Heading, @@ -5,21 +11,14 @@ import { ViewPager, EmptyCard, GeneralCardSkeleton, -} from '@ui' - -import React from 'react' -import { FormattedMessage, useIntl } from 'react-intl' -import { Image, SafeAreaView, TouchableOpacity } from 'react-native' -import styled, { useTheme } from 'styled-components/native' -import { ApolloError } from '@apollo/client' - + AirDiscountCard, +} from '../../ui' import illustrationSrc from '../../assets/illustrations/le-jobs-s2.png' import { navigateTo } from '../../lib/deep-linking' import { GetAirDiscountQuery, useGetAirDiscountQuery, } from '../../graphql/types/schema' -import { AirDiscountCard } from '@ui/lib/card/air-discount-card' import { screenWidth } from '../../utils/dimensions' const Host = styled.View` diff --git a/apps/native/app/src/screens/home/applications-module.tsx b/apps/native/app/src/screens/home/applications-module.tsx index 0b0210b7cdae..c916f738a571 100644 --- a/apps/native/app/src/screens/home/applications-module.tsx +++ b/apps/native/app/src/screens/home/applications-module.tsx @@ -1,10 +1,10 @@ -import { EmptyCard, StatusCardSkeleton } from '@ui' import React from 'react' import { useIntl } from 'react-intl' import styled from 'styled-components' import { Image, SafeAreaView, View } from 'react-native' import { ApolloError } from '@apollo/client' +import { EmptyCard, StatusCardSkeleton } from '../../ui' import leJobss3 from '../../assets/illustrations/le-jobs-s3.png' import { Application, diff --git a/apps/native/app/src/screens/home/hello-module.tsx b/apps/native/app/src/screens/home/hello-module.tsx index 7bab98b63b9b..ef94534fdc9e 100644 --- a/apps/native/app/src/screens/home/hello-module.tsx +++ b/apps/native/app/src/screens/home/hello-module.tsx @@ -1,11 +1,10 @@ -import { Typography, Skeleton } from '@ui' import * as FileSystem from 'expo-file-system' - import React, { useEffect } from 'react' import { FormattedMessage } from 'react-intl' import { Image, SafeAreaView } from 'react-native' import styled, { useTheme } from 'styled-components/native' +import { Typography, Skeleton } from '../../ui' import { useAuthStore } from '../../stores/auth-store' import { usePreferencesStore } from '../../stores/preferences-store' import { useGetFrontPageImageQuery } from '../../graphql/types/schema' diff --git a/apps/native/app/src/screens/home/home-options.tsx b/apps/native/app/src/screens/home/home-options.tsx index b79ace437971..153272ea9e0a 100644 --- a/apps/native/app/src/screens/home/home-options.tsx +++ b/apps/native/app/src/screens/home/home-options.tsx @@ -1,10 +1,10 @@ -import { Heading, TableViewCell, Typography } from '@ui' import React from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { Platform, SafeAreaView, ScrollView, Switch } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import { useTheme } from 'styled-components' +import { Heading, TableViewCell, Typography } from '../../ui' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { preferencesStore, diff --git a/apps/native/app/src/screens/home/home.tsx b/apps/native/app/src/screens/home/home.tsx index f228c0843a88..2b02f0f5d313 100644 --- a/apps/native/app/src/screens/home/home.tsx +++ b/apps/native/app/src/screens/home/home.tsx @@ -1,4 +1,3 @@ -import { TopLine } from '@ui' import React, { ReactElement, useCallback, @@ -17,6 +16,7 @@ import CodePush from 'react-native-code-push' import { NavigationFunctionComponent } from 'react-native-navigation' import { BottomTabsIndicator } from '../../components/bottom-tabs-indicator/bottom-tabs-indicator' +import { TopLine } from '../../ui' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { useAndroidNotificationPermission } from '../../hooks/use-android-notification-permission' import { useConnectivityIndicator } from '../../hooks/use-connectivity-indicator' diff --git a/apps/native/app/src/screens/home/inbox-module.tsx b/apps/native/app/src/screens/home/inbox-module.tsx index 73ac02f007c5..a4d3ab7d3efb 100644 --- a/apps/native/app/src/screens/home/inbox-module.tsx +++ b/apps/native/app/src/screens/home/inbox-module.tsx @@ -1,17 +1,17 @@ -import { - Typography, - Heading, - ChevronRight, - ListItemSkeleton, - EmptyCard, -} from '@ui' - import React from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { Image, SafeAreaView, TouchableOpacity } from 'react-native' import styled, { useTheme } from 'styled-components/native' import { ApolloError } from '@apollo/client' +import { + Typography, + Heading, + ChevronRight, + ListItemSkeleton, + EmptyCard, + InboxCard, +} from '../../ui' import leCompanys3 from '../../assets/illustrations/le-company-s3.png' import { navigateTo } from '../../lib/deep-linking' import { @@ -19,7 +19,6 @@ import { useListDocumentsQuery, } from '../../graphql/types/schema' import { useOrganizationsStore } from '../../stores/organizations-store' -import { InboxCard } from '@ui/lib/card/inbox-card' const Host = styled.View` margin-bottom: ${({ theme }) => theme.spacing[2]}px; diff --git a/apps/native/app/src/screens/home/licenses-module.tsx b/apps/native/app/src/screens/home/licenses-module.tsx index b8a3f9ecb108..b3bf4ab5ae49 100644 --- a/apps/native/app/src/screens/home/licenses-module.tsx +++ b/apps/native/app/src/screens/home/licenses-module.tsx @@ -1,3 +1,9 @@ +import React from 'react' +import { FormattedMessage, useIntl } from 'react-intl' +import { Image, SafeAreaView, TouchableOpacity } from 'react-native' +import styled, { useTheme } from 'styled-components/native' +import { ApolloError } from '@apollo/client' + import { Typography, Heading, @@ -5,14 +11,7 @@ import { ViewPager, EmptyCard, GeneralCardSkeleton, -} from '@ui' - -import React from 'react' -import { FormattedMessage, useIntl } from 'react-intl' -import { Image, SafeAreaView, TouchableOpacity } from 'react-native' -import styled, { useTheme } from 'styled-components/native' -import { ApolloError } from '@apollo/client' - +} from '../../ui' import { navigateTo } from '../../lib/deep-linking' import { GenericLicenseType, diff --git a/apps/native/app/src/screens/home/onboarding-module.tsx b/apps/native/app/src/screens/home/onboarding-module.tsx index 62eca38b932a..7e024fecc09f 100644 --- a/apps/native/app/src/screens/home/onboarding-module.tsx +++ b/apps/native/app/src/screens/home/onboarding-module.tsx @@ -1,13 +1,13 @@ -import { Close, Heading, ViewPager, WelcomeCard } from '@ui' import React from 'react' import { SafeAreaView, TouchableOpacity } from 'react-native' import { useTheme } from 'styled-components/native' +import { useIntl } from 'react-intl' + +import { Close, Heading, ViewPager, WelcomeCard } from '../../ui' import illustration1 from '../../assets/illustrations/digital-services-m3.png' import illustration3 from '../../assets/illustrations/le-company-s2.png' import illustration2 from '../../assets/illustrations/le-retirement-s3-large.png' import illustration4 from '../../assets/illustrations/le_jobs_s5.png' - -import { useIntl } from 'react-intl' import { useAuthStore } from '../../stores/auth-store' import { usePreferencesStore } from '../../stores/preferences-store' diff --git a/apps/native/app/src/screens/home/vehicles-module.tsx b/apps/native/app/src/screens/home/vehicles-module.tsx index 34c5c709c86d..7a8686d2cc60 100644 --- a/apps/native/app/src/screens/home/vehicles-module.tsx +++ b/apps/native/app/src/screens/home/vehicles-module.tsx @@ -1,3 +1,9 @@ +import React, { useMemo } from 'react' +import { FormattedMessage, useIntl } from 'react-intl' +import { Image, SafeAreaView, TouchableOpacity } from 'react-native' +import styled, { useTheme } from 'styled-components/native' +import { ApolloError } from '@apollo/client' + import { Typography, Heading, @@ -5,14 +11,7 @@ import { ViewPager, EmptyCard, GeneralCardSkeleton, -} from '@ui' - -import React, { useMemo } from 'react' -import { FormattedMessage, useIntl } from 'react-intl' -import { Image, SafeAreaView, TouchableOpacity } from 'react-native' -import styled, { useTheme } from 'styled-components/native' -import { ApolloError } from '@apollo/client' - +} from '../../ui' import illustrationSrc from '../../assets/illustrations/le-moving-s4.png' import { navigateTo } from '../../lib/deep-linking' import { VehicleItem } from '../vehicles/components/vehicle-item' diff --git a/apps/native/app/src/screens/inbox/inbox-filter.tsx b/apps/native/app/src/screens/inbox/inbox-filter.tsx index 121def2ffdd8..700cc8470dca 100644 --- a/apps/native/app/src/screens/inbox/inbox-filter.tsx +++ b/apps/native/app/src/screens/inbox/inbox-filter.tsx @@ -1,3 +1,8 @@ +import { useEffect, useState } from 'react' +import { useIntl } from 'react-intl' +import { ScrollView, View } from 'react-native' +import { Navigation } from 'react-native-navigation' + import { Accordion, AccordionItem, @@ -5,11 +10,7 @@ import { Checkbox, DatePickerInput, theme, -} from '@ui' -import { useEffect, useState } from 'react' -import { useIntl } from 'react-intl' -import { ScrollView, View } from 'react-native' -import { Navigation } from 'react-native-navigation' +} from '../../ui' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { useConnectivityIndicator } from '../../hooks/use-connectivity-indicator' import { diff --git a/apps/native/app/src/screens/inbox/inbox.tsx b/apps/native/app/src/screens/inbox/inbox.tsx index ea6c5ab10e02..fb83ce224dd1 100644 --- a/apps/native/app/src/screens/inbox/inbox.tsx +++ b/apps/native/app/src/screens/inbox/inbox.tsx @@ -1,13 +1,3 @@ -import { - Button, - EmptyList, - ListItem, - ListItemSkeleton, - SearchBar, - Tag, - TopLine, - InboxCard, -} from '@ui' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useIntl } from 'react-intl' import { @@ -26,6 +16,17 @@ import { } from 'react-native-navigation' import { useNavigationComponentDidAppear } from 'react-native-navigation-hooks/dist' import styled, { useTheme } from 'styled-components/native' + +import { + Button, + EmptyList, + ListItem, + ListItemSkeleton, + SearchBar, + Tag, + TopLine, + InboxCard, +} from '../../ui' import filterIcon from '../../assets/icons/filter-icon.png' import inboxReadIcon from '../../assets/icons/inbox-read.png' import illustrationSrc from '../../assets/illustrations/le-company-s3.png' diff --git a/apps/native/app/src/screens/license-scanner/license-scan-detail.tsx b/apps/native/app/src/screens/license-scanner/license-scan-detail.tsx index 0bf8dacd2215..8e1c92ca83e9 100644 --- a/apps/native/app/src/screens/license-scanner/license-scan-detail.tsx +++ b/apps/native/app/src/screens/license-scanner/license-scan-detail.tsx @@ -1,4 +1,3 @@ -import { ScanResultCard, SupportedGenericLicenseTypes } from '@ui' import { useIntl } from 'react-intl' import { Navigation, @@ -7,6 +6,8 @@ import { } from 'react-native-navigation' import { useNavigationButtonPress } from 'react-native-navigation-hooks/dist' import styled from 'styled-components/native' + +import { ScanResultCard, SupportedGenericLicenseTypes } from '../../ui' import { LICENSE_SCANNER_DONE } from '../../constants/navigation-buttons' import { VerifyLicenseBarcodeError, diff --git a/apps/native/app/src/screens/license-scanner/license-scanner.tsx b/apps/native/app/src/screens/license-scanner/license-scanner.tsx index e5bcd13c9be4..2c08022a210b 100644 --- a/apps/native/app/src/screens/license-scanner/license-scanner.tsx +++ b/apps/native/app/src/screens/license-scanner/license-scanner.tsx @@ -1,4 +1,3 @@ -import { Bubble, Button, theme } from '@ui' import { impactAsync, ImpactFeedbackStyle } from 'expo-haptics' import React, { useCallback, useEffect, useRef, useState } from 'react' import { useIntl } from 'react-intl' @@ -34,6 +33,8 @@ import { } from 'react-native-vision-camera' import { CodeScanner, CodeType } from 'react-native-vision-camera' import styled from 'styled-components/native' + +import { Bubble, Button, theme } from '../../ui' import flashligth from '../../assets/icons/flashlight.png' import { LICENSE_SCANNER_DONE } from '../../constants/navigation-buttons' import { diff --git a/apps/native/app/src/screens/login/login.tsx b/apps/native/app/src/screens/login/login.tsx index c2f8a19c3509..2c25f637b067 100644 --- a/apps/native/app/src/screens/login/login.tsx +++ b/apps/native/app/src/screens/login/login.tsx @@ -1,4 +1,3 @@ -import { Button, dynamicColor, font, Illustration } from '@ui' import React, { useEffect, useState } from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { @@ -15,6 +14,8 @@ import { } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import styled from 'styled-components/native' + +import { Button, dynamicColor, font, Illustration } from '../../ui' import logo from '../../assets/logo/logo-64w.png' import { useBrowser } from '../../lib/use-browser' import { useAuthStore } from '../../stores/auth-store' diff --git a/apps/native/app/src/screens/login/testing-login.tsx b/apps/native/app/src/screens/login/testing-login.tsx index f598ec587f0c..921a4980105a 100644 --- a/apps/native/app/src/screens/login/testing-login.tsx +++ b/apps/native/app/src/screens/login/testing-login.tsx @@ -1,4 +1,3 @@ -import { Button, dynamicColor, font } from '@ui' import React, { useEffect, useState } from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { @@ -15,6 +14,8 @@ import { } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import styled from 'styled-components/native' + +import { Button, dynamicColor, font } from '../../ui' import logo from '../../assets/logo/logo-64w.png' import testinglogo from '../../assets/logo/testing-logo-64w.png' import { environments, isTestingApp, useConfig } from '../../config' diff --git a/apps/native/app/src/screens/more/more.tsx b/apps/native/app/src/screens/more/more.tsx index 358ae75ae4d3..57fd1d1dbbac 100644 --- a/apps/native/app/src/screens/more/more.tsx +++ b/apps/native/app/src/screens/more/more.tsx @@ -1,10 +1,11 @@ -import { FamilyMemberCard, MoreCard } from '@ui' import React, { useState } from 'react' import { useIntl } from 'react-intl' import { SafeAreaView, ScrollView, TouchableHighlight } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import { useNavigationComponentDidAppear } from 'react-native-navigation-hooks' import styled, { useTheme } from 'styled-components/native' + +import { FamilyMemberCard, MoreCard } from '../../ui' import assetsIcon from '../../assets/icons/assets.png' import familyIcon from '../../assets/icons/family.png' import financeIcon from '../../assets/icons/finance.png' diff --git a/apps/native/app/src/screens/more/personal-info.tsx b/apps/native/app/src/screens/more/personal-info.tsx index 87417710ad0c..1bb9f87e3b40 100644 --- a/apps/native/app/src/screens/more/personal-info.tsx +++ b/apps/native/app/src/screens/more/personal-info.tsx @@ -1,4 +1,3 @@ -import { Alert, Button, Input, InputRow, NavigationBarSheet } from '@ui' import React from 'react' import { useIntl } from 'react-intl' import { ScrollView, View } from 'react-native' @@ -6,6 +5,8 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Alert, Button, Input, InputRow, NavigationBarSheet } from '../../ui' import { useNationalRegistryUserQuery } from '../../graphql/types/schema' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { navigateTo } from '../../lib/deep-linking' diff --git a/apps/native/app/src/screens/notifications/notifications.tsx b/apps/native/app/src/screens/notifications/notifications.tsx index 913c1b6d6d50..0326ee9d0463 100644 --- a/apps/native/app/src/screens/notifications/notifications.tsx +++ b/apps/native/app/src/screens/notifications/notifications.tsx @@ -1,13 +1,4 @@ -import { - Button, - NavigationBarSheet, - NotificationCard, - Problem, - ListItemSkeleton, - EmptyList, -} from '@ui' import { useApolloClient } from '@apollo/client' - import { dismissAllNotificationsAsync } from 'expo-notifications' import React, { useCallback, useEffect, useMemo, useState } from 'react' import { useIntl } from 'react-intl' @@ -24,6 +15,15 @@ import { } from 'react-native-navigation' import { useTheme } from 'styled-components' import styled from 'styled-components/native' + +import { + Button, + NavigationBarSheet, + NotificationCard, + Problem, + ListItemSkeleton, + EmptyList, +} from '../../ui' import { GetUserNotificationsQuery, Notification, @@ -32,7 +32,6 @@ import { useMarkAllNotificationsAsSeenMutation, useMarkUserNotificationAsReadMutation, } from '../../graphql/types/schema' - import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { navigateTo, navigateToUniversalLink } from '../../lib/deep-linking' import { useNotificationsStore } from '../../stores/notifications-store' diff --git a/apps/native/app/src/screens/onboarding/onboarding-biometrics.tsx b/apps/native/app/src/screens/onboarding/onboarding-biometrics.tsx index 96009ba82e73..a8bde5809c16 100644 --- a/apps/native/app/src/screens/onboarding/onboarding-biometrics.tsx +++ b/apps/native/app/src/screens/onboarding/onboarding-biometrics.tsx @@ -1,4 +1,3 @@ -import { Button, CancelButton, Illustration, Onboarding } from '@ui' import { AuthenticationType, authenticateAsync, @@ -8,6 +7,8 @@ import React, { useEffect, useState } from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { AppState, Platform } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' + +import { Button, CancelButton, Illustration, Onboarding } from '../../ui' import finger from '../../assets/icons/finger-16.png' import iris from '../../assets/icons/iris-16.png' import { preferencesStore } from '../../stores/preferences-store' diff --git a/apps/native/app/src/screens/onboarding/onboarding-notifications.tsx b/apps/native/app/src/screens/onboarding/onboarding-notifications.tsx index 6b9a4ec8727a..8b8a59204f6c 100644 --- a/apps/native/app/src/screens/onboarding/onboarding-notifications.tsx +++ b/apps/native/app/src/screens/onboarding/onboarding-notifications.tsx @@ -1,7 +1,8 @@ -import { Button, CancelButton, Illustration, Onboarding } from '@ui' import React from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { NavigationFunctionComponent } from 'react-native-navigation' + +import { Button, CancelButton, Illustration, Onboarding } from '../../ui' import allow from '../../assets/icons/allow.png' import { preferencesStore } from '../../stores/preferences-store' import { nextOnboardingStep } from '../../utils/onboarding' diff --git a/apps/native/app/src/screens/onboarding/onboarding-pin-code.tsx b/apps/native/app/src/screens/onboarding/onboarding-pin-code.tsx index 0c73703ae5c0..132cd806bc37 100644 --- a/apps/native/app/src/screens/onboarding/onboarding-pin-code.tsx +++ b/apps/native/app/src/screens/onboarding/onboarding-pin-code.tsx @@ -1,4 +1,3 @@ -import { CancelButton, dynamicColor, font } from '@ui' import React, { useEffect, useState } from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { Image, SafeAreaView, View } from 'react-native' @@ -8,6 +7,8 @@ import { NavigationFunctionComponent, } from 'react-native-navigation' import styled from 'styled-components/native' + +import { CancelButton, dynamicColor, font } from '../../ui' import logo from '../../assets/logo/logo-64w.png' import { PinKeypad } from '../../components/pin-keypad/pin-keypad' import { VisualizedPinCode } from '../../components/visualized-pin-code/visualized-pin-code' diff --git a/apps/native/app/src/screens/passkey/passkey.tsx b/apps/native/app/src/screens/passkey/passkey.tsx index 6cd5d67130d5..a6ba79951324 100644 --- a/apps/native/app/src/screens/passkey/passkey.tsx +++ b/apps/native/app/src/screens/passkey/passkey.tsx @@ -1,4 +1,3 @@ -import { Button, Typography, NavigationBarSheet, LinkText } from '@ui' import React, { useEffect, useState } from 'react' import { useIntl, FormattedMessage } from 'react-intl' import { @@ -15,6 +14,8 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Button, Typography, NavigationBarSheet, LinkText } from '../../ui' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import logo from '../../assets/logo/logo-64w.png' import externalLink from '../../assets/icons/external-link.png' diff --git a/apps/native/app/src/screens/settings/edit-bank-info.tsx b/apps/native/app/src/screens/settings/edit-bank-info.tsx index a6de5f0f03e8..ce12824385b5 100644 --- a/apps/native/app/src/screens/settings/edit-bank-info.tsx +++ b/apps/native/app/src/screens/settings/edit-bank-info.tsx @@ -1,4 +1,3 @@ -import { Button, NavigationBarSheet, TextField, Typography } from '@ui' import React, { useEffect } from 'react' import { useIntl } from 'react-intl' import { Alert, ScrollView, View } from 'react-native' @@ -6,6 +5,8 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Button, NavigationBarSheet, TextField, Typography } from '../../ui' import { useGetProfileQuery } from '../../graphql/types/schema' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { bankInfoObject, stringifyBankData } from '../../lib/bank-info-helper' diff --git a/apps/native/app/src/screens/settings/edit-confirm.tsx b/apps/native/app/src/screens/settings/edit-confirm.tsx index c624d83e259a..1e201ad189c1 100644 --- a/apps/native/app/src/screens/settings/edit-confirm.tsx +++ b/apps/native/app/src/screens/settings/edit-confirm.tsx @@ -1,10 +1,3 @@ -import { - Button, - CancelButton, - NavigationBarSheet, - TextField, - Typography, -} from '@ui' import React from 'react' import { useIntl } from 'react-intl' import { Alert, ScrollView, View } from 'react-native' @@ -12,6 +5,14 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { + Button, + CancelButton, + NavigationBarSheet, + TextField, + Typography, +} from '../../ui' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { testIDs } from '../../utils/test-ids' import { useUpdateUserProfile } from './utils/profile-queries' diff --git a/apps/native/app/src/screens/settings/edit-email.tsx b/apps/native/app/src/screens/settings/edit-email.tsx index 12994c6d11f9..649c808bdcd8 100644 --- a/apps/native/app/src/screens/settings/edit-email.tsx +++ b/apps/native/app/src/screens/settings/edit-email.tsx @@ -1,5 +1,4 @@ import { useApolloClient } from '@apollo/client' -import { Button, NavigationBarSheet, TextField, Typography } from '@ui' import React, { useEffect } from 'react' import { useIntl } from 'react-intl' import { Alert, ScrollView, View } from 'react-native' @@ -7,6 +6,8 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Button, NavigationBarSheet, TextField, Typography } from '../../ui' import { CreateEmailVerificationDocument, CreateEmailVerificationMutation, diff --git a/apps/native/app/src/screens/settings/edit-phone.tsx b/apps/native/app/src/screens/settings/edit-phone.tsx index 88cc19ff8b7b..169e337304ab 100644 --- a/apps/native/app/src/screens/settings/edit-phone.tsx +++ b/apps/native/app/src/screens/settings/edit-phone.tsx @@ -1,5 +1,4 @@ import { useApolloClient } from '@apollo/client' -import { Button, NavigationBarSheet, TextField, Typography } from '@ui' import React, { useEffect, useState } from 'react' import { useIntl } from 'react-intl' import { Alert, ScrollView, View } from 'react-native' @@ -7,6 +6,8 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Button, NavigationBarSheet, TextField, Typography } from '../../ui' import { CreateSmsVerificationDocument, CreateSmsVerificationMutation, diff --git a/apps/native/app/src/screens/settings/settings.tsx b/apps/native/app/src/screens/settings/settings.tsx index 81f1f49e81c6..3538ec341113 100644 --- a/apps/native/app/src/screens/settings/settings.tsx +++ b/apps/native/app/src/screens/settings/settings.tsx @@ -1,11 +1,4 @@ import { useApolloClient } from '@apollo/client' -import { - Alert, - NavigationBarSheet, - TableViewAccessory, - TableViewCell, - TableViewGroup, -} from '@ui' import { authenticateAsync } from 'expo-local-authentication' import React, { useEffect, useRef, useState } from 'react' import { useIntl } from 'react-intl' @@ -26,6 +19,14 @@ import { NavigationFunctionComponent, } from 'react-native-navigation' import { useTheme } from 'styled-components/native' + +import { + Alert, + NavigationBarSheet, + TableViewAccessory, + TableViewCell, + TableViewGroup, +} from '../../ui' import editIcon from '../../assets/icons/edit.png' import chevronForward from '../../ui/assets/icons/chevron-forward.png' import { PressableHighlight } from '../../components/pressable-highlight/pressable-highlight' diff --git a/apps/native/app/src/screens/update-app/update-app.tsx b/apps/native/app/src/screens/update-app/update-app.tsx index 9f9b06863c1c..b0c498865482 100644 --- a/apps/native/app/src/screens/update-app/update-app.tsx +++ b/apps/native/app/src/screens/update-app/update-app.tsx @@ -1,4 +1,3 @@ -import { Button, Typography, NavigationBarSheet } from '@ui' import React, { useEffect } from 'react' import { useIntl, FormattedMessage } from 'react-intl' import { View, Image, SafeAreaView, Linking } from 'react-native' @@ -7,6 +6,8 @@ import { Navigation, NavigationFunctionComponent, } from 'react-native-navigation' + +import { Button, Typography, NavigationBarSheet } from '../../ui' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import logo from '../../assets/logo/logo-64w.png' import illustrationSrc from '../../assets/illustrations/digital-services-m1-dots.png' diff --git a/apps/native/app/src/screens/vaccinations/components/vaccination-card.tsx b/apps/native/app/src/screens/vaccinations/components/vaccination-card.tsx index 122f512a8ae2..22ed1fc34af6 100644 --- a/apps/native/app/src/screens/vaccinations/components/vaccination-card.tsx +++ b/apps/native/app/src/screens/vaccinations/components/vaccination-card.tsx @@ -1,3 +1,9 @@ +import { useState } from 'react' +import { FormattedMessage, useIntl } from 'react-intl' +import { TouchableOpacity, View } from 'react-native' +import styled, { useTheme } from 'styled-components/native' +import { Markdown } from '../../../ui/lib/markdown/markdown' + import { Badge, ExpandableCard, @@ -5,13 +11,7 @@ import { Skeleton, Typography, dynamicColor, -} from '@ui' -import { useState } from 'react' -import { FormattedMessage, useIntl } from 'react-intl' -import { TouchableOpacity, View } from 'react-native' -import styled, { useTheme } from 'styled-components/native' -import { Markdown } from '../../../ui/lib/markdown/markdown' - +} from '../../../ui' import chevronDown from '../../../assets/icons/chevron-down.png' import clockIcon from '../../../assets/icons/clock.png' import externalLinkIcon from '../../../assets/icons/external-link.png' diff --git a/apps/native/app/src/screens/vaccinations/vaccinations.tsx b/apps/native/app/src/screens/vaccinations/vaccinations.tsx index 61c976ce77a5..388f8a0a98a4 100644 --- a/apps/native/app/src/screens/vaccinations/vaccinations.tsx +++ b/apps/native/app/src/screens/vaccinations/vaccinations.tsx @@ -1,16 +1,16 @@ -import { - GeneralCardSkeleton, - Heading, - Problem, - TabButtons, - Typography, -} from '@ui' import React, { useCallback, useState } from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { RefreshControl, SafeAreaView, ScrollView, View } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import styled from 'styled-components/native' +import { + GeneralCardSkeleton, + Heading, + Problem, + TabButtons, + Typography, +} from '../../ui' import { useGetVaccinationsQuery } from '../../graphql/types/schema' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { useConnectivityIndicator } from '../../hooks/use-connectivity-indicator' diff --git a/apps/native/app/src/screens/vehicles/components/mileage-cell.tsx b/apps/native/app/src/screens/vehicles/components/mileage-cell.tsx index 598d50120634..2cb51c87904a 100644 --- a/apps/native/app/src/screens/vehicles/components/mileage-cell.tsx +++ b/apps/native/app/src/screens/vehicles/components/mileage-cell.tsx @@ -1,6 +1,7 @@ -import { Skeleton, Typography, useDynamicColor } from '@ui' import { useIntl } from 'react-intl' import { Image, Pressable, View } from 'react-native' + +import { Skeleton, Typography, useDynamicColor } from '../../../ui' import clock from '../../../assets/icons/clock.png' export function MileageCell({ diff --git a/apps/native/app/src/screens/vehicles/components/vehicle-item.tsx b/apps/native/app/src/screens/vehicles/components/vehicle-item.tsx index afe6b8c607a5..8bb582694dcc 100644 --- a/apps/native/app/src/screens/vehicles/components/vehicle-item.tsx +++ b/apps/native/app/src/screens/vehicles/components/vehicle-item.tsx @@ -1,8 +1,9 @@ -import { Label, VehicleCard } from '@ui' import React from 'react' import { FormattedDate, FormattedMessage } from 'react-intl' import { SafeAreaView, TouchableHighlight, View, ViewStyle } from 'react-native' import styled, { useTheme } from 'styled-components/native' + +import { Label, VehicleCard } from '../../../ui' import { ListVehiclesV2Query } from '../../../graphql/types/schema' import { navigateTo } from '../../../lib/deep-linking' diff --git a/apps/native/app/src/screens/vehicles/vehicle-mileage.screen.tsx b/apps/native/app/src/screens/vehicles/vehicle-mileage.screen.tsx index e6bcd2c9ddf6..b4cfebe8576a 100644 --- a/apps/native/app/src/screens/vehicles/vehicle-mileage.screen.tsx +++ b/apps/native/app/src/screens/vehicles/vehicle-mileage.screen.tsx @@ -1,11 +1,3 @@ -import { - Button, - Divider, - NavigationBarSheet, - TextField, - Typography, - useDynamicColor, -} from '@ui' import { useCallback, useMemo, useState } from 'react' import { FormattedDate, useIntl } from 'react-intl' import { Alert, FlatList, View } from 'react-native' @@ -14,6 +6,14 @@ import { NavigationFunctionComponent, } from 'react-native-navigation' +import { + Button, + Divider, + NavigationBarSheet, + TextField, + Typography, + useDynamicColor, +} from '../../ui' import externalLinkIcon from '../../assets/icons/external-link.png' import { GetVehicleDocument, diff --git a/apps/native/app/src/screens/vehicles/vehicles-detail.tsx b/apps/native/app/src/screens/vehicles/vehicles-detail.tsx index fe31f2262dd8..e580181712ef 100644 --- a/apps/native/app/src/screens/vehicles/vehicles-detail.tsx +++ b/apps/native/app/src/screens/vehicles/vehicles-detail.tsx @@ -1,8 +1,9 @@ -import { Button, Divider, Input, InputRow } from '@ui' import React from 'react' import { useIntl } from 'react-intl' import { ScrollView, Text, View } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' + +import { Button, Divider, Input, InputRow } from '../../ui' import { useGetVehicleQuery } from '../../graphql/types/schema' import { createNavigationOptionHooks } from '../../hooks/create-navigation-option-hooks' import { useConnectivityIndicator } from '../../hooks/use-connectivity-indicator' diff --git a/apps/native/app/src/screens/vehicles/vehicles.tsx b/apps/native/app/src/screens/vehicles/vehicles.tsx index 9922c500edd5..4a4640236ed2 100644 --- a/apps/native/app/src/screens/vehicles/vehicles.tsx +++ b/apps/native/app/src/screens/vehicles/vehicles.tsx @@ -1,4 +1,3 @@ -import { EmptyList, GeneralCardSkeleton, TopLine } from '@ui' import React, { useCallback, useMemo, useRef, useState } from 'react' import { FormattedMessage } from 'react-intl' import { @@ -11,6 +10,8 @@ import { } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import { useTheme } from 'styled-components/native' + +import { EmptyList, GeneralCardSkeleton, TopLine } from '../../ui' import illustrationSrc from '../../assets/illustrations/le-moving-s4.png' import { BottomTabsIndicator } from '../../components/bottom-tabs-indicator/bottom-tabs-indicator' import { diff --git a/apps/native/app/src/screens/wallet-pass/components/field-render.tsx b/apps/native/app/src/screens/wallet-pass/components/field-render.tsx index 7e1eebbb79e1..70a5e9782072 100644 --- a/apps/native/app/src/screens/wallet-pass/components/field-render.tsx +++ b/apps/native/app/src/screens/wallet-pass/components/field-render.tsx @@ -1,7 +1,8 @@ -import { Field, FieldCard, FieldGroup, FieldLabel, FieldRow } from '@ui' import React from 'react' import { View } from 'react-native' import { useTheme } from 'styled-components' + +import { Field, FieldCard, FieldGroup, FieldLabel, FieldRow } from '../../../ui' import { GenericLicenseDataField, GenericLicenseType, diff --git a/apps/native/app/src/screens/wallet-pass/wallet-pass.tsx b/apps/native/app/src/screens/wallet-pass/wallet-pass.tsx index ed13f963ca81..f8c7ac96c40d 100644 --- a/apps/native/app/src/screens/wallet-pass/wallet-pass.tsx +++ b/apps/native/app/src/screens/wallet-pass/wallet-pass.tsx @@ -1,9 +1,3 @@ -import { - Alert as InfoAlert, - dynamicColor, - LICENSE_CARD_ROW_GAP, - LicenseCard, -} from '@ui' import * as FileSystem from 'expo-file-system' import React, { useCallback, useEffect, useRef, useState } from 'react' import { useIntl } from 'react-intl' @@ -23,6 +17,12 @@ import { NavigationFunctionComponent } from 'react-native-navigation' import PassKit, { AddPassButton } from 'react-native-passkit-wallet' import styled, { useTheme } from 'styled-components/native' +import { + Alert as InfoAlert, + dynamicColor, + LICENSE_CARD_ROW_GAP, + LicenseCard, +} from '../../ui' import { useFeatureFlag } from '../../contexts/feature-flag-provider' import { GenericLicenseType, diff --git a/apps/native/app/src/screens/wallet-passport/wallet-passport.tsx b/apps/native/app/src/screens/wallet-passport/wallet-passport.tsx index a1566d76adca..22d55641864c 100644 --- a/apps/native/app/src/screens/wallet-passport/wallet-passport.tsx +++ b/apps/native/app/src/screens/wallet-passport/wallet-passport.tsx @@ -1,15 +1,3 @@ -import { - Accordion, - AccordionItem, - Alert, - CustomLicenseType, - dynamicColor, - font, - Input, - InputRow, - LicenseCard, - LinkText, -} from '@ui' import React from 'react' import { useIntl } from 'react-intl' import { @@ -21,6 +9,19 @@ import { } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import styled from 'styled-components/native' + +import { + Accordion, + AccordionItem, + Alert, + CustomLicenseType, + dynamicColor, + font, + Input, + InputRow, + LicenseCard, + LinkText, +} from '../../ui' import IconStatusVerified from '../../assets/icons/valid.png' import IconStatusNonVerified from '../../assets/icons/warning.png' import { useGetIdentityDocumentQuery } from '../../graphql/types/schema' diff --git a/apps/native/app/src/screens/wallet/components/wallet-item.tsx b/apps/native/app/src/screens/wallet/components/wallet-item.tsx index 8a6eec412b70..b510442cbfd7 100644 --- a/apps/native/app/src/screens/wallet/components/wallet-item.tsx +++ b/apps/native/app/src/screens/wallet/components/wallet-item.tsx @@ -1,7 +1,8 @@ -import { CustomLicenseType, LicenseCard } from '@ui' import React from 'react' import { SafeAreaView, ViewStyle } from 'react-native' import styled from 'styled-components/native' + +import { CustomLicenseType, LicenseCard } from '../../../ui' import { Pressable as PressableRaw } from '../../../components/pressable/pressable' import { GenericUserLicense, diff --git a/apps/native/app/src/screens/wallet/wallet.tsx b/apps/native/app/src/screens/wallet/wallet.tsx index 4dd8de8f321d..1e8926e808eb 100644 --- a/apps/native/app/src/screens/wallet/wallet.tsx +++ b/apps/native/app/src/screens/wallet/wallet.tsx @@ -1,5 +1,3 @@ -import { Alert, EmptyList, GeneralCardSkeleton, TopLine } from '@ui' - import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useIntl } from 'react-intl' import { @@ -15,6 +13,7 @@ import SpotlightSearch from 'react-native-spotlight-search' import { useTheme } from 'styled-components/native' import { useNavigationComponentDidAppear } from 'react-native-navigation-hooks' +import { Alert, EmptyList, GeneralCardSkeleton, TopLine } from '../../ui' import illustrationSrc from '../../assets/illustrations/le-retirement-s3.png' import { BottomTabsIndicator } from '../../components/bottom-tabs-indicator/bottom-tabs-indicator' import { diff --git a/apps/native/app/src/test-setup.ts b/apps/native/app/src/test-setup.ts new file mode 100644 index 000000000000..fbf15de85357 --- /dev/null +++ b/apps/native/app/src/test-setup.ts @@ -0,0 +1 @@ +import '@testing-library/jest-native/extend-expect' diff --git a/apps/native/app/src/types/react-native.d.ts b/apps/native/app/src/types/react-native.d.ts index 18e99d85c455..7b0398d64dfc 100644 --- a/apps/native/app/src/types/react-native.d.ts +++ b/apps/native/app/src/types/react-native.d.ts @@ -85,7 +85,3 @@ declare module 'react-native-dialogs' { options: OptionsPrompt, ): Promise } - -declare module '@island.is/application/types/lib/ApplicationTypes' { - export const ApplicationConfigurations: Record -} diff --git a/apps/native/app/src/types/styled-components.d.ts b/apps/native/app/src/types/styled-components.d.ts index 3f627c3a8c23..050eed343750 100644 --- a/apps/native/app/src/types/styled-components.d.ts +++ b/apps/native/app/src/types/styled-components.d.ts @@ -1,5 +1,5 @@ -import { Theme } from '@ui' import 'styled-components' +import { Theme } from '../ui' declare module 'styled-components' { export interface Shade { diff --git a/apps/native/app/src/ui/index.ts b/apps/native/app/src/ui/index.ts index 870051ba7b87..08e58483874e 100644 --- a/apps/native/app/src/ui/index.ts +++ b/apps/native/app/src/ui/index.ts @@ -20,6 +20,7 @@ export * from './lib/card/welcome-card' export * from './lib/card/link-card' export * from './lib/card/inbox-card' export * from './lib/card/more-card' +export * from './lib/card/air-discount-card' export * from './lib/card/expandable-card' export * from './lib/checkbox/checkbox' export * from './lib/date-picker/date-picker' @@ -61,4 +62,5 @@ export * from './lib/scan-result-card/scan-result-card' export * from './lib/label/label' export * from './lib/progress-meter/progress-meter' export * from './lib/tab-buttons/tab-buttons' +export * from './lib/bullet/bullet' export * from './utils/index' diff --git a/apps/native/app/src/ui/lib/card/license-card.tsx b/apps/native/app/src/ui/lib/card/license-card.tsx index 71f6c5cfa98d..4df3e3d0cbd9 100644 --- a/apps/native/app/src/ui/lib/card/license-card.tsx +++ b/apps/native/app/src/ui/lib/card/license-card.tsx @@ -1,5 +1,3 @@ -import { Barcode } from '@ui/lib/barcode/barcode' -import { Skeleton } from '@ui/lib/skeleton/skeleton' import React from 'react' import { FormattedDate, useIntl } from 'react-intl' import { @@ -10,6 +8,9 @@ import { ViewStyle, } from 'react-native' import styled, { useTheme } from 'styled-components/native' + +import { Barcode } from '../barcode/barcode' +import { Skeleton } from '../skeleton/skeleton' import { ExpirationProgressBar } from '../../../components/progress-bar/expiration-progress-bar' import { GenericLicenseType } from '../../../graphql/types/schema' import { isString } from '../../../utils/is-string' diff --git a/apps/native/app/src/ui/lib/date-picker/date-picker.tsx b/apps/native/app/src/ui/lib/date-picker/date-picker.tsx index d31868bd922f..cd7322b451ce 100644 --- a/apps/native/app/src/ui/lib/date-picker/date-picker.tsx +++ b/apps/native/app/src/ui/lib/date-picker/date-picker.tsx @@ -4,7 +4,7 @@ import DatePicker from 'react-native-date-picker' import calendarIcon from '../../assets/icons/calendar.png' import { Typography } from '../typography/typography' -import { dynamicColor } from '@ui/utils/dynamic-color' +import { dynamicColor } from '../../utils/dynamic-color' import { useIntl } from 'react-intl' import { View, Image } from 'react-native' diff --git a/apps/native/app/src/ui/lib/detail/header.tsx b/apps/native/app/src/ui/lib/detail/header.tsx index b92bd609a363..6348a241d54a 100644 --- a/apps/native/app/src/ui/lib/detail/header.tsx +++ b/apps/native/app/src/ui/lib/detail/header.tsx @@ -4,7 +4,7 @@ import styled from 'styled-components/native' import { dynamicColor } from '../../utils/dynamic-color' import { Skeleton } from '../skeleton/skeleton' import { Typography } from '../typography/typography' -import { Label } from '@ui' +import { Label } from '../label/label' const Host = styled.View<{ hasBorder?: boolean }>` padding-bottom: ${({ theme }) => theme.spacing[1]}px; diff --git a/apps/native/app/src/ui/lib/empty-state/empty-list.tsx b/apps/native/app/src/ui/lib/empty-state/empty-list.tsx index f55706c3abf9..1c67450255dd 100644 --- a/apps/native/app/src/ui/lib/empty-state/empty-list.tsx +++ b/apps/native/app/src/ui/lib/empty-state/empty-list.tsx @@ -2,7 +2,7 @@ import React from 'react' import { View } from 'react-native' import styled from 'styled-components/native' -import { dynamicColor } from '@ui/utils' +import { dynamicColor } from '../../utils' import { Typography } from '../typography/typography' const Host = styled.View` diff --git a/apps/native/app/src/ui/lib/empty-state/empty-state.stories.tsx b/apps/native/app/src/ui/lib/empty-state/empty-state.stories.tsx index e57489d3c65e..0f1f778a85e9 100644 --- a/apps/native/app/src/ui/lib/empty-state/empty-state.stories.tsx +++ b/apps/native/app/src/ui/lib/empty-state/empty-state.stories.tsx @@ -1,8 +1,9 @@ import { text, withKnobs } from '@storybook/addon-knobs' import { storiesOf } from '@storybook/react-native' -import { LinkText } from '@ui' import React from 'react' import { Image, View } from 'react-native' + +import { LinkText } from '../link/link-text' import illustrationSrc from '../../assets/empty-list/LE-Company-S3.png' import leJobss4 from '../../assets/illustrations/le-jobs-s4.png' import { EmptyCard } from './empty-card' diff --git a/apps/native/app/src/ui/lib/input/input.tsx b/apps/native/app/src/ui/lib/input/input.tsx index 19b526329285..fdddd77efb76 100644 --- a/apps/native/app/src/ui/lib/input/input.tsx +++ b/apps/native/app/src/ui/lib/input/input.tsx @@ -2,11 +2,12 @@ import Clipboard from '@react-native-clipboard/clipboard' import React from 'react' import { Image, TouchableOpacity, View } from 'react-native' import styled from 'styled-components/native' + +import { Label } from '../label/label' import CopyIcon from '../../assets/icons/copy.png' import { dynamicColor } from '../../utils' import { Skeleton } from '../skeleton/skeleton' import { Typography } from '../typography/typography' -import { Label } from '@ui' const Host = styled.SafeAreaView<{ noBorder: boolean diff --git a/apps/native/app/src/ui/lib/label/label.tsx b/apps/native/app/src/ui/lib/label/label.tsx index c476dfe7d3ab..f08993c00506 100644 --- a/apps/native/app/src/ui/lib/label/label.tsx +++ b/apps/native/app/src/ui/lib/label/label.tsx @@ -1,6 +1,7 @@ -import { dynamicColor } from '@ui/utils' import { Image } from 'react-native' import styled, { DefaultTheme } from 'styled-components/native' + +import { dynamicColor } from '../../utils' import dangerIcon from '../../assets/alert/danger.png' import infoIcon from '../../assets/alert/info-alert.png' import warningIcon from '../../assets/alert/warning.png' diff --git a/apps/native/app/src/ui/lib/link/link-text.tsx b/apps/native/app/src/ui/lib/link/link-text.tsx index e072ff69a06b..68cac93bcee4 100644 --- a/apps/native/app/src/ui/lib/link/link-text.tsx +++ b/apps/native/app/src/ui/lib/link/link-text.tsx @@ -2,7 +2,7 @@ import React from 'react' import { Image } from 'react-native' import styled, { useTheme } from 'styled-components/native' import { ImageSourcePropType, Text } from 'react-native' -import { fontByWeight } from '@ui/utils' +import { fontByWeight } from '../../utils' const Host = styled.View` border-bottom-width: 1px; diff --git a/apps/native/app/src/ui/lib/list/list-item.tsx b/apps/native/app/src/ui/lib/list/list-item.tsx index 7017ff41282b..e40b5ca67893 100644 --- a/apps/native/app/src/ui/lib/list/list-item.tsx +++ b/apps/native/app/src/ui/lib/list/list-item.tsx @@ -1,9 +1,10 @@ -import { Label, Typography } from '@ui' import React, { isValidElement } from 'react' import { FormattedDate, useIntl } from 'react-intl' import { Image, ImageSourcePropType } from 'react-native' import styled from 'styled-components/native' +import { Typography } from '../typography/typography' +import { Label } from '../label/label' import { dynamicColor } from '../../utils' const Host = styled.SafeAreaView<{ unread?: boolean }>` diff --git a/apps/native/app/src/ui/lib/problem/problem-template.tsx b/apps/native/app/src/ui/lib/problem/problem-template.tsx index 1ebe261c581f..8e263176f4a4 100644 --- a/apps/native/app/src/ui/lib/problem/problem-template.tsx +++ b/apps/native/app/src/ui/lib/problem/problem-template.tsx @@ -1,8 +1,10 @@ -import { Colors, Typography } from '@ui' import { ReactNode } from 'react' import { Image, View } from 'react-native' import styled from 'styled-components/native' +import { Typography } from '../typography/typography' +import { Colors } from '../../utils' + type Variant = 'info' | 'error' | 'warning' export type ProblemTemplateBaseProps = { diff --git a/apps/native/app/src/ui/lib/search-bar/search-bar.tsx b/apps/native/app/src/ui/lib/search-bar/search-bar.tsx index 952643326ad4..1ce1609bfbe1 100644 --- a/apps/native/app/src/ui/lib/search-bar/search-bar.tsx +++ b/apps/native/app/src/ui/lib/search-bar/search-bar.tsx @@ -1,4 +1,3 @@ -import { useDynamicColor } from '@ui/utils' import React, { useRef, useState } from 'react' import { Animated, @@ -12,6 +11,8 @@ import { View, } from 'react-native' import styled, { useTheme } from 'styled-components/native' + +import { useDynamicColor } from '../../utils' import closeIcon from '../../assets/icons/close.png' import searchIcon from '../../assets/icons/search.png' import { font } from '../../utils/font' diff --git a/apps/native/app/src/utils/applications-utils.ts b/apps/native/app/src/utils/applications-utils.ts index c1efd10afed6..93a6c31774cc 100644 --- a/apps/native/app/src/utils/applications-utils.ts +++ b/apps/native/app/src/utils/applications-utils.ts @@ -1,4 +1,4 @@ -import { ApplicationConfigurations } from '@island.is/application/types/lib/ApplicationTypes' +import { ApplicationConfigurations } from '@island.is/application/types' import { getConfig } from '../config' import { Application, diff --git a/apps/native/app/src/utils/get-theme-with-preferences.ts b/apps/native/app/src/utils/get-theme-with-preferences.ts index ebc84f0dace3..dbc84ce835f4 100644 --- a/apps/native/app/src/utils/get-theme-with-preferences.ts +++ b/apps/native/app/src/utils/get-theme-with-preferences.ts @@ -1,6 +1,7 @@ -import { theme } from '@ui' import { Appearance, ColorSchemeName } from 'react-native' + import { AppearanceMode, ThemeMode } from '../stores/preferences-store' +import { theme } from '../ui' export const themes = { light: { diff --git a/apps/native/app/tsconfig.app.json b/apps/native/app/tsconfig.app.json new file mode 100644 index 000000000000..7b1b69d710d7 --- /dev/null +++ b/apps/native/app/tsconfig.app.json @@ -0,0 +1,18 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "types": ["node"] + }, + "files": ["../../../node_modules/@nx/react-native/typings/svg.d.ts"], + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js", "src/**/*.jsx"], + "exclude": [ + "**/*.stories.tsx", + "**/*.stories.ts", + "src/screens/devtools/storybook.tsx", + "jest.config.ts", + "src/**/*.spec.ts", + "src/**/*.spec.tsx", + "src/test-setup.ts" + ] +} diff --git a/apps/native/app/tsconfig.json b/apps/native/app/tsconfig.json index 12bba9640d48..6089be1e8395 100644 --- a/apps/native/app/tsconfig.json +++ b/apps/native/app/tsconfig.json @@ -1,18 +1,20 @@ { - "extends": "@tsconfig/react-native/tsconfig.json", + "extends": "../../../tsconfig.base.json", "compilerOptions": { - "baseUrl": ".", - "strict": true, - "paths": { - "@ui": ["src/ui"], - "@ui/*": ["src/ui/*"], - "@island.is/application/types": ["../../libs/application/types/src"], - "@island.is/application/types/*": ["../../libs/application/types/src/*"] - } + "isolatedModules": true, + "jsx": "react-native", + "lib": ["esnext"], + "resolveJsonModule": true, + "declaration": true }, - "exclude": [ - "**/*.stories.tsx", - "**/*.stories.ts", - "src/screens/devtools/storybook.tsx" + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } ] } diff --git a/apps/native/app/tsconfig.spec.json b/apps/native/app/tsconfig.spec.json new file mode 100644 index 000000000000..8f5c0a003b80 --- /dev/null +++ b/apps/native/app/tsconfig.spec.json @@ -0,0 +1,21 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ] +} diff --git a/package.json b/package.json index d27381ce9a8a..b24e96893b4d 100644 --- a/package.json +++ b/package.json @@ -262,6 +262,7 @@ "react-is": "18.3.1", "react-keyed-flatten-children": "1.2.0", "react-modal": "3.15.1", + "react-native": "0.74.5", "react-number-format": "4.9.1", "react-pdf": "9.1.0", "react-popper": "2.3.0", @@ -346,6 +347,7 @@ "@nestjs/schematics": "10.0.1", "@nestjs/testing": "10.0.5", "@nx/cypress": "19.4.0", + "@nx/devkit": "19.4.0", "@nx/eslint": "19.4.0", "@nx/eslint-plugin": "19.4.0", "@nx/express": "19.4.0", @@ -356,12 +358,16 @@ "@nx/node": "19.4.0", "@nx/playwright": "19.4.0", "@nx/react": "19.4.0", + "@nx/react-native": "19.4.0", "@nx/storybook": "19.4.0", "@nx/web": "19.4.0", "@nx/webpack": "19.4.0", "@nx/workspace": "19.4.0", "@openapitools/openapi-generator-cli": "1.0.15-4.3.1", "@playwright/test": "1.48", + "@react-native-community/cli-platform-android": "~13.6.6", + "@react-native/babel-preset": "0.74.87", + "@react-native/metro-config": "0.74.87", "@storybook/addon-a11y": "7.6.9", "@storybook/addon-essentials": "7.6.9", "@storybook/addon-mdx-gfm": "7.6.9", @@ -375,7 +381,9 @@ "@swc/helpers": "0.5.11", "@testing-library/cypress": "8.0.3", "@testing-library/jest-dom": "5.16.5", + "@testing-library/jest-native": "5.4.3", "@testing-library/react": "15.0.6", + "@testing-library/react-native": "12.9.0", "@testing-library/user-event": "14.4.3", "@types/aws-sdk": "2.7.0", "@types/aws4": "1.5.1", @@ -454,6 +462,7 @@ "jest-environment-jsdom": "29.7.0", "jest-environment-node": "29.7.0", "jest-mock-extended": "3.0.5", + "jest-react-native": "18.0.0", "jest-transform-stub": "2.0.0", "license-checker": "25.0.1", "mailparser": "3.5.0", @@ -461,6 +470,10 @@ "nodemailer-mock": "2.0.6", "nx": "19.4.0", "prettier": "2.6.2", + "react-native-svg": "15.2.0", + "react-native-svg-transformer": "1.3.0", + "react-native-web": "^0.19.11", + "react-test-renderer": "18.2.0", "sequelize-cli": "6.4.1", "sort-paths": "1.1.1", "sqlite3": "5.1.6", @@ -498,7 +511,6 @@ "node-request-interceptor@^0.5.1": "patch:node-request-interceptor@npm%3A0.5.9#./.yarn/patches/node-request-interceptor-npm-0.5.9-77e9d9c058.patch", "dd-trace@5.10.0": "patch:dd-trace@npm%3A5.10.0#./.yarn/patches/dd-trace-npm-5.10.0-184ed36e96.patch", "expo-modules-core@1.1.1": "patch:expo-modules-core@npm%3A1.1.1#./.yarn/patches/expo-modules-core-npm-1.1.1-c3861d47cb.patch", - "react-native@0.71.1": "patch:react-native@npm%3A0.71.1#./.yarn/patches/react-native-npm-0.71.1-f5d237f240.patch", "dd-trace@5.14.1": "patch:dd-trace@npm%3A5.14.1#./.yarn/patches/dd-trace-npm-5.14.1-8d45ad14d6.patch", "react-native-navigation@7.40.0": "patch:react-native-navigation@npm%3A7.40.0#./.yarn/patches/react-native-navigation-npm-7.40.0-68d0a0ab0d.patch", "expo-modules-core@1.12.15": "patch:expo-modules-core@npm%3A1.12.15#./.yarn/patches/expo-modules-core-npm-1.12.15-fdd209ec99.patch" diff --git a/tsconfig.base.json b/tsconfig.base.json index 3cab159e69be..030262426294 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -902,7 +902,6 @@ "@island.is/infra-nest-server": ["libs/infra-nest-server/src/index.ts"], "@island.is/infra-next-server": ["libs/infra-next-server/src/index.ts"], "@island.is/infra-tracing": ["libs/infra-tracing/src/index.ts"], - "@island.is/island-ui-native": ["apps/native/island-ui/src/index.ts"], "@island.is/island-ui/contentful": [ "libs/island-ui/contentful/src/index.ts" ], diff --git a/yarn.lock b/yarn.lock index 23df4b3c706e..e0441eb6d923 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2389,6 +2389,17 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.25.9, @babel/code-frame@npm:^7.26.0": + version: 7.26.2 + resolution: "@babel/code-frame@npm:7.26.2" + dependencies: + "@babel/helper-validator-identifier": ^7.25.9 + js-tokens: ^4.0.0 + picocolors: ^1.0.0 + checksum: db13f5c42d54b76c1480916485e6900748bbcb0014a8aca87f50a091f70ff4e0d0a6db63cade75eb41fcc3d2b6ba0a7f89e343def4f96f00269b41b8ab8dd7b8 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.17.10, @babel/compat-data@npm:^7.19.3": version: 7.19.4 resolution: "@babel/compat-data@npm:7.19.4" @@ -2438,6 +2449,13 @@ __metadata: languageName: node linkType: hard +"@babel/compat-data@npm:^7.25.9": + version: 7.26.2 + resolution: "@babel/compat-data@npm:7.26.2" + checksum: d52fae9b0dc59b409d6005ae6b172e89329f46d68136130065ebe923a156fc633e0f1c8600b3e319b9e0f99fd948f64991a5419e2e9431d00d9d235d5f7a7618 + languageName: node + linkType: hard + "@babel/core@npm:7.22.5": version: 7.22.5 resolution: "@babel/core@npm:7.22.5" @@ -2576,6 +2594,29 @@ __metadata: languageName: node linkType: hard +"@babel/core@npm:^7.25.2": + version: 7.26.0 + resolution: "@babel/core@npm:7.26.0" + dependencies: + "@ampproject/remapping": ^2.2.0 + "@babel/code-frame": ^7.26.0 + "@babel/generator": ^7.26.0 + "@babel/helper-compilation-targets": ^7.25.9 + "@babel/helper-module-transforms": ^7.26.0 + "@babel/helpers": ^7.26.0 + "@babel/parser": ^7.26.0 + "@babel/template": ^7.25.9 + "@babel/traverse": ^7.25.9 + "@babel/types": ^7.26.0 + convert-source-map: ^2.0.0 + debug: ^4.1.0 + gensync: ^1.0.0-beta.2 + json5: ^2.2.3 + semver: ^6.3.1 + checksum: b296084cfd818bed8079526af93b5dfa0ba70282532d2132caf71d4060ab190ba26d3184832a45accd82c3c54016985a4109ab9118674347a7e5e9bc464894e6 + languageName: node + linkType: hard + "@babel/eslint-parser@npm:^7.20.0": version: 7.24.7 resolution: "@babel/eslint-parser@npm:7.24.7" @@ -2673,6 +2714,19 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.25.9, @babel/generator@npm:^7.26.0": + version: 7.26.2 + resolution: "@babel/generator@npm:7.26.2" + dependencies: + "@babel/parser": ^7.26.2 + "@babel/types": ^7.26.0 + "@jridgewell/gen-mapping": ^0.3.5 + "@jridgewell/trace-mapping": ^0.3.25 + jsesc: ^3.0.2 + checksum: 6ff850b7d6082619f8c2f518d993cf7254cfbaa20b026282cbef5c9b2197686d076a432b18e36c4d1a42721c016df4f77a8f62c67600775d9683621d534b91b4 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.16.0, @babel/helper-annotate-as-pure@npm:^7.16.7, @babel/helper-annotate-as-pure@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-annotate-as-pure@npm:7.18.6" @@ -2812,6 +2866,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-compilation-targets@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-compilation-targets@npm:7.25.9" + dependencies: + "@babel/compat-data": ^7.25.9 + "@babel/helper-validator-option": ^7.25.9 + browserslist: ^4.24.0 + lru-cache: ^5.1.1 + semver: ^6.3.1 + checksum: 3af536e2db358b38f968abdf7d512d425d1018fef2f485d6f131a57a7bcaed32c606b4e148bb230e1508fa42b5b2ac281855a68eb78270f54698c48a83201b9b + languageName: node + linkType: hard + "@babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.21.0": version: 7.21.4 resolution: "@babel/helper-create-class-features-plugin@npm:7.21.4" @@ -3228,6 +3295,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-imports@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-module-imports@npm:7.25.9" + dependencies: + "@babel/traverse": ^7.25.9 + "@babel/types": ^7.25.9 + checksum: 1b411ce4ca825422ef7065dffae7d8acef52023e51ad096351e3e2c05837e9bf9fca2af9ca7f28dc26d596a588863d0fedd40711a88e350b736c619a80e704e6 + languageName: node + linkType: hard + "@babel/helper-module-transforms@npm:^7.18.0, @babel/helper-module-transforms@npm:^7.19.0": version: 7.19.0 resolution: "@babel/helper-module-transforms@npm:7.19.0" @@ -3335,6 +3412,19 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-transforms@npm:^7.26.0": + version: 7.26.0 + resolution: "@babel/helper-module-transforms@npm:7.26.0" + dependencies: + "@babel/helper-module-imports": ^7.25.9 + "@babel/helper-validator-identifier": ^7.25.9 + "@babel/traverse": ^7.25.9 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 942eee3adf2b387443c247a2c190c17c4fd45ba92a23087abab4c804f40541790d51ad5277e4b5b1ed8d5ba5b62de73857446b7742f835c18ebd350384e63917 + languageName: node + linkType: hard + "@babel/helper-optimise-call-expression@npm:^7.16.7, @babel/helper-optimise-call-expression@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-optimise-call-expression@npm:7.18.6" @@ -3682,6 +3772,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-string-parser@npm:7.25.9" + checksum: 6435ee0849e101681c1849868278b5aee82686ba2c1e27280e5e8aca6233af6810d39f8e4e693d2f2a44a3728a6ccfd66f72d71826a94105b86b731697cdfa99 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1": version: 7.19.1 resolution: "@babel/helper-validator-identifier@npm:7.19.1" @@ -3710,6 +3807,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-validator-identifier@npm:7.25.9" + checksum: 5b85918cb1a92a7f3f508ea02699e8d2422fe17ea8e82acd445006c0ef7520fbf48e3dbcdaf7b0a1d571fc3a2715a29719e5226636cb6042e15fe6ed2a590944 + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-validator-option@npm:7.18.6" @@ -3745,6 +3849,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-option@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/helper-validator-option@npm:7.25.9" + checksum: 9491b2755948ebbdd68f87da907283698e663b5af2d2b1b02a2765761974b1120d5d8d49e9175b167f16f72748ffceec8c9cf62acfbee73f4904507b246e2b3d + languageName: node + linkType: hard + "@babel/helper-wrap-function@npm:^7.18.9": version: 7.20.5 resolution: "@babel/helper-wrap-function@npm:7.20.5" @@ -3856,6 +3967,16 @@ __metadata: languageName: node linkType: hard +"@babel/helpers@npm:^7.26.0": + version: 7.26.0 + resolution: "@babel/helpers@npm:7.26.0" + dependencies: + "@babel/template": ^7.25.9 + "@babel/types": ^7.26.0 + checksum: d77fe8d45033d6007eadfa440355c1355eed57902d5a302f450827ad3d530343430a21210584d32eef2f216ae463d4591184c6fc60cf205bbf3a884561469200 + languageName: node + linkType: hard + "@babel/highlight@npm:^7.10.4, @babel/highlight@npm:^7.18.6": version: 7.18.6 resolution: "@babel/highlight@npm:7.18.6" @@ -3955,6 +4076,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.25.9, @babel/parser@npm:^7.26.0, @babel/parser@npm:^7.26.2": + version: 7.26.2 + resolution: "@babel/parser@npm:7.26.2" + dependencies: + "@babel/types": ^7.26.0 + bin: + parser: ./bin/babel-parser.js + checksum: c88b5ea0adf357ef909cdc2c31e284a154943edc59f63f6e8a4c20bf773a1b2f3d8c2205e59c09ca7cdad91e7466300114548876529277a80651b6436a48d5d9 + languageName: node + linkType: hard + "@babel/parser@npm:^7.9.4": version: 7.20.13 resolution: "@babel/parser@npm:7.20.13" @@ -8001,6 +8133,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.25.0": + version: 7.26.0 + resolution: "@babel/runtime@npm:7.26.0" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: c8e2c0504ab271b3467a261a8f119bf2603eb857a0d71e37791f4e3fae00f681365073cc79f141ddaa90c6077c60ba56448004ad5429d07ac73532be9f7cf28a + languageName: node + linkType: hard + "@babel/template@npm:^7.0.0, @babel/template@npm:^7.20.7": version: 7.20.7 resolution: "@babel/template@npm:7.20.7" @@ -8067,6 +8208,32 @@ __metadata: languageName: node linkType: hard +"@babel/template@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/template@npm:7.25.9" + dependencies: + "@babel/code-frame": ^7.25.9 + "@babel/parser": ^7.25.9 + "@babel/types": ^7.25.9 + checksum: 103641fea19c7f4e82dc913aa6b6ac157112a96d7c724d513288f538b84bae04fb87b1f1e495ac1736367b1bc30e10f058b30208fb25f66038e1f1eb4e426472 + languageName: node + linkType: hard + +"@babel/traverse--for-generate-function-map@npm:@babel/traverse@^7.25.3, @babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3": + version: 7.25.3 + resolution: "@babel/traverse@npm:7.25.3" + dependencies: + "@babel/code-frame": ^7.24.7 + "@babel/generator": ^7.25.0 + "@babel/parser": ^7.25.3 + "@babel/template": ^7.25.0 + "@babel/types": ^7.25.2 + debug: ^4.3.1 + globals: ^11.1.0 + checksum: 5661308b1357816f1d4e2813a5dd82c6053617acc08c5c95db051b8b6577d07c4446bc861c9a5e8bf294953ac8266ae13d7d9d856b6b889fc0d34c1f51abbd8c + languageName: node + linkType: hard + "@babel/traverse@npm:7, @babel/traverse@npm:^7.20.0, @babel/traverse@npm:^7.20.5, @babel/traverse@npm:^7.20.7, @babel/traverse@npm:^7.21.0, @babel/traverse@npm:^7.21.2, @babel/traverse@npm:^7.21.4": version: 7.21.4 resolution: "@babel/traverse@npm:7.21.4" @@ -8103,21 +8270,6 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3": - version: 7.25.3 - resolution: "@babel/traverse@npm:7.25.3" - dependencies: - "@babel/code-frame": ^7.24.7 - "@babel/generator": ^7.25.0 - "@babel/parser": ^7.25.3 - "@babel/template": ^7.25.0 - "@babel/types": ^7.25.2 - debug: ^4.3.1 - globals: ^11.1.0 - checksum: 5661308b1357816f1d4e2813a5dd82c6053617acc08c5c95db051b8b6577d07c4446bc861c9a5e8bf294953ac8266ae13d7d9d856b6b889fc0d34c1f51abbd8c - languageName: node - linkType: hard - "@babel/traverse@npm:^7.22.1": version: 7.22.4 resolution: "@babel/traverse@npm:7.22.4" @@ -8190,6 +8342,21 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.25.9": + version: 7.25.9 + resolution: "@babel/traverse@npm:7.25.9" + dependencies: + "@babel/code-frame": ^7.25.9 + "@babel/generator": ^7.25.9 + "@babel/parser": ^7.25.9 + "@babel/template": ^7.25.9 + "@babel/types": ^7.25.9 + debug: ^4.3.1 + globals: ^11.1.0 + checksum: 901d325662ff1dd9bc51de00862e01055fa6bc374f5297d7e3731f2f0e268bbb1d2141f53fa82860aa308ee44afdcf186a948f16c83153927925804b95a9594d + languageName: node + linkType: hard + "@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.11, @babel/types@npm:^7.16.0, @babel/types@npm:^7.16.8, @babel/types@npm:^7.18.10, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.19.0, @babel/types@npm:^7.19.3, @babel/types@npm:^7.19.4, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": version: 7.19.4 resolution: "@babel/types@npm:7.19.4" @@ -8267,6 +8434,16 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.25.9, @babel/types@npm:^7.26.0": + version: 7.26.0 + resolution: "@babel/types@npm:7.26.0" + dependencies: + "@babel/helper-string-parser": ^7.25.9 + "@babel/helper-validator-identifier": ^7.25.9 + checksum: a3dd37dabac693018872da96edb8c1843a605c1bfacde6c3f504fba79b972426a6f24df70aa646356c0c1b19bdd2c722c623c684a996c002381071680602280d + languageName: node + linkType: hard + "@base2/pretty-print-object@npm:1.0.1": version: 1.0.1 resolution: "@base2/pretty-print-object@npm:1.0.1" @@ -13296,6 +13473,8 @@ __metadata: "@react-native/eslint-config": 0.74.87 "@react-native/metro-config": 0.74.87 "@react-native/typescript-config": 0.74.87 + "@testing-library/jest-native": 5.4.3 + "@testing-library/react-native": 12.9.0 "@tsconfig/react-native": 3.0.5 "@types/intl": ^1 "@types/jest": ^29.2.1 @@ -13306,7 +13485,6 @@ __metadata: babel-jest: ^29.6.3 babel-loader: ^8.3.0 babel-plugin-formatjs: 10.3.9 - babel-plugin-module-resolver: 5.0.2 compare-versions: 6.1.1 configcat-js: 7.0.0 dynamic-color: 0.3.0 @@ -13319,8 +13497,9 @@ __metadata: intl: 1.2.5 jest: 29.7.0 lodash: 4.17.21 + metro-config: 0.81.0 path-to-regexp: 6.2.2 - react: 18.2.0 + react: 18.3.1 react-intl: 5.20.12 react-native: 0.74.5 react-native-app-auth: 7.2.0 @@ -15144,6 +15323,15 @@ __metadata: languageName: node linkType: hard +"@nrwl/react-native@npm:19.4.0": + version: 19.4.0 + resolution: "@nrwl/react-native@npm:19.4.0" + dependencies: + "@nx/react-native": 19.4.0 + checksum: c107fcb4b618d6cd055dbebfab93b2d63429d9a4d417d5337746184245180f2136b98dba36b2a9d6b03969aac71f8b0abb689c5321ef6044c6e007e1f415fa48 + languageName: node + linkType: hard + "@nrwl/react@npm:19.4.0": version: 19.4.0 resolution: "@nrwl/react@npm:19.4.0" @@ -15603,6 +15791,32 @@ __metadata: languageName: node linkType: hard +"@nx/react-native@npm:19.4.0": + version: 19.4.0 + resolution: "@nx/react-native@npm:19.4.0" + dependencies: + "@nrwl/react-native": 19.4.0 + "@nx/devkit": 19.4.0 + "@nx/eslint": 19.4.0 + "@nx/jest": 19.4.0 + "@nx/js": 19.4.0 + "@nx/react": 19.4.0 + "@nx/workspace": 19.4.0 + ajv: ^8.12.0 + chalk: ^4.1.0 + enhanced-resolve: ^5.8.3 + fs-extra: ^11.1.0 + glob: 7.1.4 + ignore: ^5.0.4 + metro-config: ~0.80.4 + metro-resolver: ~0.80.4 + node-fetch: ^2.6.7 + tsconfig-paths: ^4.1.2 + tslib: ^2.3.0 + checksum: 1b6834ff4997932a131b28bfbc898022f00146799291f5d1a1d24b7d9ed0dac8fe79094e4e8a936b6e9a1cd430a8d2bf02acec5ccec2a49c33921ab864994f23 + languageName: node + linkType: hard + "@nx/react@npm:19.4.0": version: 19.4.0 resolution: "@nx/react@npm:19.4.0" @@ -16998,7 +17212,7 @@ __metadata: languageName: node linkType: hard -"@react-native-community/cli-platform-android@npm:13.6.9": +"@react-native-community/cli-platform-android@npm:13.6.9, @react-native-community/cli-platform-android@npm:~13.6.6": version: 13.6.9 resolution: "@react-native-community/cli-platform-android@npm:13.6.9" dependencies: @@ -17444,6 +17658,13 @@ __metadata: languageName: node linkType: hard +"@react-native/normalize-colors@npm:^0.74.1": + version: 0.74.88 + resolution: "@react-native/normalize-colors@npm:0.74.88" + checksum: 348d0f1b9802e824843ec58ed90f72af078b81dd576f72c45caa1ed9846ea733b0dab932e431f88ebc40a186e7443875b64e8e2cf8e669a59abef0aedf2d9aa7 + languageName: node + linkType: hard + "@react-native/typescript-config@npm:0.74.87": version: 0.74.87 resolution: "@react-native/typescript-config@npm:0.74.87" @@ -19988,6 +20209,15 @@ __metadata: languageName: node linkType: hard +"@svgr/babel-plugin-transform-react-native-svg@npm:8.1.0": + version: 8.1.0 + resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:8.1.0" + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 85b434a57572f53bd2b9f0606f253e1fcf57b4a8c554ec3f2d43ed17f50d8cae200cb3aaf1ec9d626e1456e8b135dce530ae047eb0bed6d4bf98a752d6640459 + languageName: node + linkType: hard + "@svgr/babel-plugin-transform-svg-component@npm:8.0.0": version: 8.0.0 resolution: "@svgr/babel-plugin-transform-svg-component@npm:8.0.0" @@ -20015,6 +20245,24 @@ __metadata: languageName: node linkType: hard +"@svgr/babel-preset@npm:8.1.0": + version: 8.1.0 + resolution: "@svgr/babel-preset@npm:8.1.0" + dependencies: + "@svgr/babel-plugin-add-jsx-attribute": 8.0.0 + "@svgr/babel-plugin-remove-jsx-attribute": 8.0.0 + "@svgr/babel-plugin-remove-jsx-empty-expression": 8.0.0 + "@svgr/babel-plugin-replace-jsx-attribute-value": 8.0.0 + "@svgr/babel-plugin-svg-dynamic-title": 8.0.0 + "@svgr/babel-plugin-svg-em-dimensions": 8.0.0 + "@svgr/babel-plugin-transform-react-native-svg": 8.1.0 + "@svgr/babel-plugin-transform-svg-component": 8.0.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 3a67930f080b8891e1e8e2595716b879c944d253112bae763dce59807ba23454d162216c8d66a0a0e3d4f38a649ecd6c387e545d1e1261dd69a68e9a3392ee08 + languageName: node + linkType: hard + "@svgr/core@npm:8.0.0": version: 8.0.0 resolution: "@svgr/core@npm:8.0.0" @@ -20028,6 +20276,19 @@ __metadata: languageName: node linkType: hard +"@svgr/core@npm:^8.1.0": + version: 8.1.0 + resolution: "@svgr/core@npm:8.1.0" + dependencies: + "@babel/core": ^7.21.3 + "@svgr/babel-preset": 8.1.0 + camelcase: ^6.2.0 + cosmiconfig: ^8.1.3 + snake-case: ^3.0.4 + checksum: da4a12865c7dc59829d58df8bd232d6c85b7115fda40da0d2f844a1a51886e2e945560596ecfc0345d37837ac457de86a931e8b8d8550e729e0c688c02250d8a + languageName: node + linkType: hard + "@svgr/hast-util-to-babel-ast@npm:8.0.0": version: 8.0.0 resolution: "@svgr/hast-util-to-babel-ast@npm:8.0.0" @@ -20052,6 +20313,20 @@ __metadata: languageName: node linkType: hard +"@svgr/plugin-jsx@npm:^8.1.0": + version: 8.1.0 + resolution: "@svgr/plugin-jsx@npm:8.1.0" + dependencies: + "@babel/core": ^7.21.3 + "@svgr/babel-preset": 8.1.0 + "@svgr/hast-util-to-babel-ast": 8.0.0 + svg-parser: ^2.0.4 + peerDependencies: + "@svgr/core": "*" + checksum: 0418a9780753d3544912ee2dad5d2cf8d12e1ba74df8053651b3886aeda54d5f0f7d2dece0af5e0d838332c4f139a57f0dabaa3ca1afa4d1a765efce6a7656f2 + languageName: node + linkType: hard + "@svgr/plugin-svgo@npm:8.0.1": version: 8.0.1 resolution: "@svgr/plugin-svgo@npm:8.0.1" @@ -20065,6 +20340,19 @@ __metadata: languageName: node linkType: hard +"@svgr/plugin-svgo@npm:^8.1.0": + version: 8.1.0 + resolution: "@svgr/plugin-svgo@npm:8.1.0" + dependencies: + cosmiconfig: ^8.1.3 + deepmerge: ^4.3.1 + svgo: ^3.0.2 + peerDependencies: + "@svgr/core": "*" + checksum: 59d9d214cebaacca9ca71a561f463d8b7e5a68ca9443e4792a42d903acd52259b1790c0680bc6afecc3f00a255a6cbd7ea278a9f625bac443620ea58a590c2d0 + languageName: node + linkType: hard + "@svgr/webpack@npm:^8.0.1": version: 8.0.1 resolution: "@svgr/webpack@npm:8.0.1" @@ -20692,6 +20980,42 @@ __metadata: languageName: node linkType: hard +"@testing-library/jest-native@npm:5.4.3": + version: 5.4.3 + resolution: "@testing-library/jest-native@npm:5.4.3" + dependencies: + chalk: ^4.1.2 + jest-diff: ^29.0.1 + jest-matcher-utils: ^29.0.1 + pretty-format: ^29.0.3 + redent: ^3.0.0 + peerDependencies: + react: ">=16.0.0" + react-native: ">=0.59" + react-test-renderer: ">=16.0.0" + checksum: 2a4ebfeff09523860771cfddac6fcc3faa2f855dc63255b9efc016e727132320f16f935cec9717d6d79cfa6715fce6ded877215c8ec85d236a5c3136a65b1020 + languageName: node + linkType: hard + +"@testing-library/react-native@npm:12.9.0": + version: 12.9.0 + resolution: "@testing-library/react-native@npm:12.9.0" + dependencies: + jest-matcher-utils: ^29.7.0 + pretty-format: ^29.7.0 + redent: ^3.0.0 + peerDependencies: + jest: ">=28.0.0" + react: ">=16.8.0" + react-native: ">=0.59" + react-test-renderer: ">=16.8.0" + peerDependenciesMeta: + jest: + optional: true + checksum: 88115b22c127f39b2e1e8098dc1c93ea9c7393800a24f4f380bed64425cc685f98cad5b56b9cb48d85f0dbed1f0f208d0de44137c6e789c98161ff2715f70646 + languageName: node + linkType: hard + "@testing-library/react@npm:15.0.6": version: 15.0.6 resolution: "@testing-library/react@npm:15.0.6" @@ -25995,19 +26319,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-module-resolver@npm:5.0.2": - version: 5.0.2 - resolution: "babel-plugin-module-resolver@npm:5.0.2" - dependencies: - find-babel-config: ^2.1.1 - glob: ^9.3.3 - pkg-up: ^3.1.0 - reselect: ^4.1.7 - resolve: ^1.22.8 - checksum: f1d198acbbbd0b76c9c0c4aacbf9f1ef90f8d36b3d5209d9e7a75cadee2113a73711550ebddeb9464d143b71df19adc75e165dff99ada2614d7ea333affe3b5a - languageName: node - linkType: hard - "babel-plugin-polyfill-corejs2@npm:^0.3.3": version: 0.3.3 resolution: "babel-plugin-polyfill-corejs2@npm:0.3.3" @@ -29625,6 +29936,15 @@ __metadata: languageName: node linkType: hard +"css-in-js-utils@npm:^3.1.0": + version: 3.1.0 + resolution: "css-in-js-utils@npm:3.1.0" + dependencies: + hyphenate-style-name: ^1.0.3 + checksum: 066318e918c04a5e5bce46b38fe81052ea6ac051bcc6d3c369a1d59ceb1546cb2b6086901ab5d22be084122ee3732169996a3dfb04d3406eaee205af77aec61b + languageName: node + linkType: hard + "css-loader@npm:^6.4.0, css-loader@npm:^6.7.1": version: 6.8.1 resolution: "css-loader@npm:6.8.1" @@ -31816,6 +32136,16 @@ __metadata: languageName: node linkType: hard +"enhanced-resolve@npm:^5.8.3": + version: 5.17.1 + resolution: "enhanced-resolve@npm:5.17.1" + dependencies: + graceful-fs: ^4.2.4 + tapable: ^2.2.0 + checksum: 4bc38cf1cea96456f97503db7280394177d1bc46f8f87c267297d04f795ac5efa81e48115a2f5b6273c781027b5b6bfc5f62b54df629e4d25fa7001a86624f59 + languageName: node + linkType: hard + "enquirer@npm:^2.3.6, enquirer@npm:~2.3.6": version: 2.3.6 resolution: "enquirer@npm:2.3.6" @@ -33698,6 +34028,13 @@ __metadata: languageName: node linkType: hard +"exponential-backoff@npm:^3.1.1": + version: 3.1.1 + resolution: "exponential-backoff@npm:3.1.1" + checksum: 3d21519a4f8207c99f7457287291316306255a328770d320b401114ec8481986e4e467e854cb9914dd965e0a1ca810a23ccb559c642c88f4c7f55c55778a9b48 + languageName: node + linkType: hard + "express-validator@npm:6.14.0": version: 6.14.0 resolution: "express-validator@npm:6.14.0" @@ -34019,6 +34356,13 @@ __metadata: languageName: node linkType: hard +"fast-loops@npm:^1.1.3": + version: 1.1.4 + resolution: "fast-loops@npm:1.1.4" + checksum: 8031a20f465ef35ac4ad98258470250636112d34f7e4efcb4ef21f3ced99df95a1ef1f0d6943df729a1e3e12a9df9319f3019df8cc1a0e0ed5a118bd72e505f9 + languageName: node + linkType: hard + "fast-memoize@npm:^2.5.2": version: 2.5.2 resolution: "fast-memoize@npm:2.5.2" @@ -34161,6 +34505,21 @@ __metadata: languageName: node linkType: hard +"fbjs@npm:^3.0.4": + version: 3.0.5 + resolution: "fbjs@npm:3.0.5" + dependencies: + cross-fetch: ^3.1.5 + fbjs-css-vars: ^1.0.0 + loose-envify: ^1.0.0 + object-assign: ^4.1.0 + promise: ^7.1.1 + setimmediate: ^1.0.5 + ua-parser-js: ^1.0.35 + checksum: e609b5b64686bc96495a5c67728ed9b2710b9b3d695c5759c5f5e47c9483d1c323543ac777a86459e3694efc5712c6ce7212e944feb19752867d699568bb0e54 + languageName: node + linkType: hard + "fd-slicer@npm:~1.1.0": version: 1.1.0 resolution: "fd-slicer@npm:1.1.0" @@ -34377,16 +34736,6 @@ __metadata: languageName: node linkType: hard -"find-babel-config@npm:^2.1.1": - version: 2.1.1 - resolution: "find-babel-config@npm:2.1.1" - dependencies: - json5: ^2.2.3 - path-exists: ^4.0.0 - checksum: 4be54397339520e0cd49870acb10366684ffc001fd0b7bffedd0fe9d3e1d82234692d3cb4e5ba95280a35887238ba6f82dc79569a13a3749ae3931c23e0b3a99 - languageName: node - linkType: hard - "find-cache-dir@npm:^2.0.0": version: 2.1.0 resolution: "find-cache-dir@npm:2.1.0" @@ -35656,18 +36005,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:^9.3.3": - version: 9.3.5 - resolution: "glob@npm:9.3.5" - dependencies: - fs.realpath: ^1.0.0 - minimatch: ^8.0.2 - minipass: ^4.2.4 - path-scurry: ^1.6.1 - checksum: 94b093adbc591bc36b582f77927d1fb0dbf3ccc231828512b017601408be98d1fe798fc8c0b19c6f2d1a7660339c3502ce698de475e9d938ccbb69b47b647c84 - languageName: node - linkType: hard - "global-dirs@npm:^3.0.0": version: 3.0.0 resolution: "global-dirs@npm:3.0.0" @@ -36381,6 +36718,20 @@ __metadata: languageName: node linkType: hard +"hermes-estree@npm:0.23.1": + version: 0.23.1 + resolution: "hermes-estree@npm:0.23.1" + checksum: 0f63edc365099304f4cd8e91a3666a4fb5a2a47baee751dc120df9201640112865944cae93617f554af71be9827e96547f9989f4972d6964ecc121527295fec6 + languageName: node + linkType: hard + +"hermes-estree@npm:0.24.0": + version: 0.24.0 + resolution: "hermes-estree@npm:0.24.0" + checksum: 23d09013c824cd4628f6bae50c7a703cbafcc26ff1802cb35547fac41be4aac6e9892656bb6eb495e5c8c4b1287311dad8eab0f541ff8f1d2f0265b75053002e + languageName: node + linkType: hard + "hermes-parser@npm:0.19.1": version: 0.19.1 resolution: "hermes-parser@npm:0.19.1" @@ -36399,6 +36750,24 @@ __metadata: languageName: node linkType: hard +"hermes-parser@npm:0.23.1": + version: 0.23.1 + resolution: "hermes-parser@npm:0.23.1" + dependencies: + hermes-estree: 0.23.1 + checksum: a08008928aea9ea9a2cab2c0fac3cffa21f7869ab3fabb68e5add0fe057737a0c352d7a446426f7956172ccc8f2d4a215b4fc20d1d08354fc8dc16772c248fce + languageName: node + linkType: hard + +"hermes-parser@npm:0.24.0": + version: 0.24.0 + resolution: "hermes-parser@npm:0.24.0" + dependencies: + hermes-estree: 0.24.0 + checksum: c23cb81d320cedc74841c254ea54d94328f65aa6259375d48ab2b5a3ad2b528c55058726d852376811e4018636d8fd9305a4b2bfa5a962297c1baa57444be172 + languageName: node + linkType: hard + "hermes-profile-transformer@npm:^0.0.6": version: 0.0.6 resolution: "hermes-profile-transformer@npm:0.0.6" @@ -36979,6 +37348,13 @@ __metadata: languageName: node linkType: hard +"hyphenate-style-name@npm:^1.0.3": + version: 1.1.0 + resolution: "hyphenate-style-name@npm:1.1.0" + checksum: b9ed74e29181d96bd58a2d0e62fc4a19879db591dba268275829ff0ae595fcdf11faafaeaa63330a45c3004664d7db1f0fc7cdb372af8ee4615ed8260302c207 + languageName: node + linkType: hard + "hypher@npm:0.2.5": version: 0.2.5 resolution: "hypher@npm:0.2.5" @@ -37315,6 +37691,16 @@ __metadata: languageName: node linkType: hard +"inline-style-prefixer@npm:^6.0.1": + version: 6.0.4 + resolution: "inline-style-prefixer@npm:6.0.4" + dependencies: + css-in-js-utils: ^3.1.0 + fast-loops: ^1.1.3 + checksum: caf7a75d18acbedc7e3b8bfac17563082becd2df6b65accad964a6afdf490329b42315c37fe65ba0177cc10fd32809eb40d62aba23a0118c74d87d4fc58defa2 + languageName: node + linkType: hard + "inquirer-select-directory@npm:^1.2.0": version: 1.2.0 resolution: "inquirer-select-directory@npm:1.2.0" @@ -38616,6 +39002,7 @@ __metadata: "@nestjs/terminus": 10.2.0 "@nestjs/testing": 10.0.5 "@nx/cypress": 19.4.0 + "@nx/devkit": 19.4.0 "@nx/eslint": 19.4.0 "@nx/eslint-plugin": 19.4.0 "@nx/express": 19.4.0 @@ -38626,12 +39013,16 @@ __metadata: "@nx/node": 19.4.0 "@nx/playwright": 19.4.0 "@nx/react": 19.4.0 + "@nx/react-native": 19.4.0 "@nx/storybook": 19.4.0 "@nx/web": 19.4.0 "@nx/webpack": 19.4.0 "@nx/workspace": 19.4.0 "@openapitools/openapi-generator-cli": 1.0.15-4.3.1 "@playwright/test": 1.48 + "@react-native-community/cli-platform-android": ~13.6.6 + "@react-native/babel-preset": 0.74.87 + "@react-native/metro-config": 0.74.87 "@react-pdf/renderer": ^3.1.9 "@rehooks/component-size": 1.0.3 "@simplewebauthn/server": 10.0.0 @@ -38651,7 +39042,9 @@ __metadata: "@swc/helpers": 0.5.11 "@testing-library/cypress": 8.0.3 "@testing-library/jest-dom": 5.16.5 + "@testing-library/jest-native": 5.4.3 "@testing-library/react": 15.0.6 + "@testing-library/react-native": 12.9.0 "@testing-library/user-event": 14.4.3 "@types/archiver": 6.0.2 "@types/aws-sdk": 2.7.0 @@ -38815,6 +39208,7 @@ __metadata: jest-environment-jsdom: 29.7.0 jest-environment-node: 29.7.0 jest-mock-extended: 3.0.5 + jest-react-native: 18.0.0 jest-transform-stub: 2.0.0 js-base64: 2.5.2 js-cookie: 2.2.1 @@ -38877,6 +39271,10 @@ __metadata: react-is: 18.3.1 react-keyed-flatten-children: 1.2.0 react-modal: 3.15.1 + react-native: 0.74.5 + react-native-svg: 15.2.0 + react-native-svg-transformer: 1.3.0 + react-native-web: ^0.19.11 react-number-format: 4.9.1 react-pdf: 9.1.0 react-popper: 2.3.0 @@ -38884,6 +39282,7 @@ __metadata: react-router-dom: 6.11.2 react-select: 5.8.2 react-table: 7.7.0 + react-test-renderer: 18.2.0 react-toastify: 6.0.8 react-top-loading-bar: 2.3.1 react-use: 15.3.3 @@ -39334,7 +39733,7 @@ __metadata: languageName: node linkType: hard -"jest-diff@npm:^29.4.1, jest-diff@npm:^29.7.0": +"jest-diff@npm:^29.0.1, jest-diff@npm:^29.4.1, jest-diff@npm:^29.7.0": version: 29.7.0 resolution: "jest-diff@npm:29.7.0" dependencies: @@ -39516,6 +39915,18 @@ __metadata: languageName: node linkType: hard +"jest-matcher-utils@npm:^29.0.1, jest-matcher-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-matcher-utils@npm:29.7.0" + dependencies: + chalk: ^4.0.0 + jest-diff: ^29.7.0 + jest-get-type: ^29.6.3 + pretty-format: ^29.7.0 + checksum: d7259e5f995d915e8a37a8fd494cb7d6af24cd2a287b200f831717ba0d015190375f9f5dc35393b8ba2aae9b2ebd60984635269c7f8cff7d85b077543b7744cd + languageName: node + linkType: hard + "jest-matcher-utils@npm:^29.5.0": version: 29.5.0 resolution: "jest-matcher-utils@npm:29.5.0" @@ -39528,18 +39939,6 @@ __metadata: languageName: node linkType: hard -"jest-matcher-utils@npm:^29.7.0": - version: 29.7.0 - resolution: "jest-matcher-utils@npm:29.7.0" - dependencies: - chalk: ^4.0.0 - jest-diff: ^29.7.0 - jest-get-type: ^29.6.3 - pretty-format: ^29.7.0 - checksum: d7259e5f995d915e8a37a8fd494cb7d6af24cd2a287b200f831717ba0d015190375f9f5dc35393b8ba2aae9b2ebd60984635269c7f8cff7d85b077543b7744cd - languageName: node - linkType: hard - "jest-message-util@npm:^27.5.1": version: 27.5.1 resolution: "jest-message-util@npm:27.5.1" @@ -39626,6 +40025,15 @@ __metadata: languageName: node linkType: hard +"jest-react-native@npm:18.0.0": + version: 18.0.0 + resolution: "jest-react-native@npm:18.0.0" + peerDependencies: + react-native: ">=0.38.0" + checksum: 3c524bbdf4a030d99c8202e0f67427130a092f884935ee4c93bcb5a9e9cca8ad1f5db259315175c9b56c2eae5aefbf12ea7bc532956ba806698fbbe3d0ab2dcf + languageName: node + linkType: hard + "jest-regex-util@npm:^27.5.1": version: 27.5.1 resolution: "jest-regex-util@npm:27.5.1" @@ -40434,6 +40842,15 @@ __metadata: languageName: node linkType: hard +"jsesc@npm:^3.0.2": + version: 3.0.2 + resolution: "jsesc@npm:3.0.2" + bin: + jsesc: bin/jsesc + checksum: a36d3ca40574a974d9c2063bf68c2b6141c20da8f2a36bd3279fc802563f35f0527a6c828801295bdfb2803952cf2cf387786c2c90ed564f88d5782475abfe3c + languageName: node + linkType: hard + "jsesc@npm:~0.5.0": version: 0.5.0 resolution: "jsesc@npm:0.5.0" @@ -42997,6 +43414,18 @@ __metadata: languageName: node linkType: hard +"metro-babel-transformer@npm:0.80.12": + version: 0.80.12 + resolution: "metro-babel-transformer@npm:0.80.12" + dependencies: + "@babel/core": ^7.20.0 + flow-enums-runtime: ^0.0.6 + hermes-parser: 0.23.1 + nullthrows: ^1.1.1 + checksum: 1ea8bce0c169f3d8bf46f56da126ca52f4c8ba5ca9ffeaca987c34d269b0a3e2a54d0544bd44bfa5d0322e37f0171a52d2a2160defcbcd91ec1fd96f62b0eece + languageName: node + linkType: hard + "metro-babel-transformer@npm:0.80.9": version: 0.80.9 resolution: "metro-babel-transformer@npm:0.80.9" @@ -43008,6 +43437,27 @@ __metadata: languageName: node linkType: hard +"metro-babel-transformer@npm:0.81.0": + version: 0.81.0 + resolution: "metro-babel-transformer@npm:0.81.0" + dependencies: + "@babel/core": ^7.25.2 + flow-enums-runtime: ^0.0.6 + hermes-parser: 0.24.0 + nullthrows: ^1.1.1 + checksum: e67ef5175f574fbf4a3b6c4f5fd209eb04026cdc32a38e2ebaea21a8c1d4ca20d234aba8e3bff95bfcf60353aaaa0e6369544fe15b1d02aa07f77ab2c26cf053 + languageName: node + linkType: hard + +"metro-cache-key@npm:0.80.12": + version: 0.80.12 + resolution: "metro-cache-key@npm:0.80.12" + dependencies: + flow-enums-runtime: ^0.0.6 + checksum: 7a06601180604361339d19eb833d61b79cc188a4e6ebe73188cc10fbf3a33e711d74c81d1d19a14b6581bd9dfeebe1b253684360682d033ab55909c9995b6a18 + languageName: node + linkType: hard + "metro-cache-key@npm:0.80.9": version: 0.80.9 resolution: "metro-cache-key@npm:0.80.9" @@ -43015,6 +43465,26 @@ __metadata: languageName: node linkType: hard +"metro-cache-key@npm:0.81.0": + version: 0.81.0 + resolution: "metro-cache-key@npm:0.81.0" + dependencies: + flow-enums-runtime: ^0.0.6 + checksum: a96e4062ac0f4684f1d80c8b8c3da380c9d7be506c2bc14750d46a6850610c6e05cb1907cc5421393299f25f40575335e899667519d5435c95a09b0438619847 + languageName: node + linkType: hard + +"metro-cache@npm:0.80.12": + version: 0.80.12 + resolution: "metro-cache@npm:0.80.12" + dependencies: + exponential-backoff: ^3.1.1 + flow-enums-runtime: ^0.0.6 + metro-core: 0.80.12 + checksum: 724e33fdda6a3568572c36a3f2d3465ad1b5f3e8ded5ec116b98e0038826187ebdadd05f77e91ddc17fa71ff4dd91281793a940e7b619cac36044ed868abc01d + languageName: node + linkType: hard + "metro-cache@npm:0.80.9": version: 0.80.9 resolution: "metro-cache@npm:0.80.9" @@ -43025,6 +43495,33 @@ __metadata: languageName: node linkType: hard +"metro-cache@npm:0.81.0": + version: 0.81.0 + resolution: "metro-cache@npm:0.81.0" + dependencies: + exponential-backoff: ^3.1.1 + flow-enums-runtime: ^0.0.6 + metro-core: 0.81.0 + checksum: 0498a93b07b8125987268dde7f95b56ea61826be7834b87f03595de905210dc2675855d8dbbbc0aab0a2f50ed8be0086b096a4085f7320247e3fc6added45167 + languageName: node + linkType: hard + +"metro-config@npm:0.80.12, metro-config@npm:~0.80.4": + version: 0.80.12 + resolution: "metro-config@npm:0.80.12" + dependencies: + connect: ^3.6.5 + cosmiconfig: ^5.0.5 + flow-enums-runtime: ^0.0.6 + jest-validate: ^29.6.3 + metro: 0.80.12 + metro-cache: 0.80.12 + metro-core: 0.80.12 + metro-runtime: 0.80.12 + checksum: 49496d2bc875fbb8c89639979753377888f5ce779742a4ef487d812e7c5f3f6c87dd6ae129727f614d2fe3210f7fde08041055d29772b8c86c018e2ef08e7785 + languageName: node + linkType: hard + "metro-config@npm:0.80.9, metro-config@npm:^0.80.3": version: 0.80.9 resolution: "metro-config@npm:0.80.9" @@ -43040,6 +43537,33 @@ __metadata: languageName: node linkType: hard +"metro-config@npm:0.81.0": + version: 0.81.0 + resolution: "metro-config@npm:0.81.0" + dependencies: + connect: ^3.6.5 + cosmiconfig: ^5.0.5 + flow-enums-runtime: ^0.0.6 + jest-validate: ^29.6.3 + metro: 0.81.0 + metro-cache: 0.81.0 + metro-core: 0.81.0 + metro-runtime: 0.81.0 + checksum: 4969423a292b4aec8f604ae0f682bd62f463ee7a84459c1cf069ff0239427a01e287b97516d265a6b1ec9e8a7b3eb09ad5a8b914e469c9aff56f25473325fe29 + languageName: node + linkType: hard + +"metro-core@npm:0.80.12": + version: 0.80.12 + resolution: "metro-core@npm:0.80.12" + dependencies: + flow-enums-runtime: ^0.0.6 + lodash.throttle: ^4.1.1 + metro-resolver: 0.80.12 + checksum: 319f3965fa76fc08987cbd0228024bdbb0eaad7406e384e48929674188f1066cbc7a233053615ebd84b3ce1bbae28f59c114885fd0a0c179a580319ed69f717e + languageName: node + linkType: hard + "metro-core@npm:0.80.9, metro-core@npm:^0.80.3": version: 0.80.9 resolution: "metro-core@npm:0.80.9" @@ -43050,6 +43574,40 @@ __metadata: languageName: node linkType: hard +"metro-core@npm:0.81.0": + version: 0.81.0 + resolution: "metro-core@npm:0.81.0" + dependencies: + flow-enums-runtime: ^0.0.6 + lodash.throttle: ^4.1.1 + metro-resolver: 0.81.0 + checksum: 4e9e63d4c29f7a4f3e13ee8281c2be4458f5482de5f73d6206782cca78dc580b4d3a16516ff278313fcd1a3e4177e521b3aa0f12768fbf5cc335797557846953 + languageName: node + linkType: hard + +"metro-file-map@npm:0.80.12": + version: 0.80.12 + resolution: "metro-file-map@npm:0.80.12" + dependencies: + anymatch: ^3.0.3 + debug: ^2.2.0 + fb-watchman: ^2.0.0 + flow-enums-runtime: ^0.0.6 + fsevents: ^2.3.2 + graceful-fs: ^4.2.4 + invariant: ^2.2.4 + jest-worker: ^29.6.3 + micromatch: ^4.0.4 + node-abort-controller: ^3.1.1 + nullthrows: ^1.1.1 + walker: ^1.0.7 + dependenciesMeta: + fsevents: + optional: true + checksum: 5e6eafcfafe55fd8a9a6e5613394a20ed2a0ad433a394dcb830f017b8fc9d82ddcd715391e36abe5e98c651c074b99a806d3b04d76f2cadb225f9f5b1c92daef + languageName: node + linkType: hard + "metro-file-map@npm:0.80.9": version: 0.80.9 resolution: "metro-file-map@npm:0.80.9" @@ -43072,6 +43630,39 @@ __metadata: languageName: node linkType: hard +"metro-file-map@npm:0.81.0": + version: 0.81.0 + resolution: "metro-file-map@npm:0.81.0" + dependencies: + anymatch: ^3.0.3 + debug: ^2.2.0 + fb-watchman: ^2.0.0 + flow-enums-runtime: ^0.0.6 + fsevents: ^2.3.2 + graceful-fs: ^4.2.4 + invariant: ^2.2.4 + jest-worker: ^29.6.3 + micromatch: ^4.0.4 + node-abort-controller: ^3.1.1 + nullthrows: ^1.1.1 + walker: ^1.0.7 + dependenciesMeta: + fsevents: + optional: true + checksum: fc99466066fc57d506a90b8dbfc85b9aed3b3dfe362f42c35e24a3f0244b5f3e94b833b52b20cdd728842a1ef7e6c2132b9951a2c2d4013fb470e3a65b9971e0 + languageName: node + linkType: hard + +"metro-minify-terser@npm:0.80.12": + version: 0.80.12 + resolution: "metro-minify-terser@npm:0.80.12" + dependencies: + flow-enums-runtime: ^0.0.6 + terser: ^5.15.0 + checksum: ff527b3f04c5814db139e55ceb7689aaaf0af5c7fbb0eb5d4a6f22044932dfb10bd385d388fa7b352acd03a2d078edaf43a6b5cd11cbc87a7c5502a34fc12735 + languageName: node + linkType: hard + "metro-minify-terser@npm:0.80.9": version: 0.80.9 resolution: "metro-minify-terser@npm:0.80.9" @@ -43081,6 +43672,25 @@ __metadata: languageName: node linkType: hard +"metro-minify-terser@npm:0.81.0": + version: 0.81.0 + resolution: "metro-minify-terser@npm:0.81.0" + dependencies: + flow-enums-runtime: ^0.0.6 + terser: ^5.15.0 + checksum: 53472e5d476613c652f0e8bdf68429c80c66b71dd9a559c2185d56f41a8463ba3431353d453d2e20615875d070389ec24247ddbce67c4d7783bfc85113af18e0 + languageName: node + linkType: hard + +"metro-resolver@npm:0.80.12, metro-resolver@npm:~0.80.4": + version: 0.80.12 + resolution: "metro-resolver@npm:0.80.12" + dependencies: + flow-enums-runtime: ^0.0.6 + checksum: a520030a65afab2f3282604ef6dec802051899a356910606b8ffbc5b82a722008d9d416c8ba3d9ef9527912206586b713733b776803a6b76adac72bcb31870cd + languageName: node + linkType: hard + "metro-resolver@npm:0.80.9": version: 0.80.9 resolution: "metro-resolver@npm:0.80.9" @@ -43088,6 +43698,25 @@ __metadata: languageName: node linkType: hard +"metro-resolver@npm:0.81.0": + version: 0.81.0 + resolution: "metro-resolver@npm:0.81.0" + dependencies: + flow-enums-runtime: ^0.0.6 + checksum: 38349c79b5023d993baf30c7feeb9d60287f33e7bf559b75ce6b4177a4acd991353a0fea0a8caeec9a78efa244c8608c0e5bdff4ac64d6fda89ca0b81c9ca3fc + languageName: node + linkType: hard + +"metro-runtime@npm:0.80.12": + version: 0.80.12 + resolution: "metro-runtime@npm:0.80.12" + dependencies: + "@babel/runtime": ^7.25.0 + flow-enums-runtime: ^0.0.6 + checksum: 11a6d36c7dcf9d221f7de6989556f45d4d64cd1cdd225ec96273b584138b4aa77b7afdc9e9a9488d1dc9a3d90f8e94bb68ab149079cc6ebdb8f8f8b03462cb4f + languageName: node + linkType: hard + "metro-runtime@npm:0.80.9, metro-runtime@npm:^0.80.3": version: 0.80.9 resolution: "metro-runtime@npm:0.80.9" @@ -43097,6 +43726,33 @@ __metadata: languageName: node linkType: hard +"metro-runtime@npm:0.81.0": + version: 0.81.0 + resolution: "metro-runtime@npm:0.81.0" + dependencies: + "@babel/runtime": ^7.25.0 + flow-enums-runtime: ^0.0.6 + checksum: 812869ed71d6017d04c3affafa0b1bd4c86075569e0eb98030b8abddb59923903e3dc8eb23d7dd027384496e27010f6aad7839b0e1105e3873c31d0269fb7971 + languageName: node + linkType: hard + +"metro-source-map@npm:0.80.12": + version: 0.80.12 + resolution: "metro-source-map@npm:0.80.12" + dependencies: + "@babel/traverse": ^7.20.0 + "@babel/types": ^7.20.0 + flow-enums-runtime: ^0.0.6 + invariant: ^2.2.4 + metro-symbolicate: 0.80.12 + nullthrows: ^1.1.1 + ob1: 0.80.12 + source-map: ^0.5.6 + vlq: ^1.0.0 + checksum: 39575bff8666abd0944ec71e01a0c0eacbeab48277528608e894ffa6691c4267c389ee51ad86d5cd8e96f13782b66e1f693a3c60786bb201268678232dce6130 + languageName: node + linkType: hard + "metro-source-map@npm:0.80.9, metro-source-map@npm:^0.80.3": version: 0.80.9 resolution: "metro-source-map@npm:0.80.9" @@ -43113,6 +43769,41 @@ __metadata: languageName: node linkType: hard +"metro-source-map@npm:0.81.0": + version: 0.81.0 + resolution: "metro-source-map@npm:0.81.0" + dependencies: + "@babel/traverse": ^7.25.3 + "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3" + "@babel/types": ^7.25.2 + flow-enums-runtime: ^0.0.6 + invariant: ^2.2.4 + metro-symbolicate: 0.81.0 + nullthrows: ^1.1.1 + ob1: 0.81.0 + source-map: ^0.5.6 + vlq: ^1.0.0 + checksum: e83742c187427b009a5e15eeddd0af0ef29c6e0b88e5f0ac0ba13142e8883f45ce9d66dc8439ca080cea242e955c4f4ba0d64f8344777479ad89d97fa393ad29 + languageName: node + linkType: hard + +"metro-symbolicate@npm:0.80.12": + version: 0.80.12 + resolution: "metro-symbolicate@npm:0.80.12" + dependencies: + flow-enums-runtime: ^0.0.6 + invariant: ^2.2.4 + metro-source-map: 0.80.12 + nullthrows: ^1.1.1 + source-map: ^0.5.6 + through2: ^2.0.1 + vlq: ^1.0.0 + bin: + metro-symbolicate: src/index.js + checksum: b775e4613deec421f6287918d0055c50bb2a38fe3f72581eb70b9441e4497c9c7413c2929c579b24fb76893737b6d5af83a5f6cd8c032e2a83957091f82ec5de + languageName: node + linkType: hard + "metro-symbolicate@npm:0.80.9": version: 0.80.9 resolution: "metro-symbolicate@npm:0.80.9" @@ -43129,6 +43820,37 @@ __metadata: languageName: node linkType: hard +"metro-symbolicate@npm:0.81.0": + version: 0.81.0 + resolution: "metro-symbolicate@npm:0.81.0" + dependencies: + flow-enums-runtime: ^0.0.6 + invariant: ^2.2.4 + metro-source-map: 0.81.0 + nullthrows: ^1.1.1 + source-map: ^0.5.6 + through2: ^2.0.1 + vlq: ^1.0.0 + bin: + metro-symbolicate: src/index.js + checksum: 33990dc3722096beb0fabce5d8d2961b8f400e1f2aa6c19ce9760f9d739b63f25c7bd844e37e0de42e7f95c125431f7e42a7ad0b92b9aee8d214fecdfb4018e7 + languageName: node + linkType: hard + +"metro-transform-plugins@npm:0.80.12": + version: 0.80.12 + resolution: "metro-transform-plugins@npm:0.80.12" + dependencies: + "@babel/core": ^7.20.0 + "@babel/generator": ^7.20.0 + "@babel/template": ^7.0.0 + "@babel/traverse": ^7.20.0 + flow-enums-runtime: ^0.0.6 + nullthrows: ^1.1.1 + checksum: 85c99c367d6c0b9721af744fc980372329c6d37711177660e2d5e2dbe5e92e2cd853604eb8a513ad824eafbed84663472fa304cbbe2036957ee8688b72c2324c + languageName: node + linkType: hard + "metro-transform-plugins@npm:0.80.9": version: 0.80.9 resolution: "metro-transform-plugins@npm:0.80.9" @@ -43142,6 +43864,41 @@ __metadata: languageName: node linkType: hard +"metro-transform-plugins@npm:0.81.0": + version: 0.81.0 + resolution: "metro-transform-plugins@npm:0.81.0" + dependencies: + "@babel/core": ^7.25.2 + "@babel/generator": ^7.25.0 + "@babel/template": ^7.25.0 + "@babel/traverse": ^7.25.3 + flow-enums-runtime: ^0.0.6 + nullthrows: ^1.1.1 + checksum: fea77e227c856cd3a41f55ddcde9852d7408cd3ceb4b434f23e02e5122a95f0a29b1950adae0b806d96bfb26581c1160c4bc62942888698394fcc4e85e0b8ee7 + languageName: node + linkType: hard + +"metro-transform-worker@npm:0.80.12": + version: 0.80.12 + resolution: "metro-transform-worker@npm:0.80.12" + dependencies: + "@babel/core": ^7.20.0 + "@babel/generator": ^7.20.0 + "@babel/parser": ^7.20.0 + "@babel/types": ^7.20.0 + flow-enums-runtime: ^0.0.6 + metro: 0.80.12 + metro-babel-transformer: 0.80.12 + metro-cache: 0.80.12 + metro-cache-key: 0.80.12 + metro-minify-terser: 0.80.12 + metro-source-map: 0.80.12 + metro-transform-plugins: 0.80.12 + nullthrows: ^1.1.1 + checksum: 90684b1f1163bfc84b11bfc01082a38de2a5dd9f7bcabc524bc84f1faff32222954f686a60bc0f464d3e46e86c4c01435111e2ed0e9767a5efbfaf205f55245e + languageName: node + linkType: hard + "metro-transform-worker@npm:0.80.9": version: 0.80.9 resolution: "metro-transform-worker@npm:0.80.9" @@ -43162,6 +43919,79 @@ __metadata: languageName: node linkType: hard +"metro-transform-worker@npm:0.81.0": + version: 0.81.0 + resolution: "metro-transform-worker@npm:0.81.0" + dependencies: + "@babel/core": ^7.25.2 + "@babel/generator": ^7.25.0 + "@babel/parser": ^7.25.3 + "@babel/types": ^7.25.2 + flow-enums-runtime: ^0.0.6 + metro: 0.81.0 + metro-babel-transformer: 0.81.0 + metro-cache: 0.81.0 + metro-cache-key: 0.81.0 + metro-minify-terser: 0.81.0 + metro-source-map: 0.81.0 + metro-transform-plugins: 0.81.0 + nullthrows: ^1.1.1 + checksum: 0fa08b09f4e503183af789e39629dd0fdf4209f3453c0642cdef5e683e69644ec925bcccb2bdb3439059c11fc1418b3bcdd7dc38c768183c3deb8e2bc050e604 + languageName: node + linkType: hard + +"metro@npm:0.80.12": + version: 0.80.12 + resolution: "metro@npm:0.80.12" + dependencies: + "@babel/code-frame": ^7.0.0 + "@babel/core": ^7.20.0 + "@babel/generator": ^7.20.0 + "@babel/parser": ^7.20.0 + "@babel/template": ^7.0.0 + "@babel/traverse": ^7.20.0 + "@babel/types": ^7.20.0 + accepts: ^1.3.7 + chalk: ^4.0.0 + ci-info: ^2.0.0 + connect: ^3.6.5 + debug: ^2.2.0 + denodeify: ^1.2.1 + error-stack-parser: ^2.0.6 + flow-enums-runtime: ^0.0.6 + graceful-fs: ^4.2.4 + hermes-parser: 0.23.1 + image-size: ^1.0.2 + invariant: ^2.2.4 + jest-worker: ^29.6.3 + jsc-safe-url: ^0.2.2 + lodash.throttle: ^4.1.1 + metro-babel-transformer: 0.80.12 + metro-cache: 0.80.12 + metro-cache-key: 0.80.12 + metro-config: 0.80.12 + metro-core: 0.80.12 + metro-file-map: 0.80.12 + metro-resolver: 0.80.12 + metro-runtime: 0.80.12 + metro-source-map: 0.80.12 + metro-symbolicate: 0.80.12 + metro-transform-plugins: 0.80.12 + metro-transform-worker: 0.80.12 + mime-types: ^2.1.27 + nullthrows: ^1.1.1 + serialize-error: ^2.1.0 + source-map: ^0.5.6 + strip-ansi: ^6.0.0 + throat: ^5.0.0 + ws: ^7.5.10 + yargs: ^17.6.2 + bin: + metro: src/cli.js + checksum: 8016f7448e6e0947bd38633c01c3daad47b5a29d4a7294ebe922fa3c505430f78861d85965ecfc6f41d9b209e2663cac0f23c99a80a3f941a19de564203fcdb8 + languageName: node + linkType: hard + "metro@npm:0.80.9, metro@npm:^0.80.3": version: 0.80.9 resolution: "metro@npm:0.80.9" @@ -43215,6 +44045,58 @@ __metadata: languageName: node linkType: hard +"metro@npm:0.81.0": + version: 0.81.0 + resolution: "metro@npm:0.81.0" + dependencies: + "@babel/code-frame": ^7.24.7 + "@babel/core": ^7.25.2 + "@babel/generator": ^7.25.0 + "@babel/parser": ^7.25.3 + "@babel/template": ^7.25.0 + "@babel/traverse": ^7.25.3 + "@babel/types": ^7.25.2 + accepts: ^1.3.7 + chalk: ^4.0.0 + ci-info: ^2.0.0 + connect: ^3.6.5 + debug: ^2.2.0 + denodeify: ^1.2.1 + error-stack-parser: ^2.0.6 + flow-enums-runtime: ^0.0.6 + graceful-fs: ^4.2.4 + hermes-parser: 0.24.0 + image-size: ^1.0.2 + invariant: ^2.2.4 + jest-worker: ^29.6.3 + jsc-safe-url: ^0.2.2 + lodash.throttle: ^4.1.1 + metro-babel-transformer: 0.81.0 + metro-cache: 0.81.0 + metro-cache-key: 0.81.0 + metro-config: 0.81.0 + metro-core: 0.81.0 + metro-file-map: 0.81.0 + metro-resolver: 0.81.0 + metro-runtime: 0.81.0 + metro-source-map: 0.81.0 + metro-symbolicate: 0.81.0 + metro-transform-plugins: 0.81.0 + metro-transform-worker: 0.81.0 + mime-types: ^2.1.27 + nullthrows: ^1.1.1 + serialize-error: ^2.1.0 + source-map: ^0.5.6 + strip-ansi: ^6.0.0 + throat: ^5.0.0 + ws: ^7.5.10 + yargs: ^17.6.2 + bin: + metro: src/cli.js + checksum: 326f13e281ba696361c64b1c6bb77ff5b284771a103a78d446f7944ef8baf89e724bd2a76859c5c4e7adc9e94de2c6619755899efdde9bf1e24d3399e7c7cc00 + languageName: node + linkType: hard + "micromark-core-commonmark@npm:^1.0.0, micromark-core-commonmark@npm:^1.0.1": version: 1.1.0 resolution: "micromark-core-commonmark@npm:1.1.0" @@ -43804,15 +44686,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^8.0.2": - version: 8.0.4 - resolution: "minimatch@npm:8.0.4" - dependencies: - brace-expansion: ^2.0.1 - checksum: 2e46cffb86bacbc524ad45a6426f338920c529dd13f3a732cc2cf7618988ee1aae88df4ca28983285aca9e0f45222019ac2d14ebd17c1edadd2ee12221ab801a - languageName: node - linkType: hard - "minimatch@npm:^9.0.4": version: 9.0.4 resolution: "minimatch@npm:9.0.4" @@ -43938,13 +44811,6 @@ __metadata: languageName: node linkType: hard -"minipass@npm:^4.2.4": - version: 4.2.8 - resolution: "minipass@npm:4.2.8" - checksum: 7f4914d5295a9a30807cae5227a37a926e6d910c03f315930fde52332cf0575dfbc20295318f91f0baf0e6bb11a6f668e30cde8027dea7a11b9d159867a3c830 - languageName: node - linkType: hard - "minipass@npm:^5.0.0 || ^6.0.2 || ^7.0.0": version: 7.0.3 resolution: "minipass@npm:7.0.3" @@ -45422,6 +46288,15 @@ __metadata: languageName: node linkType: hard +"ob1@npm:0.80.12": + version: 0.80.12 + resolution: "ob1@npm:0.80.12" + dependencies: + flow-enums-runtime: ^0.0.6 + checksum: c78af51d6ecf47ba5198bc7eb27d0456a287589533f1445e6d595e2d067f6f8038da02a98e5faa4a6c3d0c04f77c570bc9b29c652fec55518884c40c73212f17 + languageName: node + linkType: hard + "ob1@npm:0.80.9": version: 0.80.9 resolution: "ob1@npm:0.80.9" @@ -45429,6 +46304,15 @@ __metadata: languageName: node linkType: hard +"ob1@npm:0.81.0": + version: 0.81.0 + resolution: "ob1@npm:0.81.0" + dependencies: + flow-enums-runtime: ^0.0.6 + checksum: f3215ccf72604b4db5f9cfc6c83454a136a035ffd26faffec2c100d5810b87599cc95e167888320f3865959a5f9762c03de20a9e40cf66fc13706886820a9523 + languageName: node + linkType: hard + "object-assign@npm:^4, object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -46437,6 +47321,13 @@ __metadata: languageName: node linkType: hard +"path-dirname@npm:^1.0.2": + version: 1.0.2 + resolution: "path-dirname@npm:1.0.2" + checksum: 0d2f6604ae05a252a0025318685f290e2764ecf9c5436f203cdacfc8c0b17c24cdedaa449d766beb94ab88cc7fc70a09ec21e7933f31abc2b719180883e5e33f + languageName: node + linkType: hard + "path-exists@npm:^2.0.0": version: 2.1.0 resolution: "path-exists@npm:2.1.0" @@ -46521,7 +47412,7 @@ __metadata: languageName: node linkType: hard -"path-scurry@npm:^1.11.1, path-scurry@npm:^1.6.1": +"path-scurry@npm:^1.11.1": version: 1.11.1 resolution: "path-scurry@npm:1.11.1" dependencies: @@ -46937,15 +47828,6 @@ __metadata: languageName: node linkType: hard -"pkg-up@npm:^3.1.0": - version: 3.1.0 - resolution: "pkg-up@npm:3.1.0" - dependencies: - find-up: ^3.0.0 - checksum: 5bac346b7c7c903613c057ae3ab722f320716199d753f4a7d053d38f2b5955460f3e6ab73b4762c62fd3e947f58e04f1343e92089e7bb6091c90877406fcd8c8 - languageName: node - linkType: hard - "playwright-core@npm:1.48.2": version: 1.48.2 resolution: "playwright-core@npm:1.48.2" @@ -47678,7 +48560,7 @@ __metadata: languageName: node linkType: hard -"pretty-format@npm:^29.7.0": +"pretty-format@npm:^29.0.3, pretty-format@npm:^29.7.0": version: 29.7.0 resolution: "pretty-format@npm:29.7.0" dependencies: @@ -49147,6 +50029,21 @@ __metadata: languageName: node linkType: hard +"react-native-svg-transformer@npm:1.3.0": + version: 1.3.0 + resolution: "react-native-svg-transformer@npm:1.3.0" + dependencies: + "@svgr/core": ^8.1.0 + "@svgr/plugin-jsx": ^8.1.0 + "@svgr/plugin-svgo": ^8.1.0 + path-dirname: ^1.0.2 + peerDependencies: + react-native: ">=0.59.0" + react-native-svg: ">=12.0.0" + checksum: 09dc490aad05b8f44289fc6c1b75a2fce92b95b27cacb07f320f29c0d0edcc6979fbd6a46f0b13e6b0681c2ae4652d08af35c018bbbd29324db3579a869a4502 + languageName: node + linkType: hard + "react-native-svg@npm:15.2.0": version: 15.2.0 resolution: "react-native-svg@npm:15.2.0" @@ -49180,6 +50077,25 @@ __metadata: languageName: node linkType: hard +"react-native-web@npm:^0.19.11": + version: 0.19.13 + resolution: "react-native-web@npm:0.19.13" + dependencies: + "@babel/runtime": ^7.18.6 + "@react-native/normalize-colors": ^0.74.1 + fbjs: ^3.0.4 + inline-style-prefixer: ^6.0.1 + memoize-one: ^6.0.0 + nullthrows: ^1.1.1 + postcss-value-parser: ^4.2.0 + styleq: ^0.1.3 + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: 15077f88204cb980203b8e3784c092c8c25c972bf281db43fd4ccc9696b603380a49f5289fb9a742daddd8e7599baab5798a1f1c857bdd634add827bc39fd8d8 + languageName: node + linkType: hard + "react-native-webview@npm:13.8.6": version: 13.8.6 resolution: "react-native-webview@npm:13.8.6" @@ -49623,15 +50539,6 @@ __metadata: languageName: node linkType: hard -"react@npm:18.2.0": - version: 18.2.0 - resolution: "react@npm:18.2.0" - dependencies: - loose-envify: ^1.1.0 - checksum: 88e38092da8839b830cda6feef2e8505dec8ace60579e46aa5490fc3dc9bba0bd50336507dc166f43e3afc1c42939c09fe33b25fae889d6f402721dcd78fca1b - languageName: node - linkType: hard - "react@npm:18.3.1": version: 18.3.1 resolution: "react@npm:18.3.1" @@ -50580,13 +51487,6 @@ __metadata: languageName: node linkType: hard -"reselect@npm:^4.1.7": - version: 4.1.8 - resolution: "reselect@npm:4.1.8" - checksum: a4ac87cedab198769a29be92bc221c32da76cfdad6911eda67b4d3e7136dca86208c3b210e31632eae31ebd2cded18596f0dd230d3ccc9e978df22f233b5583e - languageName: node - linkType: hard - "resize-observer-polyfill@npm:^1.5.1": version: 1.5.1 resolution: "resize-observer-polyfill@npm:1.5.1" @@ -50698,7 +51598,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.22.2, resolve@npm:^1.22.4, resolve@npm:^1.22.8": +"resolve@npm:^1.22.2, resolve@npm:^1.22.4": version: 1.22.8 resolution: "resolve@npm:1.22.8" dependencies: @@ -50785,7 +51685,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@^1.22.2#~builtin, resolve@patch:resolve@^1.22.4#~builtin, resolve@patch:resolve@^1.22.8#~builtin": +"resolve@patch:resolve@^1.22.2#~builtin, resolve@patch:resolve@^1.22.4#~builtin": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#~builtin::version=1.22.8&hash=07638b" dependencies: @@ -53642,6 +54542,13 @@ __metadata: languageName: node linkType: hard +"styleq@npm:^0.1.3": + version: 0.1.3 + resolution: "styleq@npm:0.1.3" + checksum: 14a8d23abd914166a9b4bd04ed753bd91363f0e029ee4a94ec2c7dc37d3213fe01fceee22dc655288da3ae89f5dc01cec42d5e2b58478b0dea33bf5bdf509be1 + languageName: node + linkType: hard + "stylis@npm:4.2.0": version: 4.2.0 resolution: "stylis@npm:4.2.0" @@ -55497,6 +56404,15 @@ __metadata: languageName: node linkType: hard +"ua-parser-js@npm:^1.0.35": + version: 1.0.39 + resolution: "ua-parser-js@npm:1.0.39" + bin: + ua-parser-js: script/cli.js + checksum: 19455df8c2348ef53f2e150e7406d3a025a619c2fd69722a1e63363d5ba8d91731ef7585f2dce7d8f14c8782734b4d704c05f246dca5f7565b5ae7d318084f2a + languageName: node + linkType: hard + "uc-first-array@npm:^1.1.10": version: 1.1.10 resolution: "uc-first-array@npm:1.1.10" From 10938221e4a6b8b46b3560604dbd50351dacb0f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sn=C3=A6r=20Seljan=20=C3=9E=C3=B3roddsson?= Date: Tue, 10 Dec 2024 15:55:30 +0000 Subject: [PATCH 56/57] fix(auth-react): Remove auth-react library in favour of bff (#17102) Co-authored-by: snaerseljan Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- libs/auth/react/.babelrc | 4 - libs/auth/react/.eslintrc.json | 10 - libs/auth/react/README.md | 106 ------ libs/auth/react/babel-jest.config.json | 15 - libs/auth/react/jest.config.ts | 16 - libs/auth/react/project.json | 19 - libs/auth/react/src/index.ts | 14 - libs/auth/react/src/lib/AuthSettings.spec.ts | 61 ---- libs/auth/react/src/lib/AuthSettings.ts | 84 ----- libs/auth/react/src/lib/auth/Auth.css.ts | 5 - libs/auth/react/src/lib/auth/Auth.state.ts | 86 ----- libs/auth/react/src/lib/auth/AuthContext.tsx | 64 ---- .../react/src/lib/auth/AuthErrorScreen.tsx | 38 -- .../src/lib/auth/AuthProvider.mocked.spec.tsx | 44 --- .../react/src/lib/auth/AuthProvider.spec.tsx | 241 ------------- libs/auth/react/src/lib/auth/AuthProvider.tsx | 333 ------------------ .../react/src/lib/auth/CheckIdpSession.tsx | 189 ---------- .../src/lib/auth/MockedAuthProvider.spec.tsx | 39 -- .../react/src/lib/auth/MockedAuthProvider.tsx | 27 -- libs/auth/react/src/lib/authLink.ts | 12 - libs/auth/react/src/lib/createMockUser.ts | 19 - .../auth/react/src/lib/getAccessToken.spec.ts | 152 -------- libs/auth/react/src/lib/getAccessToken.ts | 44 --- libs/auth/react/src/lib/userManager.ts | 64 ---- .../react/src/lib/utils/tinyMemoize.spec.ts | 57 --- libs/auth/react/src/lib/utils/tinyMemoize.ts | 17 - .../react/src/lib/utils/toStringScope.spec.ts | 12 - .../auth/react/src/lib/utils/toStringScope.ts | 2 - libs/auth/react/test/setup.ts | 1 - libs/auth/react/tsconfig.json | 17 - libs/auth/react/tsconfig.lib.json | 19 - libs/auth/react/tsconfig.spec.json | 20 -- libs/island-ui/storybook/config/main.ts | 1 - tsconfig.base.json | 1 - 34 files changed, 1833 deletions(-) delete mode 100644 libs/auth/react/.babelrc delete mode 100644 libs/auth/react/.eslintrc.json delete mode 100644 libs/auth/react/README.md delete mode 100644 libs/auth/react/babel-jest.config.json delete mode 100644 libs/auth/react/jest.config.ts delete mode 100644 libs/auth/react/project.json delete mode 100644 libs/auth/react/src/index.ts delete mode 100644 libs/auth/react/src/lib/AuthSettings.spec.ts delete mode 100644 libs/auth/react/src/lib/AuthSettings.ts delete mode 100644 libs/auth/react/src/lib/auth/Auth.css.ts delete mode 100644 libs/auth/react/src/lib/auth/Auth.state.ts delete mode 100644 libs/auth/react/src/lib/auth/AuthContext.tsx delete mode 100644 libs/auth/react/src/lib/auth/AuthErrorScreen.tsx delete mode 100644 libs/auth/react/src/lib/auth/AuthProvider.mocked.spec.tsx delete mode 100644 libs/auth/react/src/lib/auth/AuthProvider.spec.tsx delete mode 100644 libs/auth/react/src/lib/auth/AuthProvider.tsx delete mode 100644 libs/auth/react/src/lib/auth/CheckIdpSession.tsx delete mode 100644 libs/auth/react/src/lib/auth/MockedAuthProvider.spec.tsx delete mode 100644 libs/auth/react/src/lib/auth/MockedAuthProvider.tsx delete mode 100644 libs/auth/react/src/lib/authLink.ts delete mode 100644 libs/auth/react/src/lib/createMockUser.ts delete mode 100644 libs/auth/react/src/lib/getAccessToken.spec.ts delete mode 100644 libs/auth/react/src/lib/getAccessToken.ts delete mode 100644 libs/auth/react/src/lib/userManager.ts delete mode 100644 libs/auth/react/src/lib/utils/tinyMemoize.spec.ts delete mode 100644 libs/auth/react/src/lib/utils/tinyMemoize.ts delete mode 100644 libs/auth/react/src/lib/utils/toStringScope.spec.ts delete mode 100644 libs/auth/react/src/lib/utils/toStringScope.ts delete mode 100644 libs/auth/react/test/setup.ts delete mode 100644 libs/auth/react/tsconfig.json delete mode 100644 libs/auth/react/tsconfig.lib.json delete mode 100644 libs/auth/react/tsconfig.spec.json diff --git a/libs/auth/react/.babelrc b/libs/auth/react/.babelrc deleted file mode 100644 index e05c199e361e..000000000000 --- a/libs/auth/react/.babelrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "presets": ["@nx/react/babel"], - "plugins": [] -} diff --git a/libs/auth/react/.eslintrc.json b/libs/auth/react/.eslintrc.json deleted file mode 100644 index 4f027ee445be..000000000000 --- a/libs/auth/react/.eslintrc.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": ["plugin:@nx/react", "../../../.eslintrc.json"], - "ignorePatterns": ["!**/*"], - "rules": {}, - "overrides": [ - { "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], "rules": {} }, - { "files": ["*.ts", "*.tsx"], "rules": {} }, - { "files": ["*.js", "*.jsx"], "rules": {} } - ] -} diff --git a/libs/auth/react/README.md b/libs/auth/react/README.md deleted file mode 100644 index 039c3d156ae7..000000000000 --- a/libs/auth/react/README.md +++ /dev/null @@ -1,106 +0,0 @@ -# @island.is/auth/react - -Manage authentication in React (non-next) single page applications. - -- Handles oidc-client and callback routes. -- Handles authentication flow with loading screen. -- Manages user context. -- Renews access tokens on demand (when calling APIs) instead of continuously. This helps us support an (eg) 1-8 hour IDS session, depending on how long the user is active. -- Preloads a new access token some time before it expires (when calling APIs). -- Monitor the IDS session and restart login flow if the user is not logged in anymore. - -## Usage - -### Configure - -In the startup of your app (e.g. `Main.tsx`) you need to configure some authentication parameters: - -```typescript -import { configure } from '@island.is/auth/react' -import { environment } from './environments' - -configure({ - // You should usually configure these: - authority: environment.identityServer.authority, - client_id: '@island.is/web', - scope: [ - 'openid', - 'profile', - 'api_resource.scope', - '@island.is/applications:read', - ], - // These can be overridden to control callback urls. - // These are the default values: - baseUrl: `${window.location.origin}`, - redirectPath: '/auth/callback', - redirectPathSilent: '/auth/callback-silent', -}) -``` - -### Authenticate - -The configure function also accepts all oidc-client UserManager settings. - -Then you can render the Authenticator component around your application to wrap it with user authentication. - -```typescript jsx -ReactDOM.render( - - - - - , - document.getElementById('root'), -) -``` - -By default, it only renders its children after signing the user in. It will render a loading screen in the meantime. - -{% hint style="info" %} -Note: Authenticator must be rendered inside React Router to set up callback routes. -{% endhint %} - -### Get access token - -You can configure authentication for your GraphQL client like this: - -```typescript -import { - ApolloClient, - InMemoryCache, - HttpLink, - ApolloLink, -} from '@apollo/client' -import { authLink } from '@island.is/auth/react' - -const httpLink = new HttpLink(/* snip */) - -export const client = new ApolloClient({ - link: ApolloLink.from([authLink, httpLink]), - cache: new InMemoryCache(), -}) -``` - -You can also manually get the access token like this: - -```typescript -import { getAccessToken } from '@island.is/auth/react' - -const accessToken = await getAccessToken() -``` - -### Token renew and IDS session - -When you call `getAccessToken` or make requests with `authLink`, we renew the access token on demand if it has expired. We also preload a new access token if you are actively requesting the access token before it expires. - -Note that if the user has been inactive, they might experience a delay when they come back and call an API, while we renew the access token. - -Every time we renew the access token, the IDS session is extended. When this is written, the IDS maintains a 1 hour session that can be extended up to 8 hours. - -Be careful not to do continuous API requests on an interval when the user might not be active. - -Later we may implement an "updateActive" function that can be called to extend the IDS session in case the user is active but not calling any APIs. - -## Running unit tests - -Run `nx test auth-react` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/libs/auth/react/babel-jest.config.json b/libs/auth/react/babel-jest.config.json deleted file mode 100644 index f83bce0d90ea..000000000000 --- a/libs/auth/react/babel-jest.config.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "presets": [ - [ - "@babel/preset-env", - { - "targets": { - "node": "current" - } - } - ], - "@babel/preset-typescript", - "@babel/preset-react" - ], - "plugins": ["@vanilla-extract/babel-plugin"] -} diff --git a/libs/auth/react/jest.config.ts b/libs/auth/react/jest.config.ts deleted file mode 100644 index 336f7f818324..000000000000 --- a/libs/auth/react/jest.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* eslint-disable */ -export default { - displayName: 'auth-react', - preset: './jest.preset.js', - rootDir: '../../..', - roots: [__dirname], - setupFilesAfterEnv: [`${__dirname}/test/setup.ts`], - transform: { - '^.+\\.[tj]sx?$': [ - 'babel-jest', - { cwd: __dirname, configFile: `${__dirname}/babel-jest.config.json` }, - ], - }, - moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], - coverageDirectory: '/coverage/libs/auth/react', -} diff --git a/libs/auth/react/project.json b/libs/auth/react/project.json deleted file mode 100644 index 51a8e03a6a39..000000000000 --- a/libs/auth/react/project.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "auth-react", - "$schema": "../../../node_modules/nx/schemas/project-schema.json", - "sourceRoot": "libs/auth/react/src", - "projectType": "library", - "tags": ["lib:react-spa", "scope:react-spa"], - "targets": { - "lint": { - "executor": "@nx/eslint:lint" - }, - "test": { - "executor": "@nx/jest:jest", - "outputs": ["{workspaceRoot}/coverage/libs/auth/react"], - "options": { - "jestConfig": "libs/auth/react/jest.config.ts" - } - } - } -} diff --git a/libs/auth/react/src/index.ts b/libs/auth/react/src/index.ts deleted file mode 100644 index 99c01c660442..000000000000 --- a/libs/auth/react/src/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Components -export * from './lib/auth/AuthProvider' -export * from './lib/auth/MockedAuthProvider' -export * from './lib/auth/AuthContext' - -// Lib -export * from './lib/userManager' -export * from './lib/authLink' -export { getAccessToken } from './lib/getAccessToken' - -// Types -export type { MockUser } from './lib/createMockUser' -export type { AuthSettings } from './lib/AuthSettings' -export * from './lib/createMockUser' diff --git a/libs/auth/react/src/lib/AuthSettings.spec.ts b/libs/auth/react/src/lib/AuthSettings.spec.ts deleted file mode 100644 index a2edf3423518..000000000000 --- a/libs/auth/react/src/lib/AuthSettings.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { mergeAuthSettings } from './AuthSettings' - -describe('mergeAuthSettings', () => { - it('provides good defaults', () => { - // act - const settings = mergeAuthSettings({ - client_id: 'test-client', - authority: 'https://innskra.island.is', - }) - - // assert - expect(settings).toMatchInlineSnapshot(` - Object { - "authority": "https://innskra.island.is", - "automaticSilentRenew": false, - "baseUrl": "http://localhost", - "checkSessionPath": "/connect/sessioninfo", - "client_id": "test-client", - "loadUserInfo": true, - "mergeClaims": true, - "monitorSession": false, - "post_logout_redirect_uri": "http://localhost", - "redirectPath": "/auth/callback", - "redirectPathSilent": "/auth/callback-silent", - "response_type": "code", - "revokeTokenTypes": Array [ - "refresh_token", - ], - "revokeTokensOnSignout": true, - "silent_redirect_uri": "http://localhost/auth/callback-silent", - "userStore": WebStorageStateStore { - "_logger": Logger { - "_name": "WebStorageStateStore", - }, - "_prefix": "oidc.", - "_store": Storage {}, - }, - } - `) - }) - - it('creates uris from baseUrl and redirect paths', () => { - // act - const settings = mergeAuthSettings({ - authority: 'https://innskra.island.is', - client_id: 'test-client', - baseUrl: 'https://island.is', - redirectPath: '/auth', - redirectPathSilent: '/auth-silent', - }) - - // assert - expect(settings).toMatchObject({ - baseUrl: 'https://island.is', - post_logout_redirect_uri: 'https://island.is', - redirectPath: '/auth', - redirectPathSilent: '/auth-silent', - silent_redirect_uri: 'https://island.is/auth-silent', - }) - }) -}) diff --git a/libs/auth/react/src/lib/AuthSettings.ts b/libs/auth/react/src/lib/AuthSettings.ts deleted file mode 100644 index c35c7ee8c65a..000000000000 --- a/libs/auth/react/src/lib/AuthSettings.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { storageFactory } from '@island.is/shared/utils' -import { UserManagerSettings, WebStorageStateStore } from 'oidc-client-ts' - -export interface AuthSettings - extends Omit { - /* - * Used to create redirect uris. Should not end with slash. - * Default: window.location.origin - */ - baseUrl?: string - - /* - * Used to handle login callback and to build a default value for `redirect_uri` with baseUrl. Should be - * relative from baseUrl and start with a "/". - * Default: "/auth/callback" - */ - redirectPath?: string - - /** - * Used to handle login callback and to build a default value for `silent_redirect_uri` with baseUrl. - * Should be relative from baseUrl and start with a "/". - * Default: "/auth/callback-silent" - */ - redirectPathSilent?: string - - /** - * Used to support login flow triggered by the authorisation server or another party. Should be relative from baseUrl - * and start with a "/". - * More information: https://openid.net/specs/openid-connect-standard-1_0-21.html#client_Initiate_login - * Default: undefined - */ - initiateLoginPath?: string - - /** - * Prefix for storing user access tokens in session storage. - */ - userStorePrefix?: string - - /** - * Allow to pass the scope as an array. - */ - scope?: string[] - - /** - * Which URL to send the user to after switching users. - */ - switchUserRedirectUrl?: string - - /** - * Which PATH on the AUTHORITY to use for checking the session expiry. - */ - checkSessionPath?: string -} - -export const mergeAuthSettings = (settings: AuthSettings): AuthSettings => { - const baseUrl = settings.baseUrl ?? window.location.origin - const redirectPath = settings.redirectPath ?? '/auth/callback' - const redirectPathSilent = - settings.redirectPathSilent ?? '/auth/callback-silent' - - // Many Open ID Connect features only work when on the same domain as the IDS (with first party cookies) - const onIdsDomain = /(is|dev)land.is$/.test(window.location.origin) - - return { - baseUrl, - redirectPath, - redirectPathSilent, - automaticSilentRenew: false, - checkSessionPath: '/connect/sessioninfo', - silent_redirect_uri: `${baseUrl}${redirectPathSilent}`, - post_logout_redirect_uri: baseUrl, - response_type: 'code', - revokeTokenTypes: ['refresh_token'], - revokeTokensOnSignout: true, - loadUserInfo: true, - monitorSession: onIdsDomain, - userStore: new WebStorageStateStore({ - store: storageFactory(() => sessionStorage), - prefix: settings.userStorePrefix, - }), - mergeClaims: true, - ...settings, - } -} diff --git a/libs/auth/react/src/lib/auth/Auth.css.ts b/libs/auth/react/src/lib/auth/Auth.css.ts deleted file mode 100644 index 62d600b0be1b..000000000000 --- a/libs/auth/react/src/lib/auth/Auth.css.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { style } from '@vanilla-extract/css' - -export const fullScreen = style({ - height: '100vh', -}) diff --git a/libs/auth/react/src/lib/auth/Auth.state.ts b/libs/auth/react/src/lib/auth/Auth.state.ts deleted file mode 100644 index 74a1b1f9b785..000000000000 --- a/libs/auth/react/src/lib/auth/Auth.state.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { User } from '@island.is/shared/types' - -export type AuthState = - | 'logged-out' - | 'loading' - | 'logged-in' - | 'failed' - | 'switching' - | 'logging-out' - -export interface AuthReducerState { - userInfo: User | null - authState: AuthState - isAuthenticated: boolean -} - -export enum ActionType { - SIGNIN_START = 'SIGNIN_START', - SIGNIN_SUCCESS = 'SIGNIN_SUCCESS', - SIGNIN_FAILURE = 'SIGNIN_FAILURE', - LOGGING_OUT = 'LOGGING_OUT', - LOGGED_OUT = 'LOGGED_OUT', - USER_LOADED = 'USER_LOADED', - SWITCH_USER = 'SWITCH_USER', -} - -export interface Action { - type: ActionType - // eslint-disable-next-line @typescript-eslint/no-explicit-any - payload?: any -} - -export const initialState: AuthReducerState = { - userInfo: null, - authState: 'logged-out', - isAuthenticated: false, -} - -export const reducer = ( - state: AuthReducerState, - action: Action, -): AuthReducerState => { - switch (action.type) { - case ActionType.SIGNIN_START: - return { - ...state, - authState: 'loading', - } - case ActionType.SIGNIN_SUCCESS: - return { - ...state, - userInfo: action.payload, - - authState: 'logged-in', - isAuthenticated: true, - } - case ActionType.USER_LOADED: - return state.isAuthenticated - ? { - ...state, - userInfo: action.payload, - } - : state - case ActionType.SIGNIN_FAILURE: - return { - ...state, - authState: 'failed', - } - case ActionType.LOGGING_OUT: - return { - ...state, - authState: 'logging-out', - } - case ActionType.SWITCH_USER: - return { - ...state, - authState: 'switching', - } - case ActionType.LOGGED_OUT: - return { - ...initialState, - } - default: - return state - } -} diff --git a/libs/auth/react/src/lib/auth/AuthContext.tsx b/libs/auth/react/src/lib/auth/AuthContext.tsx deleted file mode 100644 index e6d65ea5d94a..000000000000 --- a/libs/auth/react/src/lib/auth/AuthContext.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { createContext, useContext } from 'react' - -import { AuthReducerState, initialState } from './Auth.state' - -export interface AuthContextType extends AuthReducerState { - signIn(): void - signInSilent(): void - switchUser(nationalId?: string): void - signOut(): void - authority?: string -} - -export const defaultAuthContext = { - ...initialState, - signIn() { - // Intentionally empty - }, - signInSilent() { - // Intentionally empty - }, - switchUser(_nationalId?: string) { - // Intentionally empty - }, - signOut() { - // Intentionally empty - }, -} - -export const AuthContext = createContext(defaultAuthContext) - -const warnDeprecated = (hookName: string, alternative: string) => { - console.warn( - `[Deprecation Warning] "${hookName}" is being replaced by BFF auth pattern Please use "${alternative}" from "libs/react-spa/bff".`, - ) -} - -/** - * @deprecated Use useBff from `libs/react-spa/bff` instead. - */ -export const useAuth = () => { - warnDeprecated('useAuth', 'useBff') - - const context = useContext(AuthContext) - - if (!context) { - throw new Error('useAuth must be used within a AuthProvider') - } - - return context -} - -/** - * @deprecated Use useUserInfo from `libs/react-spa/bff` instead. - */ -export const useUserInfo = () => { - warnDeprecated('useUserInfo', 'useUserInfo') - const { userInfo } = useAuth() - - if (!userInfo) { - throw new Error('User info is not available. Is the user authenticated?') - } - - return userInfo -} diff --git a/libs/auth/react/src/lib/auth/AuthErrorScreen.tsx b/libs/auth/react/src/lib/auth/AuthErrorScreen.tsx deleted file mode 100644 index 27bcfe5aeb03..000000000000 --- a/libs/auth/react/src/lib/auth/AuthErrorScreen.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react' -import { Box, Button, ProblemTemplate } from '@island.is/island-ui/core' - -import * as styles from './Auth.css' - -type AuthenticatorErrorScreenProps = { - /** - * Retry callback - */ - onRetry(): void -} - -// This screen is unfortunately not translated because at this point we don't -// have a user locale, nor an access token to fetch translations. -export const AuthErrorScreen = ({ onRetry }: AuthenticatorErrorScreenProps) => ( - - - Vinsamlegast reyndu aftur síðar.{' '} - - Reyna aftur - - > - } - /> - -) diff --git a/libs/auth/react/src/lib/auth/AuthProvider.mocked.spec.tsx b/libs/auth/react/src/lib/auth/AuthProvider.mocked.spec.tsx deleted file mode 100644 index 3cc638999238..000000000000 --- a/libs/auth/react/src/lib/auth/AuthProvider.mocked.spec.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { FC } from 'react' -import { render, screen } from '@testing-library/react' -import { MemoryRouter } from 'react-router-dom' - -import { configureMock } from '../userManager' -import { useAuth } from './AuthContext' -import { AuthProvider } from './AuthProvider' - -const Wrapper: FC> = ({ children }) => ( - {children} -) -const Greeting = () => { - const { userInfo } = useAuth() - return <>Hello {userInfo?.profile.name}> -} -const renderAuthenticator = () => - render( - - - - - , - { wrapper: Wrapper }, - ) - -describe('AuthProvider', () => { - const expectAuthenticated = (name: string) => - screen.findByText(`Hello ${name}`) - - it('authenticates with non-expired user', async () => { - // Arrange - configureMock({ - profile: { - name: 'John Doe', - }, - }) - - // Act - renderAuthenticator() - - // Assert - await expectAuthenticated('John Doe') - }) -}) diff --git a/libs/auth/react/src/lib/auth/AuthProvider.spec.tsx b/libs/auth/react/src/lib/auth/AuthProvider.spec.tsx deleted file mode 100644 index ab1d4a03049f..000000000000 --- a/libs/auth/react/src/lib/auth/AuthProvider.spec.tsx +++ /dev/null @@ -1,241 +0,0 @@ -import { act, render, screen, waitFor } from '@testing-library/react' -import { UserManagerEvents } from 'oidc-client-ts' -import { BrowserRouter } from 'react-router-dom' - -import { getAuthSettings, getUserManager } from '../userManager' -import { useAuth } from './AuthContext' -import { AuthProvider } from './AuthProvider' -import { createNationalId } from '@island.is/testing/fixtures' - -const BASE_PATH = '/basepath' -const INITIATE_LOGIN_PATH = '/login' - -jest.mock('../userManager') -const mockedGetUserManager = getUserManager as jest.Mock -const mockedGetAuthSettings = getAuthSettings as jest.Mock - -const Greeting = () => { - const { userInfo } = useAuth() - return <>Hello {userInfo?.profile.name}> -} - -const renderAuthenticator = (route = BASE_PATH) => { - window.history.pushState({}, 'Test page', route) - - return render( - - - - - , - { wrapper: BrowserRouter }, - ) -} - -type MinimalUser = { - expired?: boolean - profile?: { - name: string - } -} -type MinimalUserManager = { - events: { - addUserLoaded: (cb: UserManagerEvents) => void - addUserSignedOut: jest.Mock - removeUserLoaded: () => void - removeUserSignedOut: () => void - } - getUser: jest.Mock> - signinRedirect: jest.Mock - signinSilent: jest.Mock - signinRedirectCallback: jest.Mock - removeUser: jest.Mock -} - -describe('AuthProvider', () => { - let userManager: MinimalUserManager - - const expectSignin = () => - waitFor(() => { - if (userManager.signinRedirect.mock.calls.length === 0) { - throw new Error('... wait') - } - }) - - const expectAuthenticated = (name: string) => - screen.findByText(`Hello ${name}`) - - beforeEach(() => { - userManager = { - events: { - addUserLoaded: jest.fn(), - addUserSignedOut: jest.fn(), - removeUserLoaded: jest.fn(), - removeUserSignedOut: jest.fn(), - }, - getUser: jest.fn(), - signinRedirect: jest.fn(), - signinSilent: jest.fn(), - signinRedirectCallback: jest.fn(), - removeUser: jest.fn(), - } - mockedGetUserManager.mockReturnValue(userManager) - mockedGetAuthSettings.mockReturnValue({ - baseUrl: BASE_PATH, - initiateLoginPath: INITIATE_LOGIN_PATH, - redirectPath: '/callback', - redirectPathSilent: '/callback-silent', - }) - }) - - it('starts signin flow when no stored user', async () => { - // Act - renderAuthenticator() - - // Assert - await expectSignin() - }) - - it('should show a progress bar while authenticating', async () => { - // Act - const { getByRole } = renderAuthenticator() - - // Assert - getByRole('progressbar') - await expectSignin() - }) - - it('authenticates with non-expired user', async () => { - // Arrange - userManager.getUser.mockResolvedValue({ - expired: false, - profile: { - name: 'John', - }, - }) - - // Act - renderAuthenticator() - - // Assert - await expectAuthenticated('John') - }) - - it('removes user and starts signin flow if user is logged out', async () => { - // Arrange - userManager.getUser.mockResolvedValue({ - expired: false, - profile: { - name: 'John', - }, - }) - renderAuthenticator() - await expectAuthenticated('John') - expect(userManager.events.addUserSignedOut).toHaveBeenCalled() - const handler = userManager.events.addUserSignedOut.mock.calls[0][0] - - // Act - await act(async () => { - await handler() - }) - - // Assert - await expectSignin() - expect(userManager.removeUser).toHaveBeenCalled() - }) - - it('performs silent signin with expired user', async () => { - // Arrange - userManager.getUser.mockResolvedValue({ - expired: true, - }) - userManager.signinSilent.mockResolvedValue({ - profile: { - name: 'Doe', - }, - }) - - // Act - renderAuthenticator() - - // Assert - await expectAuthenticated('Doe') - expect(userManager.signinSilent).toHaveBeenCalled() - }) - - it('starts signin flow if silent signin fails', async () => { - // Arrange - userManager.getUser.mockResolvedValue({ - expired: true, - }) - userManager.signinSilent.mockRejectedValue(new Error('Not signed in')) - - // Act - renderAuthenticator() - - // Assert - await expectSignin() - expect(userManager.signinSilent).toHaveBeenCalled() - }) - - // prettier-ignore - it.each` - params - ${{ prompt: 'login' }} - ${{ prompt: 'select_account' }} - ${{ - login_hint: createNationalId('company'), - target_link_uri: `${BASE_PATH}/test`, - }} - `( - 'starts 3rd party initiated login flow with params $params', - async (params: { prompt?: string, login_hint?: string, target_link_uri?: string }) => { - // Arrange - const searchParams = new URLSearchParams(params) - - // Act - renderAuthenticator( - `${BASE_PATH}${INITIATE_LOGIN_PATH}?${searchParams.toString()}`, - ) - - // Assert - await expectSignin() - expect(userManager.signinRedirect).toHaveBeenCalledWith({ - state: params.target_link_uri?.slice(BASE_PATH.length) ?? '/', - login_hint: params.login_hint, - prompt: params.prompt, - }) - }, - ) - - it('shows error screen if signin has an error', async () => { - // Arrange - const testRoute = `${BASE_PATH}${mockedGetAuthSettings().redirectPath}` - userManager.signinRedirectCallback.mockRejectedValue( - new Error('Test error'), - ) - - // Act - renderAuthenticator(testRoute) - - // Assert - await screen.findByText('Innskráning mistókst') - await screen.findByRole('button', { name: 'Reyna aftur' }) - }) - - it('shows error screen if IDS is unavailable', async () => { - // Arrange - // When the OIDC client library fails to load the /.well-known/openid-configuration - // the signinRedirect methods rejects the Promise with an Error. - userManager.signinRedirect.mockRejectedValueOnce( - new Error('Internal Server Error'), - ) - - // Act - renderAuthenticator() - - // Assert - await screen.findByText('Innskráning mistókst') - await screen.findByRole('button', { name: 'Reyna aftur' }) - }) -}) diff --git a/libs/auth/react/src/lib/auth/AuthProvider.tsx b/libs/auth/react/src/lib/auth/AuthProvider.tsx deleted file mode 100644 index 8243291478b5..000000000000 --- a/libs/auth/react/src/lib/auth/AuthProvider.tsx +++ /dev/null @@ -1,333 +0,0 @@ -import React, { - useCallback, - useEffect, - useMemo, - useReducer, - ReactNode, - useState, -} from 'react' -import type { SigninRedirectArgs, User } from 'oidc-client-ts' - -import { useEffectOnce } from '@island.is/react-spa/shared' -import { isDefined } from '@island.is/shared/utils' -import { LoadingScreen } from '@island.is/react/components' - -import { getAuthSettings, getUserManager } from '../userManager' -import { ActionType, initialState, reducer } from './Auth.state' -import { AuthSettings } from '../AuthSettings' -import { AuthContext } from './AuthContext' -import { AuthErrorScreen } from './AuthErrorScreen' -import { CheckIdpSession } from './CheckIdpSession' - -interface AuthProviderProps { - /** - * If true, Authenticator automatically starts login flow and does not render children until user is fully logged in. - * If false, children are responsible for rendering a login button and loading indicator. - * Default: true - */ - autoLogin?: boolean - /** - * The base path of the application. - */ - basePath: string - children: ReactNode -} - -type GetReturnUrl = { - returnUrl: string -} & Pick - -const isCurrentRoute = (url: string, path?: string) => - isDefined(path) && url.startsWith(path) - -const getReturnUrl = ({ redirectPath, returnUrl }: GetReturnUrl) => { - if (redirectPath && returnUrl.startsWith(redirectPath)) { - return '/' - } - - return returnUrl -} - -const getCurrentUrl = (basePath: string) => { - const url = `${window.location.pathname}${window.location.search}${window.location.hash}` - - if (url.startsWith(basePath)) { - return url.slice(basePath.length) - } - - return '/' -} - -export const AuthProvider = ({ - children, - autoLogin = true, - basePath, -}: AuthProviderProps) => { - const [state, dispatch] = useReducer(reducer, initialState) - const [error, setError] = useState() - const userManager = getUserManager() - const authSettings = getAuthSettings() - const monitorUserSession = !authSettings.scope?.includes('offline_access') - - const signinRedirect = useCallback( - async (args: SigninRedirectArgs) => { - try { - await userManager.signinRedirect(args) - // On success Nothing more happens here since browser will redirect to IDS. - } catch (error) { - // On error we set the error state to show the error screen which provides the users with a retry button. - console.error(error) - setError(error) - } - }, - [userManager, setError], - ) - - const signIn = useCallback( - async function signIn() { - dispatch({ - type: ActionType.SIGNIN_START, - }) - - return signinRedirect({ - state: getReturnUrl({ - returnUrl: getCurrentUrl(basePath), - redirectPath: authSettings.redirectPath, - }), - }) - }, - [dispatch, authSettings, basePath], - ) - - const signInSilent = useCallback( - async function signInSilent() { - let user = null - dispatch({ - type: ActionType.SIGNIN_START, - }) - try { - user = await userManager.signinSilent() - dispatch({ type: ActionType.SIGNIN_SUCCESS, payload: user }) - } catch (error) { - console.error('AuthProvider: Silent signin failed', error) - dispatch({ type: ActionType.SIGNIN_FAILURE }) - } - - return user - }, - [userManager, dispatch], - ) - - const switchUser = useCallback( - async function switchUser(nationalId?: string) { - const args = - nationalId !== undefined - ? { - login_hint: nationalId, - /** - * TODO: remove this. - * It is currently required to switch delegations, but we'd like - * the IDS to handle login_required and other potential road - * blocks. Now OidcSignIn is handling login_required. - */ - prompt: 'none', - } - : { - prompt: 'select_account', - } - - dispatch({ - type: ActionType.SWITCH_USER, - }) - - return signinRedirect({ - state: - authSettings.switchUserRedirectUrl ?? - getReturnUrl({ - returnUrl: getCurrentUrl(basePath), - redirectPath: authSettings.redirectPath, - }), - ...args, - }) - // Nothing more happens here since browser will redirect to IDS. - }, - [userManager, dispatch, authSettings, basePath], - ) - - const signOut = useCallback( - async function signOut() { - dispatch({ - type: ActionType.LOGGING_OUT, - }) - await userManager.signoutRedirect() - }, - [userManager, dispatch], - ) - - const checkLogin = useCallback( - async function checkLogin() { - dispatch({ - type: ActionType.SIGNIN_START, - }) - const storedUser = await userManager.getUser() - - // Check expiry. - if (storedUser && !storedUser.expired) { - dispatch({ - type: ActionType.SIGNIN_SUCCESS, - payload: storedUser, - }) - } else if (autoLogin) { - // If we find a user in SessionStorage, there's a fine chance that - // it's just an expired token, and we can silently log in. - if (storedUser && (await signInSilent())) { - return - } - - // If all else fails, redirect to the login page. - await signIn() - } else { - // When not performing autologin, silently check if there's an IDP session. - await signInSilent() - } - }, - [userManager, dispatch, signIn, signInSilent, autoLogin], - ) - - const hasUserInfo = state.userInfo !== null - useEffect(() => { - // Only add events when we have userInfo, to avoid race conditions with - // oidc hooks. - if (!hasUserInfo) { - return - } - - // This is raised when a new user state has been loaded with a silent login. - const userLoaded = (user: User) => { - dispatch({ - type: ActionType.USER_LOADED, - payload: user, - }) - } - - // This is raised when the user is signed out of the IDP. - const userSignedOut = async () => { - dispatch({ - type: ActionType.LOGGED_OUT, - }) - await userManager.removeUser() - - if (autoLogin) { - signIn() - } - } - - userManager.events.addUserLoaded(userLoaded) - userManager.events.addUserSignedOut(userSignedOut) - return () => { - userManager.events.removeUserLoaded(userLoaded) - userManager.events.removeUserSignedOut(userSignedOut) - } - }, [dispatch, userManager, signIn, autoLogin, hasUserInfo]) - - const init = async () => { - const currentUrl = getCurrentUrl(basePath) - - if (isCurrentRoute(currentUrl, authSettings.redirectPath)) { - try { - const user = await userManager.signinRedirectCallback( - window.location.href, - ) - - const url = typeof user.state === 'string' ? user.state : '/' - window.history.replaceState(null, '', basePath + url) - - dispatch({ - type: ActionType.SIGNIN_SUCCESS, - payload: user, - }) - } catch (e) { - if (e.error === 'login_required') { - // If trying to switch delegations and the IDS session is expired, we'll - // see this error. So we'll try a proper signin. - return signinRedirect({ state: e.state }) - } - console.error('Error in oidc callback', e) - setError(e) - } - } else if (isCurrentRoute(currentUrl, authSettings.redirectPathSilent)) { - const userManager = getUserManager() - userManager.signinSilentCallback().catch((e) => { - console.log(e) - setError(e) - }) - } else if (isCurrentRoute(currentUrl, authSettings.initiateLoginPath)) { - const userManager = getUserManager() - const searchParams = new URL(window.location.href).searchParams - - const loginHint = searchParams.get('login_hint') - const targetLinkUri = searchParams.get('target_link_uri') - const path = - targetLinkUri && - authSettings.baseUrl && - targetLinkUri.startsWith(authSettings.baseUrl) - ? targetLinkUri.slice(authSettings.baseUrl.length) - : '/' - let prompt = searchParams.get('prompt') - prompt = - prompt && ['login', 'select_account'].includes(prompt) ? prompt : null - - const args = { - state: path, - prompt: prompt ?? undefined, - login_hint: loginHint ?? undefined, - } - return signinRedirect(args) - } else { - checkLogin() - } - } - - useEffectOnce(() => { - init() - }) - - const context = useMemo( - () => ({ - ...state, - signIn, - signInSilent, - switchUser, - signOut, - authority: authSettings.authority, - }), - [state, signIn, signInSilent, switchUser, signOut, authSettings.authority], - ) - - const url = getCurrentUrl(basePath) - const isLoading = - !state.userInfo || - // We need to display loading screen if current route is the redirectPath or redirectPathSilent. - // This is because these paths are not part of our React Router routes. - isCurrentRoute(url, authSettings?.redirectPath) || - isCurrentRoute(url, authSettings?.redirectPathSilent) - - const onRetry = () => { - window.location.href = basePath - } - - return ( - - {error ? ( - - ) : isLoading ? ( - - ) : ( - <> - {monitorUserSession && } - {children} - > - )} - - ) -} diff --git a/libs/auth/react/src/lib/auth/CheckIdpSession.tsx b/libs/auth/react/src/lib/auth/CheckIdpSession.tsx deleted file mode 100644 index 1890dbd1efd6..000000000000 --- a/libs/auth/react/src/lib/auth/CheckIdpSession.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import addSeconds from 'date-fns/addSeconds' -import { useCallback, useEffect, useReducer, useRef } from 'react' -import { getAuthSettings, getUserManager } from '../userManager' - -const UserSessionMessageType = 'SessionInfo' - -interface UserSessionMessage { - // Type to use to filter postMessage messages - type: typeof UserSessionMessageType - - // Status of the message received from IDP. - status: 'Ok' | 'No Session' | 'Failure' - - // The time when the authenticated session expires. - expiresUtc?: string - - // Number of seconds until the session expires. - expiresIn?: number - - // Boolean flag to indicated if the Expires time is passed. - isExpired?: boolean -} - -interface UserSessionState { - /* The expected time when the user session is ending. */ - sessionEnd: Date | null - - /** - * An interval function that checks if the expected sessionEnd has passed. - * When set this indicates that the user has an active session. - */ - intervalHandle: ReturnType | null - - /* The number of times we have tried to load the iframe to receive a new session info message. */ - retryCount: number -} - -const MAX_RETRIES = 2 -const ACTIVE_SESSION_DELAY = 5 * 1000 -const CHECK_SESSION_INTERVAL = 2 * 1000 - -const EMPTY_SESSION: UserSessionState = { - retryCount: 0, - sessionEnd: null, - intervalHandle: null, -} - -/** - * This component monitors if the user session is active on the Identity Provider (IDP). - * When it detects that the user session is expired it redirects to the sign-in page on the IDP. - * - * It loads a script from the IDP's 'connect/sessioninfo' endpoint into an iframe. - * The script uses the postMessage API to post UserSessionMessage, which contains - * details if the session is expired or after how many seconds it will expire. - * We use these details to register an interval to monitor the session expiration. - */ -export const CheckIdpSession = () => { - const userManager = getUserManager() - const authSettings = getAuthSettings() - const iframeSrc = `${authSettings.authority}${authSettings.checkSessionPath}` - const [iframeId, reloadIframe] = useReducer((id) => id + 1, 0) - const userSession = useRef({ ...EMPTY_SESSION }) - - const isActive = useCallback(() => { - // When intervalHandle is set it means we have registered - // a setInterval to monitor an active user session. - return !!userSession.current.intervalHandle - }, []) - - const hasBeenActive = useCallback(() => { - // When sessionEnd is set it means the has been active - // as we have an earlier UserSessionMessage. - return !!userSession.current.sessionEnd - }, []) - - const resetUserSession = useCallback(() => { - if (userSession.current.intervalHandle) { - clearInterval(userSession.current.intervalHandle) - } - userSession.current.intervalHandle = null - userSession.current.retryCount = 0 - // Intentionally not resetting sessionEnd as it - // indicates that the user has had session before. - }, []) - - const signInRedirect = useCallback(async () => { - await userManager.removeUser() - return window.location.reload() - }, [userManager]) - - const checkActiveSession = useCallback(() => { - setTimeout(() => { - const { retryCount } = userSession.current - - if (!isActive() && retryCount > MAX_RETRIES && hasBeenActive()) { - // We were unable to retrieve a message from the IDP after max retries and have a reason - // to believe that the session is expired (an earlier UserSessionMessage has expired). - // So we reload the window just to be safe. This causes one of three things to happen: - // - If the iframe is broken and the user does have a valid IDP session, they'll generally reload where they were. - // - If the iframe is broken and the user does not have a valid IDP session, they're sent to the login page. - // - If the user has a network problem, then they'll see a browser error screen, but at least any sensitive information is not visible any more. - window.location.reload() - } else if (!isActive() && retryCount < MAX_RETRIES) { - userSession.current.retryCount += 1 - // We are unable to retrieve a message from the IDP, - // so we reload the iframe to retry without reloading the window. - reloadIframe() - } - }, ACTIVE_SESSION_DELAY) - }, [isActive, hasBeenActive]) - - const messageHandler = useCallback( - async ({ data, origin }: MessageEvent): Promise => { - const sessionInfo = data as UserSessionMessage - - // Check if the postMessage is meant for us - if ( - origin !== authSettings.authority || - sessionInfo.type !== UserSessionMessageType - ) { - return - } - - if (sessionInfo && sessionInfo.status === 'Ok') { - // SessionInfo was found, check if it is valid or expired - if (sessionInfo.isExpired) { - return signInRedirect() - } else if (!isActive() && sessionInfo.expiresIn !== undefined) { - userSession.current.sessionEnd = addSeconds( - new Date(), - sessionInfo.expiresIn, - ) - - userSession.current.intervalHandle = setInterval(() => { - const now = new Date() - - if ( - userSession.current.sessionEnd && - now > userSession.current.sessionEnd - ) { - // The expected session end has passed but the user might have extended their session. - // So we reset the session state and reload the iframe to query new session info from the IDP. - resetUserSession() - reloadIframe() - } - }, CHECK_SESSION_INTERVAL) - } - } else if ( - sessionInfo && - sessionInfo.status === 'No Session' && - hasBeenActive() - ) { - return signInRedirect() - } - - // Silent failure as we have failed to get sessionInfo but the user still might have valid session. - // So we only trigger the signInRedirect flow when we get definite response about expired session. - }, - [ - authSettings.authority, - signInRedirect, - isActive, - hasBeenActive, - resetUserSession, - ], - ) - - useEffect(() => { - window.addEventListener('message', messageHandler) - - return () => { - window.removeEventListener('message', messageHandler) - } - }, [messageHandler]) - - return ( - - ) -} diff --git a/libs/auth/react/src/lib/auth/MockedAuthProvider.spec.tsx b/libs/auth/react/src/lib/auth/MockedAuthProvider.spec.tsx deleted file mode 100644 index c90e77a95cb0..000000000000 --- a/libs/auth/react/src/lib/auth/MockedAuthProvider.spec.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from 'react' -import { render } from '@testing-library/react' -import '@testing-library/jest-dom' -import { useAuth } from './AuthContext' -import { MockedAuthProvider } from './MockedAuthProvider' - -const TestConsumer = () => { - const { userInfo } = useAuth() - return User: {userInfo ? userInfo.profile.name : 'Not logged in'} -} - -describe('MockedAuthProvider', () => { - it('defaults to unauthenticated', () => { - const { getByText } = render( - - - , - ) - expect(getByText(/^User:/)).toHaveTextContent('User: Not logged in') - }) - - it('provides good default values for user', () => { - const { getByText } = render( - - - , - ) - expect(getByText(/^User:/)).toHaveTextContent('User: Mock') - }) - - it('supports overriding user details', () => { - const { getByText } = render( - - - , - ) - expect(getByText(/^User:/)).toHaveTextContent('User: Peter') - }) -}) diff --git a/libs/auth/react/src/lib/auth/MockedAuthProvider.tsx b/libs/auth/react/src/lib/auth/MockedAuthProvider.tsx deleted file mode 100644 index 4da0e2de69e7..000000000000 --- a/libs/auth/react/src/lib/auth/MockedAuthProvider.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, { FC } from 'react' -import { AuthContext, defaultAuthContext } from './AuthContext' -import { createMockUser, MockUser } from '../createMockUser' - -interface MockedAuthenticatorProps { - user?: MockUser - signOut?: () => void - switchUser?: (nationalId?: string) => void -} - -export const MockedAuthProvider: FC< - React.PropsWithChildren -> = ({ children, signOut, switchUser, user }) => { - const userInfo = user ? createMockUser(user) : null - return ( - - {children} - - ) -} diff --git a/libs/auth/react/src/lib/authLink.ts b/libs/auth/react/src/lib/authLink.ts deleted file mode 100644 index c097a99ec488..000000000000 --- a/libs/auth/react/src/lib/authLink.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { setContext } from '@apollo/client/link/context' -import { getAccessToken } from './getAccessToken' - -export const authLink = setContext(async (_, { headers }) => { - const token = await getAccessToken() - return { - headers: { - ...headers, - authorization: token ? `Bearer ${token}` : '', - }, - } -}) diff --git a/libs/auth/react/src/lib/createMockUser.ts b/libs/auth/react/src/lib/createMockUser.ts deleted file mode 100644 index d7b7c87f62eb..000000000000 --- a/libs/auth/react/src/lib/createMockUser.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { User } from '@island.is/shared/types' - -export interface MockUser extends Partial> { - profile?: Partial -} - -export const createMockUser = (user?: MockUser) => - ({ - profile: { - name: 'Mock', - locale: 'is', - nationalId: '0000000000', - ...user?.profile, - }, - expired: false, - expires_in: 9999, - scopes: [], - ...user, - } as User) diff --git a/libs/auth/react/src/lib/getAccessToken.spec.ts b/libs/auth/react/src/lib/getAccessToken.spec.ts deleted file mode 100644 index 56a67f0d76e2..000000000000 --- a/libs/auth/react/src/lib/getAccessToken.spec.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { - getAccessToken, - MIN_EXPIRY_FOR_PRE_SIGNIN, - MIN_EXPIRY_FOR_RE_SIGNIN, -} from './getAccessToken' -import { getUserManager } from './userManager' - -jest.mock('./userManager') -const mockedGetUserManager = getUserManager as jest.Mock - -type MinimalUser = { - access_token: string - expires_in: number -} - -type MinimalUserManager = { - getUser: jest.Mock> - signinSilent: jest.Mock> -} - -describe('getAccessToken', () => { - let userManager: MinimalUserManager - - beforeEach(() => { - userManager = { - getUser: jest.fn(), - signinSilent: jest.fn(), - } - mockedGetUserManager.mockReturnValue(userManager) - }) - - it('gets access token from user', async () => { - // Arrange - const tokenValue = 'Test token' - userManager.getUser.mockResolvedValue({ - access_token: tokenValue, - expires_in: 1000, - }) - - // Act - const token = await getAccessToken() - - // Assert - expect(userManager.signinSilent).not.toHaveBeenCalled() - expect(token).toBe(tokenValue) - }) - - it("returns null if there's no user", async () => { - // Arrange - userManager.getUser.mockResolvedValue(null) - - // Act - const token = await getAccessToken() - - // Assert - expect(token).toBe(null) - }) - - it("renews token if it's expired", async () => { - // Arrange - const tokenValue = 'Test token' - userManager.getUser.mockResolvedValue({ - access_token: 'unused', - expires_in: -100, - }) - userManager.signinSilent.mockResolvedValue({ - access_token: tokenValue, - expires_in: 1000, - }) - - // Act - const token = await getAccessToken() - - // Assert - expect(userManager.signinSilent).toHaveBeenCalled() - expect(token).toBe(tokenValue) - }) - - it("renews token if it's almost expired", async () => { - // Arrange - const tokenValue = 'Test token' - userManager.getUser.mockResolvedValue({ - access_token: 'unused', - expires_in: MIN_EXPIRY_FOR_RE_SIGNIN - 1, - }) - userManager.signinSilent.mockResolvedValue({ - access_token: tokenValue, - expires_in: 1000, - }) - - // Act - const token = await getAccessToken() - - // Assert - expect(userManager.signinSilent).toHaveBeenCalled() - expect(token).toBe(tokenValue) - }) - - it("returns null if there's no user when silently renewing", async () => { - // Arrange - userManager.getUser.mockResolvedValue({ - access_token: 'unused', - expires_in: -100, - }) - userManager.signinSilent.mockResolvedValue(null) - - // Act - const token = await getAccessToken() - - // Assert - expect(token).toBe(null) - }) - - it("prefetches token if it's about to expire but returns old token", async () => { - // Arrange - const tokenValue = 'Test token' - userManager.getUser.mockResolvedValue({ - access_token: tokenValue, - expires_in: MIN_EXPIRY_FOR_PRE_SIGNIN - 1, - }) - userManager.signinSilent.mockResolvedValue(null) - - // Act - const token = await getAccessToken() - - // Assert - expect(userManager.signinSilent).toHaveBeenCalled() - expect(token).toBe(tokenValue) - }) - - it('only silently renews once at a time', async () => { - // Arrange - const tokenValue = 'Test token' - userManager.getUser.mockResolvedValue({ - access_token: 'unused', - expires_in: -100, - }) - userManager.signinSilent - .mockResolvedValueOnce({ access_token: tokenValue, expires_in: 1000 }) - .mockResolvedValueOnce(null) - - // Act - const promise1 = getAccessToken() - const promise2 = getAccessToken() - const [result1, result2] = await Promise.all([promise1, promise2]) - - // Assert - expect(userManager.signinSilent).toHaveBeenCalledTimes(1) - expect(result1).toBe(tokenValue) - expect(result2).toBe(tokenValue) - }) -}) diff --git a/libs/auth/react/src/lib/getAccessToken.ts b/libs/auth/react/src/lib/getAccessToken.ts deleted file mode 100644 index 6a5e15e8d7ff..000000000000 --- a/libs/auth/react/src/lib/getAccessToken.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { getUserManager } from './userManager' -import { tinyMemoize } from './utils/tinyMemoize' - -// Should not use access tokens that are expired or just about to expire. -// Get a new one just in case. -export const MIN_EXPIRY_FOR_RE_SIGNIN = 10 - -// If we call an API a couple of minutes before an access token, we assume -// the user is still around and actively using the client. Then we prefetch a -// new access token before it expires, to avoid request delays. -export const MIN_EXPIRY_FOR_PRE_SIGNIN = 120 - -const fetchNewToken = tinyMemoize(() => { - // This can fail if IDP session is finished. This is ignored here and dealt - // with in Authenticator. - return getUserManager() - .signinSilent() - .catch(() => null) -}) - -export const getAccessToken = async () => { - let user = await getUserManager().getUser() - if (!user) { - return null - } - - // Token is either expired, or just about to expire. We should get a new - // token either way. - if (!user.expires_in || user.expires_in < MIN_EXPIRY_FOR_RE_SIGNIN) { - user = await fetchNewToken() - if (!user) { - return null - } - } - - // We're still active but the token will expire soon. We'll make sure to - // prefetch a new token for later. - else if (!user.expires_in || user.expires_in < MIN_EXPIRY_FOR_PRE_SIGNIN) { - // Intentionally not awaited. We don't want to delay the current request. - fetchNewToken() - } - - return user.access_token -} diff --git a/libs/auth/react/src/lib/userManager.ts b/libs/auth/react/src/lib/userManager.ts deleted file mode 100644 index 1c685778018d..000000000000 --- a/libs/auth/react/src/lib/userManager.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { User, UserManager } from 'oidc-client-ts' - -import { AuthSettings, mergeAuthSettings } from './AuthSettings' -import { toStringScope } from './utils/toStringScope' -import { createMockUser, MockUser } from './createMockUser' - -let authSettings: AuthSettings | null = null -let userManager: UserManager | null = null - -export const getUserManager = (): UserManager => { - if (userManager === null) { - throw new Error('Tried to access user manager before calling configure') - } - return userManager -} - -export const getAuthSettings = (): AuthSettings => { - if (authSettings === null) { - throw new Error('Tried to access auth settings before calling configure') - } - return authSettings -} - -export const configure = (settings: AuthSettings) => { - authSettings = mergeAuthSettings(settings) - - userManager = new UserManager({ - ...authSettings, - scope: toStringScope(settings.scope), - redirect_uri: `${authSettings.baseUrl}${authSettings.redirectPath}`, - }) - - return userManager -} - -export const configureMock = (user?: MockUser) => { - authSettings = mergeAuthSettings({ - client_id: 'test-client', - authority: 'https://innskra.island.is', - }) - - const userInfo = createMockUser(user) - const empty = () => { - /* intentionally empty */ - } - - userManager = { - getUser(): Promise { - return Promise.resolve(userInfo) - }, - signinSilent(): Promise { - return Promise.resolve(userInfo) - }, - signinRedirect: empty, - events: { - addUserSignedOut: empty, - addUserLoaded: empty, - removeUserLoaded: empty, - removeUserSignedOut: empty, - }, - } as unknown as UserManager -} - -export { User, UserManager } diff --git a/libs/auth/react/src/lib/utils/tinyMemoize.spec.ts b/libs/auth/react/src/lib/utils/tinyMemoize.spec.ts deleted file mode 100644 index e427cfc099a6..000000000000 --- a/libs/auth/react/src/lib/utils/tinyMemoize.spec.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { tinyMemoize } from './tinyMemoize' - -type ResolveFn = (value?: number | PromiseLike) => void - -describe('tinyMemoize', () => { - let resolve: ResolveFn - let slowFunction: jest.Mock - let memoizedFunction: () => Promise - - beforeEach(() => { - // arrange - slowFunction = jest.fn( - () => - new Promise((res) => { - resolve = res - }), - ) - memoizedFunction = tinyMemoize(slowFunction) - }) - - it("memoizes calls to an async function while it's running", async () => { - // arrange - const value = Math.random() - - // act - const promise1 = memoizedFunction() - const promise2 = memoizedFunction() - resolve(value) - const results = await Promise.all([promise1, promise2]) - - // assert - expect(slowFunction).toHaveBeenCalledTimes(1) - expect(promise1).toBe(promise2) - expect(results).toStrictEqual([value, value]) - }) - - it('stops memoizing as soon as the async function finishes', async () => { - // arrange - const value1 = Math.random() - const value2 = value1 + 0.1 - - // act - const promise1 = memoizedFunction() - resolve(value1) - const result1 = await promise1 - - const promise2 = memoizedFunction() - resolve(value2) - const result2 = await promise2 - - // assert - expect(slowFunction).toHaveBeenCalledTimes(2) - expect(promise1).not.toBe(promise2) - expect(result1).toEqual(value1) - expect(result2).toEqual(value2) - }) -}) diff --git a/libs/auth/react/src/lib/utils/tinyMemoize.ts b/libs/auth/react/src/lib/utils/tinyMemoize.ts deleted file mode 100644 index 90bf5e40bf20..000000000000 --- a/libs/auth/react/src/lib/utils/tinyMemoize.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Makes sure there's only one call to an async function active at any time. - */ -export const tinyMemoize = (asyncFunction: () => Promise) => { - let lastPromise: Promise | null - return () => { - if (!lastPromise) { - lastPromise = asyncFunction() - lastPromise.finally(() => { - lastPromise = null - }) - return lastPromise - } - - return lastPromise - } -} diff --git a/libs/auth/react/src/lib/utils/toStringScope.spec.ts b/libs/auth/react/src/lib/utils/toStringScope.spec.ts deleted file mode 100644 index 928bfca6f130..000000000000 --- a/libs/auth/react/src/lib/utils/toStringScope.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { toStringScope } from './toStringScope' - -describe('toStringScope', () => { - it('should return a string for an array of scope', () => { - expect(toStringScope(['scope1', 'scope2'])).toBe('scope1 scope2') - }) - - it('should return an empty string when no scope is defined', () => { - expect(toStringScope()).toBe('') - expect(toStringScope([])).toBe('') - }) -}) diff --git a/libs/auth/react/src/lib/utils/toStringScope.ts b/libs/auth/react/src/lib/utils/toStringScope.ts deleted file mode 100644 index 50730e02b5b4..000000000000 --- a/libs/auth/react/src/lib/utils/toStringScope.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const toStringScope = (array?: string[]): string => - (array ?? []).join(' ') diff --git a/libs/auth/react/test/setup.ts b/libs/auth/react/test/setup.ts deleted file mode 100644 index d6e27b1da068..000000000000 --- a/libs/auth/react/test/setup.ts +++ /dev/null @@ -1 +0,0 @@ -import '@vanilla-extract/css/disableRuntimeStyles' diff --git a/libs/auth/react/tsconfig.json b/libs/auth/react/tsconfig.json deleted file mode 100644 index 45d3d172fde4..000000000000 --- a/libs/auth/react/tsconfig.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "jsx": "react-jsx", - "allowJs": true - }, - "files": [], - "include": [], - "references": [ - { - "path": "./tsconfig.lib.json" - }, - { - "path": "./tsconfig.spec.json" - } - ] -} diff --git a/libs/auth/react/tsconfig.lib.json b/libs/auth/react/tsconfig.lib.json deleted file mode 100644 index 7da3e9d24116..000000000000 --- a/libs/auth/react/tsconfig.lib.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "../../../dist/out-tsc", - "types": ["node"] - }, - "files": [ - "../../../node_modules/@nx/react/typings/cssmodule.d.ts", - "../../../node_modules/@nx/react/typings/image.d.ts" - ], - "exclude": [ - "**/*.spec.ts", - "**/*.test.ts", - "**/*.spec.tsx", - "**/*.test.tsx", - "jest.config.ts" - ], - "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] -} diff --git a/libs/auth/react/tsconfig.spec.json b/libs/auth/react/tsconfig.spec.json deleted file mode 100644 index e1535ba9d07c..000000000000 --- a/libs/auth/react/tsconfig.spec.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "outDir": "../../../dist/out-tsc", - "module": "commonjs", - "types": ["jest", "node"] - }, - "include": [ - "**/*.spec.ts", - "**/*.test.ts", - "**/*.spec.tsx", - "**/*.test.tsx", - "**/*.spec.js", - "**/*.test.js", - "**/*.spec.jsx", - "**/*.test.jsx", - "**/*.d.ts", - "jest.config.ts" - ] -} diff --git a/libs/island-ui/storybook/config/main.ts b/libs/island-ui/storybook/config/main.ts index 3c9e5ccccacd..051bf8baecdd 100644 --- a/libs/island-ui/storybook/config/main.ts +++ b/libs/island-ui/storybook/config/main.ts @@ -75,7 +75,6 @@ const config: StorybookConfig = { '@island.is/application/ui-components': rootDir( '../../../application/ui-components/src', ), - '@island.is/auth/react': rootDir('../../../auth/react/src'), '@island.is/shared/constants': rootDir('../../../shared/constants/src'), '@island.is/shared/form-fields': rootDir( '../../../shared/form-fields/src', diff --git a/tsconfig.base.json b/tsconfig.base.json index 030262426294..7be0f1c28845 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -574,7 +574,6 @@ "@island.is/application/utils": ["libs/application/utils/src/index.ts"], "@island.is/auth-api-lib": ["libs/auth-api-lib/src/index.ts"], "@island.is/auth-nest-tools": ["libs/auth-nest-tools/src/index.ts"], - "@island.is/auth/react": ["libs/auth/react/src/index.ts"], "@island.is/auth/scopes": ["libs/auth/scopes/src/index.ts"], "@island.is/auth/shared": ["libs/auth/shared/src/index.ts"], "@island.is/cache": ["libs/cache/src/index.ts"], From 04230408fe8b13da28c3628b3b6bb10177fd4ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3r=C3=B0ur=20H?= Date: Wed, 11 Dec 2024 01:10:05 +0000 Subject: [PATCH 57/57] fix(ads): Update code for explicit and manual use (#17193) * Update code for explicit and manual use * Fix tests to handle logging warn and info --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../app/modules/discount/discount.service.ts | 21 +++++++++++++++ .../test/unit/discount.service.spec.ts | 4 ++- .../src/app/modules/user/user.service.ts | 26 ++++++++++++++++--- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/apps/air-discount-scheme/backend/src/app/modules/discount/discount.service.ts b/apps/air-discount-scheme/backend/src/app/modules/discount/discount.service.ts index 993a82013473..9ccc135e8265 100644 --- a/apps/air-discount-scheme/backend/src/app/modules/discount/discount.service.ts +++ b/apps/air-discount-scheme/backend/src/app/modules/discount/discount.service.ts @@ -17,6 +17,8 @@ import { UserService } from '../user/user.service' import type { User as AuthUser } from '@island.is/auth-nest-tools' import { ExplicitFlight } from './dto/ExplicitFlight.dto' import { CreateSuperExplicitDiscountCodeParams } from './dto' +import type { Logger } from '@island.is/logging' +import { LOGGER_PROVIDER } from '@island.is/logging' interface CachedDiscount { user: User @@ -47,6 +49,8 @@ export class DiscountService { @InjectModel(ExplicitCode) private explicitModel: typeof ExplicitCode, + @Inject(LOGGER_PROVIDER) + private readonly logger: Logger, private readonly userService: UserService, ) {} @@ -197,13 +201,19 @@ export class DiscountService { unConnectedFlights: Flight[] | ExplicitFlight[], isExplicit: boolean, flightLegs = 1, + isManual?: boolean, ): Promise | null> { const user = await this.userService.getUserInfoByNationalId( nationalId, auth, + isExplicit, + isManual, ) if (!user) { + this.logger.warn('User by national id not found for explicit discount.', { + category: 'ads-backend', + }) return null } // overwrite credit since validation may return 0 depending on what the problem is @@ -212,10 +222,19 @@ export class DiscountService { user.fund.credit = 2 //making sure we can get flight from and to user.fund.total = 2 } else { + this.logger.warn( + `User fund used requirements not met: ${user.fund.used}.`, + { + category: 'ads-backend', + }, + ) return null } } if (user.fund.credit === 0 && user.fund.total !== undefined) { + this.logger.warn(`User fund no credit, has total: ${user.fund.total}.`, { + category: 'ads-backend', + }) return null } @@ -477,6 +496,7 @@ export class DiscountService { ], } + const isManual = true const discount = await this.createExplicitDiscountCode( auth, body.nationalId, @@ -487,6 +507,7 @@ export class DiscountService { body.needsConnectionFlight ? [flight] : [], isExplicit, 1, + isManual, ) if (!discount) { throw new Error(`Could not create explicit discount`) diff --git a/apps/air-discount-scheme/backend/src/app/modules/discount/test/unit/discount.service.spec.ts b/apps/air-discount-scheme/backend/src/app/modules/discount/test/unit/discount.service.spec.ts index a5b95b9ca4b4..46c9da2b5f8f 100644 --- a/apps/air-discount-scheme/backend/src/app/modules/discount/test/unit/discount.service.spec.ts +++ b/apps/air-discount-scheme/backend/src/app/modules/discount/test/unit/discount.service.spec.ts @@ -64,7 +64,9 @@ describe('DiscountService', () => { { provide: LOGGER_PROVIDER, useClass: jest.fn(() => ({ - error: () => ({}), + error: jest.fn(), + info: jest.fn(), + warn: jest.fn(), })), }, { diff --git a/apps/air-discount-scheme/backend/src/app/modules/user/user.service.ts b/apps/air-discount-scheme/backend/src/app/modules/user/user.service.ts index 0761e5880450..034f80c06872 100644 --- a/apps/air-discount-scheme/backend/src/app/modules/user/user.service.ts +++ b/apps/air-discount-scheme/backend/src/app/modules/user/user.service.ts @@ -17,6 +17,12 @@ const ONE_WEEK = 604800 // seconds const CACHE_KEY = 'userService' const MAX_AGE_LIMIT = 18 +const DEFAULT_FUND: Fund = { + credit: 2, + total: 2, + used: 0, +} + interface CustodianCache { custodians: Array } @@ -42,6 +48,7 @@ export class UserService { private async getFund( user: NationalRegistryUser, auth?: AuthUser, + isManual?: boolean, ): Promise { const { used, unused, total } = await this.flightService.countThisYearsFlightLegsByNationalId( @@ -49,7 +56,7 @@ export class UserService { ) let meetsADSRequirements = false - if (this.flightService.isADSPostalCode(user.postalcode)) { + if (this.flightService.isADSPostalCode(user.postalcode) || isManual) { meetsADSRequirements = true } else if (info(user.nationalId).age < MAX_AGE_LIMIT) { // NationalId is a minor and doesn't live in ADS postal codes. @@ -95,20 +102,33 @@ export class UserService { nationalId: string, model: new (user: NationalRegistryUser, fund: Fund) => T, auth: AuthUser, + isExplicit?: boolean, + isManual?: boolean, ): Promise { const user = await this.nationalRegistryService.getUser(nationalId, auth) if (!user) { return null } - const fund = await this.getFund(user, auth) + if (isExplicit) { + return new model(user, DEFAULT_FUND) + } + const fund = await this.getFund(user, auth, isManual) return new model(user, fund) } async getUserInfoByNationalId( nationalId: string, auth: AuthUser, + isExplicit?: boolean, + isManual?: boolean, ): Promise { - return this.getUserByNationalId(nationalId, User, auth) + return this.getUserByNationalId( + nationalId, + User, + auth, + isExplicit, + isManual, + ) } async getMultipleUsersByNationalIdArray(
${member.above}
${member.below}
${above}
${below}
${member.name}${afterMarkup}
${name}${afterMarkup}
${additionalSignature}
${hyphenate( + additionalSignature, + )}