diff --git a/apps/contentful-apps/components/EntryListSearch.tsx b/apps/contentful-apps/components/EntryListSearch.tsx
new file mode 100644
index 000000000000..90efc883c5ab
--- /dev/null
+++ b/apps/contentful-apps/components/EntryListSearch.tsx
@@ -0,0 +1,155 @@
+import { useRef, useState } from 'react'
+import { useDebounce } from 'react-use'
+import {
+ CollectionProp,
+ EntryProps,
+ KeyValueMap,
+ QueryOptions,
+} from 'contentful-management'
+import {
+ Box,
+ EntryCard,
+ Pagination,
+ Spinner,
+ Stack,
+ Text,
+ TextInput,
+} from '@contentful/f36-components'
+import { useCMA, useSDK } from '@contentful/react-apps-toolkit'
+
+import { DEFAULT_LOCALE } from '../constants'
+
+const SEARCH_DEBOUNCE_TIME_IN_MS = 300
+
+interface EntryListSearchProps {
+ contentTypeId: string
+ contentTypeLabel: string
+ contentTypeTitleField: string
+ itemsPerPage?: number
+ onEntryClick?: (entry: EntryProps) => void
+ query?: QueryOptions
+}
+
+export const EntryListSearch = ({
+ itemsPerPage = 4,
+ contentTypeId,
+ contentTypeLabel,
+ contentTypeTitleField,
+ onEntryClick,
+ query,
+}: EntryListSearchProps) => {
+ const sdk = useSDK()
+ const cma = useCMA()
+
+ const searchValueRef = useRef('')
+ const [searchValue, setSearchValue] = useState('')
+ const [listItemResponse, setListItemResponse] =
+ useState>>()
+ const [loading, setLoading] = useState(false)
+ const [page, setPage] = useState(0)
+ const pageRef = useRef(0)
+ const [counter, setCounter] = useState(0)
+
+ const skip = itemsPerPage * page
+
+ useDebounce(
+ async () => {
+ setLoading(true)
+ try {
+ const response = await cma.entry.getMany({
+ query: {
+ content_type: contentTypeId,
+ limit: itemsPerPage,
+ skip,
+ [`fields.${contentTypeTitleField}[match]`]: searchValue,
+ 'sys.archivedAt[exists]': false,
+ ...query,
+ },
+ })
+ if (
+ searchValueRef.current === searchValue &&
+ pageRef.current === page
+ ) {
+ setListItemResponse(response)
+ }
+ } finally {
+ setLoading(false)
+ }
+ },
+ SEARCH_DEBOUNCE_TIME_IN_MS,
+ [page, searchValue, counter],
+ )
+
+ return (
+
+ {
+ searchValueRef.current = ev.target.value
+ setSearchValue(ev.target.value)
+ setPage(0)
+ pageRef.current = 0
+ }}
+ />
+
+
+
+
+
+ {listItemResponse?.items?.length > 0 && (
+ <>
+
+
+ {listItemResponse.items.map((item) => (
+ {
+ if (onEntryClick) {
+ onEntryClick(item)
+ return
+ }
+
+ sdk.navigator
+ .openEntry(item.sys.id, {
+ slideIn: { waitForClose: true },
+ })
+ .then(() => {
+ setCounter((c) => c + 1)
+ })
+ }}
+ />
+ ))}
+
+
+ {
+ pageRef.current = newPage
+ setPage(newPage)
+ }}
+ />
+ >
+ )}
+
+ {listItemResponse?.items?.length === 0 && (
+
+ No entry was found
+
+ )}
+
+ )
+}
diff --git a/apps/contentful-apps/components/editors/lists/GenericListEditor/GenericListEditor.tsx b/apps/contentful-apps/components/editors/lists/GenericListEditor/GenericListEditor.tsx
index 0740aff1d59d..2eac9ec947d2 100644
--- a/apps/contentful-apps/components/editors/lists/GenericListEditor/GenericListEditor.tsx
+++ b/apps/contentful-apps/components/editors/lists/GenericListEditor/GenericListEditor.tsx
@@ -1,27 +1,14 @@
-import { useMemo, useRef, useState } from 'react'
-import { useDebounce } from 'react-use'
-import { CollectionProp, EntryProps, KeyValueMap } from 'contentful-management'
+import { useMemo, useState } from 'react'
import dynamic from 'next/dynamic'
import { EditorExtensionSDK } from '@contentful/app-sdk'
-import {
- Box,
- Button,
- EntryCard,
- FormControl,
- Pagination,
- Spinner,
- Stack,
- Text,
- TextInput,
-} from '@contentful/f36-components'
+import { Box, Button, FormControl } from '@contentful/f36-components'
import { PlusIcon } from '@contentful/f36-icons'
import { useCMA, useSDK } from '@contentful/react-apps-toolkit'
+import { EntryListSearch } from '../../../../components/EntryListSearch'
import { mapLocalesToFieldApis } from '../../utils'
-const SEARCH_DEBOUNCE_TIME_IN_MS = 300
const LIST_ITEM_CONTENT_TYPE_ID = 'genericListItem'
-const LIST_ITEMS_PER_PAGE = 4
const createLocaleToFieldMapping = (sdk: EditorExtensionSDK) => {
return {
@@ -58,51 +45,11 @@ export const GenericListEditor = () => {
const sdk = useSDK()
const cma = useCMA()
- const searchValueRef = useRef('')
- const [searchValue, setSearchValue] = useState('')
- const [listItemResponse, setListItemResponse] =
- useState>>()
- const [loading, setLoading] = useState(false)
- const [page, setPage] = useState(0)
- const pageRef = useRef(0)
-
/** Counter that's simply used to refresh the list when an item gets created */
- const [counter, setCounter] = useState(0)
-
- const skip = LIST_ITEMS_PER_PAGE * page
+ const [_, setCounter] = useState(0)
const defaultLocale = sdk.locales.default
- useDebounce(
- async () => {
- setLoading(true)
- try {
- const response = await cma.entry.getMany({
- environmentId: sdk.ids.environment,
- spaceId: sdk.ids.space,
- query: {
- content_type: LIST_ITEM_CONTENT_TYPE_ID,
- limit: LIST_ITEMS_PER_PAGE,
- skip,
- 'fields.internalTitle[match]': searchValue,
- 'fields.genericList.sys.id': sdk.entry.getSys().id,
- 'sys.archivedAt[exists]': false,
- },
- })
- if (
- searchValueRef.current === searchValue &&
- pageRef.current === page
- ) {
- setListItemResponse(response)
- }
- } finally {
- setLoading(false)
- }
- },
- SEARCH_DEBOUNCE_TIME_IN_MS,
- [page, searchValue, counter],
- )
-
const createListItem = async () => {
const cardIntro = {}
@@ -189,70 +136,14 @@ export const GenericListEditor = () => {
}>Add item
-
- {
- searchValueRef.current = ev.target.value
- setSearchValue(ev.target.value)
- setPage(0)
- pageRef.current = 0
- }}
- />
-
-
-
-
-
- {listItemResponse?.items?.length > 0 && (
- <>
-
-
- {listItemResponse.items.map((item) => (
- {
- sdk.navigator
- .openEntry(item.sys.id, {
- slideIn: { waitForClose: true },
- })
- .then(() => {
- setCounter((c) => c + 1)
- })
- }}
- />
- ))}
-
-
- {
- pageRef.current = newPage
- setPage(newPage)
- }}
- />
- >
- )}
-
- {listItemResponse?.items?.length === 0 && (
-
- No item was found
-
- )}
-
+
)
}
diff --git a/apps/contentful-apps/pages/fields/generic-tag-group-items-field.tsx b/apps/contentful-apps/pages/fields/generic-tag-group-items-field.tsx
index 6a577e389b9d..bdd4a8f9a83e 100644
--- a/apps/contentful-apps/pages/fields/generic-tag-group-items-field.tsx
+++ b/apps/contentful-apps/pages/fields/generic-tag-group-items-field.tsx
@@ -1,37 +1,17 @@
-import { useEffect, useRef, useState } from 'react'
-import { useDebounce } from 'react-use'
+import { useEffect, useState } from 'react'
import type { FieldExtensionSDK } from '@contentful/app-sdk'
-import {
- Box,
- Button,
- EntryCard,
- Pagination,
- Spinner,
- Stack,
- Text,
- TextInput,
-} from '@contentful/f36-components'
+import { Box, Button } from '@contentful/f36-components'
import { PlusIcon } from '@contentful/f36-icons'
import { useCMA, useSDK } from '@contentful/react-apps-toolkit'
-const LIST_ITEMS_PER_PAGE = 4
-const SEARCH_DEBOUNCE_TIME_IN_MS = 300
+import { EntryListSearch } from '../../components/EntryListSearch'
const GenericTagGroupItemsField = () => {
- const [page, setPage] = useState(0)
- const pageRef = useRef(0)
- const [searchValue, setSearchValue] = useState('')
- const searchValueRef = useRef('')
- const [listItemResponse, setListItemResponse] = useState(null)
- const [isLoading, setIsLoading] = useState(false)
-
- const [counter, setCounter] = useState(0)
+ const [_, setCounter] = useState(0)
const sdk = useSDK()
const cma = useCMA()
- const skip = LIST_ITEMS_PER_PAGE * page
-
const createGenericTag = async () => {
const tag = await cma.entry.create(
{
@@ -62,35 +42,6 @@ const GenericTagGroupItemsField = () => {
})
}
- useDebounce(
- async () => {
- setIsLoading(true)
- try {
- const response = await cma.entry.getMany({
- query: {
- content_type: 'genericTag',
- limit: LIST_ITEMS_PER_PAGE,
- skip,
- 'fields.internalTitle[match]': searchValue,
- 'fields.genericTagGroup.sys.id': sdk.entry.getSys().id,
- 'sys.archivedAt[exists]': false,
- },
- })
-
- if (
- searchValueRef.current === searchValue &&
- pageRef.current === page
- ) {
- setListItemResponse(response)
- }
- } finally {
- setIsLoading(false)
- }
- },
- SEARCH_DEBOUNCE_TIME_IN_MS,
- [page, searchValue, counter],
- )
-
useEffect(() => {
sdk.window.startAutoResizer()
return () => {
@@ -108,71 +59,14 @@ const GenericTagGroupItemsField = () => {
}>Create tag
-
- {
- searchValueRef.current = ev.target.value
- setSearchValue(ev.target.value)
- setPage(0)
- pageRef.current = 0
- }}
- />
-
-
-
-
-
- {listItemResponse?.items?.length > 0 && (
- <>
-
-
- {listItemResponse.items.map((item) => (
- {
- sdk.navigator
- .openEntry(item.sys.id, {
- slideIn: { waitForClose: true },
- })
- .then(() => {
- setCounter((c) => c + 1)
- })
- }}
- />
- ))}
-
-
- {
- pageRef.current = newPage
- setPage(newPage)
- }}
- />
- >
- )}
-
- {listItemResponse?.items?.length === 0 && (
-
- No item was found
-
- )}
-
+
)
}
diff --git a/apps/judicial-system/api/src/app/modules/case/interceptors/case.transformer.spec.ts b/apps/judicial-system/api/src/app/modules/case/interceptors/case.transformer.spec.ts
index 5c76205ba8d7..f73423c0f6d4 100644
--- a/apps/judicial-system/api/src/app/modules/case/interceptors/case.transformer.spec.ts
+++ b/apps/judicial-system/api/src/app/modules/case/interceptors/case.transformer.spec.ts
@@ -731,6 +731,54 @@ describe('getIndictmentInfo', () => {
indictmentVerdictAppealDeadlineExpired: false,
})
})
+
+ it('should return correct indictment info when the indictment ruling decision is FINE and the appeal deadline is not expired', () => {
+ const rulingDate = new Date().toISOString()
+ const defendants = [
+ {
+ serviceRequirement: ServiceRequirement.NOT_REQUIRED,
+ verdictViewDate: undefined,
+ } as Defendant,
+ ]
+
+ const indictmentInfo = getIndictmentInfo(
+ CaseIndictmentRulingDecision.FINE,
+ rulingDate,
+ defendants,
+ )
+
+ expect(indictmentInfo).toEqual({
+ indictmentAppealDeadline: new Date(
+ new Date(rulingDate).setDate(new Date(rulingDate).getDate() + 3),
+ ).toISOString(),
+ indictmentVerdictViewedByAll: true,
+ indictmentVerdictAppealDeadlineExpired: false,
+ })
+ })
+
+ it('should return correct indictment info when the indictment ruling decision is FINE and the appeal deadline is expired', () => {
+ const rulingDate = '2024-05-26T21:51:19.156Z'
+ const defendants = [
+ {
+ serviceRequirement: ServiceRequirement.NOT_REQUIRED,
+ verdictViewDate: undefined,
+ } as Defendant,
+ ]
+
+ const indictmentInfo = getIndictmentInfo(
+ CaseIndictmentRulingDecision.FINE,
+ rulingDate,
+ defendants,
+ )
+
+ expect(indictmentInfo).toEqual({
+ indictmentAppealDeadline: new Date(
+ new Date(rulingDate).setDate(new Date(rulingDate).getDate() + 3),
+ ).toISOString(),
+ indictmentVerdictViewedByAll: true,
+ indictmentVerdictAppealDeadlineExpired: true,
+ })
+ })
})
describe('getIndictmentDefendantsInfo', () => {
diff --git a/apps/judicial-system/api/src/app/modules/case/interceptors/case.transformer.ts b/apps/judicial-system/api/src/app/modules/case/interceptors/case.transformer.ts
index bf8139e14014..f01909d284f1 100644
--- a/apps/judicial-system/api/src/app/modules/case/interceptors/case.transformer.ts
+++ b/apps/judicial-system/api/src/app/modules/case/interceptors/case.transformer.ts
@@ -2,6 +2,7 @@ import {
CaseAppealDecision,
CaseIndictmentRulingDecision,
EventType,
+ FINE_APPEAL_WINDOW_DAYS,
getIndictmentVerdictAppealDeadlineStatus,
getStatementDeadline,
isRequestCase,
@@ -151,6 +152,8 @@ export const getIndictmentInfo = (
eventLog?: EventLog[],
): IndictmentInfo => {
const indictmentInfo: IndictmentInfo = {}
+ const isFine = rulingDecision === CaseIndictmentRulingDecision.FINE
+ const isRuling = rulingDecision === CaseIndictmentRulingDecision.RULING
if (!rulingDate) {
return indictmentInfo
@@ -158,13 +161,16 @@ export const getIndictmentInfo = (
const theRulingDate = new Date(rulingDate)
indictmentInfo.indictmentAppealDeadline = new Date(
- theRulingDate.getTime() + getDays(VERDICT_APPEAL_WINDOW_DAYS),
+ theRulingDate.getTime() +
+ getDays(isFine ? FINE_APPEAL_WINDOW_DAYS : VERDICT_APPEAL_WINDOW_DAYS),
).toISOString()
const verdictInfo = defendants?.map<[boolean, Date | undefined]>(
(defendant) => [
- rulingDecision === CaseIndictmentRulingDecision.RULING,
- defendant.serviceRequirement === ServiceRequirement.NOT_REQUIRED
+ isRuling || isFine,
+ isFine
+ ? theRulingDate
+ : defendant.serviceRequirement === ServiceRequirement.NOT_REQUIRED
? new Date()
: defendant.verdictViewDate
? new Date(defendant.verdictViewDate)
@@ -173,7 +179,7 @@ export const getIndictmentInfo = (
)
const [indictmentVerdictViewedByAll, indictmentVerdictAppealDeadlineExpired] =
- getIndictmentVerdictAppealDeadlineStatus(verdictInfo)
+ getIndictmentVerdictAppealDeadlineStatus(verdictInfo, isFine)
indictmentInfo.indictmentVerdictViewedByAll = indictmentVerdictViewedByAll
indictmentInfo.indictmentVerdictAppealDeadlineExpired =
indictmentVerdictAppealDeadlineExpired
@@ -189,6 +195,8 @@ export const getIndictmentDefendantsInfo = (theCase: Case) => {
return theCase.defendants?.map((defendant) => {
const serviceRequired =
defendant.serviceRequirement === ServiceRequirement.REQUIRED
+ const isFine =
+ theCase.indictmentRulingDecision === CaseIndictmentRulingDecision.FINE
const { verdictViewDate } = defendant
@@ -196,7 +204,10 @@ export const getIndictmentDefendantsInfo = (theCase: Case) => {
const verdictAppealDeadline = baseDate
? new Date(
- new Date(baseDate).getTime() + getDays(VERDICT_APPEAL_WINDOW_DAYS),
+ new Date(baseDate).getTime() +
+ getDays(
+ isFine ? FINE_APPEAL_WINDOW_DAYS : VERDICT_APPEAL_WINDOW_DAYS,
+ ),
).toISOString()
: undefined
diff --git a/apps/judicial-system/backend/src/app/modules/case/filters/case.filter.ts b/apps/judicial-system/backend/src/app/modules/case/filters/case.filter.ts
index e4200b4dd5ee..36dbfdc8671c 100644
--- a/apps/judicial-system/backend/src/app/modules/case/filters/case.filter.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/filters/case.filter.ts
@@ -277,7 +277,9 @@ const canPrisonAdminUserAccessCase = (
// Check case indictment ruling decision access
if (
- theCase.indictmentRulingDecision !== CaseIndictmentRulingDecision.RULING
+ theCase.indictmentRulingDecision !==
+ CaseIndictmentRulingDecision.RULING &&
+ theCase.indictmentRulingDecision !== CaseIndictmentRulingDecision.FINE
) {
return false
}
diff --git a/apps/judicial-system/backend/src/app/modules/case/filters/cases.filter.ts b/apps/judicial-system/backend/src/app/modules/case/filters/cases.filter.ts
index 34ae100c239d..bc5f2f9f3a5f 100644
--- a/apps/judicial-system/backend/src/app/modules/case/filters/cases.filter.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/filters/cases.filter.ts
@@ -211,7 +211,12 @@ const getPrisonAdminUserCasesQueryFilter = (): WhereOptions => {
{
type: indictmentCases,
state: CaseState.COMPLETED,
- indictment_ruling_decision: CaseIndictmentRulingDecision.RULING,
+ indictment_ruling_decision: {
+ [Op.or]: [
+ CaseIndictmentRulingDecision.RULING,
+ CaseIndictmentRulingDecision.FINE,
+ ],
+ },
indictment_review_decision: IndictmentCaseReviewDecision.ACCEPT,
id: {
[Op.in]: Sequelize.literal(`
diff --git a/apps/judicial-system/backend/src/app/modules/case/filters/test/cases.filter.spec.ts b/apps/judicial-system/backend/src/app/modules/case/filters/test/cases.filter.spec.ts
index cb6a69ebaf7c..4d6027e5d5b4 100644
--- a/apps/judicial-system/backend/src/app/modules/case/filters/test/cases.filter.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/filters/test/cases.filter.spec.ts
@@ -388,7 +388,12 @@ describe('getCasesQueryFilter', () => {
{
type: indictmentCases,
state: CaseState.COMPLETED,
- indictment_ruling_decision: CaseIndictmentRulingDecision.RULING,
+ indictment_ruling_decision: {
+ [Op.or]: [
+ CaseIndictmentRulingDecision.RULING,
+ CaseIndictmentRulingDecision.FINE,
+ ],
+ },
indictment_review_decision: IndictmentCaseReviewDecision.ACCEPT,
id: {
[Op.in]: Sequelize.literal(`
diff --git a/apps/judicial-system/backend/src/app/modules/case/filters/test/prisonAdminUserFilter.spec.ts b/apps/judicial-system/backend/src/app/modules/case/filters/test/prisonAdminUserFilter.spec.ts
index 570de65e3f81..9d147a4ca169 100644
--- a/apps/judicial-system/backend/src/app/modules/case/filters/test/prisonAdminUserFilter.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/case/filters/test/prisonAdminUserFilter.spec.ts
@@ -132,6 +132,7 @@ describe.each(prisonSystemRoles)('prison admin user %s', (role) => {
(state) => {
const accessibleCaseIndictmentRulingDecisions = [
CaseIndictmentRulingDecision.RULING,
+ CaseIndictmentRulingDecision.FINE,
]
describe.each(
diff --git a/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.spec.tsx b/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.spec.tsx
new file mode 100644
index 000000000000..48ac69307bbc
--- /dev/null
+++ b/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.spec.tsx
@@ -0,0 +1,80 @@
+import faker from 'faker'
+import { render, screen } from '@testing-library/react'
+
+import {
+ CaseIndictmentRulingDecision,
+ CaseType,
+ Defendant,
+} from '../../graphql/schema'
+import { mockCase } from '../../utils/mocks'
+import {
+ ApolloProviderWrapper,
+ FormContextWrapper,
+ IntlProviderWrapper,
+} from '../../utils/testHelpers'
+import BlueBoxWithDate from './BlueBoxWithDate'
+
+jest.mock('next/router', () => ({
+ useRouter() {
+ return {
+ pathname: '',
+ query: {
+ id: 'test_id',
+ },
+ }
+ },
+}))
+
+describe('BlueBoxWithDate', () => {
+ const name = faker.name.firstName()
+ const rulingDate = new Date().toISOString()
+
+ const mockDefendant: Defendant = {
+ name,
+ id: faker.datatype.uuid(),
+ }
+
+ it('renders correctly when ruling decision is FINE', () => {
+ render(
+
+
+
+
+
+
+ ,
+ )
+
+ expect(screen.getByText('Viðurlagaákvörðun')).toBeInTheDocument()
+ expect(screen.getByText(name)).toBeInTheDocument()
+ })
+
+ it('renders correctly when ruling decision is RULING', () => {
+ render(
+
+
+
+
+
+
+ ,
+ )
+
+ expect(screen.getByText('Birting dóms')).toBeInTheDocument()
+ expect(screen.getByText(name)).toBeInTheDocument()
+ })
+})
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 a68972df8229..a3f57cbebd4c 100644
--- a/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.strings.ts
+++ b/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.strings.ts
@@ -70,4 +70,17 @@ export const strings = defineMessages({
description:
'Notaður sem texti í valmynd fyrir aðgerðina að senda mál til fullnustu',
},
+ indictmentRulingDecisionFine: {
+ id: 'judicial.system.core:blue_box_with_date.indictment_ruling_decision_fine',
+ defaultMessage: 'Viðurlagaákvörðun',
+ description:
+ 'Notaður sem titill í svæði þar sem kærufrestur viðurlagaákvörðunar er tekinn fram',
+ },
+ fineAppealDeadline: {
+ id: 'judicial.system.core:blue_box_with_date.fine_appeal_deadline',
+ defaultMessage:
+ 'Kærufrestur Ríkissaksóknara {appealDeadlineIsInThePast, select, true {var} other {er}} til {appealDeadline}',
+ description:
+ 'Notaður sem titill í svæði þar sem kærufrestur viðurlagaákvörðunar er tekinn fram',
+ },
})
diff --git a/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.tsx b/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.tsx
index 6a404390e8c9..50ecd41a0c44 100644
--- a/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.tsx
+++ b/apps/judicial-system/web/src/components/BlueBoxWithIcon/BlueBoxWithDate.tsx
@@ -18,8 +18,8 @@ import { VERDICT_APPEAL_WINDOW_DAYS } from '@island.is/judicial-system/types'
import { errors } from '@island.is/judicial-system-web/messages'
import {
+ CaseIndictmentRulingDecision,
Defendant,
- IndictmentCaseReviewDecision,
ServiceRequirement,
} from '../../graphql/schema'
import { formatDateForServer, useDefendants } from '../../utils/hooks'
@@ -32,12 +32,11 @@ import * as styles from './BlueBoxWithIcon.css'
interface Props {
defendant: Defendant
- indictmentReviewDecision?: IndictmentCaseReviewDecision | null
icon?: IconMapIcon
}
const BlueBoxWithDate: FC = (props) => {
- const { defendant, indictmentReviewDecision, icon } = props
+ const { defendant, icon } = props
const { formatMessage } = useIntl()
const [dates, setDates] = useState<{
verdictViewDate?: Date
@@ -52,6 +51,9 @@ const BlueBoxWithDate: FC = (props) => {
const { workingCase, setWorkingCase } = useContext(FormContext)
const router = useRouter()
+ const isFine =
+ workingCase.indictmentRulingDecision === CaseIndictmentRulingDecision.FINE
+
const serviceRequired =
defendant.serviceRequirement === ServiceRequirement.REQUIRED
@@ -133,34 +135,45 @@ const BlueBoxWithDate: FC = (props) => {
const textItems = useMemo(() => {
const texts = []
- if (serviceRequired) {
+ if (isFine) {
texts.push(
- formatMessage(strings.defendantVerdictViewedDate, {
- date: formatDate(dates.verdictViewDate ?? defendant.verdictViewDate),
+ formatMessage(strings.fineAppealDeadline, {
+ appealDeadlineIsInThePast: defendant.isVerdictAppealDeadlineExpired,
+ appealDeadline: formatDate(defendant.verdictAppealDeadline),
}),
)
- }
-
- texts.push(
- formatMessage(appealExpirationInfo.message, {
- appealExpirationDate: appealExpirationInfo.date,
- }),
- )
+ } else {
+ if (serviceRequired) {
+ texts.push(
+ formatMessage(strings.defendantVerdictViewedDate, {
+ date: formatDate(
+ dates.verdictViewDate ?? defendant.verdictViewDate,
+ ),
+ }),
+ )
+ }
- if (defendant.verdictAppealDate) {
texts.push(
- formatMessage(strings.defendantAppealDate, {
- date: formatDate(defendant.verdictAppealDate),
+ formatMessage(appealExpirationInfo.message, {
+ appealExpirationDate: appealExpirationInfo.date,
}),
)
- }
- if (defendant.sentToPrisonAdminDate && defendant.isSentToPrisonAdmin) {
- texts.push(
- formatMessage(strings.sendToPrisonAdminDate, {
- date: formatDate(defendant.sentToPrisonAdminDate),
- }),
- )
+ if (defendant.verdictAppealDate) {
+ texts.push(
+ formatMessage(strings.defendantAppealDate, {
+ date: formatDate(defendant.verdictAppealDate),
+ }),
+ )
+ }
+
+ if (defendant.sentToPrisonAdminDate && defendant.isSentToPrisonAdmin) {
+ texts.push(
+ formatMessage(strings.sendToPrisonAdminDate, {
+ date: formatDate(defendant.sentToPrisonAdminDate),
+ }),
+ )
+ }
}
return texts
@@ -169,10 +182,13 @@ const BlueBoxWithDate: FC = (props) => {
appealExpirationInfo.message,
dates.verdictViewDate,
defendant.isSentToPrisonAdmin,
+ defendant.isVerdictAppealDeadlineExpired,
defendant.sentToPrisonAdminDate,
defendant.verdictAppealDate,
+ defendant.verdictAppealDeadline,
defendant.verdictViewDate,
formatMessage,
+ isFine,
serviceRequired,
])
@@ -205,7 +221,9 @@ const BlueBoxWithDate: FC = (props) => {
@@ -217,7 +235,7 @@ const BlueBoxWithDate: FC = (props) => {
- {(!serviceRequired || defendant.verdictViewDate) &&
+ {(!serviceRequired || defendant.verdictViewDate || isFine) &&
textItems.map((text, index) => (
= (props) => {
{defendant.verdictAppealDate ||
- defendant.isVerdictAppealDeadlineExpired ? null : !serviceRequired ||
- defendant.verdictViewDate ? (
+ defendant.isVerdictAppealDeadlineExpired ||
+ isFine ? null : !serviceRequired || defendant.verdictViewDate ? (
= (props) => {
variant="text"
onClick={handleSendToPrisonAdmin}
size="small"
- disabled={!indictmentReviewDecision || !defendant.verdictViewDate}
+ disabled={
+ !workingCase.indictmentReviewDecision ||
+ (!isFine && !defendant.verdictViewDate)
+ }
>
{formatMessage(strings.sendToPrisonAdmin)}
diff --git a/apps/judicial-system/web/src/components/InfoCard/useInfoCardItems.strings.ts b/apps/judicial-system/web/src/components/InfoCard/useInfoCardItems.strings.ts
index 57689f0fa174..1cff30e2a52b 100644
--- a/apps/judicial-system/web/src/components/InfoCard/useInfoCardItems.strings.ts
+++ b/apps/judicial-system/web/src/components/InfoCard/useInfoCardItems.strings.ts
@@ -28,14 +28,16 @@ export const strings = defineMessages({
description: 'Notaður sem titill á "ákvörðun" hluta af yfirliti ákæru.',
},
reviewTagAppealed: {
- id: 'judicial.system.core:info_card_indictment.review_tag_appealed_v1',
- defaultMessage: 'Áfrýja dómi',
+ id: 'judicial.system.core:info_card_indictment.review_tag_appealed_v3',
+ defaultMessage:
+ '{isFine, select, true {Kæra viðurlagaákvörðun} other {Áfrýja dómi}}',
description:
'Notað sem texti á tagg fyrir "Áfrýjun" tillögu í yfirliti ákæru.',
},
reviewTagAccepted: {
- id: 'judicial.system.core:info_card_indictment.review_tag_completed_v1',
- defaultMessage: 'Una dómi',
+ id: 'judicial.system.core:info_card_indictment.review_tag_completed_v2',
+ defaultMessage:
+ 'Una {isFine, select, true {viðurlagaákvörðun} other {dómi}}',
description: 'Notað sem texti á tagg fyrir "Una" tillögu í yfirliti ákæru.',
},
indictmentReviewedDateTitle: {
diff --git a/apps/judicial-system/web/src/components/InfoCard/useInfoCardItems.tsx b/apps/judicial-system/web/src/components/InfoCard/useInfoCardItems.tsx
index 1a0c7ccf70c3..a2112beb8c66 100644
--- a/apps/judicial-system/web/src/components/InfoCard/useInfoCardItems.tsx
+++ b/apps/judicial-system/web/src/components/InfoCard/useInfoCardItems.tsx
@@ -15,6 +15,7 @@ import { core } from '@island.is/judicial-system-web/messages'
import { requestCourtDate } from '@island.is/judicial-system-web/messages'
import {
Case,
+ CaseIndictmentRulingDecision,
CaseType,
IndictmentCaseReviewDecision,
} from '@island.is/judicial-system-web/src/graphql/schema'
@@ -279,6 +280,11 @@ const useInfoCardItems = () => {
IndictmentCaseReviewDecision.ACCEPT
? strings.reviewTagAccepted
: strings.reviewTagAppealed,
+ {
+ isFine:
+ workingCase.indictmentRulingDecision ===
+ CaseIndictmentRulingDecision.FINE,
+ },
),
],
}
diff --git a/apps/judicial-system/web/src/components/index.ts b/apps/judicial-system/web/src/components/index.ts
index adf54284e0c6..5dc0a2ff8240 100644
--- a/apps/judicial-system/web/src/components/index.ts
+++ b/apps/judicial-system/web/src/components/index.ts
@@ -1,6 +1,7 @@
export { CourtCaseInfo, ProsecutorCaseInfo } from './CaseInfo/CaseInfo'
export { default as AccordionListItem } from './AccordionListItem/AccordionListItem'
export { default as BlueBox } from './BlueBox/BlueBox'
+export { default as BlueBoxWithDate } from './BlueBoxWithIcon/BlueBoxWithDate'
export { default as CaseDates } from './CaseDates/CaseDates'
export { default as CaseFile } from './CaseFile/CaseFile'
export { default as CaseFileList } from './CaseFileList/CaseFileList'
diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.strings.ts b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.strings.ts
index bda2b5b7b75f..3418534bf794 100644
--- a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.strings.ts
+++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.strings.ts
@@ -22,9 +22,9 @@ export const strings = defineMessages({
description: 'Notaður sem titill á yfirliti ákæru.',
},
reviewerSubtitle: {
- id: 'judicial.system.core:public_prosecutor.indictments.overview.reviewer_subtitle',
+ id: 'judicial.system.core:public_prosecutor.indictments.overview.reviewer_subtitle_v2',
defaultMessage:
- 'Frestur til að áfrýja dómi rennur út {indictmentAppealDeadline}',
+ 'Frestur til að {isFine, select, true {kæra viðurlagaákvörðun} other {áfrýja dómi}} {appealDeadlineIsInThePast, select, true {rann} other {rennur}} út {indictmentAppealDeadline}',
description: 'Notaður sem undirtitill á yfirliti ákæru.',
},
reviewerAssignedModalTitle: {
diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx
index 6ac0b378751e..36abf477db77 100644
--- a/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx
+++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Indictments/Overview/Overview.tsx
@@ -8,6 +8,7 @@ import { formatDate } from '@island.is/judicial-system/formatters'
import { core, titles } from '@island.is/judicial-system-web/messages'
import {
BlueBox,
+ BlueBoxWithDate,
CourtCaseInfo,
FormContentContainer,
FormContext,
@@ -23,8 +24,8 @@ import {
// useIndictmentsLawsBroken, NOTE: Temporarily hidden while list of laws broken is not complete
UserContext,
} from '@island.is/judicial-system-web/src/components'
-import BlueBoxWithDate from '@island.is/judicial-system-web/src/components/BlueBoxWithIcon/BlueBoxWithDate'
import { useProsecutorSelectionUsersQuery } from '@island.is/judicial-system-web/src/components/ProsecutorSelection/prosecutorSelectionUsers.generated'
+import { CaseIndictmentRulingDecision } from '@island.is/judicial-system-web/src/graphql/schema'
import { useCase } from '@island.is/judicial-system-web/src/utils/hooks'
import { strings } from './Overview.strings'
@@ -102,11 +103,7 @@ export const Overview = () => {
{workingCase.defendants?.map((defendant) => (
-
+
))}
@@ -130,9 +127,14 @@ export const Overview = () => {
description={
{fm(strings.reviewerSubtitle, {
+ isFine:
+ workingCase.indictmentRulingDecision ===
+ CaseIndictmentRulingDecision.FINE,
indictmentAppealDeadline: formatDate(
workingCase.indictmentAppealDeadline,
),
+ appealDeadlineIsInThePast:
+ workingCase.indictmentVerdictAppealDeadlineExpired,
})}
}
diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesReviewed.strings.ts b/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesReviewed.strings.ts
index 85cb3bb24b15..130b6c5b87da 100644
--- a/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesReviewed.strings.ts
+++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesReviewed.strings.ts
@@ -18,6 +18,12 @@ export const strings = defineMessages({
description:
'Notað sem texti á tagg fyrir "Unun" tillögu í yfirlesin mál málalista',
},
+ reviewTagFineAppealed: {
+ id: 'judicial.system.core:public_prosecutor.tables.cases_reviewed.review_tag_fine_appealed',
+ defaultMessage: 'Kært',
+ description:
+ 'Notað sem texti á tagg fyrir "Kært" tillögu í yfirlesin mál málalista',
+ },
infoContainerMessage: {
id: 'judicial.system.core:public_prosecutor.tables.cases_reviewed.info_container_message',
defaultMessage: 'Engin yfirlesin mál.',
diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesReviewed.tsx b/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesReviewed.tsx
index e6a4b98ab6c2..3462a41a096e 100644
--- a/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesReviewed.tsx
+++ b/apps/judicial-system/web/src/routes/PublicProsecutor/Tables/CasesReviewed.tsx
@@ -4,6 +4,7 @@ import { AnimatePresence } from 'framer-motion'
import { Tag, Text } from '@island.is/island-ui/core'
import { capitalize } from '@island.is/judicial-system/formatters'
+import { CaseIndictmentRulingDecision } from '@island.is/judicial-system/types'
import { core, tables } from '@island.is/judicial-system-web/messages'
import { SectionHeading } from '@island.is/judicial-system-web/src/components'
import { useContextMenu } from '@island.is/judicial-system-web/src/components/ContextMenu/ContextMenu'
@@ -31,13 +32,19 @@ const CasesReviewed: FC = ({ loading, cases }) => {
const { formatMessage } = useIntl()
const { openCaseInNewTabMenuItem } = useContextMenu()
- const decisionMapping = {
- [IndictmentCaseReviewDecision.ACCEPT]: formatMessage(
- strings.reviewTagAccepted,
- ),
- [IndictmentCaseReviewDecision.APPEAL]: formatMessage(
- strings.reviewTagAppealed,
- ),
+ const indictmentReviewDecisionMapping = (
+ reviewDecision: IndictmentCaseReviewDecision,
+ isFine: boolean,
+ ) => {
+ if (reviewDecision === IndictmentCaseReviewDecision.ACCEPT) {
+ return formatMessage(strings.reviewTagAccepted)
+ } else if (reviewDecision === IndictmentCaseReviewDecision.APPEAL) {
+ return formatMessage(
+ isFine ? strings.reviewTagFineAppealed : strings.reviewTagAppealed,
+ )
+ } else {
+ return null
+ }
}
const getVerdictViewTag = (row: CaseListEntry) => {
@@ -49,7 +56,9 @@ const CasesReviewed: FC = ({ loading, cases }) => {
row.defendants.some((defendant) => defendant.isSentToPrisonAdmin),
)
- if (someDefendantIsSentToPrisonAdmin) {
+ if (row.indictmentRulingDecision === CaseIndictmentRulingDecision.FINE) {
+ return null
+ } else if (someDefendantIsSentToPrisonAdmin) {
variant = 'red'
message = strings.tagVerdictViewSentToPrisonAdmin
} else if (!row.indictmentVerdictViewedByAll) {
@@ -112,7 +121,11 @@ const CasesReviewed: FC = ({ loading, cases }) => {
cell: (row) => (
{row.indictmentReviewDecision &&
- decisionMapping[row.indictmentReviewDecision]}
+ indictmentReviewDecisionMapping(
+ row.indictmentReviewDecision,
+ row.indictmentRulingDecision ===
+ CaseIndictmentRulingDecision.FINE,
+ )}
),
},
diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/components/ReviewDecision/ReviewDecision.css.ts b/apps/judicial-system/web/src/routes/PublicProsecutor/components/ReviewDecision/ReviewDecision.css.ts
index f3b85cfcbefe..f0211d02ca05 100644
--- a/apps/judicial-system/web/src/routes/PublicProsecutor/components/ReviewDecision/ReviewDecision.css.ts
+++ b/apps/judicial-system/web/src/routes/PublicProsecutor/components/ReviewDecision/ReviewDecision.css.ts
@@ -4,7 +4,12 @@ import { theme } from '@island.is/island-ui/theme'
export const gridRow = style({
display: 'grid',
- gridTemplateColumns: '1.6fr 1fr',
+ gridTemplateColumns: '1fr auto',
gridGap: theme.spacing[1],
- marginBottom: theme.spacing[1],
+
+ '@media': {
+ [`screen and (max-width: ${theme.breakpoints.lg}px)`]: {
+ gridTemplateColumns: '1fr',
+ },
+ },
})
diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/components/ReviewDecision/ReviewDecision.strings.ts b/apps/judicial-system/web/src/routes/PublicProsecutor/components/ReviewDecision/ReviewDecision.strings.ts
index 796a7372f889..c9250aa3fd48 100644
--- a/apps/judicial-system/web/src/routes/PublicProsecutor/components/ReviewDecision/ReviewDecision.strings.ts
+++ b/apps/judicial-system/web/src/routes/PublicProsecutor/components/ReviewDecision/ReviewDecision.strings.ts
@@ -2,14 +2,14 @@ import { defineMessages } from 'react-intl'
export const strings = defineMessages({
title: {
- id: 'judicial.system.core:public_prosecutor.indictments.review_decision.title',
- defaultMessage: 'Ákvörðun um áfrýjun',
+ id: 'judicial.system.core:public_prosecutor.indictments.review_decision.title_v1',
+ defaultMessage: 'Ákvörðun um {isFine, select, true {kæru} other {áfrýjun}}',
description: 'Notaður sem titill á ákvörðum um áfrýjun boxi fyrir ákæru.',
},
subtitle: {
- id: 'judicial.system.core:public_prosecutor.indictments.review_decision.subtitle',
+ id: 'judicial.system.core:public_prosecutor.indictments.review_decision.subtitle_v1',
defaultMessage:
- 'Frestur til að áfrýja dómi rennur út {indictmentAppealDeadline}',
+ 'Frestur til að {isFine, select, true {kæra viðurlagaákvörðun} other {áfrýja dómi}} {appealDeadlineIsInThePast, select, true {rann} other {rennur}} út {indictmentAppealDeadline}',
description:
'Notaður sem undirtitill á ákvörðum um áfrýjun boxi fyrir ákæru.',
},
@@ -24,6 +24,18 @@ export const strings = defineMessages({
defaultMessage: 'Una héraðsdómi',
description: 'Notaður sem texti fyrir "Una héraðsdómi" radio takka.',
},
+ appealFineToCourtOfAppeals: {
+ id: 'judicial.system.core:public_prosecutor.indictments.review_decision.appeal_fine_to_court_of_appeals',
+ defaultMessage: 'Kæra viðurlagaákvörðun til Landsréttar',
+ description:
+ 'Notaður sem texti fyrir "Kæra viðurlagaákvörðun til Landsréttar" radio takka.',
+ },
+ acceptFineDecision: {
+ id: 'judicial.system.core:public_prosecutor.indictments.review_decision.accept_fine_decision',
+ defaultMessage: 'Una viðurlagaákvörðun',
+ description:
+ 'Notaður sem texti fyrir "Kæra viðurlagaákvörðun" radio takka.',
+ },
reviewModalTitle: {
id: 'judicial.system.core:indictments_review.title',
defaultMessage: 'Staðfesta ákvörðun',
@@ -31,11 +43,16 @@ export const strings = defineMessages({
},
reviewModalText: {
id: 'judicial.system.core:indictments_review.modal_text',
-
defaultMessage:
'Ertu viss um að þú viljir {reviewerDecision, select, ACCEPT {una héraðsdómi} APPEAL {áfrýja héraðsdómi til Landsréttar} other {halda áfram}}?',
description: 'Notaður sem texti í yfirlitsglugga um yfirlit ákæru.',
},
+ reviewModalTextFine: {
+ id: 'judicial.system.core:indictments_review.modal_text_fine',
+ defaultMessage:
+ 'Ertu viss um að þú viljir {reviewerDecision, select, ACCEPT {una viðurlagaákvörðun} APPEAL {kæra viðurlagaákvörðun til Landsréttar} other {halda áfram}}?',
+ description: 'Notaður sem texti í yfirlitsglugga um yfirlit ákæru.',
+ },
reviewModalPrimaryButtonText: {
id: 'judicial.system.core:indictments_review.modal_primary_button_text',
defaultMessage: 'Staðfesta',
diff --git a/apps/judicial-system/web/src/routes/PublicProsecutor/components/ReviewDecision/ReviewDecision.tsx b/apps/judicial-system/web/src/routes/PublicProsecutor/components/ReviewDecision/ReviewDecision.tsx
index ff25436bea93..119153159b66 100644
--- a/apps/judicial-system/web/src/routes/PublicProsecutor/components/ReviewDecision/ReviewDecision.tsx
+++ b/apps/judicial-system/web/src/routes/PublicProsecutor/components/ReviewDecision/ReviewDecision.tsx
@@ -21,25 +21,28 @@ import * as styles from './ReviewDecision.css'
interface Props {
caseId: string
indictmentAppealDeadline?: string
+ indictmentAppealDeadlineIsInThePast?: boolean
modalVisible?: boolean
setModalVisible: Dispatch>
+ isFine: boolean
onSelect?: () => void
}
export const ReviewDecision: FC = (props) => {
- const { user } = useContext(UserContext)
- const router = useRouter()
- const { formatMessage: fm } = useIntl()
- const { updateCase } = useCase()
-
const {
caseId,
indictmentAppealDeadline,
+ indictmentAppealDeadlineIsInThePast,
modalVisible,
setModalVisible,
+ isFine,
onSelect,
} = props
+ const { user } = useContext(UserContext)
+ const router = useRouter()
+ const { formatMessage: fm } = useIntl()
+ const { updateCase } = useCase()
const [indictmentReviewDecision, setIndictmentReviewDecision] = useState<
IndictmentCaseReviewDecision | undefined
>(undefined)
@@ -58,11 +61,15 @@ export const ReviewDecision: FC = (props) => {
const options = [
{
- label: fm(strings.appealToCourtOfAppeals),
+ label: fm(
+ isFine
+ ? strings.appealFineToCourtOfAppeals
+ : strings.appealToCourtOfAppeals,
+ ),
value: IndictmentCaseReviewDecision.APPEAL,
},
{
- label: fm(strings.acceptDecision),
+ label: fm(isFine ? strings.acceptFineDecision : strings.acceptDecision),
value: IndictmentCaseReviewDecision.ACCEPT,
},
]
@@ -74,11 +81,13 @@ export const ReviewDecision: FC = (props) => {
return (
{fm(strings.subtitle, {
+ isFine,
indictmentAppealDeadline: formatDate(indictmentAppealDeadline),
+ appealDeadlineIsInThePast: indictmentAppealDeadlineIsInThePast,
})}
}
@@ -107,9 +116,12 @@ export const ReviewDecision: FC = (props) => {
{modalVisible && (
setModalVisible(false)}
diff --git a/apps/judicial-system/web/src/routes/Shared/IndictmentOverview/IndictmentOverview.tsx b/apps/judicial-system/web/src/routes/Shared/IndictmentOverview/IndictmentOverview.tsx
index 83b140c09ed3..d5d2a465c780 100644
--- a/apps/judicial-system/web/src/routes/Shared/IndictmentOverview/IndictmentOverview.tsx
+++ b/apps/judicial-system/web/src/routes/Shared/IndictmentOverview/IndictmentOverview.tsx
@@ -285,8 +285,15 @@ const IndictmentOverview: FC = () => {
indictmentAppealDeadline={
workingCase.indictmentAppealDeadline ?? ''
}
+ indictmentAppealDeadlineIsInThePast={
+ workingCase.indictmentVerdictAppealDeadlineExpired ?? false
+ }
modalVisible={modalVisible}
setModalVisible={setModalVisible}
+ isFine={
+ workingCase.indictmentRulingDecision ===
+ CaseIndictmentRulingDecision.FINE
+ }
onSelect={() => setIsReviewDecisionSelected(true)}
/>
)}
diff --git a/apps/services/auth/admin-api/infra/auth-admin-api.ts b/apps/services/auth/admin-api/infra/auth-admin-api.ts
index 0fc25a62d5d4..cf773f0dbd94 100644
--- a/apps/services/auth/admin-api/infra/auth-admin-api.ts
+++ b/apps/services/auth/admin-api/infra/auth-admin-api.ts
@@ -4,7 +4,12 @@ import {
service,
ServiceBuilder,
} from '../../../../../infra/src/dsl/dsl'
-import { Base, Client, RskProcuring } from '../../../../../infra/src/dsl/xroad'
+import {
+ Base,
+ Client,
+ NationalRegistryAuthB2C,
+ RskProcuring,
+} from '../../../../../infra/src/dsl/xroad'
const REDIS_NODE_CONFIG = {
dev: json([
@@ -84,8 +89,10 @@ export const serviceSetup = (): ServiceBuilder<'services-auth-admin-api'> => {
'/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET',
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME',
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD',
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET:
+ '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET',
})
- .xroad(Base, Client, RskProcuring)
+ .xroad(Base, Client, RskProcuring, NationalRegistryAuthB2C)
.ingress({
primary: {
host: {
diff --git a/apps/services/auth/admin-api/src/app/app.module.ts b/apps/services/auth/admin-api/src/app/app.module.ts
index dc56c6fe0343..5f947672c654 100644
--- a/apps/services/auth/admin-api/src/app/app.module.ts
+++ b/apps/services/auth/admin-api/src/app/app.module.ts
@@ -9,8 +9,10 @@ import {
import { AuthModule } from '@island.is/auth-nest-tools'
import { RskRelationshipsClientConfig } from '@island.is/clients-rsk-relationships'
import { NationalRegistryClientConfig } from '@island.is/clients/national-registry-v2'
+import { NationalRegistryV3ClientConfig } from '@island.is/clients/national-registry-v3'
import { CompanyRegistryConfig } from '@island.is/clients/rsk/company-registry'
import { SyslumennClientConfig } from '@island.is/clients/syslumenn'
+import { ZendeskServiceConfig } from '@island.is/clients/zendesk'
import { AuditModule } from '@island.is/nest/audit'
import { IdsClientConfig, XRoadConfig } from '@island.is/nest/config'
import { FeatureFlagConfig } from '@island.is/nest/feature-flags'
@@ -31,7 +33,6 @@ import { ProvidersModule } from './v2/providers/providers.module'
import { ScopesModule } from './v2/scopes/scopes.module'
import { ClientSecretsModule } from './v2/secrets/client-secrets.module'
import { TenantsModule } from './v2/tenants/tenants.module'
-import { ZendeskServiceConfig } from '@island.is/clients/zendesk'
@Module({
imports: [
@@ -61,6 +62,7 @@ import { ZendeskServiceConfig } from '@island.is/clients/zendesk'
DelegationConfig,
RskRelationshipsClientConfig,
NationalRegistryClientConfig,
+ NationalRegistryV3ClientConfig,
CompanyRegistryConfig,
FeatureFlagConfig,
XRoadConfig,
diff --git a/apps/services/auth/delegation-api/src/app/app.module.ts b/apps/services/auth/delegation-api/src/app/app.module.ts
index ac10af9d95f1..71b347827378 100644
--- a/apps/services/auth/delegation-api/src/app/app.module.ts
+++ b/apps/services/auth/delegation-api/src/app/app.module.ts
@@ -9,6 +9,7 @@ import {
import { AuthModule } from '@island.is/auth-nest-tools'
import { RskRelationshipsClientConfig } from '@island.is/clients-rsk-relationships'
import { NationalRegistryClientConfig } from '@island.is/clients/national-registry-v2'
+import { NationalRegistryV3ClientConfig } from '@island.is/clients/national-registry-v3'
import { CompanyRegistryConfig } from '@island.is/clients/rsk/company-registry'
import { SyslumennClientConfig } from '@island.is/clients/syslumenn'
import { AuditModule } from '@island.is/nest/audit'
@@ -48,6 +49,7 @@ import { ScopesModule } from './scopes/scopes.module'
FeatureFlagConfig,
IdsClientConfig,
NationalRegistryClientConfig,
+ NationalRegistryV3ClientConfig,
RskRelationshipsClientConfig,
CompanyRegistryConfig,
XRoadConfig,
diff --git a/apps/services/auth/delegation-api/src/app/delegations/test/me-delegations.access-incoming.spec.ts b/apps/services/auth/delegation-api/src/app/delegations/test/me-delegations.access-incoming.spec.ts
index 6f5c526d1188..377a7771410b 100644
--- a/apps/services/auth/delegation-api/src/app/delegations/test/me-delegations.access-incoming.spec.ts
+++ b/apps/services/auth/delegation-api/src/app/delegations/test/me-delegations.access-incoming.spec.ts
@@ -8,9 +8,11 @@ import {
Delegation,
DelegationScope,
DelegationsIndexService,
+ NationalRegistryV3FeatureService,
} from '@island.is/auth-api-lib'
import { RskRelationshipsClient } from '@island.is/clients-rsk-relationships'
import { NationalRegistryClientService } from '@island.is/clients/national-registry-v2'
+import { NationalRegistryV3ClientService } from '@island.is/clients/national-registry-v3'
import { CompanyRegistryClientService } from '@island.is/clients/rsk/company-registry'
import {
expectMatchingDelegations,
@@ -23,189 +25,219 @@ import { accessIncomingTestCases } from '../../../../test/access-incoming-test-c
import { setupWithAuth } from '../../../../test/setup'
import { filterExpectedDelegations } from './utils'
+const fromName: string = faker.name.findName()
+
describe('MeDelegationsController', () => {
- describe.each(Object.keys(accessIncomingTestCases))(
- 'Incoming Access with test case: %s',
- (caseName) => {
- const testCase = accessIncomingTestCases[caseName]
- let app: TestApp
- let server: request.SuperTest
- let factory: FixtureFactory
- let delegations: Delegation[] = []
- const fromName = faker.name.findName()
- let delegationIndexService: DelegationsIndexService
-
- beforeAll(async () => {
- // Arrange
- app = await setupWithAuth({
- user: testCase.user,
- customScopeRules: testCase.customScopeRules,
- })
- server = request(app.getHttpServer())
- delegationIndexService = app.get(DelegationsIndexService)
- const rskRelationshipsClientService = app.get(RskRelationshipsClient)
- const nationalRegistryClientService = app.get(
- NationalRegistryClientService,
- )
- const companyRegistryClientService = app.get(
- CompanyRegistryClientService,
- )
- jest
- .spyOn(nationalRegistryClientService, 'getCustodyChildren')
- .mockImplementation(async () => [])
- jest
- .spyOn(nationalRegistryClientService, 'getIndividual')
- .mockImplementation(async (nationalId: string) =>
- createNationalRegistryUser({
- nationalId,
- name: fromName,
- }),
- )
- jest
- .spyOn(rskRelationshipsClientService, 'getIndividualRelationships')
- .mockImplementation(async () => null)
- jest
- .spyOn(companyRegistryClientService, 'getCompany')
- .mockImplementation(async () => null)
- jest
- .spyOn(delegationIndexService, 'indexDelegations')
- .mockImplementation()
- jest
- .spyOn(delegationIndexService, 'indexCustomDelegations')
- .mockImplementation()
-
- factory = new FixtureFactory(app)
- await Promise.all(
- testCase.domains.map((domain) => factory.createDomain(domain)),
- )
- await Promise.all(
- (testCase.accessTo ?? []).map((scope) =>
- factory.createApiScopeUserAccess({
- nationalId: testCase.user.nationalId,
- scope,
- }),
- ),
- )
- })
-
- beforeEach(async () => {
- if (delegations) {
- await Delegation.destroy({
- where: {
- id: { [Op.in]: delegations.map((delegation) => delegation.id) },
- },
+ describe.each([false, true])(
+ 'national registry v3 featureflag: %s',
+ (featureFlag) => {
+ describe.each(Object.keys(accessIncomingTestCases))(
+ 'Incoming Access with test case: %s',
+ (caseName) => {
+ const testCase = accessIncomingTestCases[caseName]
+ let app: TestApp
+ let server: request.SuperTest
+ let factory: FixtureFactory
+ let delegations: Delegation[] = []
+ let delegationIndexService: DelegationsIndexService
+
+ beforeAll(async () => {
+ // Arrange
+ app = await setupWithAuth({
+ user: testCase.user,
+ customScopeRules: testCase.customScopeRules,
+ })
+ server = request(app.getHttpServer())
+ delegationIndexService = app.get(DelegationsIndexService)
+ const rskRelationshipsClientService = app.get(
+ RskRelationshipsClient,
+ )
+ const nationalRegistryClientService = app.get(
+ NationalRegistryClientService,
+ )
+ const nationalRegistryV3ClientService = app.get(
+ NationalRegistryV3ClientService,
+ )
+ const companyRegistryClientService = app.get(
+ CompanyRegistryClientService,
+ )
+ const nationalRegistryV3FeatureService = app.get(
+ NationalRegistryV3FeatureService,
+ )
+ jest
+ .spyOn(nationalRegistryClientService, 'getCustodyChildren')
+ .mockImplementation(async () => [])
+ jest
+ .spyOn(nationalRegistryClientService, 'getIndividual')
+ .mockImplementation(async (nationalId: string) =>
+ createNationalRegistryUser({
+ nationalId,
+ name: fromName,
+ }),
+ )
+ jest
+ .spyOn(nationalRegistryV3ClientService, 'getAllDataIndividual')
+ .mockImplementation(async (nationalId: string) => {
+ return { kennitala: nationalId, nafn: fromName }
+ })
+ jest
+ .spyOn(
+ rskRelationshipsClientService,
+ 'getIndividualRelationships',
+ )
+ .mockImplementation(async () => null)
+ jest
+ .spyOn(companyRegistryClientService, 'getCompany')
+ .mockImplementation(async () => null)
+ jest
+ .spyOn(delegationIndexService, 'indexDelegations')
+ .mockImplementation()
+ jest
+ .spyOn(delegationIndexService, 'indexCustomDelegations')
+ .mockImplementation()
+ jest
+ .spyOn(nationalRegistryV3FeatureService, 'getValue')
+ .mockImplementation(async () => featureFlag)
+ factory = new FixtureFactory(app)
+ await Promise.all(
+ testCase.domains.map((domain) => factory.createDomain(domain)),
+ )
+ await Promise.all(
+ (testCase.accessTo ?? []).map((scope) =>
+ factory.createApiScopeUserAccess({
+ nationalId: testCase.user.nationalId,
+ scope,
+ }),
+ ),
+ )
+ })
+
+ beforeEach(async () => {
+ if (delegations) {
+ await Delegation.destroy({
+ where: {
+ id: {
+ [Op.in]: delegations.map((delegation) => delegation.id),
+ },
+ },
+ })
+ }
+
+ delegations = await Promise.all(
+ (testCase.delegations ?? []).map((delegation) =>
+ factory.createCustomDelegation({
+ toNationalId: testCase.user.nationalId,
+ fromName,
+ ...delegation,
+ }),
+ ),
+ )
})
- }
-
- delegations = await Promise.all(
- (testCase.delegations ?? []).map((delegation) =>
- factory.createCustomDelegation({
- toNationalId: testCase.user.nationalId,
- fromName,
- ...delegation,
- }),
- ),
- )
- })
-
- it('GET /v1/me/delegations?direction=incoming filters delegations', async () => {
- // Arrange
- const expectedDelegations = filterExpectedDelegations(
- delegations,
- testCase.expected,
- )
-
- // Act
- const res = await server.get('/v1/me/delegations?direction=incoming')
-
- // Assert
- expect(delegationIndexService.indexDelegations).toHaveBeenCalled()
- expect(res.status).toEqual(200)
- expectMatchingDelegations(res.body, expectedDelegations)
- })
-
- if (testCase.expected.length > 0) {
- it.each(testCase.expected)(
- 'GET /v1/me/delegation/:id finds delegation in domain $name',
- async (domain) => {
+
+ it('GET /v1/me/delegations?direction=incoming filters delegations', async () => {
// Arrange
- const expectedDelegation = filterExpectedDelegations(
+ const expectedDelegations = filterExpectedDelegations(
delegations,
testCase.expected,
- ).find((delegation) => delegation.domainName === domain.name)
- assert(expectedDelegation)
+ )
// Act
const res = await server.get(
- `/v1/me/delegations/${expectedDelegation.id}`,
+ '/v1/me/delegations?direction=incoming',
)
// Assert
+ expect(delegationIndexService.indexDelegations).toHaveBeenCalled()
expect(res.status).toEqual(200)
- expectMatchingDelegations(res.body, expectedDelegation)
- },
- )
-
- it.each(testCase.expected)(
- 'DELETE /v1/me/delegations/:id removes access to delegation',
- async (domain) => {
- // Arrange
- const delegationScopeModel = app.get(
- getModelToken(DelegationScope),
- )
- const delegationModel = app.get(
- getModelToken(Delegation),
- )
- const delegation = delegations.find(
- (delegation) =>
- delegation.domainName === domain.name &&
- delegation.toNationalId === testCase.user.nationalId,
- )
- assert(delegation)
+ expectMatchingDelegations(res.body, expectedDelegations)
+ })
- // Act
- const res = await server.delete(
- `/v1/me/delegations/${delegation.id}`,
+ if (testCase.expected.length > 0) {
+ it.each(testCase.expected)(
+ 'GET /v1/me/delegation/:id finds delegation in domain $name',
+ async (domain) => {
+ // Arrange
+ const expectedDelegation = filterExpectedDelegations(
+ delegations,
+ testCase.expected,
+ ).find((delegation) => delegation.domainName === domain.name)
+ assert(expectedDelegation)
+
+ // Act
+ const res = await server.get(
+ `/v1/me/delegations/${expectedDelegation.id}`,
+ )
+
+ // Assert
+ expect(res.status).toEqual(200)
+ expectMatchingDelegations(res.body, expectedDelegation)
+ },
)
- // Assert
- expect(res.status).toEqual(204)
- expect(
- delegationIndexService.indexCustomDelegations,
- ).toHaveBeenCalled()
-
- const delegationAfter = await delegationModel.findByPk(
- delegation.id,
+ it.each(testCase.expected)(
+ 'DELETE /v1/me/delegations/:id removes access to delegation',
+ async (domain) => {
+ // Arrange
+ const delegationScopeModel = app.get(
+ getModelToken(DelegationScope),
+ )
+ const delegationModel = app.get(
+ getModelToken(Delegation),
+ )
+ const delegation = delegations.find(
+ (delegation) =>
+ delegation.domainName === domain.name &&
+ delegation.toNationalId === testCase.user.nationalId,
+ )
+ assert(delegation)
+
+ // Act
+ const res = await server.delete(
+ `/v1/me/delegations/${delegation.id}`,
+ )
+
+ // Assert
+ expect(res.status).toEqual(204)
+ expect(
+ delegationIndexService.indexCustomDelegations,
+ ).toHaveBeenCalled()
+
+ const delegationAfter = await delegationModel.findByPk(
+ delegation.id,
+ )
+ expect(delegationAfter).toBeNull()
+
+ const scopesAfter = await delegationScopeModel.findAll({
+ where: {
+ delegationId: delegation.id,
+ scopeName:
+ delegation.delegationScopes?.map(
+ (scope) => scope.scopeName,
+ ) ?? [],
+ },
+ })
+ expect(scopesAfter).toHaveLength(0)
+ },
)
- expect(delegationAfter).toBeNull()
-
- const scopesAfter = await delegationScopeModel.findAll({
- where: {
- delegationId: delegation.id,
- scopeName:
- delegation.delegationScopes?.map(
- (scope) => scope.scopeName,
- ) ?? [],
+ }
+
+ if (testCase.expected.length === 0 && delegations.length > 0) {
+ it.each(delegations)(
+ 'GET /v1/me/delegation/:id returns no content response for $name',
+ async (delegation) => {
+ // Act
+ const res = await server.get(
+ `/v1/me/delegations/${delegation.id}`,
+ )
+
+ // Assert
+ expect(res.status).toEqual(204)
+ expect(res.body).toMatchObject({})
},
- })
- expect(scopesAfter).toHaveLength(0)
- },
- )
- }
-
- if (testCase.expected.length === 0 && delegations.length > 0) {
- it.each(delegations)(
- 'GET /v1/me/delegation/:id returns no content response for $name',
- async (delegation) => {
- // Act
- const res = await server.get(`/v1/me/delegations/${delegation.id}`)
-
- // Assert
- expect(res.status).toEqual(204)
- expect(res.body).toMatchObject({})
- },
- )
- }
+ )
+ }
+ },
+ )
},
)
})
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 f0050b0d9e9e..5c07ba95334d 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
@@ -13,6 +13,7 @@ import {
Domain,
InactiveReason,
MergedDelegationDTO,
+ NationalRegistryV3FeatureService,
PersonalRepresentative,
PersonalRepresentativeDelegationTypeModel,
PersonalRepresentativeRight,
@@ -22,6 +23,7 @@ import {
UNKNOWN_NAME,
} from '@island.is/auth-api-lib'
import { NationalRegistryClientService } from '@island.is/clients/national-registry-v2'
+import { NationalRegistryV3ClientService } from '@island.is/clients/national-registry-v3'
import {
createClient,
createDomain,
@@ -54,374 +56,190 @@ import {
} from '../../../test/stubs/personalRepresentativeStubs'
describe('Personal Representative DelegationsController', () => {
- describe('Given a user is authenticated', () => {
- let app: TestApp
- let factory: FixtureFactory
- let server: request.SuperTest
- let apiScopeModel: typeof ApiScope
- let clientModel: typeof Client
- let prScopePermission: typeof PersonalRepresentativeScopePermission
- let apiScopeDelegationTypeModel: typeof ApiScopeDelegationType
- let prModel: typeof PersonalRepresentative
- let prRightsModel: typeof PersonalRepresentativeRight
- let prRightTypeModel: typeof PersonalRepresentativeRightType
- let prTypeModel: typeof PersonalRepresentativeType
- let prDelegationTypeModel: typeof PersonalRepresentativeDelegationTypeModel
- let delegationTypeModel: typeof DelegationTypeModel
- let nationalRegistryApi: NationalRegistryClientService
- let delegationProviderModel: typeof DelegationProviderModel
- let delegationIndexService: DelegationsIndexService
-
- const client = createClient({
- clientId: '@island.is/webapp',
- })
-
- const scopeValid1 = 'scope/valid1'
- const scopeValid2 = 'scope/valid2'
- const scopeValid1and2 = 'scope/valid1and2'
- const scopeUnactiveType = 'scope/unactiveType'
- const scopeOutdated = 'scope/outdated'
- const disabledScope = 'disabledScope'
-
- client.allowedScopes = Object.values([
- scopeValid1,
- scopeValid2,
- scopeValid1and2,
- scopeUnactiveType,
- scopeOutdated,
- disabledScope,
- ]).map((s) => ({
- clientId: client.clientId,
- scopeName: s,
- }))
-
- const userNationalId = getFakeNationalId()
-
- const user = createCurrentUser({
- nationalId: userNationalId,
- scope: [defaultScopes.testUserHasAccess.name],
- client: client.clientId,
- })
-
- const domain = createDomain()
-
- beforeAll(async () => {
- app = await setupWithAuth({
- user,
- })
- server = request(app.getHttpServer())
-
- prTypeModel = app.get(
- getModelToken(PersonalRepresentativeType),
- )
-
- await prTypeModel.create(personalRepresentativeType)
-
- const domainModel = app.get(getModelToken(Domain))
- await domainModel.create(domain)
-
- apiScopeModel = app.get(getModelToken(ApiScope))
- prModel = app.get(
- getModelToken(PersonalRepresentative),
- )
- prRightsModel = app.get(
- getModelToken(PersonalRepresentativeRight),
- )
- prRightTypeModel = app.get(
- getModelToken(PersonalRepresentativeRightType),
- )
- prScopePermission = app.get(
- getModelToken(PersonalRepresentativeScopePermission),
- )
- apiScopeDelegationTypeModel = app.get(
- getModelToken(ApiScopeDelegationType),
- )
- prDelegationTypeModel = app.get<
- typeof PersonalRepresentativeDelegationTypeModel
- >(getModelToken(PersonalRepresentativeDelegationTypeModel))
- delegationTypeModel = app.get(
- getModelToken(DelegationTypeModel),
- )
- delegationProviderModel = app.get(
- getModelToken(DelegationProviderModel),
- )
- clientModel = app.get(getModelToken(Client))
- nationalRegistryApi = app.get(NationalRegistryClientService)
- delegationIndexService = app.get(DelegationsIndexService)
- factory = new FixtureFactory(app)
- })
-
- const createDelegationTypeAndProvider = async (rightCode: string[]) => {
- const newDelegationProvider = await delegationProviderModel.create({
- id: AuthDelegationProvider.PersonalRepresentativeRegistry,
- name: 'Talsmannagrunnur',
- description: 'Talsmannagrunnur',
- delegationTypes: [],
- })
-
- await delegationTypeModel.bulkCreate(
- rightCode.map((code) => {
- return {
- id: getPersonalRepresentativeDelegationType(code),
- providerId: newDelegationProvider.id,
- name: getPersonalRepresentativeDelegationType(code),
- description: `Personal representative delegation type for right type ${code}`,
- }
- }),
- )
- }
-
- afterAll(async () => {
- await app.cleanUp()
- })
-
- describe('and given we have 3 valid, 1 not yet active and 1 outdate right types', () => {
- type rightsTypeStatus = 'valid' | 'unactivated' | 'outdated'
- type rightsType = [code: string, status: rightsTypeStatus]
- const rightsTypes: rightsType[] = [
- ['valid1', 'valid'],
- ['valid2', 'valid'],
- ['unactivated', 'unactivated'],
- ['outdated', 'outdated'],
- ]
-
- beforeAll(async () => {
- await prRightTypeModel.bulkCreate(
- rightsTypes.map(([code, status]) => {
- switch (status) {
- case 'valid':
- return getPersonalRepresentativeRightType(code)
- case 'unactivated':
- return getPersonalRepresentativeRightType(
- code,
- faker.date.soon(7),
- )
- case 'outdated':
- return getPersonalRepresentativeRightType(
- code,
- faker.date.recent(5),
- faker.date.recent(),
- )
- }
- }),
- )
- await createDelegationTypeAndProvider(rightsTypes.map(([code]) => code))
-
- client.supportedDelegationTypes = delegationTypes
- await factory.createClient(client)
- })
-
- afterAll(async () => {
- await prRightTypeModel.destroy({
- where: {},
- cascade: true,
- truncate: true,
- force: true,
+ describe.each([false, true])(
+ 'national registry v3 featureflag: %s',
+ (featureFlag) => {
+ describe('Given a user is authenticated', () => {
+ let app: TestApp
+ let factory: FixtureFactory
+ let server: request.SuperTest
+ let apiScopeModel: typeof ApiScope
+ let clientModel: typeof Client
+ let prScopePermission: typeof PersonalRepresentativeScopePermission
+ let apiScopeDelegationTypeModel: typeof ApiScopeDelegationType
+ let prModel: typeof PersonalRepresentative
+ let prRightsModel: typeof PersonalRepresentativeRight
+ let prRightTypeModel: typeof PersonalRepresentativeRightType
+ let prTypeModel: typeof PersonalRepresentativeType
+ let prDelegationTypeModel: typeof PersonalRepresentativeDelegationTypeModel
+ let delegationTypeModel: typeof DelegationTypeModel
+ let nationalRegistryApi: NationalRegistryClientService
+ let nationalRegistryV3Api: NationalRegistryV3ClientService
+ let delegationProviderModel: typeof DelegationProviderModel
+ let delegationIndexService: DelegationsIndexService
+
+ const client = createClient({
+ clientId: '@island.is/webapp',
})
- await delegationTypeModel.destroy({
- where: {},
- cascade: true,
- truncate: true,
- force: true,
- })
- await delegationProviderModel.destroy({
- where: {},
- cascade: true,
- truncate: true,
- force: true,
+
+ const scopeValid1 = 'scope/valid1'
+ const scopeValid2 = 'scope/valid2'
+ const scopeValid1and2 = 'scope/valid1and2'
+ const scopeUnactiveType = 'scope/unactiveType'
+ const scopeOutdated = 'scope/outdated'
+ const disabledScope = 'disabledScope'
+
+ client.allowedScopes = Object.values([
+ scopeValid1,
+ scopeValid2,
+ scopeValid1and2,
+ scopeUnactiveType,
+ scopeOutdated,
+ disabledScope,
+ ]).map((s) => ({
+ clientId: client.clientId,
+ scopeName: s,
+ }))
+
+ const userNationalId = getFakeNationalId()
+
+ const user = createCurrentUser({
+ nationalId: userNationalId,
+ scope: [defaultScopes.testUserHasAccess.name],
+ client: client.clientId,
})
- })
- describe.each([
- [1, 0, 0, 2, 1],
- [2, 0, 0, 1, 0],
- [0, 0, 0, 0, 0],
- [0, 1, 0, 0, 1],
- [0, 0, 1, 0, 0],
- [0, 1, 1, 0, 2],
- [1, 1, 1, 0, 0],
- ])(
- 'and given user has %d active representees with valid rights, %d active representees with outdated rights and %d active representees with unactivated',
- (
- valid: number,
- outdated: number,
- unactivated: number,
- deceased: number,
- nationalRegistryErrors: number,
- ) => {
- let nationalRegistryApiSpy: jest.SpyInstance
- const validRepresentedPersons: NameIdTuple[] = []
- const outdatedRepresentedPersons: NameIdTuple[] = []
- const unactivatedRepresentedPersons: NameIdTuple[] = []
- const errorNationalIdsRepresentedPersons: NameIdTuple[] = []
- const deceasedNationalIds = times(deceased, getFakeNationalId)
- const errorNationalIds = times(
- nationalRegistryErrors,
- getFakeNationalId,
+ const domain = createDomain()
+
+ beforeAll(async () => {
+ app = await setupWithAuth({
+ user,
+ })
+ server = request(app.getHttpServer())
+
+ prTypeModel = app.get(
+ getModelToken(PersonalRepresentativeType),
)
- beforeAll(async () => {
- for (let i = 0; i < valid; i++) {
- const representedPerson: NameIdTuple = [
- getFakeName(),
- getFakeNationalId(),
- ]
- const relationship = getPersonalRepresentativeRelationship(
- userNationalId,
- representedPerson[1],
- )
- validRepresentedPersons.push(representedPerson)
- await prModel.create(relationship)
- await prRightsModel.create(
- getPersonalRepresentativeRights('valid1', relationship.id),
- )
- await prDelegationTypeModel.create({
- personalRepresentativeId: relationship.id,
- delegationTypeId:
- getPersonalRepresentativeDelegationType('valid1'),
- })
- }
-
- for (let i = 0; i < outdated; i++) {
- const representedPerson: NameIdTuple = [
- getFakeName(),
- getFakeNationalId(),
- ]
- const relationship = getPersonalRepresentativeRelationship(
- userNationalId,
- representedPerson[1],
- )
- outdatedRepresentedPersons.push(representedPerson)
- await prModel.create(relationship)
- await prRightsModel.create(
- getPersonalRepresentativeRights('outdated', relationship.id),
- )
- await prDelegationTypeModel.create({
- personalRepresentativeId: relationship.id,
- delegationTypeId:
- getPersonalRepresentativeDelegationType('outdated'),
- })
- }
-
- for (let i = 0; i < unactivated; i++) {
- const representedPerson: NameIdTuple = [
- getFakeName(),
- getFakeNationalId(),
- ]
- const relationship = getPersonalRepresentativeRelationship(
- userNationalId,
- representedPerson[1],
- )
- unactivatedRepresentedPersons.push(representedPerson)
- await prModel.create(relationship)
- await prRightsModel.create(
- getPersonalRepresentativeRights('unactivated', relationship.id),
- )
- await prDelegationTypeModel.create({
- personalRepresentativeId: relationship.id,
- delegationTypeId:
- getPersonalRepresentativeDelegationType('unactivated'),
- })
- }
-
- for (let i = 0; i < deceased; i++) {
- const representedPerson: NameIdTuple = [
- getFakeName(),
- deceasedNationalIds[i],
- ]
- const relationship = getPersonalRepresentativeRelationship(
- userNationalId,
- representedPerson[1],
- )
+ await prTypeModel.create(personalRepresentativeType)
- await prModel.create(relationship)
- await prRightsModel.create(
- getPersonalRepresentativeRights('valid1', relationship.id),
- )
- await prDelegationTypeModel.create({
- personalRepresentativeId: relationship.id,
- delegationTypeId:
- getPersonalRepresentativeDelegationType('valid1'),
- })
- }
-
- for (let i = 0; i < nationalRegistryErrors; i++) {
- const representedPerson: NameIdTuple = [
- UNKNOWN_NAME,
- errorNationalIds[i],
- ]
- const relationship = getPersonalRepresentativeRelationship(
- userNationalId,
- representedPerson[1],
- )
+ const domainModel = app.get(getModelToken(Domain))
+ await domainModel.create(domain)
- errorNationalIdsRepresentedPersons.push(representedPerson)
- // Create Personal Representative model which will have nationalIdRepresentedPerson throw an error
- // when national registry api getIndividual is called
- await prModel.create(relationship)
- await prRightsModel.create(
- getPersonalRepresentativeRights('valid1', relationship.id),
- )
- await prDelegationTypeModel.create({
- personalRepresentativeId: relationship.id,
- delegationTypeId:
- getPersonalRepresentativeDelegationType('valid1'),
- })
- }
-
- const nationalRegistryUsers = [
- ...validRepresentedPersons.map(([name, nationalId]) =>
- createNationalRegistryUser({ name, nationalId }),
- ),
- ...outdatedRepresentedPersons.map(([name, nationalId]) =>
- createNationalRegistryUser({ name, nationalId }),
- ),
- ...unactivatedRepresentedPersons.map(([name, nationalId]) =>
- createNationalRegistryUser({ name, nationalId }),
- ),
- ]
+ apiScopeModel = app.get(getModelToken(ApiScope))
+ prModel = app.get(
+ getModelToken(PersonalRepresentative),
+ )
+ prRightsModel = app.get(
+ getModelToken(PersonalRepresentativeRight),
+ )
+ prRightTypeModel = app.get(
+ getModelToken(PersonalRepresentativeRightType),
+ )
+ prScopePermission = app.get<
+ typeof PersonalRepresentativeScopePermission
+ >(getModelToken(PersonalRepresentativeScopePermission))
+ apiScopeDelegationTypeModel = app.get(
+ getModelToken(ApiScopeDelegationType),
+ )
+ prDelegationTypeModel = app.get<
+ typeof PersonalRepresentativeDelegationTypeModel
+ >(getModelToken(PersonalRepresentativeDelegationTypeModel))
+ delegationTypeModel = app.get(
+ getModelToken(DelegationTypeModel),
+ )
+ delegationProviderModel = app.get(
+ getModelToken(DelegationProviderModel),
+ )
+ clientModel = app.get(getModelToken(Client))
+ 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)
+ })
- nationalRegistryApiSpy = jest
- .spyOn(nationalRegistryApi, 'getIndividual')
- .mockImplementation(async (id) => {
- if (deceasedNationalIds.includes(id)) {
- return null
- }
+ const createDelegationTypeAndProvider = async (rightCode: string[]) => {
+ const newDelegationProvider = await delegationProviderModel.create({
+ id: AuthDelegationProvider.PersonalRepresentativeRegistry,
+ name: 'Talsmannagrunnur',
+ description: 'Talsmannagrunnur',
+ delegationTypes: [],
+ })
- if (
- errorNationalIds.find(
- (errorNationalId) => id === errorNationalId,
- )
- ) {
- throw new Error('National registry error')
- }
+ await delegationTypeModel.bulkCreate(
+ rightCode.map((code) => {
+ return {
+ id: getPersonalRepresentativeDelegationType(code),
+ providerId: newDelegationProvider.id,
+ name: getPersonalRepresentativeDelegationType(code),
+ description: `Personal representative delegation type for right type ${code}`,
+ }
+ }),
+ )
+ }
- const user = nationalRegistryUsers.find(
- (u) =>
- u?.nationalId === id &&
- // Make sure we don't return a user that has been marked as deceased
- !deceasedNationalIds.includes(u?.nationalId),
- )
+ afterAll(async () => {
+ await app.cleanUp()
+ })
- return user ?? null
- })
+ describe('and given we have 3 valid, 1 not yet active and 1 outdate right types', () => {
+ type rightsTypeStatus = 'valid' | 'unactivated' | 'outdated'
+ type rightsType = [code: string, status: rightsTypeStatus]
+ const rightsTypes: rightsType[] = [
+ ['valid1', 'valid'],
+ ['valid2', 'valid'],
+ ['unactivated', 'unactivated'],
+ ['outdated', 'outdated'],
+ ]
+
+ beforeAll(async () => {
+ await prRightTypeModel.bulkCreate(
+ rightsTypes.map(([code, status]) => {
+ switch (status) {
+ case 'valid':
+ return getPersonalRepresentativeRightType(code)
+ case 'unactivated':
+ return getPersonalRepresentativeRightType(
+ code,
+ faker.date.soon(7),
+ )
+ case 'outdated':
+ return getPersonalRepresentativeRightType(
+ code,
+ faker.date.recent(5),
+ faker.date.recent(),
+ )
+ }
+ }),
+ )
+ await createDelegationTypeAndProvider(
+ rightsTypes.map(([code]) => code),
+ )
+
+ client.supportedDelegationTypes = delegationTypes
+ await factory.createClient(client)
})
afterAll(async () => {
- jest.clearAllMocks()
- await prRightsModel.destroy({
+ await prRightTypeModel.destroy({
where: {},
cascade: true,
truncate: true,
force: true,
})
- await prModel.destroy({
+ await delegationTypeModel.destroy({
where: {},
cascade: true,
truncate: true,
force: true,
})
- await prDelegationTypeModel.destroy({
+ await delegationProviderModel.destroy({
where: {},
cascade: true,
truncate: true,
@@ -429,215 +247,409 @@ describe('Personal Representative DelegationsController', () => {
})
})
- describe('when user calls GET /v2/delegations', () => {
- const path = '/v2/delegations'
- let response: request.Response
- let body: MergedDelegationDTO[]
+ describe.each([
+ [1, 0, 0, 2, 1],
+ [2, 0, 0, 1, 0],
+ [0, 0, 0, 0, 0],
+ [0, 1, 0, 0, 1],
+ [0, 0, 1, 0, 0],
+ [0, 1, 1, 0, 2],
+ [1, 1, 1, 0, 0],
+ ])(
+ 'and given user has %d active representees with valid rights, %d active representees with outdated rights and %d active representees with unactivated',
+ (
+ valid: number,
+ outdated: number,
+ unactivated: number,
+ deceased: number,
+ nationalRegistryErrors: number,
+ ) => {
+ let nationalRegistryApiSpy: jest.SpyInstance
+ let nationalRegistryV3ApiSpy: jest.SpyInstance
+ const validRepresentedPersons: NameIdTuple[] = []
+ const outdatedRepresentedPersons: NameIdTuple[] = []
+ const unactivatedRepresentedPersons: NameIdTuple[] = []
+ const errorNationalIdsRepresentedPersons: NameIdTuple[] = []
+ const deceasedNationalIds = times(deceased, getFakeNationalId)
+ const errorNationalIds = times(
+ nationalRegistryErrors,
+ getFakeNationalId,
+ )
- beforeAll(async () => {
- response = await server.get(path)
- body = response.body
- })
+ beforeAll(async () => {
+ for (let i = 0; i < valid; i++) {
+ const representedPerson: NameIdTuple = [
+ getFakeName(),
+ getFakeNationalId(),
+ ]
+ const relationship = getPersonalRepresentativeRelationship(
+ userNationalId,
+ representedPerson[1],
+ )
+ validRepresentedPersons.push(representedPerson)
+ await prModel.create(relationship)
+ await prRightsModel.create(
+ getPersonalRepresentativeRights('valid1', relationship.id),
+ )
+ await prDelegationTypeModel.create({
+ personalRepresentativeId: relationship.id,
+ delegationTypeId:
+ getPersonalRepresentativeDelegationType('valid1'),
+ })
+ }
- it('should have a an OK return status', () => {
- expect(response.status).toEqual(200)
- })
+ for (let i = 0; i < outdated; i++) {
+ const representedPerson: NameIdTuple = [
+ getFakeName(),
+ getFakeNationalId(),
+ ]
+ const relationship = getPersonalRepresentativeRelationship(
+ userNationalId,
+ representedPerson[1],
+ )
+ outdatedRepresentedPersons.push(representedPerson)
+ await prModel.create(relationship)
+ await prRightsModel.create(
+ getPersonalRepresentativeRights(
+ 'outdated',
+ relationship.id,
+ ),
+ )
+ await prDelegationTypeModel.create({
+ personalRepresentativeId: relationship.id,
+ delegationTypeId:
+ getPersonalRepresentativeDelegationType('outdated'),
+ })
+ }
- it(`should return ${valid} ${
- valid === 1 ? 'item' : 'items'
- } `, () => {
- expect(body).toHaveLength(valid + nationalRegistryErrors)
- })
+ for (let i = 0; i < unactivated; i++) {
+ const representedPerson: NameIdTuple = [
+ getFakeName(),
+ getFakeNationalId(),
+ ]
+ const relationship = getPersonalRepresentativeRelationship(
+ userNationalId,
+ representedPerson[1],
+ )
+ unactivatedRepresentedPersons.push(representedPerson)
+ await prModel.create(relationship)
+ await prRightsModel.create(
+ getPersonalRepresentativeRights(
+ 'unactivated',
+ relationship.id,
+ ),
+ )
+ await prDelegationTypeModel.create({
+ personalRepresentativeId: relationship.id,
+ delegationTypeId:
+ getPersonalRepresentativeDelegationType('unactivated'),
+ })
+ }
- it('should have the nationalId of the user as the representer', () => {
- expect(
- body.every((d) => d.toNationalId === userNationalId),
- ).toBeTruthy()
- })
+ for (let i = 0; i < deceased; i++) {
+ const representedPerson: NameIdTuple = [
+ getFakeName(),
+ deceasedNationalIds[i],
+ ]
+ const relationship = getPersonalRepresentativeRelationship(
+ userNationalId,
+ representedPerson[1],
+ )
- it('should only have the nationalId of the valid representees', () => {
- expect(body.map((d) => d.fromNationalId).sort()).toEqual(
- [
- ...validRepresentedPersons.map(([_, id]) => id),
- ...errorNationalIdsRepresentedPersons.map(([_, id]) => id),
- ].sort(),
- )
- })
+ await prModel.create(relationship)
+ await prRightsModel.create(
+ getPersonalRepresentativeRights('valid1', relationship.id),
+ )
+ await prDelegationTypeModel.create({
+ personalRepresentativeId: relationship.id,
+ delegationTypeId:
+ getPersonalRepresentativeDelegationType('valid1'),
+ })
+ }
- it(`should only have ${
- valid + nationalRegistryErrors === 1 ? 'name' : 'names'
- } of the valid represented ${
- valid + nationalRegistryErrors === 1 ? 'person' : 'persons'
- }`, () => {
- expect(body.map((d) => d.fromName).sort()).toEqual([
- ...validRepresentedPersons.map(([name, _]) => name).sort(),
- ...errorNationalIdsRepresentedPersons.map(([name]) => name),
- ])
- })
+ for (let i = 0; i < nationalRegistryErrors; i++) {
+ const representedPerson: NameIdTuple = [
+ UNKNOWN_NAME,
+ errorNationalIds[i],
+ ]
+ const relationship = getPersonalRepresentativeRelationship(
+ userNationalId,
+ representedPerson[1],
+ )
- it(`should have fetched the ${
- valid + deceased + nationalRegistryErrors === 1 ? 'name' : 'names'
- } of the valid represented ${
- valid + deceased + nationalRegistryErrors === 1
- ? 'person'
- : 'persons'
- } from nationalRegistryApi`, () => {
- expect(nationalRegistryApiSpy).toHaveBeenCalledTimes(
- valid + deceased + nationalRegistryErrors,
- )
- })
+ errorNationalIdsRepresentedPersons.push(representedPerson)
+ // Create Personal Representative model which will have nationalIdRepresentedPerson throw an error
+ // when national registry api getIndividual is called
+ await prModel.create(relationship)
+ await prRightsModel.create(
+ getPersonalRepresentativeRights('valid1', relationship.id),
+ )
+ await prDelegationTypeModel.create({
+ personalRepresentativeId: relationship.id,
+ delegationTypeId:
+ getPersonalRepresentativeDelegationType('valid1'),
+ })
+ }
- it('should have the delegation type claims of PersonalRepresentative', () => {
- expect(
- body.every(
- (d) =>
- d.types[0] === AuthDelegationType.PersonalRepresentative,
- ),
- ).toBeTruthy()
- })
+ const nationalRegistryUsers = [
+ ...validRepresentedPersons.map(([name, nationalId]) =>
+ createNationalRegistryUser({ name, nationalId }),
+ ),
+ ...outdatedRepresentedPersons.map(([name, nationalId]) =>
+ createNationalRegistryUser({ name, nationalId }),
+ ),
+ ...unactivatedRepresentedPersons.map(([name, nationalId]) =>
+ createNationalRegistryUser({ name, nationalId }),
+ ),
+ ]
+
+ nationalRegistryApiSpy = jest
+ .spyOn(nationalRegistryApi, 'getIndividual')
+ .mockImplementation(async (id) => {
+ if (deceasedNationalIds.includes(id)) {
+ return null
+ }
+
+ if (
+ errorNationalIds.find(
+ (errorNationalId) => id === errorNationalId,
+ )
+ ) {
+ throw new Error('National registry error')
+ }
+
+ const user = nationalRegistryUsers.find(
+ (u) =>
+ u?.nationalId === id &&
+ // Make sure we don't return a user that has been marked as deceased
+ !deceasedNationalIds.includes(u?.nationalId),
+ )
+
+ return user ?? null
+ })
+
+ nationalRegistryV3ApiSpy = jest
+ .spyOn(nationalRegistryV3Api, 'getAllDataIndividual')
+ .mockImplementation(async (id) => {
+ if (
+ errorNationalIds.find(
+ (errorNationalId) => id === errorNationalId,
+ )
+ ) {
+ throw new Error('National registry error')
+ }
+
+ if (deceasedNationalIds.includes(id)) {
+ return {
+ kennitala: id,
+ afdrif: 'LÉST',
+ }
+ }
+
+ const user = nationalRegistryUsers.find(
+ (u) => u?.nationalId === id,
+ )
+
+ return user
+ ? {
+ kennitala: id,
+ nafn: user?.name,
+ afdrif: null,
+ }
+ : null
+ })
+ })
- it('should have made prModels inactive for deceased persons', async () => {
- // Arrange
- const expectedModels = await prModel.findAll({
- where: {
- nationalIdRepresentedPerson: deceasedNationalIds,
- inactive: true,
- inactiveReason: InactiveReason.DECEASED_PARTY,
- },
+ afterAll(async () => {
+ jest.clearAllMocks()
+ await prRightsModel.destroy({
+ where: {},
+ cascade: true,
+ truncate: true,
+ force: true,
+ })
+ await prModel.destroy({
+ where: {},
+ cascade: true,
+ truncate: true,
+ force: true,
+ })
+ await prDelegationTypeModel.destroy({
+ where: {},
+ cascade: true,
+ truncate: true,
+ force: true,
+ })
})
- // Assert
- expect(expectedModels.length).toEqual(deceased)
+ describe('when user calls GET /v2/delegations', () => {
+ const path = '/v2/delegations'
+ let response: request.Response
+ let body: MergedDelegationDTO[]
- expectedModels.forEach((model) => {
- expect(model.inactive).toEqual(true)
- expect(model.inactiveReason).toEqual(
- InactiveReason.DECEASED_PARTY,
- )
- })
- })
+ beforeAll(async () => {
+ response = await server.get(path)
+ body = response.body
+ })
- it('should return delegation if national registry api getIndividual throws an error', async () => {
- // Arrange
- const expectedModels = await prModel.findAll({
- where: {
- nationalIdRepresentedPerson: errorNationalIds,
- },
- })
+ it('should have a an OK return status', () => {
+ expect(response.status).toEqual(200)
+ })
- // Assert
- expect(expectedModels.length).toEqual(errorNationalIds.length)
- })
+ it(`should return ${valid} ${
+ valid === 1 ? 'item' : 'items'
+ } `, () => {
+ expect(body).toHaveLength(valid + nationalRegistryErrors)
+ })
- it('should index delegations', () => {
- expect(delegationIndexService.indexDelegations).toHaveBeenCalled()
- })
- })
- },
- )
-
- describe('and given we have a combination of scopes for personal representative', () => {
- type scopesType = [name: string, enabled: boolean, rightTypes: string[]]
- const scopes: scopesType[] = [
- [scopeValid1, true, ['valid1']],
- [scopeValid2, true, ['valid2']],
- [scopeValid1and2, true, ['valid1', 'valid2']],
- [scopeUnactiveType, true, ['unactivated']],
- [scopeOutdated, true, ['outdated']],
- [disabledScope, false, ['valid1']],
- ]
+ it('should have the nationalId of the user as the representer', () => {
+ expect(
+ body.every((d) => d.toNationalId === userNationalId),
+ ).toBeTruthy()
+ })
- beforeAll(async () => {
- const apiScopes = scopes.flatMap(([name, enabled, types]) => ({
- name: name,
- enabled,
- domainName: domain.name,
- supportedDelegationTypes: types.map((rt) =>
- getPersonalRepresentativeDelegationType(rt),
- ),
- }))
-
- await Promise.all(
- apiScopes.map((scope) => factory.createApiScope(scope)),
- )
+ it('should only have the nationalId of the valid representees', () => {
+ expect(body.map((d) => d.fromNationalId).sort()).toEqual(
+ [
+ ...validRepresentedPersons.map(([_, id]) => id),
+ ...errorNationalIdsRepresentedPersons.map(
+ ([_, id]) => id,
+ ),
+ ].sort(),
+ )
+ })
- await prScopePermission.bulkCreate(
- scopes.flatMap(([name, _, types]) =>
- types.map((rt) => getScopePermission(rt, name)),
- ),
- )
- })
+ it(`should only have ${
+ valid + nationalRegistryErrors === 1 ? 'name' : 'names'
+ } of the valid represented ${
+ valid + nationalRegistryErrors === 1 ? 'person' : 'persons'
+ }`, () => {
+ expect(body.map((d) => d.fromName).sort()).toEqual([
+ ...validRepresentedPersons.map(([name, _]) => name).sort(),
+ ...errorNationalIdsRepresentedPersons.map(([name]) => name),
+ ])
+ })
- afterAll(async () => {
- await prScopePermission.destroy({
- where: {},
- cascade: true,
- truncate: true,
- force: true,
- })
- await apiScopeDelegationTypeModel.destroy({
- where: {},
- cascade: true,
- truncate: true,
- force: true,
- })
- await apiScopeModel.destroy({
- where: {},
- cascade: true,
- truncate: true,
- force: true,
- })
- })
+ it(`should have fetched the ${
+ valid + deceased + nationalRegistryErrors === 1
+ ? 'name'
+ : 'names'
+ } of the valid represented ${
+ valid + deceased + nationalRegistryErrors === 1
+ ? 'person'
+ : 'persons'
+ } from nationalRegistryApi`, () => {
+ featureFlag
+ ? expect(nationalRegistryV3ApiSpy).toHaveBeenCalledTimes(
+ valid + deceased + nationalRegistryErrors,
+ )
+ : expect(nationalRegistryApiSpy).toHaveBeenCalledTimes(
+ valid + deceased + nationalRegistryErrors,
+ )
+ })
+
+ it('should have the delegation type claims of PersonalRepresentative', () => {
+ expect(
+ body.every(
+ (d) =>
+ d.types[0] ===
+ AuthDelegationType.PersonalRepresentative,
+ ),
+ ).toBeTruthy()
+ })
+
+ it('should have made prModels inactive for deceased persons', async () => {
+ // Arrange
+ const expectedModels = await prModel.findAll({
+ where: {
+ nationalIdRepresentedPerson: deceasedNationalIds,
+ inactive: true,
+ inactiveReason: InactiveReason.DECEASED_PARTY,
+ },
+ })
+
+ // Assert
+ expect(expectedModels.length).toEqual(deceased)
+
+ expectedModels.forEach((model) => {
+ expect(model.inactive).toEqual(true)
+ expect(model.inactiveReason).toEqual(
+ InactiveReason.DECEASED_PARTY,
+ )
+ })
+ })
- describe.each([
- [['valid1'], [scopeValid1, scopeValid1and2]],
- [['valid2'], [scopeValid2, scopeValid1and2]],
- [
- ['valid1', 'valid2'],
- [scopeValid1, scopeValid2, scopeValid1and2],
- ],
- [[], []],
- // [['unactivated'], []],
- // [['outdated'], []],
- ])(
- 'and given user is representing persons with rights %p',
- (rights, expected) => {
- const representeeNationalId = getFakeNationalId()
+ it('should return delegation if national registry api getIndividual throws an error', async () => {
+ // Arrange
+ const expectedModels = await prModel.findAll({
+ where: {
+ nationalIdRepresentedPerson: errorNationalIds,
+ },
+ })
+
+ // Assert
+ expect(expectedModels.length).toEqual(errorNationalIds.length)
+ })
+
+ it('should index delegations', () => {
+ expect(
+ delegationIndexService.indexDelegations,
+ ).toHaveBeenCalled()
+ })
+ })
+ },
+ )
+
+ describe('and given we have a combination of scopes for personal representative', () => {
+ type scopesType = [
+ name: string,
+ enabled: boolean,
+ rightTypes: string[],
+ ]
+ const scopes: scopesType[] = [
+ [scopeValid1, true, ['valid1']],
+ [scopeValid2, true, ['valid2']],
+ [scopeValid1and2, true, ['valid1', 'valid2']],
+ [scopeUnactiveType, true, ['unactivated']],
+ [scopeOutdated, true, ['outdated']],
+ [disabledScope, false, ['valid1']],
+ ]
beforeAll(async () => {
- const relationship = getPersonalRepresentativeRelationship(
- userNationalId,
- representeeNationalId,
+ const apiScopes = scopes.flatMap(([name, enabled, types]) => ({
+ name: name,
+ enabled,
+ domainName: domain.name,
+ supportedDelegationTypes: types.map((rt) =>
+ getPersonalRepresentativeDelegationType(rt),
+ ),
+ }))
+
+ await Promise.all(
+ apiScopes.map((scope) => factory.createApiScope(scope)),
)
- await prModel.create(relationship)
- await prRightsModel.bulkCreate(
- rights.map((r) =>
- getPersonalRepresentativeRights(r, relationship.id),
+ await prScopePermission.bulkCreate(
+ scopes.flatMap(([name, _, types]) =>
+ types.map((rt) => getScopePermission(rt, name)),
),
)
- await prDelegationTypeModel.bulkCreate(
- rights.map((r) => ({
- personalRepresentativeId: relationship.id,
- delegationTypeId: getPersonalRepresentativeDelegationType(r),
- })),
- )
})
afterAll(async () => {
- await prRightsModel.destroy({
+ await prScopePermission.destroy({
where: {},
cascade: true,
truncate: true,
force: true,
})
- await prModel.destroy({
+ await apiScopeDelegationTypeModel.destroy({
where: {},
cascade: true,
truncate: true,
force: true,
})
- await prDelegationTypeModel.destroy({
+ await apiScopeModel.destroy({
where: {},
cascade: true,
truncate: true,
@@ -645,34 +657,95 @@ describe('Personal Representative DelegationsController', () => {
})
})
- describe('when user calls GET /delegations/scopes', () => {
- const path = '/delegations/scopes'
- let response: request.Response
- let body: string[]
+ describe.each([
+ [['valid1'], [scopeValid1, scopeValid1and2]],
+ [['valid2'], [scopeValid2, scopeValid1and2]],
+ [
+ ['valid1', 'valid2'],
+ [scopeValid1, scopeValid2, scopeValid1and2],
+ ],
+ [[], []],
+ // [['unactivated'], []],
+ // [['outdated'], []],
+ ])(
+ 'and given user is representing persons with rights %p',
+ (rights, expected) => {
+ const representeeNationalId = getFakeNationalId()
+
+ beforeAll(async () => {
+ const relationship = getPersonalRepresentativeRelationship(
+ userNationalId,
+ representeeNationalId,
+ )
- beforeAll(async () => {
- response = await server.get(`${path}`).query({
- fromNationalId: representeeNationalId,
- delegationType: rights.map((r) =>
- getPersonalRepresentativeDelegationType(r),
- ),
+ await prModel.create(relationship)
+ await prRightsModel.bulkCreate(
+ rights.map((r) =>
+ getPersonalRepresentativeRights(r, relationship.id),
+ ),
+ )
+ await prDelegationTypeModel.bulkCreate(
+ rights.map((r) => ({
+ personalRepresentativeId: relationship.id,
+ delegationTypeId:
+ getPersonalRepresentativeDelegationType(r),
+ })),
+ )
})
- body = response.body
- })
- it('should have a an OK return status', () => {
- expect(response.status).toEqual(200)
- })
+ afterAll(async () => {
+ await prRightsModel.destroy({
+ where: {},
+ cascade: true,
+ truncate: true,
+ force: true,
+ })
+ await prModel.destroy({
+ where: {},
+ cascade: true,
+ truncate: true,
+ force: true,
+ })
+ await prDelegationTypeModel.destroy({
+ where: {},
+ cascade: true,
+ truncate: true,
+ force: true,
+ })
+ })
- it(`should return ${
- expected.length === 0 ? 'no scopes' : JSON.stringify(expected)
- }`, () => {
- expect(body.sort()).toEqual(expected.sort())
- })
- })
- },
- )
+ describe('when user calls GET /delegations/scopes', () => {
+ const path = '/delegations/scopes'
+ let response: request.Response
+ let body: string[]
+
+ beforeAll(async () => {
+ response = await server.get(`${path}`).query({
+ fromNationalId: representeeNationalId,
+ delegationType: rights.map((r) =>
+ getPersonalRepresentativeDelegationType(r),
+ ),
+ })
+ body = response.body
+ })
+
+ it('should have a an OK return status', () => {
+ expect(response.status).toEqual(200)
+ })
+
+ it(`should return ${
+ expected.length === 0
+ ? 'no scopes'
+ : JSON.stringify(expected)
+ }`, () => {
+ expect(body.sort()).toEqual(expected.sort())
+ })
+ })
+ },
+ )
+ })
+ })
})
- })
- })
+ },
+ )
})
diff --git a/apps/services/auth/ids-api/src/app/delegations/test/delegations-filters.spec.ts b/apps/services/auth/ids-api/src/app/delegations/test/delegations-filters.spec.ts
index 526bf4262a4a..eb6c5a885989 100644
--- a/apps/services/auth/ids-api/src/app/delegations/test/delegations-filters.spec.ts
+++ b/apps/services/auth/ids-api/src/app/delegations/test/delegations-filters.spec.ts
@@ -4,9 +4,13 @@ import faker from 'faker'
import { Sequelize } from 'sequelize-typescript'
import request from 'supertest'
-import { MergedDelegationDTO } from '@island.is/auth-api-lib'
+import {
+ MergedDelegationDTO,
+ NationalRegistryV3FeatureService,
+} from '@island.is/auth-api-lib'
import { RskRelationshipsClient } from '@island.is/clients-rsk-relationships'
import { NationalRegistryClientService } from '@island.is/clients/national-registry-v2'
+import { NationalRegistryV3ClientService } from '@island.is/clients/national-registry-v3'
import { FixtureFactory } from '@island.is/services/auth/testing'
import {
AuthDelegationProvider,
@@ -23,166 +27,190 @@ import { testCases } from './delegations-filters-test-cases'
import { user } from './delegations-filters-types'
describe('DelegationsController', () => {
- let sequelize: Sequelize
- let app: TestApp
- let server: request.SuperTest
- let factory: FixtureFactory
- let nationalRegistryApi: NationalRegistryClientService
- let rskApi: RskRelationshipsClient
- beforeAll(async () => {
- app = await setupWithAuth({
- user: user,
- })
- sequelize = await app.resolve(getConnectionToken() as Type)
-
- server = request(app.getHttpServer())
-
- nationalRegistryApi = app.get(NationalRegistryClientService)
- jest
- .spyOn(nationalRegistryApi, 'getIndividual')
- .mockImplementation(async (nationalId: string) =>
- createNationalRegistryUser({
- nationalId,
- name: faker.name.findName(),
- }),
- )
- rskApi = app.get(RskRelationshipsClient)
-
- factory = new FixtureFactory(app)
- })
-
- afterAll(async () => {
- await app.cleanUp()
- })
-
- describe.each(Object.keys(testCases))(
- 'Delegation filtering with test case: %s',
- (caseName) => {
- const testCase = testCases[caseName]
- testCase.user = user
- const path = '/v2/delegations'
-
+ describe.each([false, true])(
+ 'national registry v3 featureflag: %s',
+ (featureFlag) => {
+ let sequelize: Sequelize
+ let app: TestApp
+ let server: request.SuperTest
+ let factory: FixtureFactory
+ let nationalRegistryApi: NationalRegistryClientService
+ let nationalRegistryV3Api: NationalRegistryV3ClientService
+ let rskApi: RskRelationshipsClient
beforeAll(async () => {
- await truncate(sequelize)
-
- await Promise.all(
- testCase.domains.map((domain) => factory.createDomain(domain)),
- )
+ app = await setupWithAuth({
+ user: user,
+ })
+ sequelize = await app.resolve(getConnectionToken() as Type)
- await factory.createClient(testCase.client)
-
- await Promise.all(
- testCase.clientAllowedScopes.map((scope) =>
- factory.createClientAllowedScope(scope),
- ),
- )
+ server = request(app.getHttpServer())
- await Promise.all(
- testCase.apiScopes.map((scope) => factory.createApiScope(scope)),
- )
-
- await Promise.all(
- testCase.apiScopeUserAccess.map((access) =>
- factory.createApiScopeUserAccess(access),
- ),
- )
-
- await Promise.all(
- testCase.customDelegations.map((delegation) =>
- factory.createCustomDelegation(delegation),
- ),
- )
-
- await Promise.all(
- testCase.fromLegalRepresentative.map((nationalId) =>
- factory.createDelegationIndexRecord({
- fromNationalId: nationalId,
- toNationalId: testCase.user.nationalId,
- type: AuthDelegationType.LegalRepresentative,
- provider: AuthDelegationProvider.DistrictCommissionersRegistry,
+ nationalRegistryApi = app.get(NationalRegistryClientService)
+ jest
+ .spyOn(nationalRegistryApi, 'getIndividual')
+ .mockImplementation(async (nationalId: string) =>
+ createNationalRegistryUser({
+ nationalId,
+ name: faker.name.findName(),
}),
- ),
+ )
+ nationalRegistryV3Api = app.get(NationalRegistryV3ClientService)
+ jest
+ .spyOn(nationalRegistryV3Api, 'getAllDataIndividual')
+ .mockImplementation(async (nationalId: string) => {
+ const user = createNationalRegistryUser({
+ nationalId: nationalId,
+ })
+
+ return { kennitala: user.nationalId, nafn: user.name }
+ })
+ rskApi = app.get(RskRelationshipsClient)
+
+ const nationalRegistryV3FeatureService = app.get(
+ NationalRegistryV3FeatureService,
)
-
jest
- .spyOn(nationalRegistryApi, 'getCustodyChildren')
- .mockImplementation(async () => testCase.fromChildren)
+ .spyOn(nationalRegistryV3FeatureService, 'getValue')
+ .mockImplementation(async () => featureFlag)
- jest
- .spyOn(rskApi, 'getIndividualRelationships')
- .mockImplementation(async () => testCase.procuration)
+ factory = new FixtureFactory(app)
})
- let res: request.Response
-
- it(`GET ${path} returns correct filtered delegations`, async () => {
- res = await server.get(path)
-
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(testCase.expectedFrom.length)
- expect(
- res.body.map((d: MergedDelegationDTO) => d.fromNationalId).sort(),
- ).toEqual(testCase.expectedFrom.sort())
- if (testCase.expectedTypes) {
- expect(res.body[0].types.sort()).toEqual(
- testCase.expectedTypes.sort(),
- )
- }
+ afterAll(async () => {
+ await app.cleanUp()
})
- },
- )
-
- describe('verify', () => {
- const testCase = testCases['legalRepresentative1']
- testCase.user = user
- const path = '/v1/delegations/verify'
- beforeAll(async () => {
- await truncate(sequelize)
-
- await Promise.all(
- testCase.domains.map((domain) => factory.createDomain(domain)),
+ describe.each(Object.keys(testCases))(
+ 'Delegation filtering with test case: %s',
+ (caseName) => {
+ const testCase = testCases[caseName]
+ testCase.user = user
+ const path = '/v2/delegations'
+
+ beforeAll(async () => {
+ await truncate(sequelize)
+
+ await Promise.all(
+ testCase.domains.map((domain) => factory.createDomain(domain)),
+ )
+
+ await factory.createClient(testCase.client)
+
+ await Promise.all(
+ testCase.clientAllowedScopes.map((scope) =>
+ factory.createClientAllowedScope(scope),
+ ),
+ )
+
+ await Promise.all(
+ testCase.apiScopes.map((scope) => factory.createApiScope(scope)),
+ )
+
+ await Promise.all(
+ testCase.apiScopeUserAccess.map((access) =>
+ factory.createApiScopeUserAccess(access),
+ ),
+ )
+
+ await Promise.all(
+ testCase.customDelegations.map((delegation) =>
+ factory.createCustomDelegation(delegation),
+ ),
+ )
+
+ await Promise.all(
+ testCase.fromLegalRepresentative.map((nationalId) =>
+ factory.createDelegationIndexRecord({
+ fromNationalId: nationalId,
+ toNationalId: testCase.user.nationalId,
+ type: AuthDelegationType.LegalRepresentative,
+ provider:
+ AuthDelegationProvider.DistrictCommissionersRegistry,
+ }),
+ ),
+ )
+
+ jest
+ .spyOn(nationalRegistryApi, 'getCustodyChildren')
+ .mockImplementation(async () => testCase.fromChildren)
+
+ jest
+ .spyOn(rskApi, 'getIndividualRelationships')
+ .mockImplementation(async () => testCase.procuration)
+ })
+
+ let res: request.Response
+
+ it(`GET ${path} returns correct filtered delegations`, async () => {
+ res = await server.get(path)
+
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(testCase.expectedFrom.length)
+ expect(
+ res.body.map((d: MergedDelegationDTO) => d.fromNationalId).sort(),
+ ).toEqual(testCase.expectedFrom.sort())
+ if (testCase.expectedTypes) {
+ expect(res.body[0].types.sort()).toEqual(
+ testCase.expectedTypes.sort(),
+ )
+ }
+ })
+ },
)
- await factory.createClient(testCase.client)
+ describe('verify', () => {
+ const testCase = testCases['legalRepresentative1']
+ testCase.user = user
+ const path = '/v1/delegations/verify'
- await Promise.all(
- testCase.clientAllowedScopes.map((scope) =>
- factory.createClientAllowedScope(scope),
- ),
- )
+ beforeAll(async () => {
+ await truncate(sequelize)
- await Promise.all(
- testCase.apiScopes.map((scope) => factory.createApiScope(scope)),
- )
+ await Promise.all(
+ testCase.domains.map((domain) => factory.createDomain(domain)),
+ )
- await factory.createDelegationIndexRecord({
- fromNationalId: nonExistingLegalRepresentativeNationalId,
- toNationalId: testCase.user.nationalId,
- type: AuthDelegationType.LegalRepresentative,
- provider: AuthDelegationProvider.DistrictCommissionersRegistry,
- })
- })
+ await factory.createClient(testCase.client)
- let res: request.Response
- it(`POST ${path} returns verified response`, async () => {
- res = await server.post(path).send({
- fromNationalId: testCase.fromLegalRepresentative[0],
- delegationTypes: [AuthDelegationType.LegalRepresentative],
- })
+ await Promise.all(
+ testCase.clientAllowedScopes.map((scope) =>
+ factory.createClientAllowedScope(scope),
+ ),
+ )
- expect(res.status).toEqual(200)
- expect(res.body.verified).toEqual(true)
- })
+ await Promise.all(
+ testCase.apiScopes.map((scope) => factory.createApiScope(scope)),
+ )
- it(`POST ${path} returns non-verified response`, async () => {
- res = await server.post(path).send({
- fromNationalId: nonExistingLegalRepresentativeNationalId,
- delegationTypes: [AuthDelegationType.LegalRepresentative],
+ await factory.createDelegationIndexRecord({
+ fromNationalId: nonExistingLegalRepresentativeNationalId,
+ toNationalId: testCase.user.nationalId,
+ type: AuthDelegationType.LegalRepresentative,
+ provider: AuthDelegationProvider.DistrictCommissionersRegistry,
+ })
+ })
+
+ let res: request.Response
+ it(`POST ${path} returns verified response`, async () => {
+ res = await server.post(path).send({
+ fromNationalId: testCase.fromLegalRepresentative[0],
+ delegationTypes: [AuthDelegationType.LegalRepresentative],
+ })
+
+ expect(res.status).toEqual(200)
+ expect(res.body.verified).toEqual(true)
+ })
+
+ it(`POST ${path} returns non-verified response`, async () => {
+ res = await server.post(path).send({
+ fromNationalId: nonExistingLegalRepresentativeNationalId,
+ delegationTypes: [AuthDelegationType.LegalRepresentative],
+ })
+
+ expect(res.status).toEqual(200)
+ expect(res.body.verified).toEqual(false)
+ })
})
-
- expect(res.status).toEqual(200)
- expect(res.body.verified).toEqual(false)
- })
- })
+ },
+ )
})
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 4abd4987bd31..f2dee16513d6 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,
@@ -12,8 +12,10 @@ import {
DelegationScope,
DelegationTypeModel,
Domain,
+ NationalRegistryV3FeatureService,
} from '@island.is/auth-api-lib'
import { NationalRegistryClientService } from '@island.is/clients/national-registry-v2'
+import { NationalRegistryV3ClientService } from '@island.is/clients/national-registry-v3'
import {
createClient,
createDomain,
@@ -33,246 +35,273 @@ import { defaultScopes, setupWithAuth } from '../../../../test/setup'
import { getFakeNationalId } from '../../../../test/stubs/genericStubs'
describe('DelegationsController', () => {
- describe('Given a user is authenticated', () => {
- let app: TestApp
- let factory: FixtureFactory
- let server: request.SuperTest
-
- let apiScopeModel: typeof ApiScope
- let apiScopeDelegationTypeModel: typeof ApiScopeDelegationType
- let delegationDelegationTypeModel: typeof DelegationDelegationType
- let delegationModel: typeof Delegation
- let delegationTypeModel: typeof DelegationTypeModel
- let nationalRegistryApi: NationalRegistryClientService
- let delegationProviderModel: typeof DelegationProviderModel
- let delegationScopesModel: typeof DelegationScope
-
- const client = createClient({
- clientId: '@island.is/webapp',
- })
-
- const scopeValid1 = 'scope/valid1'
- const scopeValid2 = 'scope/valid2'
- const scopeValid1and2 = 'scope/valid1and2'
- const scopeUnactiveType = 'scope/unactiveType'
- const scopeOutdated = 'scope/outdated'
- const disabledScope = 'disabledScope'
-
- client.allowedScopes = Object.values([
- scopeValid1,
- scopeValid2,
- scopeValid1and2,
- scopeUnactiveType,
- scopeOutdated,
- disabledScope,
- ]).map((s) => ({
- clientId: client.clientId,
- scopeName: s,
- }))
-
- const userNationalId = getFakeNationalId()
-
- const user = createCurrentUser({
- nationalId: userNationalId,
- scope: [defaultScopes.testUserHasAccess.name],
- client: client.clientId,
- })
-
- const domain = createDomain()
-
- beforeAll(async () => {
- app = await setupWithAuth({
- user,
- })
- server = request(app.getHttpServer())
-
- const domainModel = app.get(getModelToken(Domain))
- await domainModel.create(domain)
-
- apiScopeModel = app.get(getModelToken(ApiScope))
-
- apiScopeDelegationTypeModel = app.get(
- getModelToken(ApiScopeDelegationType),
- )
- delegationTypeModel = app.get(
- getModelToken(DelegationTypeModel),
- )
- delegationProviderModel = app.get(
- getModelToken(DelegationProviderModel),
- )
- delegationScopesModel = app.get(
- getModelToken(DelegationScope),
- )
- delegationModel = app.get(getModelToken(Delegation))
- delegationDelegationTypeModel = app.get(
- getModelToken(DelegationDelegationType),
- )
- nationalRegistryApi = app.get(NationalRegistryClientService)
- factory = new FixtureFactory(app)
- })
-
- afterAll(async () => {
- await app.cleanUp()
- })
-
- describe('GET with general mandate delegation type', () => {
- const representeeNationalId = getFakeNationalId()
- let nationalRegistryApiSpy: jest.SpyInstance
- const scopeNames = [
- 'api-scope/generalMandate1',
- 'api-scope/generalMandate2',
- 'api-scope/generalMandate3',
- ]
-
- beforeAll(async () => {
- client.supportedDelegationTypes = [
- AuthDelegationType.GeneralMandate,
- AuthDelegationType.LegalGuardian,
- ]
- await factory.createClient(client)
-
- const delegations = await delegationModel.create({
- id: uuid(),
- fromDisplayName: 'Test',
- fromNationalId: representeeNationalId,
- toNationalId: userNationalId,
- toName: 'Test',
+ describe.each([false, true])(
+ 'national registry v3 featureflag: %s',
+ (featureFlag) => {
+ describe('Given a user is authenticated', () => {
+ let app: TestApp
+ let factory: FixtureFactory
+ let server: request.SuperTest
+
+ let apiScopeModel: typeof ApiScope
+ let apiScopeDelegationTypeModel: typeof ApiScopeDelegationType
+ let delegationDelegationTypeModel: typeof DelegationDelegationType
+ let delegationModel: typeof Delegation
+ let delegationTypeModel: typeof DelegationTypeModel
+ let nationalRegistryApi: NationalRegistryClientService
+ let nationalRegistryV3Api: NationalRegistryV3ClientService
+ let delegationProviderModel: typeof DelegationProviderModel
+ let delegationScopesModel: typeof DelegationScope
+
+ const client = createClient({
+ clientId: '@island.is/webapp',
+ })
+
+ const scopeValid1 = 'scope/valid1'
+ const scopeValid2 = 'scope/valid2'
+ const scopeValid1and2 = 'scope/valid1and2'
+ const scopeUnactiveType = 'scope/unactiveType'
+ const scopeOutdated = 'scope/outdated'
+ const disabledScope = 'disabledScope'
+
+ client.allowedScopes = Object.values([
+ scopeValid1,
+ scopeValid2,
+ scopeValid1and2,
+ scopeUnactiveType,
+ scopeOutdated,
+ disabledScope,
+ ]).map((s) => ({
+ clientId: client.clientId,
+ scopeName: s,
+ }))
+
+ const userNationalId = getFakeNationalId()
+
+ const user = createCurrentUser({
+ nationalId: userNationalId,
+ scope: [defaultScopes.testUserHasAccess.name],
+ client: client.clientId,
})
- await delegationProviderModel.create({
- id: AuthDelegationProvider.Custom,
- name: 'Custom',
- description: 'Custom',
+ const domain = createDomain()
+
+ beforeAll(async () => {
+ app = await setupWithAuth({
+ user,
+ })
+ server = request(app.getHttpServer())
+
+ const domainModel = app.get(getModelToken(Domain))
+ await domainModel.create(domain)
+
+ apiScopeModel = app.get(getModelToken(ApiScope))
+
+ apiScopeDelegationTypeModel = app.get(
+ getModelToken(ApiScopeDelegationType),
+ )
+ delegationTypeModel = app.get(
+ getModelToken(DelegationTypeModel),
+ )
+ delegationProviderModel = app.get(
+ getModelToken(DelegationProviderModel),
+ )
+ delegationScopesModel = app.get(
+ getModelToken(DelegationScope),
+ )
+ delegationModel = app.get(
+ getModelToken(Delegation),
+ )
+ delegationDelegationTypeModel = app.get<
+ typeof DelegationDelegationType
+ >(getModelToken(DelegationDelegationType))
+ nationalRegistryApi = app.get(NationalRegistryClientService)
+ nationalRegistryV3Api = app.get(NationalRegistryV3ClientService)
+ const nationalRegistryV3FeatureService = app.get(
+ NationalRegistryV3FeatureService,
+ )
+ jest
+ .spyOn(nationalRegistryV3FeatureService, 'getValue')
+ .mockImplementation(async () => featureFlag)
+ factory = new FixtureFactory(app)
})
- await delegationDelegationTypeModel.create({
- delegationId: delegations.id,
- delegationTypeId: AuthDelegationType.GeneralMandate,
+ afterAll(async () => {
+ await app.cleanUp()
})
- await apiScopeModel.bulkCreate(
- scopeNames.map((name) => ({
- name,
- domainName: domain.name,
- enabled: true,
- description: `${name}: description`,
- displayName: `${name}: display name`,
- })),
- )
-
- // set 2 of 3 scopes to have general mandate delegation type
- await apiScopeDelegationTypeModel.bulkCreate([
- {
- apiScopeName: scopeNames[0],
- delegationType: AuthDelegationType.GeneralMandate,
- },
- {
- apiScopeName: scopeNames[1],
- delegationType: AuthDelegationType.GeneralMandate,
- },
- ])
-
- nationalRegistryApiSpy = jest
- .spyOn(nationalRegistryApi, 'getIndividual')
- .mockImplementation(async (id) => {
- const user = createNationalRegistryUser({
- nationalId: representeeNationalId,
+ describe('GET with general mandate delegation type', () => {
+ const representeeNationalId = getFakeNationalId()
+ let nationalRegistryApiSpy: jest.SpyInstance
+ let nationalRegistryV3ApiSpy: jest.SpyInstance
+ const scopeNames = [
+ 'api-scope/generalMandate1',
+ 'api-scope/generalMandate2',
+ 'api-scope/generalMandate3',
+ ]
+
+ beforeAll(async () => {
+ client.supportedDelegationTypes = [
+ AuthDelegationType.GeneralMandate,
+ AuthDelegationType.LegalGuardian,
+ ]
+ await factory.createClient(client)
+
+ const delegations = await delegationModel.create({
+ id: uuid(),
+ fromDisplayName: 'Test',
+ fromNationalId: representeeNationalId,
+ toNationalId: userNationalId,
+ toName: 'Test',
+ })
+
+ await delegationProviderModel.create({
+ id: AuthDelegationProvider.Custom,
+ name: 'Custom',
+ description: 'Custom',
+ })
+
+ await delegationDelegationTypeModel.create({
+ delegationId: delegations.id,
+ delegationTypeId: AuthDelegationType.GeneralMandate,
})
- return user ?? null
+ await apiScopeModel.bulkCreate(
+ scopeNames.map((name) => ({
+ name,
+ domainName: domain.name,
+ enabled: true,
+ description: `${name}: description`,
+ displayName: `${name}: display name`,
+ })),
+ )
+
+ // set 2 of 3 scopes to have general mandate delegation type
+ await apiScopeDelegationTypeModel.bulkCreate([
+ {
+ apiScopeName: scopeNames[0],
+ delegationType: AuthDelegationType.GeneralMandate,
+ },
+ {
+ apiScopeName: scopeNames[1],
+ 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()
- })
+ afterAll(async () => {
+ await app.cleanUp()
+ nationalRegistryApiSpy.mockClear()
+ nationalRegistryV3ApiSpy.mockClear()
+ })
- it('should return mergedDelegationDTO with the generalMandate', async () => {
- const response = await server.get('/v2/delegations')
+ 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)
- })
+ expect(response.status).toEqual(200)
+ expect(response.body).toHaveLength(1)
+ })
- it('should get all general mandate scopes', async () => {
- const response = await server.get('/delegations/scopes').query({
- fromNationalId: representeeNationalId,
- delegationType: [AuthDelegationType.GeneralMandate],
- })
+ it('should get all general mandate scopes', async () => {
+ const response = await server.get('/delegations/scopes').query({
+ fromNationalId: representeeNationalId,
+ delegationType: [AuthDelegationType.GeneralMandate],
+ })
- expect(response.status).toEqual(200)
- expect(response.body).toEqual([scopeNames[0], scopeNames[1]])
- })
+ expect(response.status).toEqual(200)
+ expect(response.body).toEqual([scopeNames[0], scopeNames[1]])
+ })
- it('should only return valid general mandates', async () => {
- const newNationalId = getFakeNationalId()
- const newDelegation = await delegationModel.create({
- id: uuid(),
- fromDisplayName: 'Test',
- fromNationalId: newNationalId,
- toNationalId: userNationalId,
- toName: 'Test',
- })
+ it('should only return valid general mandates', async () => {
+ const newNationalId = getFakeNationalId()
+ const newDelegation = await delegationModel.create({
+ id: uuid(),
+ fromDisplayName: 'Test',
+ fromNationalId: newNationalId,
+ toNationalId: userNationalId,
+ toName: 'Test',
+ })
- await delegationDelegationTypeModel.create({
- delegationId: newDelegation.id,
- delegationTypeId: AuthDelegationType.GeneralMandate,
- validTo: addDays(new Date(), -2),
- })
+ await delegationDelegationTypeModel.create({
+ delegationId: newDelegation.id,
+ delegationTypeId: AuthDelegationType.GeneralMandate,
+ validTo: addDays(new Date(), -2),
+ })
- const response = await server.get('/delegations/scopes').query({
- fromNationalId: newNationalId,
- delegationType: [AuthDelegationType.GeneralMandate],
- })
+ const response = await server.get('/delegations/scopes').query({
+ fromNationalId: newNationalId,
+ delegationType: [AuthDelegationType.GeneralMandate],
+ })
- expect(response.status).toEqual(200)
- expect(response.body).toEqual([])
- })
+ expect(response.status).toEqual(200)
+ expect(response.body).toEqual([])
+ })
- it('should return all general mandate scopes and other preset scopes', async () => {
- const newDelegation = await delegationModel.create({
- id: uuid(),
- fromDisplayName: 'Test',
- fromNationalId: representeeNationalId,
- domainName: domain.name,
- toNationalId: userNationalId,
- toName: 'Test',
- })
+ it('should return all general mandate scopes and other preset scopes', async () => {
+ const newDelegation = await delegationModel.create({
+ id: uuid(),
+ fromDisplayName: 'Test',
+ fromNationalId: representeeNationalId,
+ domainName: domain.name,
+ toNationalId: userNationalId,
+ toName: 'Test',
+ })
- await delegationTypeModel.create({
- id: AuthDelegationType.Custom,
- name: 'custom',
- description: 'custom',
- providerId: AuthDelegationProvider.Custom,
- })
+ await delegationTypeModel.create({
+ id: AuthDelegationType.Custom,
+ name: 'custom',
+ description: 'custom',
+ providerId: AuthDelegationProvider.Custom,
+ })
- await delegationScopesModel.create({
- id: uuid(),
- delegationId: newDelegation.id,
- scopeName: scopeNames[2],
- // set valid from as yesterday and valid to as tomorrow
- validFrom: addDays(new Date(), -1),
- validTo: addDays(new Date(), 1),
- })
+ await delegationScopesModel.create({
+ id: uuid(),
+ delegationId: newDelegation.id,
+ scopeName: scopeNames[2],
+ // set valid from as yesterday and valid to as tomorrow
+ validFrom: addDays(new Date(), -1),
+ validTo: addDays(new Date(), 1),
+ })
- await apiScopeDelegationTypeModel.create({
- apiScopeName: scopeNames[2],
- delegationType: AuthDelegationType.LegalGuardian,
- })
+ await apiScopeDelegationTypeModel.create({
+ apiScopeName: scopeNames[2],
+ delegationType: AuthDelegationType.LegalGuardian,
+ })
- const response = await server.get('/delegations/scopes').query({
- fromNationalId: representeeNationalId,
- delegationType: [
- AuthDelegationType.GeneralMandate,
- AuthDelegationType.LegalGuardian,
- ],
- })
+ const response = await server.get('/delegations/scopes').query({
+ fromNationalId: representeeNationalId,
+ delegationType: [
+ AuthDelegationType.GeneralMandate,
+ AuthDelegationType.LegalGuardian,
+ ],
+ })
- expect(response.status).toEqual(200)
- expect(response.body).toEqual(expect.arrayContaining(scopeNames))
- expect(response.body).toHaveLength(scopeNames.length)
+ expect(response.status).toEqual(200)
+ expect(response.body).toEqual(expect.arrayContaining(scopeNames))
+ expect(response.body).toHaveLength(scopeNames.length)
+ })
+ })
})
- })
- })
+ },
+ )
})
diff --git a/apps/services/auth/personal-representative/infra/personal-representative.ts b/apps/services/auth/personal-representative/infra/personal-representative.ts
index 180b75ec9891..42928e86db9f 100644
--- a/apps/services/auth/personal-representative/infra/personal-representative.ts
+++ b/apps/services/auth/personal-representative/infra/personal-representative.ts
@@ -1,5 +1,10 @@
import { json, service, ServiceBuilder } from '../../../../../infra/src/dsl/dsl'
-import { Base, Client, RskProcuring } from '../../../../../infra/src/dsl/xroad'
+import {
+ Base,
+ Client,
+ NationalRegistryAuthB2C,
+ RskProcuring,
+} from '../../../../../infra/src/dsl/xroad'
const REDIS_NODE_CONFIG = {
dev: json([
@@ -62,8 +67,10 @@ export const serviceSetup =
'/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET',
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME',
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD',
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET:
+ '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET',
})
- .xroad(Base, Client, RskProcuring)
+ .xroad(Base, Client, RskProcuring, NationalRegistryAuthB2C)
.ingress({
primary: {
host: {
diff --git a/apps/services/auth/personal-representative/src/app/app.module.ts b/apps/services/auth/personal-representative/src/app/app.module.ts
index e46d8b36eeed..0c89f4dd3a5a 100644
--- a/apps/services/auth/personal-representative/src/app/app.module.ts
+++ b/apps/services/auth/personal-representative/src/app/app.module.ts
@@ -8,8 +8,10 @@ import {
import { AuthModule } from '@island.is/auth-nest-tools'
import { RskRelationshipsClientConfig } from '@island.is/clients-rsk-relationships'
import { NationalRegistryClientConfig } from '@island.is/clients/national-registry-v2'
+import { NationalRegistryV3ClientConfig } from '@island.is/clients/national-registry-v3'
import { CompanyRegistryConfig } from '@island.is/clients/rsk/company-registry'
import { SyslumennClientConfig } from '@island.is/clients/syslumenn'
+import { ZendeskServiceConfig } from '@island.is/clients/zendesk'
import { AuditModule } from '@island.is/nest/audit'
import {
ConfigModule,
@@ -17,12 +19,11 @@ import {
XRoadConfig,
} from '@island.is/nest/config'
import { FeatureFlagConfig } from '@island.is/nest/feature-flags'
-import { ZendeskServiceConfig } from '@island.is/clients/zendesk'
import { environment } from '../environments'
-import { PersonalRepresentativeTypesModule } from './modules/personalRepresentativeTypes/personalRepresentativeTypes.module'
import { AccessLogsModule } from './modules/accessLogs/accessLogs.module'
import { PersonalRepresentativesModule } from './modules/personalRepresentatives/personalRepresentatives.module'
+import { PersonalRepresentativeTypesModule } from './modules/personalRepresentativeTypes/personalRepresentativeTypes.module'
import { RightTypesModule } from './modules/rightTypes/rightTypes.module'
@Module({
@@ -38,6 +39,7 @@ import { RightTypesModule } from './modules/rightTypes/rightTypes.module'
DelegationConfig,
IdsClientConfig,
NationalRegistryClientConfig,
+ NationalRegistryV3ClientConfig,
RskRelationshipsClientConfig,
CompanyRegistryConfig,
XRoadConfig,
diff --git a/apps/services/auth/public-api/infra/auth-public-api.ts b/apps/services/auth/public-api/infra/auth-public-api.ts
index b27663150b40..609588c46271 100644
--- a/apps/services/auth/public-api/infra/auth-public-api.ts
+++ b/apps/services/auth/public-api/infra/auth-public-api.ts
@@ -1,5 +1,10 @@
import { json, service, ServiceBuilder } from '../../../../../infra/src/dsl/dsl'
-import { Base, Client, RskProcuring } from '../../../../../infra/src/dsl/xroad'
+import {
+ Base,
+ Client,
+ NationalRegistryAuthB2C,
+ RskProcuring,
+} from '../../../../../infra/src/dsl/xroad'
const REDIS_NODE_CONFIG = {
dev: json([
@@ -86,8 +91,10 @@ export const serviceSetup = (): ServiceBuilder<'services-auth-public-api'> => {
'/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET',
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME',
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD',
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET:
+ '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET',
})
- .xroad(Base, Client, RskProcuring)
+ .xroad(Base, Client, RskProcuring, NationalRegistryAuthB2C)
.ingress({
primary: {
host: {
diff --git a/apps/services/auth/public-api/src/app/app.module.ts b/apps/services/auth/public-api/src/app/app.module.ts
index e3719da7e4b9..7fe7ba43de60 100644
--- a/apps/services/auth/public-api/src/app/app.module.ts
+++ b/apps/services/auth/public-api/src/app/app.module.ts
@@ -9,8 +9,10 @@ import {
import { AuthModule } from '@island.is/auth-nest-tools'
import { RskRelationshipsClientConfig } from '@island.is/clients-rsk-relationships'
import { NationalRegistryClientConfig } from '@island.is/clients/national-registry-v2'
+import { NationalRegistryV3ClientConfig } from '@island.is/clients/national-registry-v3'
import { CompanyRegistryConfig } from '@island.is/clients/rsk/company-registry'
import { SyslumennClientConfig } from '@island.is/clients/syslumenn'
+import { ZendeskServiceConfig } from '@island.is/clients/zendesk'
import { AuditModule } from '@island.is/nest/audit'
import {
ConfigModule,
@@ -23,7 +25,6 @@ import { ProblemModule } from '@island.is/nest/problem'
import { environment } from '../environments'
import { DelegationsModule } from './modules/delegations/delegations.module'
import { PasskeysModule } from './modules/passkeys/passkeys.module'
-import { ZendeskServiceConfig } from '@island.is/clients/zendesk'
@Module({
imports: [
@@ -42,6 +43,7 @@ import { ZendeskServiceConfig } from '@island.is/clients/zendesk'
FeatureFlagConfig,
IdsClientConfig,
NationalRegistryClientConfig,
+ NationalRegistryV3ClientConfig,
RskRelationshipsClientConfig,
CompanyRegistryConfig,
XRoadConfig,
diff --git a/apps/services/auth/public-api/src/app/modules/delegations/actorDelegations.controller.spec.ts b/apps/services/auth/public-api/src/app/modules/delegations/actorDelegations.controller.spec.ts
index d57f16dea37d..a7ad9fb47b1e 100644
--- a/apps/services/auth/public-api/src/app/modules/delegations/actorDelegations.controller.spec.ts
+++ b/apps/services/auth/public-api/src/app/modules/delegations/actorDelegations.controller.spec.ts
@@ -1,8 +1,8 @@
import { getModelToken } from '@nestjs/sequelize'
+import addYears from 'date-fns/addYears'
+import kennitala from 'kennitala'
import times from 'lodash/times'
import request from 'supertest'
-import kennitala from 'kennitala'
-import addYears from 'date-fns/addYears'
import {
ClientDelegationType,
@@ -14,6 +14,7 @@ import {
DelegationScope,
DelegationTypeModel,
MergedDelegationDTO,
+ NationalRegistryV3FeatureService,
PersonalRepresentative,
PersonalRepresentativeDelegationTypeModel,
PersonalRepresentativeRight,
@@ -26,6 +27,7 @@ import {
IndividualDto,
NationalRegistryClientService,
} from '@island.is/clients/national-registry-v2'
+import { NationalRegistryV3ClientService } from '@island.is/clients/national-registry-v3'
import {
createClient,
createDelegation,
@@ -153,923 +155,976 @@ beforeAll(() => {
})
describe('ActorDelegationsController', () => {
- describe('with auth', () => {
- let app: TestApp
- let server: request.SuperTest
- let delegationModel: typeof Delegation
- let delegationDelegationTypeModel: typeof DelegationDelegationType
- let clientDelegationTypeModel: typeof ClientDelegationType
- let nationalRegistryApi: NationalRegistryClientService
-
- beforeAll(async () => {
- // TestApp setup with auth and database
- app = await setupWithAuth({
- user,
- userName,
- nationalRegistryUser,
- client: {
- props: client,
- scopes: Scopes.slice(0, 4).map((s) => s.name),
- },
- })
- server = request(app.getHttpServer())
-
- // Get reference on Delegation and Client models to seed DB
- delegationModel = app.get(getModelToken(Delegation))
- clientDelegationTypeModel = app.get(
- getModelToken(ClientDelegationType),
- )
- delegationDelegationTypeModel = app.get(
- getModelToken(DelegationDelegationType),
- )
- nationalRegistryApi = app.get(NationalRegistryClientService)
- })
-
- beforeEach(async () => {
- return await clientDelegationTypeModel.bulkCreate(
- delegationTypes.map((type) => ({
- clientId: client.clientId,
- delegationType: type,
- })),
- {
- updateOnDuplicate: ['modified'],
- },
- )
- })
-
- afterAll(async () => {
- await app.cleanUp()
- })
-
- afterEach(async () => {
- await delegationModel.destroy({
- where: {},
- cascade: true,
- truncate: true,
- force: true,
- })
- })
-
- describe('GET /actor/delegations', () => {
- let nationalRegistryApiSpy: jest.SpyInstance
- const path = '/v1/actor/delegations'
- const query = '?direction=incoming'
- const deceasedNationalIds = times(3, () => createNationalId('person'))
- const nationalRegistryUsers = [
- nationalRegistryUser,
- ...Object.values(mockDelegations).map((delegation) =>
- createNationalRegistryUser({
- nationalId: delegation.fromNationalId,
- }),
- ),
- ...times(10, () =>
- createNationalRegistryUser({
- name: getFakeName(),
- nationalId: createNationalId('person'),
- }),
- ),
- ]
-
- beforeAll(async () => {
- nationalRegistryApiSpy = jest
- .spyOn(nationalRegistryApi, 'getIndividual')
- .mockImplementation(async (id) => {
- if (deceasedNationalIds.includes(id)) {
- return null
- }
+ describe.each([false, true])(
+ 'national registry v3 featureflag: %s',
+ (featureFlag) => {
+ describe('with auth', () => {
+ let app: TestApp
+ let server: request.SuperTest
+ let delegationModel: typeof Delegation
+ let delegationDelegationTypeModel: typeof DelegationDelegationType
+ let clientDelegationTypeModel: typeof ClientDelegationType
+ let nationalRegistryApi: NationalRegistryClientService
+ let nationalRegistryV3Api: NationalRegistryV3ClientService
- const user = nationalRegistryUsers.find((u) => u?.nationalId === id)
-
- return user ?? null
+ beforeAll(async () => {
+ // TestApp setup with auth and database
+ app = await setupWithAuth({
+ user,
+ userName,
+ nationalRegistryUser,
+ client: {
+ props: client,
+ scopes: Scopes.slice(0, 4).map((s) => s.name),
+ },
})
- })
-
- it('should return only valid delegations', async () => {
- // Arrange
- await createDelegationModels(
- delegationModel,
- Object.values(mockDelegations),
- )
- const expectedModels = await findExpectedMergedDelegationModels(
- delegationModel,
- [
- mockDelegations.incoming.id,
- mockDelegations.incomingBothValidAndNotAllowed.id,
- mockDelegations.incomingWithOtherDomain.id,
- ],
- [Scopes[0].name],
- )
-
- // Act
- const res = await server.get(`${path}${query}`)
-
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(3)
- expectMatchingMergedDelegations(
- res.body,
- updateDelegationFromNameToPersonName(
- expectedModels,
- nationalRegistryUsers,
- ),
- )
- })
+ server = request(app.getHttpServer())
- it('should return only delegations with scopes the client has access to', async () => {
- // Arrange
- await createDelegationModels(delegationModel, [
- mockDelegations.incomingWithOtherDomain,
- ])
- const expectedModel = await findExpectedMergedDelegationModels(
- delegationModel,
- mockDelegations.incomingWithOtherDomain.id,
- [Scopes[0].name],
- )
-
- // Act
- const res = await server.get(`${path}${query}`)
-
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(1)
- expectMatchingMergedDelegations(
- res.body[0],
- updateDelegationFromNameToPersonName(
- expectedModel,
- nationalRegistryUsers,
- ),
- )
- })
-
- it('should return custom delegations and general mandate when the delegationTypes filter has both types and delegation exists for both', async () => {
- // Arrange
- const delegation = createDelegation({
- fromNationalId: nationalRegistryUser.nationalId,
- toNationalId: user.nationalId,
- scopes: [],
+ // Get reference on Delegation and Client models to seed DB
+ delegationModel = app.get(
+ getModelToken(Delegation),
+ )
+ clientDelegationTypeModel = app.get(
+ getModelToken(ClientDelegationType),
+ )
+ delegationDelegationTypeModel = app.get<
+ typeof DelegationDelegationType
+ >(getModelToken(DelegationDelegationType))
+ nationalRegistryApi = app.get(NationalRegistryClientService)
+ nationalRegistryV3Api = app.get(NationalRegistryV3ClientService)
})
- await delegationModel.create(delegation)
-
- await delegationDelegationTypeModel.create({
- delegationId: delegation.id,
- delegationTypeId: AuthDelegationType.GeneralMandate,
+ beforeEach(async () => {
+ return await clientDelegationTypeModel.bulkCreate(
+ delegationTypes.map((type) => ({
+ clientId: client.clientId,
+ delegationType: type,
+ })),
+ {
+ updateOnDuplicate: ['modified'],
+ },
+ )
})
- await createDelegationModels(delegationModel, [
- mockDelegations.incomingWithOtherDomain,
- ])
-
- // Act
- const res = await server.get(
- `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&delegationTypes=${AuthDelegationType.GeneralMandate}`,
- )
-
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(2)
- expect(
- res.body
- .map((d: MergedDelegationDTO) => d.types)
- .flat()
- .sort(),
- ).toEqual(
- [AuthDelegationType.Custom, AuthDelegationType.GeneralMandate].sort(),
- )
- })
-
- it('should return a merged object with both Custom and GeneralMandate types', async () => {
- // Arrange
- const delegation = createDelegation({
- fromNationalId:
- mockDelegations.incomingWithOtherDomain.fromNationalId,
- toNationalId: user.nationalId,
- domainName: null,
- scopes: [],
+ afterAll(async () => {
+ await app.cleanUp()
})
- await delegationModel.create(delegation)
-
- await delegationDelegationTypeModel.create({
- delegationId: delegation.id,
- delegationTypeId: AuthDelegationType.GeneralMandate,
+ afterEach(async () => {
+ await delegationModel.destroy({
+ where: {},
+ cascade: true,
+ truncate: true,
+ force: true,
+ })
})
- await createDelegationModels(delegationModel, [
- mockDelegations.incomingWithOtherDomain,
- ])
-
- // Act
- const res = await server.get(
- `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&delegationTypes=${AuthDelegationType.GeneralMandate}`,
- )
-
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(1)
- expect(
- res.body
- .map((d: MergedDelegationDTO) => d.types)
- .flat()
- .sort(),
- ).toEqual(
- [AuthDelegationType.Custom, AuthDelegationType.GeneralMandate].sort(),
- )
- })
-
- it('should return only delegations related to the provided otherUser national id', async () => {
- // Arrange
- await createDelegationModels(delegationModel, [
- mockDelegations.incoming,
- ])
- const expectedModel = await findExpectedMergedDelegationModels(
- delegationModel,
- mockDelegations.incoming.id,
- [Scopes[0].name],
- )
-
- // Act
- const res = await server.get(
- `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&otherUser=${mockDelegations.incoming.fromNationalId}`,
- )
-
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(1)
- expectMatchingMergedDelegations(
- res.body[0],
- updateDelegationFromNameToPersonName(
- expectedModel,
- nationalRegistryUsers,
- ),
- )
- })
-
- it('should return only delegations related to the provided otherUser national id without the general mandate since there is none', async () => {
- // Arrange
- await createDelegationModels(delegationModel, [
- mockDelegations.incoming,
- ])
- const expectedModel = await findExpectedMergedDelegationModels(
- delegationModel,
- mockDelegations.incoming.id,
- [Scopes[0].name],
- )
-
- // Act
- const res = await server.get(
- `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&delegationTypes${AuthDelegationType.GeneralMandate}&otherUser=${mockDelegations.incoming.fromNationalId}`,
- )
-
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(1)
- expectMatchingMergedDelegations(
- res.body[0],
- updateDelegationFromNameToPersonName(
- expectedModel,
- nationalRegistryUsers,
- ),
- )
- })
-
- it('should return empty array when provided otherUser national id is not related to any delegation', async () => {
- // Arrange
- const unrelatedNationalId = createNationalId('person')
+ describe('GET /actor/delegations', () => {
+ let nationalRegistryApiSpy: jest.SpyInstance
+ let nationalRegistryV3ApiSpy: jest.SpyInstance
+ const path = '/v1/actor/delegations'
+ const query = '?direction=incoming'
+ const deceasedNationalIds = times(3, () => createNationalId('person'))
+ const nationalRegistryUsers = [
+ nationalRegistryUser,
+ ...Object.values(mockDelegations).map((delegation) =>
+ createNationalRegistryUser({
+ nationalId: delegation.fromNationalId,
+ }),
+ ),
+ ...times(10, () =>
+ createNationalRegistryUser({
+ name: getFakeName(),
+ nationalId: createNationalId('person'),
+ }),
+ ),
+ ]
- // Act
- const res = await server.get(
- `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&otherUser=${unrelatedNationalId}`,
- )
-
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(0)
- })
-
- it('should return custom delegations and not deceased delegations, when the delegationTypes filter is custom type', async () => {
- // Arrange
- await createDelegationModels(delegationModel, [
- mockDelegations.incoming,
- createDelegation({
- fromNationalId: deceasedNationalIds[0],
- toNationalId: user.nationalId,
- scopes: [Scopes[0].name],
- today,
- }),
- createDelegation({
- fromNationalId: deceasedNationalIds[1],
- toNationalId: user.nationalId,
- scopes: [Scopes[0].name],
- today,
- }),
- ])
-
- // We expect the first model to be returned, but not the second or third since they are tied to a deceased person
- const expectedModel = await findExpectedMergedDelegationModels(
- delegationModel,
- mockDelegations.incoming.id,
- [Scopes[0].name],
- )
+ beforeAll(async () => {
+ nationalRegistryApiSpy = jest
+ .spyOn(nationalRegistryApi, 'getIndividual')
+ .mockImplementation(async (id) => {
+ if (deceasedNationalIds.includes(id)) {
+ return null
+ }
+
+ const user = nationalRegistryUsers.find(
+ (u) => u?.nationalId === id,
+ )
+
+ return user ?? null
+ })
+ const nationalRegistryV3FeatureService = app.get(
+ NationalRegistryV3FeatureService,
+ )
+ jest
+ .spyOn(nationalRegistryV3FeatureService, 'getValue')
+ .mockImplementation(async () => featureFlag)
+ nationalRegistryV3ApiSpy = jest
+ .spyOn(nationalRegistryV3Api, 'getAllDataIndividual')
+ .mockImplementation(async (id) => {
+ if (deceasedNationalIds.includes(id)) {
+ return {
+ kennitala: id,
+ afdrif: 'LÉST',
+ }
+ }
+
+ const user = nationalRegistryUsers.find(
+ (u) => u?.nationalId === id,
+ )
+
+ return user
+ ? {
+ kennitala: id,
+ nafn: user?.name,
+ afdrif: null,
+ }
+ : null
+ })
+ })
- // Act
- const res = await server.get(
- `${path}${query}&delegationTypes=${AuthDelegationType.Custom}`,
- )
+ it('should return only valid delegations', async () => {
+ // Arrange
+ await createDelegationModels(
+ delegationModel,
+ Object.values(mockDelegations),
+ )
+ const expectedModels = await findExpectedMergedDelegationModels(
+ delegationModel,
+ [
+ mockDelegations.incoming.id,
+ mockDelegations.incomingBothValidAndNotAllowed.id,
+ mockDelegations.incomingWithOtherDomain.id,
+ ],
+ [Scopes[0].name],
+ )
+
+ // Act
+ const res = await server.get(`${path}${query}`)
+
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(3)
+ expectMatchingMergedDelegations(
+ res.body,
+ updateDelegationFromNameToPersonName(
+ expectedModels,
+ nationalRegistryUsers,
+ ),
+ )
+ })
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(1)
- expectMatchingMergedDelegations(
- res.body[0],
- updateDelegationFromNameToPersonName(
- expectedModel,
- nationalRegistryUsers,
- ),
- )
-
- // Verify
- const expectedModifiedModels = await delegationModel.findAll({
- where: {
- toNationalId: user.nationalId,
- },
- include: [
- {
- model: DelegationScope,
- as: 'delegationScopes',
- },
- ],
- })
+ it('should return only delegations with scopes the client has access to', async () => {
+ // Arrange
+ await createDelegationModels(delegationModel, [
+ mockDelegations.incomingWithOtherDomain,
+ ])
+ const expectedModel = await findExpectedMergedDelegationModels(
+ delegationModel,
+ mockDelegations.incomingWithOtherDomain.id,
+ [Scopes[0].name],
+ )
+
+ // Act
+ const res = await server.get(`${path}${query}`)
+
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(1)
+ expectMatchingMergedDelegations(
+ res.body[0],
+ updateDelegationFromNameToPersonName(
+ expectedModel,
+ nationalRegistryUsers,
+ ),
+ )
+ })
- expect(expectedModifiedModels.length).toEqual(1)
- })
+ it('should return custom delegations and general mandate when the delegationTypes filter has both types and delegation exists for both', async () => {
+ // Arrange
+ const delegation = createDelegation({
+ fromNationalId: nationalRegistryUser.nationalId,
+ toNationalId: user.nationalId,
+ scopes: [],
+ })
- it('should not mix up companies and individuals when processing deceased delegations [BUG]', async () => {
- // Arrange
- const incomingCompany = createDelegation({
- fromNationalId: createNationalId('company'),
- toNationalId: user.nationalId,
- scopes: [Scopes[0].name],
- today,
- })
- await createDelegationModels(delegationModel, [
- // The order of these is important to trigger the previous bug.
- incomingCompany,
- mockDelegations.incoming,
- ])
-
- // We expect both models to be returned.
- const expectedModels = await findExpectedMergedDelegationModels(
- delegationModel,
- [mockDelegations.incoming.id, incomingCompany.id],
- [Scopes[0].name],
- )
+ await delegationModel.create(delegation)
- // Act
- const res = await server.get(
- `${path}${query}&delegationTypes=${AuthDelegationType.Custom}`,
- )
+ await delegationDelegationTypeModel.create({
+ delegationId: delegation.id,
+ delegationTypeId: AuthDelegationType.GeneralMandate,
+ })
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(2)
- expectMatchingMergedDelegations(
- res.body,
- updateDelegationFromNameToPersonName(
- expectedModels,
- nationalRegistryUsers,
- ),
- )
- })
+ await createDelegationModels(delegationModel, [
+ mockDelegations.incomingWithOtherDomain,
+ ])
- it('should return delegations which only has scopes with special scope rules [BUG]', async () => {
- // Arrange
- const specialDelegation = createDelegation({
- fromNationalId: nationalRegistryUser.nationalId,
- toNationalId: user.nationalId,
- scopes: [Scopes[3].name],
- today,
- })
- await createDelegationModels(delegationModel, [specialDelegation])
+ // Act
+ const res = await server.get(
+ `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&delegationTypes=${AuthDelegationType.GeneralMandate}`,
+ )
- // We expect the delegation to be returned.
- const expectedModels = await findExpectedMergedDelegationModels(
- delegationModel,
- [specialDelegation.id],
- )
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(2)
+ expect(
+ res.body
+ .map((d: MergedDelegationDTO) => d.types)
+ .flat()
+ .sort(),
+ ).toEqual(
+ [
+ AuthDelegationType.Custom,
+ AuthDelegationType.GeneralMandate,
+ ].sort(),
+ )
+ })
- // Act
- const res = await server.get(
- `${path}${query}&delegationTypes=${AuthDelegationType.Custom}`,
- )
+ it('should return a merged object with both Custom and GeneralMandate types', async () => {
+ // Arrange
+ const delegation = createDelegation({
+ fromNationalId:
+ mockDelegations.incomingWithOtherDomain.fromNationalId,
+ toNationalId: user.nationalId,
+ domainName: null,
+ scopes: [],
+ })
- // Assert
- expect(res.status).toEqual(200)
- expectMatchingMergedDelegations(
- res.body,
- updateDelegationFromNameToPersonName(
- expectedModels,
- nationalRegistryUsers,
- ),
- )
- })
+ await delegationModel.create(delegation)
- it('should return delegations when the delegationTypes filter is empty', async () => {
- // Arrange
- await createDelegationModels(delegationModel, [
- mockDelegations.incomingWithOtherDomain,
- ])
- const expectedModel = await findExpectedMergedDelegationModels(
- delegationModel,
- mockDelegations.incomingWithOtherDomain.id,
- [Scopes[0].name],
- )
+ await delegationDelegationTypeModel.create({
+ delegationId: delegation.id,
+ delegationTypeId: AuthDelegationType.GeneralMandate,
+ })
- // Act
- const res = await server.get(`${path}${query}&delegationTypes=`)
+ await createDelegationModels(delegationModel, [
+ mockDelegations.incomingWithOtherDomain,
+ ])
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(1)
- expectMatchingMergedDelegations(
- res.body[0],
- updateDelegationFromNameToPersonName(
- expectedModel,
- nationalRegistryUsers,
- ),
- )
- })
+ // Act
+ const res = await server.get(
+ `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&delegationTypes=${AuthDelegationType.GeneralMandate}`,
+ )
- it('should not return custom delegations when the delegationTypes filter does not include custom type', async () => {
- // Arrange
- await createDelegationModels(delegationModel, [
- mockDelegations.incomingWithOtherDomain,
- ])
-
- // Act
- const res = await server.get(
- `${path}${query}&delegationTypes=${AuthDelegationType.ProcurationHolder}`,
- )
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(1)
+ expect(
+ res.body
+ .map((d: MergedDelegationDTO) => d.types)
+ .flat()
+ .sort(),
+ ).toEqual(
+ [
+ AuthDelegationType.Custom,
+ AuthDelegationType.GeneralMandate,
+ ].sort(),
+ )
+ })
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(0)
- })
+ it('should return only delegations related to the provided otherUser national id', async () => {
+ // Arrange
+ await createDelegationModels(delegationModel, [
+ mockDelegations.incoming,
+ ])
+ const expectedModel = await findExpectedMergedDelegationModels(
+ delegationModel,
+ mockDelegations.incoming.id,
+ [Scopes[0].name],
+ )
+
+ // Act
+ const res = await server.get(
+ `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&otherUser=${mockDelegations.incoming.fromNationalId}`,
+ )
+
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(1)
+ expectMatchingMergedDelegations(
+ res.body[0],
+ updateDelegationFromNameToPersonName(
+ expectedModel,
+ nationalRegistryUsers,
+ ),
+ )
+ })
- it('should return no delegation when the client does not have access to any scope', async () => {
- // Arrange
- await createDelegationModels(delegationModel, [
- mockDelegations.incomingOnlyOtherDomain,
- ])
+ it('should return only delegations related to the provided otherUser national id without the general mandate since there is none', async () => {
+ // Arrange
+ await createDelegationModels(delegationModel, [
+ mockDelegations.incoming,
+ ])
+ const expectedModel = await findExpectedMergedDelegationModels(
+ delegationModel,
+ mockDelegations.incoming.id,
+ [Scopes[0].name],
+ )
+
+ // Act
+ const res = await server.get(
+ `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&delegationTypes${AuthDelegationType.GeneralMandate}&otherUser=${mockDelegations.incoming.fromNationalId}`,
+ )
+
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(1)
+ expectMatchingMergedDelegations(
+ res.body[0],
+ updateDelegationFromNameToPersonName(
+ expectedModel,
+ nationalRegistryUsers,
+ ),
+ )
+ })
- // Act
- const res = await server.get(`${path}${query}`)
+ it('should return empty array when provided otherUser national id is not related to any delegation', async () => {
+ // Arrange
+ const unrelatedNationalId = createNationalId('person')
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(0)
- })
+ // Act
+ const res = await server.get(
+ `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&otherUser=${unrelatedNationalId}`,
+ )
- it('should return 400 BadRequest if required query paramter is missing', async () => {
- // Act
- const res = await server.get(path)
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(0)
+ })
- // Assert
- expect(res.status).toEqual(400)
- expect(res.body).toMatchObject({
- status: 400,
- type: 'https://httpstatuses.org/400',
- title: 'Bad Request',
- detail:
- "'direction' can only be set to incoming for the /actor alias",
- })
- })
+ it('should return custom delegations and not deceased delegations, when the delegationTypes filter is custom type', async () => {
+ // Arrange
+ await createDelegationModels(delegationModel, [
+ mockDelegations.incoming,
+ createDelegation({
+ fromNationalId: deceasedNationalIds[0],
+ toNationalId: user.nationalId,
+ scopes: [Scopes[0].name],
+ today,
+ }),
+ createDelegation({
+ fromNationalId: deceasedNationalIds[1],
+ toNationalId: user.nationalId,
+ scopes: [Scopes[0].name],
+ today,
+ }),
+ ])
+
+ // We expect the first model to be returned, but not the second or third since they are tied to a deceased person
+ const expectedModel = await findExpectedMergedDelegationModels(
+ delegationModel,
+ mockDelegations.incoming.id,
+ [Scopes[0].name],
+ )
+
+ // Act
+ const res = await server.get(
+ `${path}${query}&delegationTypes=${AuthDelegationType.Custom}`,
+ )
+
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(1)
+ expectMatchingMergedDelegations(
+ res.body[0],
+ updateDelegationFromNameToPersonName(
+ expectedModel,
+ nationalRegistryUsers,
+ ),
+ )
+
+ // Verify
+ const expectedModifiedModels = await delegationModel.findAll({
+ where: {
+ toNationalId: user.nationalId,
+ },
+ include: [
+ {
+ model: DelegationScope,
+ as: 'delegationScopes',
+ },
+ ],
+ })
- it('should not return delegation with no scope longer allowed for delegation', async () => {
- // Arrange
- await createDelegationModels(delegationModel, [
- mockDelegations.incomingWithNoAllowed,
- ])
+ expect(expectedModifiedModels.length).toEqual(1)
+ })
- // Act
- const res = await server.get(`${path}${query}`)
+ it('should not mix up companies and individuals when processing deceased delegations [BUG]', async () => {
+ // Arrange
+ const incomingCompany = createDelegation({
+ fromNationalId: createNationalId('company'),
+ toNationalId: user.nationalId,
+ scopes: [Scopes[0].name],
+ today,
+ })
+ await createDelegationModels(delegationModel, [
+ // The order of these is important to trigger the previous bug.
+ incomingCompany,
+ mockDelegations.incoming,
+ ])
+
+ // We expect both models to be returned.
+ const expectedModels = await findExpectedMergedDelegationModels(
+ delegationModel,
+ [mockDelegations.incoming.id, incomingCompany.id],
+ [Scopes[0].name],
+ )
+
+ // Act
+ const res = await server.get(
+ `${path}${query}&delegationTypes=${AuthDelegationType.Custom}`,
+ )
+
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(2)
+ expectMatchingMergedDelegations(
+ res.body,
+ updateDelegationFromNameToPersonName(
+ expectedModels,
+ nationalRegistryUsers,
+ ),
+ )
+ })
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(0)
- })
+ it('should return delegations which only has scopes with special scope rules [BUG]', async () => {
+ // Arrange
+ const specialDelegation = createDelegation({
+ fromNationalId: nationalRegistryUser.nationalId,
+ toNationalId: user.nationalId,
+ scopes: [Scopes[3].name],
+ today,
+ })
+ await createDelegationModels(delegationModel, [specialDelegation])
+
+ // We expect the delegation to be returned.
+ const expectedModels = await findExpectedMergedDelegationModels(
+ delegationModel,
+ [specialDelegation.id],
+ )
+
+ // Act
+ const res = await server.get(
+ `${path}${query}&delegationTypes=${AuthDelegationType.Custom}`,
+ )
+
+ // Assert
+ expect(res.status).toEqual(200)
+ expectMatchingMergedDelegations(
+ res.body,
+ updateDelegationFromNameToPersonName(
+ expectedModels,
+ nationalRegistryUsers,
+ ),
+ )
+ })
- it('should not return delegation when client does not support custom delegations', async () => {
- // Arrange
- await createDelegationModels(delegationModel, [
- mockDelegations.incoming,
- ])
- await clientDelegationTypeModel.destroy({
- where: {
- clientId: client.clientId,
- delegationType: AuthDelegationType.Custom,
- },
- })
+ it('should return delegations when the delegationTypes filter is empty', async () => {
+ // Arrange
+ await createDelegationModels(delegationModel, [
+ mockDelegations.incomingWithOtherDomain,
+ ])
+ const expectedModel = await findExpectedMergedDelegationModels(
+ delegationModel,
+ mockDelegations.incomingWithOtherDomain.id,
+ [Scopes[0].name],
+ )
+
+ // Act
+ const res = await server.get(`${path}${query}&delegationTypes=`)
+
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(1)
+ expectMatchingMergedDelegations(
+ res.body[0],
+ updateDelegationFromNameToPersonName(
+ expectedModel,
+ nationalRegistryUsers,
+ ),
+ )
+ })
- // Act
- const res = await server.get(`${path}${query}`)
+ it('should not return custom delegations when the delegationTypes filter does not include custom type', async () => {
+ // Arrange
+ await createDelegationModels(delegationModel, [
+ mockDelegations.incomingWithOtherDomain,
+ ])
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(0)
- })
+ // Act
+ const res = await server.get(
+ `${path}${query}&delegationTypes=${AuthDelegationType.ProcurationHolder}`,
+ )
- describe('with legal guardian delegations', () => {
- let getForsja: jest.SpyInstance
- let clientInstance: any
-
- const mockForKt = (kt: string): void => {
- jest.spyOn(kennitala, 'info').mockReturnValue({
- kt,
- age: 16,
- birthday: addYears(Date.now(), -15),
- birthdayReadable: '',
- type: 'person',
- valid: true,
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(0)
})
- jest.spyOn(clientInstance, 'getIndividual').mockResolvedValueOnce({
- nationalId: kt,
- name: nationalRegistryUser.name,
- } as IndividualDto)
-
- jest
- .spyOn(clientInstance, 'getCustodyChildren')
- .mockResolvedValueOnce([kt])
- }
-
- beforeEach(() => {
- clientInstance = app.get(NationalRegistryClientService)
- getForsja = jest
- .spyOn(clientInstance, 'getCustodyChildren')
- .mockResolvedValue([nationalRegistryUser.nationalId])
- })
-
- afterAll(() => {
- getForsja.mockRestore()
- })
+ it('should return no delegation when the client does not have access to any scope', async () => {
+ // Arrange
+ await createDelegationModels(delegationModel, [
+ mockDelegations.incomingOnlyOtherDomain,
+ ])
- it('should return delegations', async () => {
- const kt = '1111089030'
-
- // Arrange
- mockForKt(kt)
-
- const expectedDelegation = DelegationDTOMapper.toMergedDelegationDTO({
- fromName: nationalRegistryUser.name,
- fromNationalId: kt,
- provider: AuthDelegationProvider.NationalRegistry,
- toNationalId: user.nationalId,
- type: [
- AuthDelegationType.LegalGuardian,
- AuthDelegationType.LegalGuardianMinor,
- ],
- } as Omit & { type: AuthDelegationType | AuthDelegationType[] })
-
- // Act
- const res = await server.get(`${path}${query}`)
-
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(1)
- expect(res.body[0]).toEqual(expectedDelegation)
- })
+ // Act
+ const res = await server.get(`${path}${query}`)
- it('should not return delegations when client does not support legal guardian delegations', async () => {
- await clientDelegationTypeModel.destroy({
- where: {
- clientId: client.clientId,
- delegationType: AuthDelegationType.LegalGuardian,
- },
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(0)
})
- // Act
- const res = await server.get(`${path}${query}`)
-
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(0)
- })
-
- it('should return a legal guardian delegation since the type is included in the delegationTypes filter', async () => {
- // Act
- const res = await server.get(
- `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&delegationTypes=${AuthDelegationType.LegalGuardian}`,
- )
+ it('should return 400 BadRequest if required query paramter is missing', async () => {
+ // Act
+ const res = await server.get(path)
+
+ // Assert
+ expect(res.status).toEqual(400)
+ expect(res.body).toMatchObject({
+ status: 400,
+ type: 'https://httpstatuses.org/400',
+ title: 'Bad Request',
+ detail:
+ "'direction' can only be set to incoming for the /actor alias",
+ })
+ })
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(1)
- expect(res.body[0].types[0]).toEqual(AuthDelegationType.LegalGuardian)
- })
+ it('should not return delegation with no scope longer allowed for delegation', async () => {
+ // Arrange
+ await createDelegationModels(delegationModel, [
+ mockDelegations.incomingWithNoAllowed,
+ ])
- it('should not return a legal guardian delegation since the type is not included in the delegationTypes filter', async () => {
- // Act
- const res = await server.get(
- `${path}${query}&delegationTypes=${AuthDelegationType.Custom}`,
- )
+ // Act
+ const res = await server.get(`${path}${query}`)
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(0)
- })
- })
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(0)
+ })
- describe('with procuring delegations', () => {
- let getIndividualRelationships: jest.SpyInstance
- beforeAll(() => {
- const client = app.get(RskRelationshipsClient)
- getIndividualRelationships = jest
- .spyOn(client, 'getIndividualRelationships')
- .mockResolvedValue({
- name: nationalRegistryUser.name,
- nationalId: nationalRegistryUser.nationalId,
- relationships: [
- {
- nationalId: nationalRegistryUser.nationalId,
- name: nationalRegistryUser.name,
- },
- ],
+ it('should not return delegation when client does not support custom delegations', async () => {
+ // Arrange
+ await createDelegationModels(delegationModel, [
+ mockDelegations.incoming,
+ ])
+ await clientDelegationTypeModel.destroy({
+ where: {
+ clientId: client.clientId,
+ delegationType: AuthDelegationType.Custom,
+ },
})
- })
- afterAll(() => {
- getIndividualRelationships.mockRestore()
- })
+ // Act
+ const res = await server.get(`${path}${query}`)
- it('should return delegations', async () => {
- // Arrange
- const expectedDelegation = {
- fromName: nationalRegistryUser.name,
- fromNationalId: nationalRegistryUser.nationalId,
- provider: 'fyrirtaekjaskra',
- toNationalId: user.nationalId,
- type: 'ProcurationHolder',
- } as DelegationDTO
- // Act
- const res = await server.get(`${path}${query}`)
-
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(1)
- expect(res.body[0]).toEqual(
- DelegationDTOMapper.toMergedDelegationDTO(expectedDelegation),
- )
- })
-
- it('should not return delegations when client does not support procuring holder delegations', async () => {
- // Arrange
- await clientDelegationTypeModel.destroy({
- where: {
- clientId: client.clientId,
- delegationType: AuthDelegationType.ProcurationHolder,
- },
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(0)
})
- // Act
- const res = await server.get(`${path}${query}`)
+ describe('with legal guardian delegations', () => {
+ let getForsja: jest.SpyInstance
+ let clientInstance: any
+
+ const mockForKt = (kt: string): void => {
+ jest.spyOn(kennitala, 'info').mockReturnValue({
+ kt,
+ age: 16,
+ birthday: addYears(Date.now(), -15),
+ birthdayReadable: '',
+ type: 'person',
+ valid: true,
+ })
+
+ jest
+ .spyOn(clientInstance, 'getIndividual')
+ .mockResolvedValueOnce({
+ nationalId: kt,
+ name: nationalRegistryUser.name,
+ } as IndividualDto)
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(0)
- })
+ jest
+ .spyOn(clientInstance, 'getCustodyChildren')
+ .mockResolvedValueOnce([kt])
+ }
- it('should return a procuring holder delegation since the type is included in the delegationTypes filter', async () => {
- // Act
- const res = await server.get(
- `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&delegationTypes=${AuthDelegationType.ProcurationHolder}&delegationTypes=${AuthDelegationType.PersonalRepresentative}`,
- )
+ beforeEach(() => {
+ clientInstance = app.get(NationalRegistryClientService)
+ getForsja = jest
+ .spyOn(clientInstance, 'getCustodyChildren')
+ .mockResolvedValue([nationalRegistryUser.nationalId])
+ })
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(1)
- expect(res.body[0].types[0]).toEqual(
- AuthDelegationType.ProcurationHolder,
- )
- })
+ afterAll(() => {
+ getForsja.mockRestore()
+ })
- it('should not return a procuring holder delegation since the type is not included in the delegationTypes filter', async () => {
- // Act
- const res = await server.get(
- `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&delegationTypes=${AuthDelegationType.LegalGuardian}&delegationTypes=${AuthDelegationType.PersonalRepresentative}`,
- )
+ it('should return delegations', async () => {
+ const kt = '1111089030'
+
+ // Arrange
+ mockForKt(kt)
+
+ const expectedDelegation =
+ DelegationDTOMapper.toMergedDelegationDTO({
+ fromName: nationalRegistryUser.name,
+ fromNationalId: kt,
+ provider: AuthDelegationProvider.NationalRegistry,
+ toNationalId: user.nationalId,
+ type: [
+ AuthDelegationType.LegalGuardian,
+ AuthDelegationType.LegalGuardianMinor,
+ ],
+ } as Omit & { type: AuthDelegationType | AuthDelegationType[] })
+
+ // Act
+ const res = await server.get(`${path}${query}`)
+
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(1)
+ expect(res.body[0]).toEqual(expectedDelegation)
+ })
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(0)
- })
- })
+ it('should not return delegations when client does not support legal guardian delegations', async () => {
+ await clientDelegationTypeModel.destroy({
+ where: {
+ clientId: client.clientId,
+ delegationType: AuthDelegationType.LegalGuardian,
+ },
+ })
- describe('when user is a personal representative with one representee', () => {
- let prModel: typeof PersonalRepresentative
- let prRightsModel: typeof PersonalRepresentativeRight
- let prRightTypeModel: typeof PersonalRepresentativeRightType
- let prTypeModel: typeof PersonalRepresentativeType
- let delegationTypeModel: typeof DelegationTypeModel
- let delegationProviderModel: typeof DelegationProviderModel
- let prDelegationTypeModel: typeof PersonalRepresentativeDelegationTypeModel
+ // Act
+ const res = await server.get(`${path}${query}`)
- beforeAll(async () => {
- prTypeModel = app.get(
- 'PersonalRepresentativeTypeRepository',
- )
- prModel = app.get(
- 'PersonalRepresentativeRepository',
- )
- prRightTypeModel = app.get(
- 'PersonalRepresentativeRightTypeRepository',
- )
- prRightsModel = app.get(
- 'PersonalRepresentativeRightRepository',
- )
- delegationTypeModel = app.get(
- getModelToken(DelegationTypeModel),
- )
- delegationProviderModel = app.get(
- getModelToken(DelegationProviderModel),
- )
- prDelegationTypeModel = app.get<
- typeof PersonalRepresentativeDelegationTypeModel
- >(getModelToken(PersonalRepresentativeDelegationTypeModel))
-
- const prType = await prTypeModel.create({
- code: 'prTypeCode',
- name: 'prTypeName',
- description: 'prTypeDescription',
- })
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(0)
+ })
- const pr = await prModel.create({
- nationalIdPersonalRepresentative: user.nationalId,
- nationalIdRepresentedPerson: nationalRegistryUser.nationalId,
- personalRepresentativeTypeCode: prType.code,
- contractId: '1',
- externalUserId: '1',
- })
+ it('should return a legal guardian delegation since the type is included in the delegationTypes filter', async () => {
+ // Act
+ const res = await server.get(
+ `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&delegationTypes=${AuthDelegationType.LegalGuardian}`,
+ )
+
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(1)
+ expect(res.body[0].types[0]).toEqual(
+ AuthDelegationType.LegalGuardian,
+ )
+ })
- const dt = await delegationTypeModel.create({
- id: getPersonalRepresentativeDelegationType('prRightType'),
- providerId: AuthDelegationProvider.PersonalRepresentativeRegistry,
- name: `Personal Representative: prRightType`,
- description: `Personal representative delegation type for right type prRightType`,
- })
+ it('should not return a legal guardian delegation since the type is not included in the delegationTypes filter', async () => {
+ // Act
+ const res = await server.get(
+ `${path}${query}&delegationTypes=${AuthDelegationType.Custom}`,
+ )
- const prRightType = await prRightTypeModel.create({
- code: 'prRightType',
- description: 'prRightTypeDescription',
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(0)
+ })
})
- const prr = await prRightsModel.create({
- rightTypeCode: prRightType.code,
- personalRepresentativeId: pr.id,
- })
+ describe('with procuring delegations', () => {
+ let getIndividualRelationships: jest.SpyInstance
+ beforeAll(() => {
+ const client = app.get(RskRelationshipsClient)
+ getIndividualRelationships = jest
+ .spyOn(client, 'getIndividualRelationships')
+ .mockResolvedValue({
+ name: nationalRegistryUser.name,
+ nationalId: nationalRegistryUser.nationalId,
+ relationships: [
+ {
+ nationalId: nationalRegistryUser.nationalId,
+ name: nationalRegistryUser.name,
+ },
+ ],
+ })
+ })
- await prDelegationTypeModel.create({
- id: prr.id,
- delegationTypeId: dt.id,
- personalRepresentativeId: pr.id,
- })
- })
+ afterAll(() => {
+ getIndividualRelationships.mockRestore()
+ })
- describe('when fetched', () => {
- let response: request.Response
- let body: MergedDelegationDTO[]
+ it('should return delegations', async () => {
+ // Arrange
+ const expectedDelegation = {
+ fromName: nationalRegistryUser.name,
+ fromNationalId: nationalRegistryUser.nationalId,
+ provider: 'fyrirtaekjaskra',
+ toNationalId: user.nationalId,
+ type: 'ProcurationHolder',
+ } as DelegationDTO
+ // Act
+ const res = await server.get(`${path}${query}`)
+
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(1)
+ expect(res.body[0]).toEqual(
+ DelegationDTOMapper.toMergedDelegationDTO(expectedDelegation),
+ )
+ })
- beforeAll(async () => {
- response = await server.get(`${path}${query}`)
- body = response.body
- })
+ it('should not return delegations when client does not support procuring holder delegations', async () => {
+ // Arrange
+ await clientDelegationTypeModel.destroy({
+ where: {
+ clientId: client.clientId,
+ delegationType: AuthDelegationType.ProcurationHolder,
+ },
+ })
- it('should have a an OK return status', () => {
- expect(response.status).toEqual(200)
- })
+ // Act
+ const res = await server.get(`${path}${query}`)
- it('should return a single entity', () => {
- expect(body.length).toEqual(1)
- })
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(0)
+ })
- it('should have the nationalId of the user as the representer', () => {
- expect(
- body.some((d) => d.toNationalId === user.nationalId),
- ).toBeTruthy()
- })
+ it('should return a procuring holder delegation since the type is included in the delegationTypes filter', async () => {
+ // Act
+ const res = await server.get(
+ `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&delegationTypes=${AuthDelegationType.ProcurationHolder}&delegationTypes=${AuthDelegationType.PersonalRepresentative}`,
+ )
+
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(1)
+ expect(res.body[0].types[0]).toEqual(
+ AuthDelegationType.ProcurationHolder,
+ )
+ })
- it('should have the nationalId of the correct representee', () => {
- expect(
- body.some(
- (d) => d.fromNationalId === nationalRegistryUser.nationalId,
- ),
- ).toBeTruthy()
- })
+ it('should not return a procuring holder delegation since the type is not included in the delegationTypes filter', async () => {
+ // Act
+ const res = await server.get(
+ `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&delegationTypes=${AuthDelegationType.LegalGuardian}&delegationTypes=${AuthDelegationType.PersonalRepresentative}`,
+ )
- it('should have the name of the correct representee', () => {
- expect(
- body.some((d) => d.fromName === nationalRegistryUser.name),
- ).toBeTruthy()
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(0)
+ })
})
- it('should have the delegation type claim of PersonalRepresentative', () => {
- expect(
- body.some(
- (d) => d.types[0] === AuthDelegationType.PersonalRepresentative,
- ),
- ).toBeTruthy()
- })
- })
+ describe('when user is a personal representative with one representee', () => {
+ let prModel: typeof PersonalRepresentative
+ let prRightsModel: typeof PersonalRepresentativeRight
+ let prRightTypeModel: typeof PersonalRepresentativeRightType
+ let prTypeModel: typeof PersonalRepresentativeType
+ let delegationTypeModel: typeof DelegationTypeModel
+ let delegationProviderModel: typeof DelegationProviderModel
+ let prDelegationTypeModel: typeof PersonalRepresentativeDelegationTypeModel
+
+ beforeAll(async () => {
+ prTypeModel = app.get(
+ 'PersonalRepresentativeTypeRepository',
+ )
+ prModel = app.get(
+ 'PersonalRepresentativeRepository',
+ )
+ prRightTypeModel = app.get<
+ typeof PersonalRepresentativeRightType
+ >('PersonalRepresentativeRightTypeRepository')
+ prRightsModel = app.get(
+ 'PersonalRepresentativeRightRepository',
+ )
+ delegationTypeModel = app.get(
+ getModelToken(DelegationTypeModel),
+ )
+ delegationProviderModel = app.get(
+ getModelToken(DelegationProviderModel),
+ )
+ prDelegationTypeModel = app.get<
+ typeof PersonalRepresentativeDelegationTypeModel
+ >(getModelToken(PersonalRepresentativeDelegationTypeModel))
+
+ const prType = await prTypeModel.create({
+ code: 'prTypeCode',
+ name: 'prTypeName',
+ description: 'prTypeDescription',
+ })
+
+ const pr = await prModel.create({
+ nationalIdPersonalRepresentative: user.nationalId,
+ nationalIdRepresentedPerson: nationalRegistryUser.nationalId,
+ personalRepresentativeTypeCode: prType.code,
+ contractId: '1',
+ externalUserId: '1',
+ })
+
+ const dt = await delegationTypeModel.create({
+ id: getPersonalRepresentativeDelegationType('prRightType'),
+ providerId:
+ AuthDelegationProvider.PersonalRepresentativeRegistry,
+ name: `Personal Representative: prRightType`,
+ description: `Personal representative delegation type for right type prRightType`,
+ })
+
+ const prRightType = await prRightTypeModel.create({
+ code: 'prRightType',
+ description: 'prRightTypeDescription',
+ })
+
+ const prr = await prRightsModel.create({
+ rightTypeCode: prRightType.code,
+ personalRepresentativeId: pr.id,
+ })
+
+ await prDelegationTypeModel.create({
+ id: prr.id,
+ delegationTypeId: dt.id,
+ personalRepresentativeId: pr.id,
+ })
+ })
- it('should not return delegations when client does not support personal representative delegations', async () => {
- // Prepare
- await clientDelegationTypeModel.destroy({
- where: {
- clientId: client.clientId,
- delegationType: AuthDelegationType.PersonalRepresentative,
- },
- })
+ describe('when fetched', () => {
+ let response: request.Response
+ let body: MergedDelegationDTO[]
+
+ beforeAll(async () => {
+ response = await server.get(`${path}${query}`)
+ body = response.body
+ })
+
+ it('should have a an OK return status', () => {
+ expect(response.status).toEqual(200)
+ })
+
+ it('should return a single entity', () => {
+ expect(body.length).toEqual(1)
+ })
+
+ it('should have the nationalId of the user as the representer', () => {
+ expect(
+ body.some((d) => d.toNationalId === user.nationalId),
+ ).toBeTruthy()
+ })
+
+ it('should have the nationalId of the correct representee', () => {
+ expect(
+ body.some(
+ (d) => d.fromNationalId === nationalRegistryUser.nationalId,
+ ),
+ ).toBeTruthy()
+ })
+
+ it('should have the name of the correct representee', () => {
+ expect(
+ body.some((d) => d.fromName === nationalRegistryUser.name),
+ ).toBeTruthy()
+ })
+
+ it('should have the delegation type claim of PersonalRepresentative', () => {
+ expect(
+ body.some(
+ (d) =>
+ d.types[0] === AuthDelegationType.PersonalRepresentative,
+ ),
+ ).toBeTruthy()
+ })
+ })
- // Act
- const res = await server.get(`${path}${query}`)
+ it('should not return delegations when client does not support personal representative delegations', async () => {
+ // Prepare
+ await clientDelegationTypeModel.destroy({
+ where: {
+ clientId: client.clientId,
+ delegationType: AuthDelegationType.PersonalRepresentative,
+ },
+ })
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(0)
- })
+ // Act
+ const res = await server.get(`${path}${query}`)
- it('should return a personal representative delegation since the type is included in the delegationTypes filter', async () => {
- // Act
- const res = await server.get(
- `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&delegationTypes=${AuthDelegationType.ProcurationHolder}&delegationTypes=${AuthDelegationType.PersonalRepresentative}`,
- )
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(0)
+ })
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(1)
- expect(res.body[0].types[0]).toEqual(
- AuthDelegationType.PersonalRepresentative,
- )
- })
+ it('should return a personal representative delegation since the type is included in the delegationTypes filter', async () => {
+ // Act
+ const res = await server.get(
+ `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&delegationTypes=${AuthDelegationType.ProcurationHolder}&delegationTypes=${AuthDelegationType.PersonalRepresentative}`,
+ )
+
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(1)
+ expect(res.body[0].types[0]).toEqual(
+ AuthDelegationType.PersonalRepresentative,
+ )
+ })
- it('should not return a personal representative delegation since the type is not included in the delegationTypes filter', async () => {
- // Act
- const res = await server.get(
- `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&delegationTypes=${AuthDelegationType.LegalGuardian}&delegationTypes=${AuthDelegationType.ProcurationHolder}`,
- )
+ it('should not return a personal representative delegation since the type is not included in the delegationTypes filter', async () => {
+ // Act
+ const res = await server.get(
+ `${path}${query}&delegationTypes=${AuthDelegationType.Custom}&delegationTypes=${AuthDelegationType.LegalGuardian}&delegationTypes=${AuthDelegationType.ProcurationHolder}`,
+ )
- // Assert
- expect(res.status).toEqual(200)
- expect(res.body).toHaveLength(0)
- })
+ // Assert
+ expect(res.status).toEqual(200)
+ expect(res.body).toHaveLength(0)
+ })
- afterAll(async () => {
- await prRightsModel.destroy({
- where: {},
- cascade: true,
- truncate: true,
- force: true,
- })
- await prRightTypeModel.destroy({
- where: {},
- cascade: true,
- truncate: true,
- force: true,
- })
- await prModel.destroy({
- where: {},
- cascade: true,
- truncate: true,
- force: true,
- })
- await prTypeModel.destroy({
- where: {},
- cascade: true,
- truncate: true,
- force: true,
- })
- await prDelegationTypeModel.destroy({
- where: {},
- cascade: true,
- truncate: true,
- force: true,
- })
- await delegationTypeModel.destroy({
- where: {},
- cascade: true,
- truncate: true,
- force: true,
- })
- await delegationProviderModel.destroy({
- where: {},
- cascade: true,
- truncate: true,
- force: true,
+ afterAll(async () => {
+ await prRightsModel.destroy({
+ where: {},
+ cascade: true,
+ truncate: true,
+ force: true,
+ })
+ await prRightTypeModel.destroy({
+ where: {},
+ cascade: true,
+ truncate: true,
+ force: true,
+ })
+ await prModel.destroy({
+ where: {},
+ cascade: true,
+ truncate: true,
+ force: true,
+ })
+ await prTypeModel.destroy({
+ where: {},
+ cascade: true,
+ truncate: true,
+ force: true,
+ })
+ await prDelegationTypeModel.destroy({
+ where: {},
+ cascade: true,
+ truncate: true,
+ force: true,
+ })
+ await delegationTypeModel.destroy({
+ where: {},
+ cascade: true,
+ truncate: true,
+ force: true,
+ })
+ await delegationProviderModel.destroy({
+ where: {},
+ cascade: true,
+ truncate: true,
+ force: true,
+ })
+ })
})
})
})
- })
- })
+ },
+ )
describe('without auth and permission', () => {
it.each`
diff --git a/apps/web/layouts/organization/standalone/components/AlertBanner.tsx b/apps/web/layouts/organization/standalone/components/AlertBanner.tsx
new file mode 100644
index 000000000000..32d09cec0150
--- /dev/null
+++ b/apps/web/layouts/organization/standalone/components/AlertBanner.tsx
@@ -0,0 +1,56 @@
+import Cookies from 'js-cookie'
+
+import { AlertBanner, AlertBannerVariants } from '@island.is/island-ui/core'
+import { stringHash } from '@island.is/shared/utils'
+import { OrganizationPage } from '@island.is/web/graphql/schema'
+import { linkResolver, LinkType } from '@island.is/web/hooks'
+import { useI18n } from '@island.is/web/i18n'
+
+interface StandaloneAlertBannerProps {
+ alertBanner: OrganizationPage['alertBanner'] | null | undefined
+}
+
+export const StandaloneAlertBanner = ({
+ alertBanner,
+}: StandaloneAlertBannerProps) => {
+ const { activeLocale } = useI18n()
+
+ if (!alertBanner) {
+ return null
+ }
+
+ const bannerId = `standalone-alert-${stringHash(
+ JSON.stringify(alertBanner ?? {}),
+ )}`
+
+ if (Cookies.get(bannerId) || !alertBanner.showAlertBanner) {
+ return null
+ }
+
+ return (
+ {
+ if (alertBanner.dismissedForDays !== 0) {
+ Cookies.set(bannerId, 'hide', {
+ expires: alertBanner.dismissedForDays,
+ })
+ }
+ }}
+ closeButtonLabel={activeLocale === 'is' ? 'Loka' : 'Close'}
+ link={
+ alertBanner.linkTitle && alertBanner.link?.type && alertBanner.link.slug
+ ? {
+ href: linkResolver(alertBanner.link.type as LinkType, [
+ alertBanner.link.slug,
+ ]).href,
+ title: alertBanner.linkTitle,
+ }
+ : undefined
+ }
+ />
+ )
+}
diff --git a/apps/web/layouts/organization/standalone/standalone.tsx b/apps/web/layouts/organization/standalone/standalone.tsx
index 9007c3db63a1..5053000cbff6 100644
--- a/apps/web/layouts/organization/standalone/standalone.tsx
+++ b/apps/web/layouts/organization/standalone/standalone.tsx
@@ -15,6 +15,7 @@ import { useLinkResolver } from '@island.is/web/hooks'
import { useI18n } from '@island.is/web/i18n'
import { getBackgroundStyle } from '@island.is/web/utils/organization'
+import { StandaloneAlertBanner } from './components/AlertBanner'
import { Header, HeaderProps } from './components/Header'
import { Navigation, NavigationProps } from './components/Navigation'
@@ -204,6 +205,7 @@ export const StandaloneLayout = ({
}
/>
+
diff --git a/apps/web/pages/s/[...slugs]/index.tsx b/apps/web/pages/s/[...slugs]/index.tsx
index 8c2cbde982af..5632dbe156ba 100644
--- a/apps/web/pages/s/[...slugs]/index.tsx
+++ b/apps/web/pages/s/[...slugs]/index.tsx
@@ -31,6 +31,9 @@ import PublishedMaterial, {
import StandaloneHome, {
type StandaloneHomeProps,
} from '@island.is/web/screens/Organization/Standalone/Home'
+import StandaloneParentSubpage, {
+ StandaloneParentSubpageProps,
+} from '@island.is/web/screens/Organization/Standalone/ParentSubpage'
import SubPage, {
type SubPageProps,
} from '@island.is/web/screens/Organization/SubPage'
@@ -42,6 +45,7 @@ import { getServerSidePropsWrapper } from '@island.is/web/utils/getServerSidePro
enum PageType {
FRONTPAGE = 'frontpage',
STANDALONE_FRONTPAGE = 'standalone-frontpage',
+ STANDALONE_PARENT_SUBPAGE = 'standalone-parent-subpage',
SUBPAGE = 'subpage',
ALL_NEWS = 'news',
PUBLISHED_MATERIAL = 'published-material',
@@ -55,6 +59,9 @@ enum PageType {
const pageMap: Record> = {
[PageType.FRONTPAGE]: (props) => ,
[PageType.STANDALONE_FRONTPAGE]: (props) => ,
+ [PageType.STANDALONE_PARENT_SUBPAGE]: (props) => (
+
+ ),
[PageType.SUBPAGE]: (props) => ,
[PageType.ALL_NEWS]: (props) => ,
[PageType.PUBLISHED_MATERIAL]: (props) => ,
@@ -79,6 +86,10 @@ interface Props {
type: PageType.STANDALONE_FRONTPAGE
props: StandaloneHomeProps
}
+ | {
+ type: PageType.STANDALONE_PARENT_SUBPAGE
+ props: StandaloneParentSubpageProps
+ }
| {
type: PageType.SUBPAGE
props: {
@@ -135,32 +146,32 @@ Component.getProps = async (context) => {
const slugs = context.query.slugs as string[]
const locale = context.locale || 'is'
- // Frontpage
- if (slugs.length === 1) {
- const {
- data: { getOrganizationPage: organizationPage },
- } = await context.apolloClient.query({
- query: GET_ORGANIZATION_PAGE_QUERY,
- variables: {
- input: {
- slug: slugs[0],
- lang: locale,
- },
+ const {
+ data: { getOrganizationPage: organizationPage },
+ } = await context.apolloClient.query({
+ query: GET_ORGANIZATION_PAGE_QUERY,
+ variables: {
+ input: {
+ slug: slugs[0],
+ lang: locale,
},
- })
+ },
+ })
- if (!organizationPage) {
- throw new CustomNextError(404)
- }
+ if (!organizationPage) {
+ throw new CustomNextError(404, 'Organization page was not found')
+ }
+
+ const modifiedContext = { ...context, organizationPage }
+
+ const STANDALONE_THEME = 'standalone'
- if (organizationPage.theme === 'standalone') {
+ if (slugs.length === 1) {
+ if (organizationPage.theme === STANDALONE_THEME) {
return {
page: {
type: PageType.STANDALONE_FRONTPAGE,
- props: await StandaloneHome.getProps({
- ...context,
- organizationPage,
- }),
+ props: await StandaloneHome.getProps(modifiedContext),
},
}
}
@@ -168,7 +179,7 @@ Component.getProps = async (context) => {
return {
page: {
type: PageType.FRONTPAGE,
- props: await Home.getProps({ ...context, organizationPage }),
+ props: await Home.getProps(modifiedContext),
},
}
}
@@ -179,7 +190,7 @@ Component.getProps = async (context) => {
return {
page: {
type: PageType.ALL_NEWS,
- props: await OrganizationNewsList.getProps(context),
+ props: await OrganizationNewsList.getProps(modifiedContext),
},
}
}
@@ -187,7 +198,7 @@ Component.getProps = async (context) => {
return {
page: {
type: PageType.ALL_EVENTS,
- props: await OrganizationEventList.getProps(context),
+ props: await OrganizationEventList.getProps(modifiedContext),
},
}
}
@@ -195,7 +206,7 @@ Component.getProps = async (context) => {
return {
page: {
type: PageType.PUBLISHED_MATERIAL,
- props: await PublishedMaterial.getProps(context),
+ props: await PublishedMaterial.getProps(modifiedContext),
},
}
}
@@ -204,7 +215,7 @@ Component.getProps = async (context) => {
return {
page: {
type: PageType.ALL_NEWS,
- props: await OrganizationNewsList.getProps(context),
+ props: await OrganizationNewsList.getProps(modifiedContext),
},
}
}
@@ -212,7 +223,7 @@ Component.getProps = async (context) => {
return {
page: {
type: PageType.ALL_EVENTS,
- props: await OrganizationEventList.getProps(context),
+ props: await OrganizationEventList.getProps(modifiedContext),
},
}
}
@@ -220,18 +231,25 @@ Component.getProps = async (context) => {
return {
page: {
type: PageType.PUBLISHED_MATERIAL,
- props: await PublishedMaterial.getProps(context),
+ props: await PublishedMaterial.getProps(modifiedContext),
},
}
}
}
- // Subpage
- const props = await SubPage.getProps(context)
+ if (organizationPage.theme === STANDALONE_THEME) {
+ return {
+ page: {
+ type: PageType.STANDALONE_PARENT_SUBPAGE,
+ props: await StandaloneParentSubpage.getProps(modifiedContext),
+ },
+ }
+ }
+
return {
page: {
type: PageType.SUBPAGE,
- props,
+ props: await SubPage.getProps(modifiedContext),
},
}
}
@@ -242,7 +260,7 @@ Component.getProps = async (context) => {
return {
page: {
type: PageType.NEWS_DETAILS,
- props: await OrganizationNewsArticle.getProps(context),
+ props: await OrganizationNewsArticle.getProps(modifiedContext),
},
}
}
@@ -250,7 +268,7 @@ Component.getProps = async (context) => {
return {
page: {
type: PageType.EVENT_DETAILS,
- props: await OrganizationEventArticle.getProps(context),
+ props: await OrganizationEventArticle.getProps(modifiedContext),
},
}
}
@@ -259,7 +277,7 @@ Component.getProps = async (context) => {
return {
page: {
type: PageType.NEWS_DETAILS,
- props: await OrganizationNewsArticle.getProps(context),
+ props: await OrganizationNewsArticle.getProps(modifiedContext),
},
}
}
@@ -267,16 +285,27 @@ Component.getProps = async (context) => {
return {
page: {
type: PageType.EVENT_DETAILS,
- props: await OrganizationEventArticle.getProps(context),
+ props: await OrganizationEventArticle.getProps(modifiedContext),
},
}
}
}
+ if (organizationPage.theme === STANDALONE_THEME) {
+ return {
+ page: {
+ type: PageType.STANDALONE_PARENT_SUBPAGE,
+ props: await StandaloneParentSubpage.getProps(modifiedContext),
+ },
+ }
+ }
+
return {
page: {
type: PageType.GENERIC_LIST_ITEM,
- props: await OrganizationSubPageGenericListItem.getProps(context),
+ props: await OrganizationSubPageGenericListItem.getProps(
+ modifiedContext,
+ ),
},
}
}
diff --git a/apps/web/screens/GenericList/OrganizationSubPageGenericListItem.tsx b/apps/web/screens/GenericList/OrganizationSubPageGenericListItem.tsx
index 7bba1474eef6..062fca30e94b 100644
--- a/apps/web/screens/GenericList/OrganizationSubPageGenericListItem.tsx
+++ b/apps/web/screens/GenericList/OrganizationSubPageGenericListItem.tsx
@@ -1,10 +1,11 @@
import { useMemo } from 'react'
import { useRouter } from 'next/router'
+import { Query } from '@island.is/web/graphql/schema'
import { useLinkResolver } from '@island.is/web/hooks'
import { useI18n } from '@island.is/web/i18n'
-import { type LayoutProps, withMainLayout } from '@island.is/web/layouts/main'
-import { Screen } from '@island.is/web/types'
+import { type LayoutProps } from '@island.is/web/layouts/main'
+import { Screen, ScreenContext } from '@island.is/web/types'
import SubPage, { type SubPageProps } from '../Organization/SubPage'
import GenericListItemPage, {
@@ -19,8 +20,13 @@ export interface OrganizationSubPageGenericListItemProps {
genericListItemProps: GenericListItemPageProps
}
+type OrganizationSubPageGenericListItemScreenContext = ScreenContext & {
+ organizationPage?: Query['getOrganizationPage']
+}
+
const OrganizationSubPageGenericListItem: Screen<
- OrganizationSubPageGenericListItemProps
+ OrganizationSubPageGenericListItemProps,
+ OrganizationSubPageGenericListItemScreenContext
> = (props) => {
const { organizationPage, subpage } = props.parentProps.componentProps
const router = useRouter()
diff --git a/apps/web/screens/Organization/OrganizationEvents/OrganizationEventArticle.tsx b/apps/web/screens/Organization/OrganizationEvents/OrganizationEventArticle.tsx
index 760d40330a9e..c8295301cdfa 100644
--- a/apps/web/screens/Organization/OrganizationEvents/OrganizationEventArticle.tsx
+++ b/apps/web/screens/Organization/OrganizationEvents/OrganizationEventArticle.tsx
@@ -41,7 +41,7 @@ import { useWindowSize } from '@island.is/web/hooks/useViewport'
import { useI18n } from '@island.is/web/i18n'
import { useDateUtils } from '@island.is/web/i18n/useDateUtils'
import { withMainLayout } from '@island.is/web/layouts/main'
-import type { Screen } from '@island.is/web/types'
+import type { Screen, ScreenContext } from '@island.is/web/types'
import { CustomNextError } from '@island.is/web/units/errors'
import { extractNamespaceFromOrganization } from '@island.is/web/utils/extractNamespaceFromOrganization'
import { getOrganizationSidebarNavigationItems } from '@island.is/web/utils/organization'
@@ -141,12 +141,14 @@ export interface OrganizationEventArticleProps {
locale: Locale
}
-const OrganizationEventArticle: Screen = ({
- organizationPage,
- event,
- namespace,
- locale,
-}) => {
+type OrganizationEventArticleScreenContext = ScreenContext & {
+ organizationPage?: Query['getOrganizationPage']
+}
+
+const OrganizationEventArticle: Screen<
+ OrganizationEventArticleProps,
+ OrganizationEventArticleScreenContext
+> = ({ organizationPage, event, namespace, locale }) => {
const n = useNamespace(namespace)
const router = useRouter()
@@ -285,19 +287,26 @@ const OrganizationEventArticle: Screen = ({
>
)
}
-OrganizationEventArticle.getProps = async ({ apolloClient, query, locale }) => {
+OrganizationEventArticle.getProps = async ({
+ apolloClient,
+ query,
+ locale,
+ organizationPage: _organizationPage,
+}) => {
const [organizationPageSlug, _, eventSlug] = query.slugs as string[]
const [organizationPageResponse, eventResponse, namespace] =
await Promise.all([
- apolloClient.query({
- query: GET_ORGANIZATION_PAGE_QUERY,
- variables: {
- input: {
- slug: organizationPageSlug,
- lang: locale as Locale,
- },
- },
- }),
+ !_organizationPage
+ ? apolloClient.query({
+ query: GET_ORGANIZATION_PAGE_QUERY,
+ variables: {
+ input: {
+ slug: organizationPageSlug,
+ lang: locale as Locale,
+ },
+ },
+ })
+ : { data: { getOrganizationPage: _organizationPage } },
apolloClient.query({
query: GET_SINGLE_EVENT_QUERY,
variables: {
diff --git a/apps/web/screens/Organization/OrganizationNews/OrganizationNewsArticle.tsx b/apps/web/screens/Organization/OrganizationNews/OrganizationNewsArticle.tsx
index d5da623eb539..24bf3f060b60 100644
--- a/apps/web/screens/Organization/OrganizationNews/OrganizationNewsArticle.tsx
+++ b/apps/web/screens/Organization/OrganizationNews/OrganizationNewsArticle.tsx
@@ -28,7 +28,7 @@ import {
GET_ORGANIZATION_PAGE_QUERY,
GET_SINGLE_NEWS_ITEM_QUERY,
} from '@island.is/web/screens/queries'
-import { Screen } from '@island.is/web/types'
+import type { Screen, ScreenContext } from '@island.is/web/types'
import { CustomNextError } from '@island.is/web/units/errors'
import { extractNamespaceFromOrganization } from '@island.is/web/utils/extractNamespaceFromOrganization'
@@ -39,12 +39,14 @@ export interface OrganizationNewsArticleProps {
locale: Locale
}
-const OrganizationNewsArticle: Screen = ({
- newsItem,
- namespace,
- organizationPage,
- locale,
-}) => {
+type OrganizationNewsArticleScreenContext = ScreenContext & {
+ organizationPage?: Query['getOrganizationPage']
+}
+
+const OrganizationNewsArticle: Screen<
+ OrganizationNewsArticleProps,
+ OrganizationNewsArticleScreenContext
+> = ({ newsItem, namespace, organizationPage, locale }) => {
const router = useRouter()
const { linkResolver } = useLinkResolver()
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -163,22 +165,27 @@ const OrganizationNewsArticle: Screen = ({
)
}
-OrganizationNewsArticle.getProps = async ({ apolloClient, locale, query }) => {
+OrganizationNewsArticle.getProps = async ({
+ apolloClient,
+ locale,
+ query,
+ organizationPage: _organizationPage,
+}) => {
const [organizationPageSlug, _, newsSlug] = query.slugs as string[]
- const organizationPage = (
- await Promise.resolve(
- apolloClient.query({
- query: GET_ORGANIZATION_PAGE_QUERY,
- variables: {
- input: {
- slug: organizationPageSlug,
- lang: locale as Locale,
+ const organizationPage = !_organizationPage
+ ? (
+ await apolloClient.query({
+ query: GET_ORGANIZATION_PAGE_QUERY,
+ variables: {
+ input: {
+ slug: organizationPageSlug,
+ lang: locale as Locale,
+ },
},
- },
- }),
- )
- ).data?.getOrganizationPage
+ })
+ ).data?.getOrganizationPage
+ : _organizationPage
if (!organizationPage) {
throw new CustomNextError(
diff --git a/apps/web/screens/Organization/Standalone/ParentSubpage.tsx b/apps/web/screens/Organization/Standalone/ParentSubpage.tsx
new file mode 100644
index 000000000000..ad4e08f629c9
--- /dev/null
+++ b/apps/web/screens/Organization/Standalone/ParentSubpage.tsx
@@ -0,0 +1,251 @@
+import { useRouter } from 'next/router'
+
+import {
+ Breadcrumbs,
+ GridColumn,
+ GridContainer,
+ GridRow,
+ Stack,
+ TableOfContents,
+ Text,
+} from '@island.is/island-ui/core'
+import {
+ ContentLanguage,
+ OrganizationPage,
+ OrganizationParentSubpage,
+ OrganizationSubpage,
+ Query,
+ QueryGetNamespaceArgs,
+ QueryGetOrganizationPageArgs,
+ QueryGetOrganizationParentSubpageArgs,
+ QueryGetOrganizationSubpageArgs,
+} from '@island.is/web/graphql/schema'
+import { useLinkResolver } from '@island.is/web/hooks'
+import { useI18n } from '@island.is/web/i18n'
+import { StandaloneLayout } from '@island.is/web/layouts/organization/standalone'
+import type { Screen, ScreenContext } from '@island.is/web/types'
+import { CustomNextError } from '@island.is/web/units/errors'
+
+import {
+ GET_NAMESPACE_QUERY,
+ GET_ORGANIZATION_PAGE_QUERY,
+ GET_ORGANIZATION_PARENT_SUBPAGE_QUERY,
+ GET_ORGANIZATION_SUBPAGE_QUERY,
+} from '../../queries'
+import { SubPageContent } from '../SubPage'
+
+type StandaloneParentSubpageScreenContext = ScreenContext & {
+ organizationPage?: Query['getOrganizationPage']
+}
+
+export interface StandaloneParentSubpageProps {
+ organizationPage: OrganizationPage
+ subpage: OrganizationSubpage
+ tableOfContentHeadings: {
+ headingId: string
+ headingTitle: string
+ label: string
+ href: string
+ }[]
+ selectedHeadingId: string
+ parentSubpage: OrganizationParentSubpage
+ namespace: Record
+}
+
+const StandaloneParentSubpage: Screen<
+ StandaloneParentSubpageProps,
+ StandaloneParentSubpageScreenContext
+> = ({
+ organizationPage,
+ parentSubpage,
+ selectedHeadingId,
+ subpage,
+ tableOfContentHeadings,
+ namespace,
+}) => {
+ const router = useRouter()
+ const { activeLocale } = useI18n()
+ const { linkResolver } = useLinkResolver()
+
+ return (
+
+
+
+
+
+
+ {parentSubpage.childLinks.length > 1 && (
+
+
+ {parentSubpage.title}
+
+ {
+ const href = tableOfContentHeadings.find(
+ (heading) => heading.headingId === headingId,
+ )?.href
+ if (href) {
+ router.push(href)
+ }
+ }}
+ tableOfContentsTitle={
+ namespace?.['standaloneTableOfContentsTitle'] ??
+ activeLocale === 'is'
+ ? 'Efnisyfirlit'
+ : 'Table of contents'
+ }
+ selectedHeadingId={selectedHeadingId}
+ />
+
+ )}
+
+
+
+ 1 ? 'h2' : 'h1'
+ }
+ />
+
+
+ )
+}
+
+StandaloneParentSubpage.getProps = async ({
+ apolloClient,
+ locale,
+ query,
+ organizationPage,
+}) => {
+ const [organizationPageSlug, parentSubpageSlug, subpageSlug] = (query.slugs ??
+ []) as string[]
+
+ const [
+ {
+ data: { getOrganizationPage },
+ },
+ {
+ data: { getOrganizationParentSubpage },
+ },
+ namespace,
+ ] = await Promise.all([
+ !organizationPage
+ ? apolloClient.query({
+ query: GET_ORGANIZATION_PAGE_QUERY,
+ variables: {
+ input: {
+ slug: organizationPageSlug,
+ lang: locale as ContentLanguage,
+ },
+ },
+ })
+ : {
+ data: { getOrganizationPage: organizationPage },
+ },
+ apolloClient.query({
+ query: GET_ORGANIZATION_PARENT_SUBPAGE_QUERY,
+ variables: {
+ input: {
+ organizationPageSlug: organizationPageSlug as string,
+ slug: parentSubpageSlug as string,
+ lang: locale as ContentLanguage,
+ },
+ },
+ }),
+ apolloClient
+ .query({
+ query: GET_NAMESPACE_QUERY,
+ variables: {
+ input: {
+ namespace: 'OrganizationPages',
+ lang: locale,
+ },
+ },
+ })
+ .then((variables) =>
+ variables.data.getNamespace?.fields
+ ? JSON.parse(variables.data.getNamespace.fields)
+ : {},
+ ),
+ ])
+
+ if (!getOrganizationPage) {
+ throw new CustomNextError(404, 'Organization page not found')
+ }
+
+ if (!getOrganizationParentSubpage) {
+ throw new CustomNextError(404, 'Organization parent subpage was not found')
+ }
+
+ let selectedIndex = 0
+
+ if (subpageSlug) {
+ const index = getOrganizationParentSubpage.childLinks.findIndex(
+ (link) => link.href.split('/').pop() === subpageSlug,
+ )
+ if (index >= 0) {
+ selectedIndex = index
+ } else {
+ throw new CustomNextError(
+ 404,
+ 'Subpage belonging to an organization parent subpage was not found',
+ )
+ }
+ }
+
+ const subpage = (
+ await apolloClient.query({
+ query: GET_ORGANIZATION_SUBPAGE_QUERY,
+ variables: {
+ input: {
+ organizationSlug: organizationPageSlug,
+ slug: getOrganizationParentSubpage.childLinks[selectedIndex].href
+ .split('/')
+ .pop() as string,
+ lang: locale as ContentLanguage,
+ },
+ },
+ })
+ ).data.getOrganizationSubpage
+
+ if (!subpage) {
+ throw new CustomNextError(
+ 404,
+ 'Subpage belonging to an organization parent subpage was not found',
+ )
+ }
+
+ const tableOfContentHeadings = getOrganizationParentSubpage.childLinks.map(
+ (link) => ({
+ headingId: link.href,
+ headingTitle: link.label,
+ label: link.label,
+ href: link.href,
+ }),
+ )
+ const selectedHeadingId = tableOfContentHeadings[selectedIndex].headingId
+
+ return {
+ organizationPage: getOrganizationPage,
+ parentSubpage: getOrganizationParentSubpage,
+ subpage,
+ tableOfContentHeadings,
+ selectedHeadingId,
+ namespace,
+ }
+}
+
+export default StandaloneParentSubpage
diff --git a/apps/web/screens/Organization/SubPage.tsx b/apps/web/screens/Organization/SubPage.tsx
index 6d1dc7697cb3..6d81780073b2 100644
--- a/apps/web/screens/Organization/SubPage.tsx
+++ b/apps/web/screens/Organization/SubPage.tsx
@@ -43,7 +43,7 @@ import { extractNamespaceFromOrganization } from '@island.is/web/utils/extractNa
import { webRichText } from '@island.is/web/utils/richText'
import { safelyExtractPathnameFromUrl } from '@island.is/web/utils/safelyExtractPathnameFromUrl'
-import { Screen } from '../../types'
+import { Screen, ScreenContext } from '../../types'
import {
GET_NAMESPACE_QUERY,
GET_ORGANIZATION_PAGE_QUERY,
@@ -61,11 +61,14 @@ export interface SubPageProps {
customContentfulIds?: (string | undefined)[]
}
-const SubPageContent = ({
+export const SubPageContent = ({
subpage,
namespace,
organizationPage,
-}: Pick) => {
+ subpageTitleVariant = 'h1',
+}: Pick & {
+ subpageTitleVariant?: 'h1' | 'h2'
+}) => {
const n = useNamespace(namespace)
const { activeLocale } = useI18n()
const content = (
@@ -132,7 +135,10 @@ const SubPageContent = ({
>
<>
-
+
{subpage?.title}
@@ -208,7 +214,11 @@ const SubPageContent = ({
)
}
-const SubPage: Screen = ({
+type SubPageScreenContext = ScreenContext & {
+ organizationPage?: Query['getOrganizationPage']
+}
+
+const SubPage: Screen = ({
organizationPage,
subpage,
namespace,
@@ -357,7 +367,13 @@ const renderSlices = (
}
}
-SubPage.getProps = async ({ apolloClient, locale, query, req }) => {
+SubPage.getProps = async ({
+ apolloClient,
+ locale,
+ query,
+ req,
+ organizationPage,
+}) => {
const pathname = safelyExtractPathnameFromUrl(req.url)
const { slug, subSlug } = getSlugAndSubSlug(query, pathname)
@@ -370,15 +386,19 @@ SubPage.getProps = async ({ apolloClient, locale, query, req }) => {
},
namespace,
] = await Promise.all([
- apolloClient.query({
- query: GET_ORGANIZATION_PAGE_QUERY,
- variables: {
- input: {
- slug: slug as string,
- lang: locale as ContentLanguage,
+ !organizationPage
+ ? apolloClient.query({
+ query: GET_ORGANIZATION_PAGE_QUERY,
+ variables: {
+ input: {
+ slug: slug as string,
+ lang: locale as ContentLanguage,
+ },
+ },
+ })
+ : {
+ data: { getOrganizationPage: organizationPage },
},
- },
- }),
apolloClient.query({
query: GET_ORGANIZATION_SUBPAGE_QUERY,
variables: {
diff --git a/apps/web/screens/queries/Organization.tsx b/apps/web/screens/queries/Organization.tsx
index f5a6cd3c5e92..fea842378a77 100644
--- a/apps/web/screens/queries/Organization.tsx
+++ b/apps/web/screens/queries/Organization.tsx
@@ -416,3 +416,17 @@ export const EMAIL_SIGNUP_MUTATION = gql`
}
}
`
+
+export const GET_ORGANIZATION_PARENT_SUBPAGE_QUERY = gql`
+ query GetOrganizationParentSubpageQuery(
+ $input: GetOrganizationParentSubpageInput!
+ ) {
+ getOrganizationParentSubpage(input: $input) {
+ title
+ childLinks {
+ label
+ href
+ }
+ }
+ }
+`
diff --git a/charts/identity-server/values.dev.yaml b/charts/identity-server/values.dev.yaml
index e409d5e63565..37227fdaaedd 100644
--- a/charts/identity-server/values.dev.yaml
+++ b/charts/identity-server/values.dev.yaml
@@ -227,6 +227,10 @@ services-auth-admin-api:
IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.dev01.devland.is'
IDENTITY_SERVER_ISSUER_URL_LIST: '["https://identity-server.dev01.devland.is","https://identity-server.staging01.devland.is","https://innskra.island.is"]'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '6cf94113-d326-4e4d-b97c-1fea12d2f5e1'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentitydev.b2clogin.com/skraidentitydev.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS-DEV/GOV/10001/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentitydev.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init'
SERVERSIDE_FEATURES_ON: ''
SYSLUMENN_HOST: 'https://api.syslumenn.is/staging'
@@ -297,6 +301,7 @@ services-auth-admin-api:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
NATIONAL_REGISTRY_IDS_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
@@ -605,6 +610,10 @@ services-auth-personal-representative:
IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/auth-api'
IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.dev01.devland.is'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '6cf94113-d326-4e4d-b97c-1fea12d2f5e1'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentitydev.b2clogin.com/skraidentitydev.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS-DEV/GOV/10001/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentitydev.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init'
SERVERSIDE_FEATURES_ON: ''
SYSLUMENN_HOST: 'https://api.syslumenn.is/staging'
@@ -670,6 +679,7 @@ services-auth-personal-representative:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
ZENDESK_CONTACT_FORM_EMAIL: '/k8s/api/ZENDESK_CONTACT_FORM_EMAIL'
@@ -752,6 +762,10 @@ services-auth-public-api:
IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/auth-api'
IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.dev01.devland.is'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '6cf94113-d326-4e4d-b97c-1fea12d2f5e1'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentitydev.b2clogin.com/skraidentitydev.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS-DEV/GOV/10001/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentitydev.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=345 -r dd-trace/init'
PASSKEY_CORE_ALLOWED_ORIGINS: '["https://island.is","android:apk-key-hash:JgPeo_F6KYk-ngRa26tO2SsAtMiTBQCc7WtSgN-jRX0","android:apk-key-hash:EsLTUu5kaY7XPmMl2f7nbq4amu-PNzdYu3FecNf90wU"]'
PASSKEY_CORE_CHALLENGE_TTL_MS: '120000'
@@ -832,6 +846,7 @@ services-auth-public-api:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
NATIONAL_REGISTRY_IDS_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
diff --git a/charts/identity-server/values.prod.yaml b/charts/identity-server/values.prod.yaml
index 7a6fc66e89f3..1ceb0720407e 100644
--- a/charts/identity-server/values.prod.yaml
+++ b/charts/identity-server/values.prod.yaml
@@ -224,6 +224,10 @@ services-auth-admin-api:
IDENTITY_SERVER_ISSUER_URL: 'https://innskra.island.is'
IDENTITY_SERVER_ISSUER_URL_LIST: '["https://innskra.island.is"]'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '8271bbc2-d8de-480f-8540-ea43fc40b7ae'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentity.b2clogin.com/skraidentity.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS/GOV/6503760649/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentity.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init'
SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms'
SYSLUMENN_HOST: 'https://api.syslumenn.is/api'
@@ -294,6 +298,7 @@ services-auth-admin-api:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
NATIONAL_REGISTRY_IDS_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
@@ -602,6 +607,10 @@ services-auth-personal-representative:
IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/auth-api'
IDENTITY_SERVER_ISSUER_URL: 'https://innskra.island.is'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '8271bbc2-d8de-480f-8540-ea43fc40b7ae'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentity.b2clogin.com/skraidentity.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS/GOV/6503760649/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentity.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init'
SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms'
SYSLUMENN_HOST: 'https://api.syslumenn.is/api'
@@ -667,6 +676,7 @@ services-auth-personal-representative:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
ZENDESK_CONTACT_FORM_EMAIL: '/k8s/api/ZENDESK_CONTACT_FORM_EMAIL'
@@ -749,6 +759,10 @@ services-auth-public-api:
IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/auth-api'
IDENTITY_SERVER_ISSUER_URL: 'https://innskra.island.is'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '8271bbc2-d8de-480f-8540-ea43fc40b7ae'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentity.b2clogin.com/skraidentity.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS/GOV/6503760649/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentity.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=345 -r dd-trace/init'
PASSKEY_CORE_ALLOWED_ORIGINS: '["https://island.is","android:apk-key-hash:JgPeo_F6KYk-ngRa26tO2SsAtMiTBQCc7WtSgN-jRX0","android:apk-key-hash:EsLTUu5kaY7XPmMl2f7nbq4amu-PNzdYu3FecNf90wU"]'
PASSKEY_CORE_CHALLENGE_TTL_MS: '120000'
@@ -829,6 +843,7 @@ services-auth-public-api:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
NATIONAL_REGISTRY_IDS_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
diff --git a/charts/identity-server/values.staging.yaml b/charts/identity-server/values.staging.yaml
index 57c2727f4daa..a08cbe1f20a0 100644
--- a/charts/identity-server/values.staging.yaml
+++ b/charts/identity-server/values.staging.yaml
@@ -227,6 +227,10 @@ services-auth-admin-api:
IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.staging01.devland.is'
IDENTITY_SERVER_ISSUER_URL_LIST: '["https://identity-server.staging01.devland.is","https://innskra.island.is"]'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '6cf94113-d326-4e4d-b97c-1fea12d2f5e1'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentitydev.b2clogin.com/skraidentitydev.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS-TEST/GOV/10001/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentitydev.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init'
SERVERSIDE_FEATURES_ON: ''
SYSLUMENN_HOST: 'https://api.syslumenn.is/staging'
@@ -297,6 +301,7 @@ services-auth-admin-api:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
NATIONAL_REGISTRY_IDS_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
@@ -605,6 +610,10 @@ services-auth-personal-representative:
IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/auth-api'
IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.staging01.devland.is'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '6cf94113-d326-4e4d-b97c-1fea12d2f5e1'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentitydev.b2clogin.com/skraidentitydev.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS-TEST/GOV/10001/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentitydev.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init'
SERVERSIDE_FEATURES_ON: ''
SYSLUMENN_HOST: 'https://api.syslumenn.is/staging'
@@ -670,6 +679,7 @@ services-auth-personal-representative:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
ZENDESK_CONTACT_FORM_EMAIL: '/k8s/api/ZENDESK_CONTACT_FORM_EMAIL'
@@ -752,6 +762,10 @@ services-auth-public-api:
IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/auth-api'
IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.staging01.devland.is'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '6cf94113-d326-4e4d-b97c-1fea12d2f5e1'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentitydev.b2clogin.com/skraidentitydev.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS-TEST/GOV/10001/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentitydev.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=345 -r dd-trace/init'
PASSKEY_CORE_ALLOWED_ORIGINS: '["https://island.is","android:apk-key-hash:JgPeo_F6KYk-ngRa26tO2SsAtMiTBQCc7WtSgN-jRX0","android:apk-key-hash:EsLTUu5kaY7XPmMl2f7nbq4amu-PNzdYu3FecNf90wU"]'
PASSKEY_CORE_CHALLENGE_TTL_MS: '120000'
@@ -832,6 +846,7 @@ services-auth-public-api:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
NATIONAL_REGISTRY_IDS_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
diff --git a/charts/services/services-auth-admin-api/values.dev.yaml b/charts/services/services-auth-admin-api/values.dev.yaml
index f2b108d4a7ce..a792c06696f7 100644
--- a/charts/services/services-auth-admin-api/values.dev.yaml
+++ b/charts/services/services-auth-admin-api/values.dev.yaml
@@ -29,6 +29,10 @@ env:
IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.dev01.devland.is'
IDENTITY_SERVER_ISSUER_URL_LIST: '["https://identity-server.dev01.devland.is","https://identity-server.staging01.devland.is","https://innskra.island.is"]'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '6cf94113-d326-4e4d-b97c-1fea12d2f5e1'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentitydev.b2clogin.com/skraidentitydev.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS-DEV/GOV/10001/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentitydev.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init'
SERVERSIDE_FEATURES_ON: ''
SYSLUMENN_HOST: 'https://api.syslumenn.is/staging'
@@ -99,6 +103,7 @@ secrets:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
NATIONAL_REGISTRY_IDS_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
diff --git a/charts/services/services-auth-admin-api/values.prod.yaml b/charts/services/services-auth-admin-api/values.prod.yaml
index f392a1400099..cc6563a905ed 100644
--- a/charts/services/services-auth-admin-api/values.prod.yaml
+++ b/charts/services/services-auth-admin-api/values.prod.yaml
@@ -29,6 +29,10 @@ env:
IDENTITY_SERVER_ISSUER_URL: 'https://innskra.island.is'
IDENTITY_SERVER_ISSUER_URL_LIST: '["https://innskra.island.is"]'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '8271bbc2-d8de-480f-8540-ea43fc40b7ae'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentity.b2clogin.com/skraidentity.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS/GOV/6503760649/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentity.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init'
SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms'
SYSLUMENN_HOST: 'https://api.syslumenn.is/api'
@@ -99,6 +103,7 @@ secrets:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
NATIONAL_REGISTRY_IDS_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
diff --git a/charts/services/services-auth-admin-api/values.staging.yaml b/charts/services/services-auth-admin-api/values.staging.yaml
index 1ca982a2abb7..e67c4abfceac 100644
--- a/charts/services/services-auth-admin-api/values.staging.yaml
+++ b/charts/services/services-auth-admin-api/values.staging.yaml
@@ -29,6 +29,10 @@ env:
IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.staging01.devland.is'
IDENTITY_SERVER_ISSUER_URL_LIST: '["https://identity-server.staging01.devland.is","https://innskra.island.is"]'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '6cf94113-d326-4e4d-b97c-1fea12d2f5e1'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentitydev.b2clogin.com/skraidentitydev.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS-TEST/GOV/10001/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentitydev.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init'
SERVERSIDE_FEATURES_ON: ''
SYSLUMENN_HOST: 'https://api.syslumenn.is/staging'
@@ -99,6 +103,7 @@ secrets:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
NATIONAL_REGISTRY_IDS_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
diff --git a/charts/services/services-auth-personal-representative/values.dev.yaml b/charts/services/services-auth-personal-representative/values.dev.yaml
index 973eb041bc32..aaca50f4c0c0 100644
--- a/charts/services/services-auth-personal-representative/values.dev.yaml
+++ b/charts/services/services-auth-personal-representative/values.dev.yaml
@@ -28,6 +28,10 @@ env:
IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/auth-api'
IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.dev01.devland.is'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '6cf94113-d326-4e4d-b97c-1fea12d2f5e1'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentitydev.b2clogin.com/skraidentitydev.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS-DEV/GOV/10001/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentitydev.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init'
SERVERSIDE_FEATURES_ON: ''
SYSLUMENN_HOST: 'https://api.syslumenn.is/staging'
@@ -93,6 +97,7 @@ secrets:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
ZENDESK_CONTACT_FORM_EMAIL: '/k8s/api/ZENDESK_CONTACT_FORM_EMAIL'
diff --git a/charts/services/services-auth-personal-representative/values.prod.yaml b/charts/services/services-auth-personal-representative/values.prod.yaml
index f9f74f8d92c1..e65c0aafe6fa 100644
--- a/charts/services/services-auth-personal-representative/values.prod.yaml
+++ b/charts/services/services-auth-personal-representative/values.prod.yaml
@@ -28,6 +28,10 @@ env:
IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/auth-api'
IDENTITY_SERVER_ISSUER_URL: 'https://innskra.island.is'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '8271bbc2-d8de-480f-8540-ea43fc40b7ae'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentity.b2clogin.com/skraidentity.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS/GOV/6503760649/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentity.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init'
SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms'
SYSLUMENN_HOST: 'https://api.syslumenn.is/api'
@@ -93,6 +97,7 @@ secrets:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
ZENDESK_CONTACT_FORM_EMAIL: '/k8s/api/ZENDESK_CONTACT_FORM_EMAIL'
diff --git a/charts/services/services-auth-personal-representative/values.staging.yaml b/charts/services/services-auth-personal-representative/values.staging.yaml
index 21ec99d0547a..16fadeb06ab7 100644
--- a/charts/services/services-auth-personal-representative/values.staging.yaml
+++ b/charts/services/services-auth-personal-representative/values.staging.yaml
@@ -28,6 +28,10 @@ env:
IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/auth-api'
IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.staging01.devland.is'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '6cf94113-d326-4e4d-b97c-1fea12d2f5e1'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentitydev.b2clogin.com/skraidentitydev.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS-TEST/GOV/10001/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentitydev.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init'
SERVERSIDE_FEATURES_ON: ''
SYSLUMENN_HOST: 'https://api.syslumenn.is/staging'
@@ -93,6 +97,7 @@ secrets:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
ZENDESK_CONTACT_FORM_EMAIL: '/k8s/api/ZENDESK_CONTACT_FORM_EMAIL'
diff --git a/charts/services/services-auth-public-api/values.dev.yaml b/charts/services/services-auth-public-api/values.dev.yaml
index 4398430d7eb2..9b4cc091ee46 100644
--- a/charts/services/services-auth-public-api/values.dev.yaml
+++ b/charts/services/services-auth-public-api/values.dev.yaml
@@ -28,6 +28,10 @@ env:
IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/auth-api'
IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.dev01.devland.is'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '6cf94113-d326-4e4d-b97c-1fea12d2f5e1'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentitydev.b2clogin.com/skraidentitydev.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS-DEV/GOV/10001/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentitydev.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=345 -r dd-trace/init'
PASSKEY_CORE_ALLOWED_ORIGINS: '["https://island.is","android:apk-key-hash:JgPeo_F6KYk-ngRa26tO2SsAtMiTBQCc7WtSgN-jRX0","android:apk-key-hash:EsLTUu5kaY7XPmMl2f7nbq4amu-PNzdYu3FecNf90wU"]'
PASSKEY_CORE_CHALLENGE_TTL_MS: '120000'
@@ -108,6 +112,7 @@ secrets:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
NATIONAL_REGISTRY_IDS_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
diff --git a/charts/services/services-auth-public-api/values.prod.yaml b/charts/services/services-auth-public-api/values.prod.yaml
index cedab910898a..13e897e62429 100644
--- a/charts/services/services-auth-public-api/values.prod.yaml
+++ b/charts/services/services-auth-public-api/values.prod.yaml
@@ -28,6 +28,10 @@ env:
IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/auth-api'
IDENTITY_SERVER_ISSUER_URL: 'https://innskra.island.is'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '8271bbc2-d8de-480f-8540-ea43fc40b7ae'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentity.b2clogin.com/skraidentity.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS/GOV/6503760649/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentity.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=345 -r dd-trace/init'
PASSKEY_CORE_ALLOWED_ORIGINS: '["https://island.is","android:apk-key-hash:JgPeo_F6KYk-ngRa26tO2SsAtMiTBQCc7WtSgN-jRX0","android:apk-key-hash:EsLTUu5kaY7XPmMl2f7nbq4amu-PNzdYu3FecNf90wU"]'
PASSKEY_CORE_CHALLENGE_TTL_MS: '120000'
@@ -108,6 +112,7 @@ secrets:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
NATIONAL_REGISTRY_IDS_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
diff --git a/charts/services/services-auth-public-api/values.staging.yaml b/charts/services/services-auth-public-api/values.staging.yaml
index af05b8333772..ba19f2a7a3bf 100644
--- a/charts/services/services-auth-public-api/values.staging.yaml
+++ b/charts/services/services-auth-public-api/values.staging.yaml
@@ -28,6 +28,10 @@ env:
IDENTITY_SERVER_CLIENT_ID: '@island.is/clients/auth-api'
IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.staging01.devland.is'
LOG_LEVEL: 'info'
+ NATIONAL_REGISTRY_B2C_CLIENT_ID: '6cf94113-d326-4e4d-b97c-1fea12d2f5e1'
+ NATIONAL_REGISTRY_B2C_ENDPOINT: 'https://skraidentitydev.b2clogin.com/skraidentitydev.onmicrosoft.com/b2c_1_midlun_flow/oauth2/v2.0/token'
+ NATIONAL_REGISTRY_B2C_PATH: 'IS-TEST/GOV/10001/SKRA-Cloud-Protected/Midlun-v1'
+ NATIONAL_REGISTRY_B2C_SCOPE: 'https://skraidentitydev.onmicrosoft.com/midlun/.default'
NODE_OPTIONS: '--max-old-space-size=345 -r dd-trace/init'
PASSKEY_CORE_ALLOWED_ORIGINS: '["https://island.is","android:apk-key-hash:JgPeo_F6KYk-ngRa26tO2SsAtMiTBQCc7WtSgN-jRX0","android:apk-key-hash:EsLTUu5kaY7XPmMl2f7nbq4amu-PNzdYu3FecNf90wU"]'
PASSKEY_CORE_CHALLENGE_TTL_MS: '120000'
@@ -108,6 +112,7 @@ secrets:
CONFIGCAT_SDK_KEY: '/k8s/configcat/CONFIGCAT_SDK_KEY'
DB_PASS: '/k8s/servicesauth/DB_PASSWORD'
IDENTITY_SERVER_CLIENT_SECRET: '/k8s/services-auth/IDENTITY_SERVER_CLIENT_SECRET'
+ NATIONAL_REGISTRY_B2C_CLIENT_SECRET: '/k8s/services-auth/NATIONAL_REGISTRY_B2C_CLIENT_SECRET'
NATIONAL_REGISTRY_IDS_CLIENT_SECRET: '/k8s/xroad/client/NATIONAL-REGISTRY/IDENTITYSERVER_SECRET'
SYSLUMENN_PASSWORD: '/k8s/services-auth/SYSLUMENN_PASSWORD'
SYSLUMENN_USERNAME: '/k8s/services-auth/SYSLUMENN_USERNAME'
diff --git a/libs/application/core/src/lib/fieldBuilders.ts b/libs/application/core/src/lib/fieldBuilders.ts
index 2f1a86f6a03c..2c77343d5aca 100644
--- a/libs/application/core/src/lib/fieldBuilders.ts
+++ b/libs/application/core/src/lib/fieldBuilders.ts
@@ -46,6 +46,8 @@ import {
SliderField,
MaybeWithApplication,
MaybeWithApplicationAndFieldAndLocale,
+ DisplayField,
+ FieldsRepeaterField,
} from '@island.is/application/types'
import { Locale } from '@island.is/shared/types'
import { Colors } from '@island.is/island-ui/theme'
@@ -867,6 +869,48 @@ export const buildTableRepeaterField = (
}
}
+export const buildFieldsRepeaterField = (
+ data: Omit,
+): FieldsRepeaterField => {
+ const {
+ fields,
+ table,
+ title,
+ titleVariant,
+ formTitle,
+ formTitleVariant,
+ formTitleNumbering,
+ marginTop,
+ marginBottom,
+ removeItemButtonText,
+ addItemButtonText,
+ saveItemButtonText,
+ minRows,
+ maxRows,
+ } = data
+
+ return {
+ ...extractCommonFields(data),
+ children: undefined,
+ type: FieldTypes.FIELDS_REPEATER,
+ component: FieldComponents.FIELDS_REPEATER,
+ fields,
+ table,
+ title,
+ titleVariant,
+ formTitle,
+ formTitleVariant,
+ formTitleNumbering,
+ marginTop,
+ marginBottom,
+ removeItemButtonText,
+ addItemButtonText,
+ saveItemButtonText,
+ minRows,
+ maxRows,
+ }
+}
+
export const buildStaticTableField = (
data: Omit<
StaticTableField,
@@ -966,3 +1010,34 @@ export const buildSliderField = (
saveAsString,
}
}
+
+export const buildDisplayField = (
+ data: Omit,
+): DisplayField => {
+ const {
+ title,
+ titleVariant,
+ label,
+ variant,
+ marginTop,
+ marginBottom,
+ value,
+ suffix,
+ rightAlign,
+ } = data
+ return {
+ ...extractCommonFields(data),
+ title,
+ titleVariant,
+ label,
+ variant,
+ marginTop,
+ marginBottom,
+ type: FieldTypes.DISPLAY,
+ component: FieldComponents.DISPLAY,
+ children: undefined,
+ value,
+ suffix,
+ rightAlign,
+ }
+}
diff --git a/libs/application/core/src/lib/messages.ts b/libs/application/core/src/lib/messages.ts
index 3190a3542b1a..65cb2f5cdd67 100644
--- a/libs/application/core/src/lib/messages.ts
+++ b/libs/application/core/src/lib/messages.ts
@@ -41,6 +41,11 @@ export const coreMessages = defineMessages({
defaultMessage: 'Bæta við',
description: 'Add button',
},
+ buttonRemove: {
+ id: 'application.system:button.remove',
+ defaultMessage: 'Fjarlægja',
+ description: 'Remove button',
+ },
buttonCancel: {
id: 'application.system:button.cancel',
defaultMessage: 'Hætta við',
diff --git a/libs/application/templates/inheritance-report/src/fields/HeirsRepeater/index.tsx b/libs/application/templates/inheritance-report/src/fields/HeirsRepeater/index.tsx
index 639878598c91..768a0c81c0b4 100644
--- a/libs/application/templates/inheritance-report/src/fields/HeirsRepeater/index.tsx
+++ b/libs/application/templates/inheritance-report/src/fields/HeirsRepeater/index.tsx
@@ -213,9 +213,17 @@ export const HeirsRepeater: FC<
: valueToNumber(getValueViaPath(answers, 'netPropertyForExchange'))
const inheritanceValue = netPropertyForExchange * percentage
+ const customSpouseSharePercentage = getValueViaPath(
+ answers,
+ 'customShare.customSpouseSharePercentage',
+ )
+ const withCustomPercentage =
+ (100 - Number(customSpouseSharePercentage)) * 2
const taxFreeInheritanceValue = isSpouse
? inheritanceValue
+ : customSpouseSharePercentage
+ ? (withCustomPercentage / 100) * inheritanceTaxFreeLimit * percentage
: inheritanceTaxFreeLimit * percentage
const taxableInheritanceValue = inheritanceValue - taxFreeInheritanceValue
diff --git a/libs/application/templates/inheritance-report/src/forms/sections/applicationTypeSelection.ts b/libs/application/templates/inheritance-report/src/forms/sections/applicationTypeSelection.ts
index 10ffd1e7f6fd..2bff9470a59a 100644
--- a/libs/application/templates/inheritance-report/src/forms/sections/applicationTypeSelection.ts
+++ b/libs/application/templates/inheritance-report/src/forms/sections/applicationTypeSelection.ts
@@ -31,7 +31,8 @@ export const preSelection = buildSection({
}),
buildMultiField({
id: 'estate',
- title: m.applicationName,
+ title: m.selectEstate,
+ description: m.selectEstateDescription,
condition: (answers) => answers.applicationFor === ESTATE_INHERITANCE,
children: [
buildSelectField({
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 b679c626a58a..501dfc9f920c 100644
--- a/libs/application/templates/inheritance-report/src/forms/sections/deceased.ts
+++ b/libs/application/templates/inheritance-report/src/forms/sections/deceased.ts
@@ -84,7 +84,7 @@ export const deceased = buildSection({
}),
buildDescriptionField({
id: 'space3',
- space: 'gutter',
+ space: 'containerGutter',
title: '',
}),
buildRadioField({
@@ -123,7 +123,7 @@ export const deceased = buildSection({
),
buildDescriptionField({
id: 'space4',
- space: 'gutter',
+ space: 'containerGutter',
title: '',
}),
buildRadioField({
diff --git a/libs/application/templates/inheritance-report/src/forms/sections/heirs.ts b/libs/application/templates/inheritance-report/src/forms/sections/heirs.ts
index fd82a48babbf..5f6f38eb78eb 100644
--- a/libs/application/templates/inheritance-report/src/forms/sections/heirs.ts
+++ b/libs/application/templates/inheritance-report/src/forms/sections/heirs.ts
@@ -160,6 +160,27 @@ export const heirs = buildSection({
}),
],
}),
+ buildSubSection({
+ id: 'privateTransfer',
+ title: m.fileUploadPrivateTransfer,
+ children: [
+ buildMultiField({
+ id: 'heirsAdditionalInfo',
+ title: m.fileUploadPrivateTransfer,
+ description: m.uploadPrivateTransferUserGuidelines,
+ children: [
+ buildFileUploadField({
+ id: 'heirsAdditionalInfoPrivateTransferFiles',
+ uploadAccept: '.pdf, .doc, .docx, .jpg, .jpeg, .png, .xls, .xlsx',
+ uploadDescription: m.uploadPrivateTransferDescription,
+ title: '',
+ uploadHeader: '',
+ uploadMultiple: false,
+ }),
+ ],
+ }),
+ ],
+ }),
buildSubSection({
id: 'heirsAdditionalInfo',
title: m.heirAdditionalInfo,
@@ -183,22 +204,6 @@ export const heirs = buildSection({
rows: 4,
maxLength: 1800,
}),
- buildDescriptionField({
- id: 'heirsAdditionalInfoFilesPrivateTitle',
- title: m.fileUploadPrivateTransfer,
- description: m.uploadPrivateTransferUserGuidelines,
- titleVariant: 'h5',
- space: 'containerGutter',
- marginBottom: 'smallGutter',
- }),
- buildFileUploadField({
- id: 'heirsAdditionalInfoPrivateTransferFiles',
- uploadAccept: '.pdf, .doc, .docx, .jpg, .jpeg, .png, .xls, .xlsx',
- uploadDescription: m.uploadPrivateTransferDescription,
- title: '',
- uploadHeader: '',
- uploadMultiple: false,
- }),
buildDescriptionField({
id: 'heirsAdditionalInfoFilesOtherDocumentsTitle',
title: m.fileUploadOtherDocuments,
diff --git a/libs/application/templates/inheritance-report/src/lib/messages.ts b/libs/application/templates/inheritance-report/src/lib/messages.ts
index 0f079f314435..c13308ad57bc 100644
--- a/libs/application/templates/inheritance-report/src/lib/messages.ts
+++ b/libs/application/templates/inheritance-report/src/lib/messages.ts
@@ -67,9 +67,14 @@ export const m = defineMessages({
description: '',
},
// Application begin
- applicationName: {
- id: 'ir.application:applicationName',
- defaultMessage: 'Erfðafjárskýrsla eftir andlát',
+ selectEstate: {
+ id: 'ir.application:selectEstate',
+ defaultMessage: 'Veldu dánarbú',
+ description: '',
+ },
+ selectEstateDescription: {
+ id: 'ir.application:selectEstateDescription',
+ defaultMessage: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
description: '',
},
applicationNamePrepaid: {
diff --git a/libs/application/types/src/lib/Fields.ts b/libs/application/types/src/lib/Fields.ts
index 9ea37be69132..e5425ca66464 100644
--- a/libs/application/types/src/lib/Fields.ts
+++ b/libs/application/types/src/lib/Fields.ts
@@ -16,7 +16,7 @@ import {
StaticText,
} from './Form'
import { ApolloClient } from '@apollo/client'
-import { Application } from './Application'
+import { Application, FormValue } from './Application'
import { CallToAction } from './StateMachine'
import { Colors, theme } from '@island.is/island-ui/theme'
import { Condition } from './Condition'
@@ -64,7 +64,7 @@ export type TagVariant =
| 'mint'
| 'disabled'
-export type TableRepeaterFields =
+export type RepeaterFields =
| 'input'
| 'select'
| 'radio'
@@ -82,8 +82,8 @@ type TableRepeaterOptions =
activeField?: Record,
) => RepeaterOption[] | [])
-export type TableRepeaterItem = {
- component: TableRepeaterFields
+export type RepeaterItem = {
+ component: RepeaterFields
/**
* Defaults to true
*/
@@ -253,6 +253,7 @@ export enum FieldTypes {
NATIONAL_ID_WITH_NAME = 'NATIONAL_ID_WITH_NAME',
ACTION_CARD_LIST = 'ACTION_CARD_LIST',
TABLE_REPEATER = 'TABLE_REPEATER',
+ FIELDS_REPEATER = 'FIELDS_REPEATER',
HIDDEN_INPUT = 'HIDDEN_INPUT',
HIDDEN_INPUT_WITH_WATCHED_VALUE = 'HIDDEN_INPUT_WITH_WATCHED_VALUE',
FIND_VEHICLE = 'FIND_VEHICLE',
@@ -261,6 +262,7 @@ export enum FieldTypes {
ACCORDION = 'ACCORDION',
BANK_ACCOUNT = 'BANK_ACCOUNT',
SLIDER = 'SLIDER',
+ DISPLAY = 'DISPLAY',
}
export enum FieldComponents {
@@ -289,6 +291,7 @@ export enum FieldComponents {
NATIONAL_ID_WITH_NAME = 'NationalIdWithNameFormField',
ACTION_CARD_LIST = 'ActionCardListFormField',
TABLE_REPEATER = 'TableRepeaterFormField',
+ FIELDS_REPEATER = 'FieldsRepeaterFormField',
HIDDEN_INPUT = 'HiddenInputFormField',
FIND_VEHICLE = 'FindVehicleFormField',
VEHICLE_RADIO = 'VehicleRadioFormField',
@@ -296,6 +299,7 @@ export enum FieldComponents {
ACCORDION = 'AccordionFormField',
BANK_ACCOUNT = 'BankAccountFormField',
SLIDER = 'SliderFormField',
+ DISPLAY = 'DisplayFormField',
}
export interface CheckboxField extends InputField {
@@ -639,7 +643,7 @@ export type TableRepeaterField = BaseField & {
marginTop?: ResponsiveProp
marginBottom?: ResponsiveProp
titleVariant?: TitleVariants
- fields: Record
+ fields: Record
/**
* Maximum rows that can be added to the table.
* When the maximum is reached, the button to add a new row is disabled.
@@ -659,6 +663,41 @@ export type TableRepeaterField = BaseField & {
format?: Record string | StaticText>
}
}
+
+export type FieldsRepeaterField = BaseField & {
+ readonly type: FieldTypes.FIELDS_REPEATER
+ component: FieldComponents.FIELDS_REPEATER
+ titleVariant?: TitleVariants
+ formTitle?: StaticText
+ formTitleVariant?: TitleVariants
+ formTitleNumbering?: 'prefix' | 'suffix' | 'none'
+ removeItemButtonText?: StaticText
+ addItemButtonText?: StaticText
+ saveItemButtonText?: StaticText
+ marginTop?: ResponsiveProp
+ marginBottom?: ResponsiveProp
+ fields: Record
+ /**
+ * Maximum rows that can be added to the table.
+ * When the maximum is reached, the button to add a new row is disabled.
+ */
+ minRows?: number
+ maxRows?: number
+ table?: {
+ /**
+ * List of strings to render,
+ * if not provided it will be auto generated from the fields
+ */
+ header?: StaticText[]
+ /**
+ * List of field id's to render,
+ * if not provided it will be auto generated from the fields
+ */
+ rows?: string[]
+ format?: Record string | StaticText>
+ }
+}
+
export interface FindVehicleField extends InputField {
readonly type: FieldTypes.FIND_VEHICLE
component: FieldComponents.FIND_VEHICLE
@@ -759,6 +798,19 @@ export interface SliderField extends BaseField {
saveAsString?: boolean
}
+export interface DisplayField extends BaseField {
+ readonly type: FieldTypes.DISPLAY
+ component: FieldComponents.DISPLAY
+ marginTop?: ResponsiveProp
+ marginBottom?: ResponsiveProp
+ titleVariant?: TitleVariants
+ suffix?: MessageDescriptor | string
+ rightAlign?: boolean
+ variant?: TextFieldVariant
+ label?: MessageDescriptor | string
+ value: (answers: FormValue) => string
+}
+
export type Field =
| CheckboxField
| CustomField
@@ -786,6 +838,7 @@ export type Field =
| NationalIdWithNameField
| ActionCardListField
| TableRepeaterField
+ | FieldsRepeaterField
| HiddenInputWithWatchedValueField
| HiddenInputField
| FindVehicleField
@@ -794,3 +847,4 @@ export type Field =
| AccordionField
| BankAccountField
| SliderField
+ | DisplayField
diff --git a/libs/application/ui-fields/src/lib/DisplayFormField/DisplayFormField.tsx b/libs/application/ui-fields/src/lib/DisplayFormField/DisplayFormField.tsx
new file mode 100644
index 000000000000..ae6faba48fed
--- /dev/null
+++ b/libs/application/ui-fields/src/lib/DisplayFormField/DisplayFormField.tsx
@@ -0,0 +1,80 @@
+import { formatTextWithLocale } from '@island.is/application/core'
+import { DisplayField, FieldBaseProps } from '@island.is/application/types'
+import { Box, Text } from '@island.is/island-ui/core'
+import { useLocale } from '@island.is/localization'
+import { InputController } from '@island.is/shared/form-fields'
+import { useEffect, useState } from 'react'
+import { useFormContext } from 'react-hook-form'
+import { Locale } from '@island.is/shared/types'
+
+interface Props extends FieldBaseProps {
+ field: DisplayField
+}
+
+export const DisplayFormField = ({ field, application }: Props) => {
+ const {
+ value,
+ id,
+ title,
+ titleVariant = 'h4',
+ label,
+ variant,
+ suffix,
+ rightAlign = false,
+ } = field
+ const { watch, setValue } = useFormContext()
+ const allValues = watch()
+ const { formatMessage, lang: locale } = useLocale()
+ const [displayValue, setDisplayValue] = useState(allValues[id])
+
+ useEffect(() => {
+ const newDisplayValue = value(allValues)
+ setDisplayValue(newDisplayValue)
+ setValue(id, newDisplayValue)
+ }, [allValues])
+
+ return (
+
+ {title ? (
+
+ {formatTextWithLocale(
+ title,
+ application,
+ locale as Locale,
+ formatMessage,
+ )}
+
+ ) : null}
+
+
+
+ )
+}
diff --git a/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterFormField.stories.mdx b/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterFormField.stories.mdx
new file mode 100644
index 000000000000..081dc5c3a1dc
--- /dev/null
+++ b/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterFormField.stories.mdx
@@ -0,0 +1,128 @@
+import {
+ Meta,
+ Story,
+ Canvas,
+ ArgsTable,
+ Description,
+ Source,
+} from '@storybook/addon-docs'
+import { dedent } from 'ts-dedent'
+
+import { FieldsRepeaterFormField } from './FieldsRepeaterFormField'
+
+export const createMockApplication = (data = {}) => ({
+ id: '123',
+ assignees: [],
+ state: data.state || 'draft',
+ applicant: '111111-3000',
+ typeId: data.typeId || 'ExampleForm',
+ modified: new Date(),
+ created: new Date(),
+ attachments: {},
+ answers: data.answers || {},
+ externalData: data.externalData || {},
+})
+
+
+
+# TableRepeaterFormField
+
+### Usage in a template
+
+You can create a FieldsRepeaterFormField using the following function `FieldsTableRepeaterField`.
+Validation should be done via zod schema.
+
+
+
+The previous configuration object will result in the following component:
+
+
+
+You can also use this field into a custom component by using `` with the configuration object defined above.
+
+# Props
+
+## FieldsRepeaterFormField
+
+
diff --git a/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterFormField.tsx b/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterFormField.tsx
new file mode 100644
index 000000000000..9edc6dd1890e
--- /dev/null
+++ b/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterFormField.tsx
@@ -0,0 +1,205 @@
+import { Fragment, useEffect, useState } from 'react'
+import {
+ coreMessages,
+ formatText,
+ formatTextWithLocale,
+ getValueViaPath,
+} from '@island.is/application/core'
+import {
+ FieldBaseProps,
+ FieldsRepeaterField,
+} from '@island.is/application/types'
+import {
+ AlertMessage,
+ Box,
+ Button,
+ GridRow,
+ Stack,
+ Text,
+} from '@island.is/island-ui/core'
+import { useLocale } from '@island.is/localization'
+import { FieldDescription } from '@island.is/shared/form-fields'
+import { useFieldArray, useFormContext, useWatch } from 'react-hook-form'
+import { Item } from './FieldsRepeaterItem'
+import { Locale } from '@island.is/shared/types'
+import isEqual from 'lodash/isEqual'
+
+interface Props extends FieldBaseProps {
+ field: FieldsRepeaterField
+}
+
+export const FieldsRepeaterFormField = ({
+ application,
+ field: data,
+ showFieldName,
+ error,
+}: Props) => {
+ const {
+ id,
+ fields: rawItems,
+ description,
+ marginTop = 6,
+ marginBottom,
+ title,
+ titleVariant = 'h2',
+ formTitle,
+ formTitleVariant = 'h4',
+ formTitleNumbering = 'suffix',
+ removeItemButtonText = coreMessages.buttonRemove,
+ addItemButtonText = coreMessages.buttonAdd,
+ minRows = 1,
+ maxRows,
+ } = data
+
+ const { control, getValues, setValue } = useFormContext()
+ const answers = getValues()
+ const numberOfItemsInAnswers = getValueViaPath>(
+ answers,
+ id,
+ )?.length
+ const [numberOfItems, setNumberOfItems] = useState(
+ Math.max(numberOfItemsInAnswers ?? 0, minRows),
+ )
+ const [updatedApplication, setUpdatedApplication] = useState(application)
+
+ useEffect(() => {
+ if (!isEqual(application, updatedApplication)) {
+ setUpdatedApplication({
+ ...application,
+ answers: { ...answers },
+ })
+ }
+ }, [answers])
+
+ const items = Object.keys(rawItems).map((key) => ({
+ id: key,
+ ...rawItems[key],
+ }))
+
+ const { formatMessage, lang: locale } = useLocale()
+
+ const { fields, remove } = useFieldArray({
+ control: control,
+ name: id,
+ })
+
+ const values = useWatch({ name: data.id, control: control })
+
+ const handleNewItem = () => {
+ setNumberOfItems(numberOfItems + 1)
+ }
+
+ const handleRemoveItem = () => {
+ if (numberOfItems > (numberOfItemsInAnswers || 0)) {
+ setNumberOfItems(numberOfItems - 1)
+ } else if (numberOfItems === numberOfItemsInAnswers) {
+ setValue(id, answers[id].slice(0, -1))
+ setNumberOfItems(numberOfItems - 1)
+ } else if (
+ numberOfItemsInAnswers &&
+ numberOfItems < numberOfItemsInAnswers
+ ) {
+ const difference = numberOfItems - numberOfItemsInAnswers
+ setValue(id, answers[id].slice(0, difference))
+ setNumberOfItems(numberOfItems)
+ }
+
+ remove(numberOfItems - 1)
+ }
+
+ const repeaterFields = (index: number) =>
+ items.map((item) => (
+
+ ))
+
+ return (
+
+ {showFieldName && (
+
+ {formatTextWithLocale(
+ title,
+ application,
+ locale as Locale,
+ formatMessage,
+ )}
+
+ )}
+ {description && (
+
+ )}
+
+
+
+ {Array.from({ length: numberOfItems }).map((_i, i) => (
+
+ {(formTitleNumbering !== 'none' || formTitle) && (
+
+
+ {formTitleNumbering === 'prefix' ? `${i + 1}. ` : ''}
+ {formTitle &&
+ formatTextWithLocale(
+ formTitle,
+ application,
+ locale as Locale,
+ formatMessage,
+ )}
+ {formTitleNumbering === 'suffix' ? ` ${i + 1}` : ''}
+
+
+ )}
+ {repeaterFields(i)}
+
+ ))}
+
+
+ {numberOfItems > minRows && (
+
+
+
+ )}
+
+
+
+ {error && typeof error === 'string' && fields.length === 0 && (
+
+
+
+ )}
+
+
+ )
+}
diff --git a/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterItem.tsx b/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterItem.tsx
new file mode 100644
index 000000000000..4fd041e97550
--- /dev/null
+++ b/libs/application/ui-fields/src/lib/FieldsRepeaterFormField/FieldsRepeaterItem.tsx
@@ -0,0 +1,223 @@
+import { formatText, getValueViaPath } from '@island.is/application/core'
+import { Application, RepeaterItem } from '@island.is/application/types'
+import { GridColumn, Text } from '@island.is/island-ui/core'
+import { useLocale } from '@island.is/localization'
+import { useEffect, useRef } from 'react'
+import { useFormContext } from 'react-hook-form'
+import isEqual from 'lodash/isEqual'
+import {
+ CheckboxController,
+ DatePickerController,
+ InputController,
+ RadioController,
+ SelectController,
+ PhoneInputController,
+} from '@island.is/shared/form-fields'
+import { NationalIdWithName } from '@island.is/application/ui-components'
+
+interface ItemFieldProps {
+ application: Application
+ error?: string
+ item: RepeaterItem & { id: string }
+ dataId: string
+ index: number
+ values: Array>
+}
+
+const componentMapper = {
+ input: InputController,
+ select: SelectController,
+ checkbox: CheckboxController,
+ date: DatePickerController,
+ radio: RadioController,
+ nationalIdWithName: NationalIdWithName,
+ phone: PhoneInputController,
+}
+
+export const Item = ({
+ application,
+ error,
+ item,
+ dataId,
+ index,
+ values,
+}: ItemFieldProps) => {
+ const { formatMessage } = useLocale()
+ const { setValue, control, clearErrors } = useFormContext()
+ const prevWatchedValuesRef = useRef()
+
+ const {
+ component,
+ id: itemId,
+ backgroundColor = 'blue',
+ label = '',
+ placeholder = '',
+ options,
+ width = 'full',
+ condition,
+ readonly = false,
+ disabled = false,
+ updateValueObj,
+ defaultValue,
+ ...props
+ } = item
+ const isHalfColumn = component !== 'radio' && width === 'half'
+ const isThirdColumn = component !== 'radio' && width === 'third'
+ const span = isHalfColumn ? '1/2' : isThirdColumn ? '1/3' : '1/1'
+ const Component = componentMapper[component]
+ const id = `${dataId}[${index}].${itemId}`
+ const activeValues = index >= 0 && values ? values[index] : undefined
+
+ let watchedValues: string | (string | undefined)[] | undefined
+ if (updateValueObj) {
+ const watchedValuesId =
+ typeof updateValueObj.watchValues === 'function'
+ ? updateValueObj.watchValues(activeValues)
+ : updateValueObj.watchValues
+
+ if (watchedValuesId) {
+ if (Array.isArray(watchedValuesId)) {
+ watchedValues = watchedValuesId.map((value) => {
+ return activeValues?.[`${value}`]
+ })
+ } else {
+ watchedValues = activeValues?.[`${watchedValuesId}`]
+ }
+ }
+ }
+
+ useEffect(() => {
+ // We need to deep compare the watched values to avoid unnecessary re-renders
+ if (
+ watchedValues &&
+ !isEqual(prevWatchedValuesRef.current, watchedValues)
+ ) {
+ prevWatchedValuesRef.current = watchedValues
+ if (
+ updateValueObj &&
+ watchedValues &&
+ (Array.isArray(watchedValues)
+ ? !watchedValues.every((value) => value === undefined)
+ : true)
+ ) {
+ const finalValue = updateValueObj.valueModifier(
+ application,
+ activeValues,
+ )
+ setValue(id, finalValue)
+ }
+ }
+ }, [watchedValues, updateValueObj, activeValues, setValue, id])
+
+ const getFieldError = (id: string) => {
+ /**
+ * Errors that occur in a field-array have incorrect typing
+ * This hack is needed to get the correct type
+ */
+ const errorList = error as unknown as Record[] | undefined
+ const errors = errorList?.[index]
+ return errors?.[id]
+ }
+
+ const getDefaultValue = (
+ item: RepeaterItem,
+ application: Application,
+ activeField?: Record,
+ ) => {
+ const { defaultValue } = item
+
+ if (defaultValue === undefined) {
+ return undefined
+ }
+
+ return defaultValue(application, activeField)
+ }
+
+ let translatedOptions: any = []
+ if (typeof options === 'function') {
+ translatedOptions = options(application, activeValues)
+ } else {
+ translatedOptions =
+ options?.map((option) => ({
+ ...option,
+ label: formatText(option.label, application, formatMessage),
+ ...(option.tooltip && {
+ tooltip: formatText(option.tooltip, application, formatMessage),
+ }),
+ })) ?? []
+ }
+
+ let Readonly: boolean | undefined
+ if (typeof readonly === 'function') {
+ Readonly = readonly(application, activeValues)
+ } else {
+ Readonly = readonly
+ }
+
+ let Disabled: boolean | undefined
+ if (typeof disabled === 'function') {
+ Disabled = disabled(application, activeValues)
+ } else {
+ Disabled = disabled
+ }
+
+ let DefaultValue: any
+ if (component === 'input') {
+ DefaultValue = getDefaultValue(item, application, activeValues)
+ }
+ if (component === 'select') {
+ DefaultValue =
+ getValueViaPath(application.answers, id) ??
+ getDefaultValue(item, application, activeValues)
+ }
+ if (component === 'radio') {
+ DefaultValue =
+ (getValueViaPath(application.answers, id) as string[]) ??
+ getDefaultValue(item, application, activeValues)
+ }
+ if (component === 'checkbox') {
+ DefaultValue =
+ (getValueViaPath(application.answers, id) as string[]) ??
+ getDefaultValue(item, application, activeValues)
+ }
+ if (component === 'date') {
+ DefaultValue =
+ (getValueViaPath(application.answers, id) as string) ??
+ getDefaultValue(item, application, activeValues)
+ }
+
+ if (condition && !condition(application, activeValues)) {
+ return null
+ }
+
+ return (
+
+ {component === 'radio' && label && (
+
+ {formatText(label, application, formatMessage)}
+
+ )}
+ {
+ if (error) {
+ clearErrors(id)
+ }
+ }}
+ application={application}
+ defaultValue={DefaultValue}
+ {...props}
+ />
+
+ )
+}
diff --git a/libs/application/ui-fields/src/lib/TableRepeaterFormField/TableRepeaterItem.tsx b/libs/application/ui-fields/src/lib/TableRepeaterFormField/TableRepeaterItem.tsx
index dc5c01ae8309..5388065f176f 100644
--- a/libs/application/ui-fields/src/lib/TableRepeaterFormField/TableRepeaterItem.tsx
+++ b/libs/application/ui-fields/src/lib/TableRepeaterFormField/TableRepeaterItem.tsx
@@ -1,5 +1,5 @@
import { formatText, getValueViaPath } from '@island.is/application/core'
-import { Application, TableRepeaterItem } from '@island.is/application/types'
+import { Application, RepeaterItem } from '@island.is/application/types'
import { GridColumn, Text } from '@island.is/island-ui/core'
import { useLocale } from '@island.is/localization'
import { useEffect, useRef } from 'react'
@@ -18,7 +18,7 @@ import { NationalIdWithName } from '@island.is/application/ui-components'
interface ItemFieldProps {
application: Application
error?: string
- item: TableRepeaterItem & { id: string }
+ item: RepeaterItem & { id: string }
dataId: string
activeIndex: number
values: Array>
@@ -121,7 +121,7 @@ export const Item = ({
}
const getDefaultValue = (
- item: TableRepeaterItem,
+ item: RepeaterItem,
application: Application,
activeField?: Record,
) => {
diff --git a/libs/application/ui-fields/src/lib/TableRepeaterFormField/utils.ts b/libs/application/ui-fields/src/lib/TableRepeaterFormField/utils.ts
index f1b0c79ee077..7843b128e023 100644
--- a/libs/application/ui-fields/src/lib/TableRepeaterFormField/utils.ts
+++ b/libs/application/ui-fields/src/lib/TableRepeaterFormField/utils.ts
@@ -1,9 +1,9 @@
+import { RepeaterItem } from '@island.is/application/types'
import { coreMessages } from '@island.is/application/core'
-import { TableRepeaterItem } from '@island.is/application/types'
type Item = {
id: string
-} & TableRepeaterItem
+} & RepeaterItem
export type Value = { [key: string]: T }
@@ -42,7 +42,7 @@ const handleNationalIdWithNameItem = (
return newValues
}
-export const buildDefaultTableHeader = (items: Array) =>
+export const buildDefaultTableHeader = (items: Array) =>
items
.map((item) =>
// nationalIdWithName is a special case where the value is an object of name and nationalId
@@ -53,7 +53,7 @@ export const buildDefaultTableHeader = (items: Array) =>
.flat(2)
export const buildDefaultTableRows = (
- items: Array,
+ items: Array,
) =>
items
.map((item) =>
diff --git a/libs/application/ui-fields/src/lib/index.ts b/libs/application/ui-fields/src/lib/index.ts
index c3e3f9724309..d796e89afbe9 100644
--- a/libs/application/ui-fields/src/lib/index.ts
+++ b/libs/application/ui-fields/src/lib/index.ts
@@ -24,9 +24,11 @@ export { NationalIdWithNameFormField } from './NationalIdWithNameFormField/Natio
export { HiddenInputFormField } from './HiddenInputFormField/HiddenInputFormField'
export { ActionCardListFormField } from './ActionCardListFormField/ActionCardListFormField'
export { TableRepeaterFormField } from './TableRepeaterFormField/TableRepeaterFormField'
+export { FieldsRepeaterFormField } from './FieldsRepeaterFormField/FieldsRepeaterFormField'
export { FindVehicleFormField } from './FindVehicleFormField/FindVehicleFormField'
export { VehicleRadioFormField } from './VehicleRadioFormField/VehicleRadioFormField'
export { StaticTableFormField } from './StaticTableFormField/StaticTableFormField'
export { AccordionFormField } from './AccordionFormField/AccordionFormField'
export { BankAccountFormField } from './BankAccountFormField/BankAccountFormField'
export { SliderFormField } from './SliderFormField/SliderFormField'
+export { DisplayFormField } from './DisplayFormField/DisplayFormField'
diff --git a/libs/auth-api-lib/src/index.ts b/libs/auth-api-lib/src/index.ts
index c31273fdae15..6c4d00f2b791 100644
--- a/libs/auth-api-lib/src/index.ts
+++ b/libs/auth-api-lib/src/index.ts
@@ -61,6 +61,7 @@ export * from './lib/delegations/utils/scopes'
export * from './lib/delegations/admin/delegation-admin-custom.service'
export * from './lib/delegations/constants/names'
export * from './lib/delegations/constants/zendesk'
+export * from './lib/delegations/national-registry-v3-feature.service'
// Resources module
export * from './lib/resources/resources.module'
diff --git a/libs/auth-api-lib/src/lib/delegations/alive-status.service.ts b/libs/auth-api-lib/src/lib/delegations/alive-status.service.ts
new file mode 100644
index 000000000000..e1e4aeceb170
--- /dev/null
+++ b/libs/auth-api-lib/src/lib/delegations/alive-status.service.ts
@@ -0,0 +1,192 @@
+import { Inject, Injectable, Logger } from '@nestjs/common'
+import * as kennitala from 'kennitala'
+
+import { NationalRegistryClientService } from '@island.is/clients/national-registry-v2'
+import { NationalRegistryV3ClientService } from '@island.is/clients/national-registry-v3'
+import { CompanyRegistryClientService } from '@island.is/clients/rsk/company-registry'
+import { LOGGER_PROVIDER } from '@island.is/logging'
+
+import { UNKNOWN_NAME } from './constants/names'
+import { partitionWithIndex } from './utils/partitionWithIndex'
+
+export type NameInfo = {
+ nationalId: string
+ name: string
+}
+
+type IdentityInfo = NameInfo & { isDeceased: boolean }
+
+const decesead = 'LÉST'
+
+@Injectable()
+export class AliveStatusService {
+ constructor(
+ private readonly nationalRegistryClient: NationalRegistryClientService,
+ private readonly nationalRegistryV3Client: NationalRegistryV3ClientService,
+ private readonly companyRegistryClient: CompanyRegistryClientService,
+ @Inject(LOGGER_PROVIDER)
+ private logger: Logger,
+ ) {}
+
+ /**
+ * Divides nationalIds into alive and deceased
+ * - Makes calls for every nationalId to NationalRegistry to check if the person exists.
+ * - Divides the nationalIds into alive and deceased, based on
+ * 1. All companies will be divided into alive.
+ * 2. If the person exists in NationalRegistry, then the person is alive.
+ */
+ public async getStatus(
+ nameInfos: NameInfo[],
+ useNationalRegistryV3: boolean,
+ ): Promise<{
+ aliveNationalIds: string[]
+ deceasedNationalIds: string[]
+ aliveNameInfo: NameInfo[]
+ }> {
+ if (nameInfos.length === 0) {
+ return {
+ aliveNationalIds: [],
+ deceasedNationalIds: [],
+ aliveNameInfo: [],
+ }
+ }
+ let identitiesValuesNoError: IdentityInfo[] = []
+ try {
+ const identities = await (
+ await Promise.allSettled(
+ this.getIdentities(nameInfos, useNationalRegistryV3),
+ )
+ ).map((promiseResult) =>
+ promiseResult.status === 'fulfilled'
+ ? promiseResult.value
+ : new Error('Error getting identity'),
+ )
+
+ identitiesValuesNoError = identities.filter(this.isNotError)
+
+ const deceasedNationalIds = identitiesValuesNoError
+ .filter((identity) => identity.isDeceased)
+ .map((identity) => identity.nationalId)
+
+ return {
+ aliveNationalIds: nameInfos
+ .filter((info) => !deceasedNationalIds.includes(info.nationalId))
+ .map((info) => info.nationalId),
+ deceasedNationalIds: deceasedNationalIds,
+ aliveNameInfo: identitiesValuesNoError,
+ }
+ } catch (error) {
+ this.logger.error(`Error getting live status.`, error)
+
+ // We do not want to fail the whole request if we cannot get the live status.
+ // Therefore, we return all nationalIds as alive.
+ return {
+ aliveNationalIds: nameInfos.map((info) => info.nationalId),
+ deceasedNationalIds: [],
+ aliveNameInfo: identitiesValuesNoError,
+ }
+ }
+ }
+
+ private getIdentities(
+ nameInfos: NameInfo[],
+ useNationalRegistryV3: boolean,
+ ): Promise[] {
+ const [companies, individuals] = partitionWithIndex(nameInfos, (nameInfo) =>
+ kennitala.isCompany(nameInfo.nationalId),
+ )
+
+ const companyPromises = companies.map((nameInfo) =>
+ this.getCompanyIdentity(nameInfo),
+ )
+
+ const individualPromises = individuals.map((nameInfo) =>
+ this.getIndividualIdentity(nameInfo, useNationalRegistryV3),
+ )
+
+ return [...companyPromises, ...individualPromises]
+ }
+
+ private getCompanyIdentity(companyInfo: NameInfo): Promise {
+ // All companies will be divided into alive
+ return this.companyRegistryClient
+ .getCompany(companyInfo.nationalId)
+ .then((company) => {
+ if (company && this.isNotError(company)) {
+ return {
+ nationalId: company.nationalId,
+ name: company.name,
+ isDeceased: false,
+ }
+ } else {
+ return {
+ nationalId: companyInfo.nationalId,
+ name: companyInfo.name ?? UNKNOWN_NAME,
+ isDeceased: false,
+ }
+ }
+ })
+ }
+
+ private async getIndividualIdentity(
+ individualInfo: NameInfo,
+ useNationalRegistryV3: boolean,
+ ): Promise {
+ if (useNationalRegistryV3) {
+ return await this.nationalRegistryV3Client
+ .getAllDataIndividual(individualInfo.nationalId)
+ .then((individual) => {
+ if (
+ individual &&
+ this.isNotError(individual) &&
+ individual?.kennitala &&
+ individual?.kennitala !== null
+ ) {
+ return {
+ nationalId: individual.kennitala,
+ name: individual.nafn ?? individualInfo.name ?? UNKNOWN_NAME,
+ isDeceased: individual.afdrif === decesead,
+ }
+ } else {
+ // Pass through although Þjóðskrá API throws an error
+ return {
+ nationalId: individualInfo.nationalId,
+ name: individualInfo.name ?? UNKNOWN_NAME,
+ isDeceased: false,
+ }
+ }
+ })
+ } else {
+ return await this.getIndividualIdentityV2(individualInfo)
+ }
+ }
+
+ private getIndividualIdentityV2(
+ individualInfo: NameInfo,
+ ): Promise {
+ return this.nationalRegistryClient
+ .getIndividual(individualInfo.nationalId)
+ .then((individual) => {
+ if (individual === null) {
+ return {
+ nationalId: individualInfo.nationalId,
+ name: individualInfo.name ?? UNKNOWN_NAME,
+ isDeceased: true,
+ }
+ } else {
+ return {
+ nationalId: individual?.nationalId ?? individualInfo.nationalId,
+ name: individual?.name ?? individualInfo.name ?? UNKNOWN_NAME,
+ isDeceased: false,
+ }
+ }
+ })
+ }
+
+ /**
+ * Checks if item is not an instance of Error
+ */
+ private isNotError(item: T | Error): item is T {
+ return item instanceof Error === false
+ }
+}
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 d51a25e54cf9..ee60fb61c692 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
@@ -1,18 +1,12 @@
-import { Inject, Injectable, Logger } from '@nestjs/common'
-import { InjectModel } from '@nestjs/sequelize'
+import { Inject, Injectable } from '@nestjs/common'
import { ConfigType } from '@nestjs/config'
+import { InjectModel } from '@nestjs/sequelize'
+import startOfDay from 'date-fns/startOfDay'
import * as kennitala from 'kennitala'
import uniqBy from 'lodash/uniqBy'
import { Op } from 'sequelize'
-import startOfDay from 'date-fns/startOfDay'
import { User } from '@island.is/auth-nest-tools'
-import {
- IndividualDto,
- NationalRegistryClientService,
-} from '@island.is/clients/national-registry-v2'
-import { CompanyRegistryClientService } from '@island.is/clients/rsk/company-registry'
-import { LOGGER_PROVIDER } from '@island.is/logging'
import { AuditService } from '@island.is/nest/audit'
import { AuthDelegationType } from '@island.is/shared/types'
import { isDefined } from '@island.is/shared/utils'
@@ -20,17 +14,18 @@ import { isDefined } from '@island.is/shared/utils'
import { ApiScopeDelegationType } from '../resources/models/api-scope-delegation-type.model'
import { ApiScopeUserAccess } from '../resources/models/api-scope-user-access.model'
import { ApiScope } from '../resources/models/api-scope.model'
+import { AliveStatusService, NameInfo } from './alive-status.service'
import { UNKNOWN_NAME } from './constants/names'
+import { DelegationConfig } from './DelegationConfig'
import { ApiScopeInfo } from './delegations-incoming.service'
import { DelegationDTO } from './dto/delegation.dto'
import { MergedDelegationDTO } from './dto/merged-delegation.dto'
+import { DelegationDelegationType } from './models/delegation-delegation-type.model'
import { DelegationScope } from './models/delegation-scope.model'
import { Delegation } from './models/delegation.model'
+import { NationalRegistryV3FeatureService } from './national-registry-v3-feature.service'
import { DelegationValidity } from './types/delegationValidity'
-import { partitionWithIndex } from './utils/partitionWithIndex'
import { getScopeValidityWhereClause } from './utils/scopes'
-import { DelegationDelegationType } from './models/delegation-delegation-type.model'
-import { DelegationConfig } from './DelegationConfig'
type FindAllValidIncomingOptions = {
nationalId: string
@@ -38,11 +33,6 @@ type FindAllValidIncomingOptions = {
validity?: DelegationValidity
}
-type FromNameInfo = {
- nationalId: string
- name: string
-}
-
/**
* Service class for incoming delegations.
* This class supports domain based delegations.
@@ -54,13 +44,11 @@ export class DelegationsIncomingCustomService {
private delegationModel: typeof Delegation,
@InjectModel(ApiScopeUserAccess)
private apiScopeUserAccessModel: typeof ApiScopeUserAccess,
- private nationalRegistryClient: NationalRegistryClientService,
- private companyRegistryClient: CompanyRegistryClientService,
- @Inject(LOGGER_PROVIDER)
- private logger: Logger,
+ private aliveStatusService: AliveStatusService,
@Inject(DelegationConfig.KEY)
private delegationConfig: ConfigType,
private auditService: AuditService,
+ private readonly nationalRegistryV3FeatureService: NationalRegistryV3FeatureService,
) {}
async findAllValidIncoming(
@@ -70,6 +58,7 @@ export class DelegationsIncomingCustomService {
validity = DelegationValidity.NOW,
}: FindAllValidIncomingOptions,
useMaster = false,
+ user?: User,
): Promise {
const { delegations, fromNameInfo } = await this.findAllIncoming(
{
@@ -78,6 +67,7 @@ export class DelegationsIncomingCustomService {
domainName,
},
useMaster,
+ user,
)
const delegationDTOs = delegations.map((d) => d.toDTO())
@@ -95,6 +85,7 @@ export class DelegationsIncomingCustomService {
async findAllValidGeneralMandate(
{ nationalId }: FindAllValidIncomingOptions,
useMaster = false,
+ user?: User,
): Promise {
const { delegations, fromNameInfo } =
await this.findAllIncomingGeneralMandates(
@@ -102,6 +93,7 @@ export class DelegationsIncomingCustomService {
nationalId,
},
useMaster,
+ user,
)
return delegations.map((delegation) => {
@@ -133,10 +125,14 @@ export class DelegationsIncomingCustomService {
return []
}
- const { delegations, fromNameInfo } = await this.findAllIncoming({
- nationalId: user.nationalId,
- validity: DelegationValidity.NOW,
- })
+ const { delegations, fromNameInfo } = await this.findAllIncoming(
+ {
+ nationalId: user.nationalId,
+ validity: DelegationValidity.NOW,
+ },
+ false,
+ user,
+ )
const validDelegations = delegations
.map((d) => {
@@ -219,9 +215,13 @@ export class DelegationsIncomingCustomService {
}
const { delegations, fromNameInfo } =
- await this.findAllIncomingGeneralMandates({
- nationalId: user.nationalId,
- })
+ await this.findAllIncomingGeneralMandates(
+ {
+ nationalId: user.nationalId,
+ },
+ false,
+ user,
+ )
const mergedDelegationDTOs = uniqBy(
delegations.map((d) =>
@@ -243,7 +243,8 @@ export class DelegationsIncomingCustomService {
private async findAllIncomingGeneralMandates(
{ nationalId }: FindAllValidIncomingOptions,
useMaster = false,
- ): Promise<{ delegations: Delegation[]; fromNameInfo: FromNameInfo[] }> {
+ user?: User,
+ ): Promise<{ delegations: Delegation[]; fromNameInfo: NameInfo[] }> {
const startOfToday = startOfDay(new Date())
const delegations = await this.delegationModel.findAll({
@@ -268,10 +269,22 @@ export class DelegationsIncomingCustomService {
})
// Check live status, i.e. dead or alive for delegations
- const { aliveDelegations, deceasedDelegations, fromNameInfo } =
- await this.getLiveStatusFromDelegations(delegations)
+ const isNationalRegistryV3DeceasedStatusEnabled =
+ await this.nationalRegistryV3FeatureService.getValue(user)
+
+ const { aliveNationalIds, deceasedNationalIds, aliveNameInfo } =
+ await this.aliveStatusService.getStatus(
+ delegations.map((d) => ({
+ nationalId: d.fromNationalId,
+ name: d.fromDisplayName,
+ })),
+ isNationalRegistryV3DeceasedStatusEnabled,
+ )
- if (deceasedDelegations.length > 0) {
+ if (deceasedNationalIds.length > 0) {
+ const deceasedDelegations = delegations.filter((d) =>
+ deceasedNationalIds.includes(d.fromNationalId),
+ )
// Delete all deceased delegations by deleting them and their scopes.
const deletePromises = deceasedDelegations.map((delegation) =>
delegation.destroy(),
@@ -286,7 +299,12 @@ export class DelegationsIncomingCustomService {
})
}
- return { delegations: aliveDelegations, fromNameInfo }
+ return {
+ delegations: delegations.filter((d) =>
+ aliveNationalIds.includes(d.fromNationalId),
+ ),
+ fromNameInfo: aliveNameInfo,
+ }
}
private async findAllIncoming(
@@ -298,7 +316,8 @@ export class DelegationsIncomingCustomService {
validity: DelegationValidity
},
useMaster = false,
- ): Promise<{ delegations: Delegation[]; fromNameInfo: FromNameInfo[] }> {
+ user?: User,
+ ): Promise<{ delegations: Delegation[]; fromNameInfo: NameInfo[] }> {
let whereOptions = getScopeValidityWhereClause(validity)
if (domainName) whereOptions = { ...whereOptions, domainName: domainName }
@@ -336,10 +355,22 @@ export class DelegationsIncomingCustomService {
})
// Check live status, i.e. dead or alive for delegations
- const { aliveDelegations, deceasedDelegations, fromNameInfo } =
- await this.getLiveStatusFromDelegations(delegations)
+ const isNationalRegistryV3DeceasedStatusEnabled =
+ await this.nationalRegistryV3FeatureService.getValue(user)
+
+ const { aliveNationalIds, deceasedNationalIds, aliveNameInfo } =
+ await this.aliveStatusService.getStatus(
+ delegations.map((d) => ({
+ nationalId: d.fromNationalId,
+ name: d.fromDisplayName,
+ })),
+ isNationalRegistryV3DeceasedStatusEnabled,
+ )
- if (deceasedDelegations.length > 0) {
+ if (deceasedNationalIds.length > 0) {
+ const deceasedDelegations = delegations.filter((d) =>
+ deceasedNationalIds.includes(d.fromNationalId),
+ )
// Delete all deceased delegations by deleting them and their scopes.
const deletePromises = deceasedDelegations.map((delegation) =>
delegation.destroy(),
@@ -354,7 +385,12 @@ export class DelegationsIncomingCustomService {
})
}
- return { delegations: aliveDelegations, fromNameInfo }
+ return {
+ delegations: delegations.filter((d) =>
+ aliveNationalIds.includes(d.fromNationalId),
+ ),
+ fromNameInfo: aliveNameInfo,
+ }
}
private checkIfScopeIsValid(
@@ -389,103 +425,14 @@ export class DelegationsIncomingCustomService {
return false
}
- /**
- * Divides delegations into alive and deceased delegations
- * - Makes calls for every delegation to NationalRegistry to check if the person exists.
- * - Divides the delegations into alive and deceased delegations, based on
- * 1. All companies will be divided into alive delegations.
- * 2. If the person exists in NationalRegistry, then the delegation is alive.
- */
- private async getLiveStatusFromDelegations(
- delegations: Delegation[],
- ): Promise<{
- aliveDelegations: Delegation[]
- deceasedDelegations: Delegation[]
- fromNameInfo: FromNameInfo[]
- }> {
- if (delegations.length === 0) {
- return {
- aliveDelegations: [],
- deceasedDelegations: [],
- fromNameInfo: [],
- }
- }
-
- const delegationsPromises = delegations.map(({ fromNationalId }) =>
- kennitala.isCompany(fromNationalId)
- ? this.companyRegistryClient
- .getCompany(fromNationalId)
- .catch(this.handlerGetError)
- : this.nationalRegistryClient
- .getIndividual(fromNationalId)
- .catch(this.handlerGetError),
- )
-
- try {
- // Check if delegations is linked to a person, i.e. not deceased
- const identities = await Promise.all(delegationsPromises)
- const identitiesValuesNoError = identities
- .filter(this.isNotError)
- .filter(isDefined)
- .map((identity) => ({
- nationalId: identity.nationalId,
- name: identity.name ?? UNKNOWN_NAME,
- }))
-
- // Divide delegations into alive or deceased delegations.
- const [aliveDelegations, deceasedDelegations] = partitionWithIndex(
- delegations,
- ({ fromNationalId }, index) =>
- // All companies will be divided into aliveDelegations
- kennitala.isCompany(fromNationalId) ||
- // Pass through although Þjóðskrá API throws an error since it is not required to view the delegation.
- identities[index] instanceof Error ||
- // Make sure we can match the person to the delegation, i.e. not deceased
- (identities[index] as IndividualDto)?.nationalId === fromNationalId,
- )
-
- return {
- aliveDelegations,
- deceasedDelegations,
- fromNameInfo: identitiesValuesNoError,
- }
- } catch (error) {
- this.logger.error(
- `Error getting live status from delegations. Delegations: ${delegations.map(
- (d) => d.id,
- )}`,
- error,
- )
-
- // We do not want to fail the whole request if we cannot get the live status from delegations.
- // Therefore, we return all delegations as alive delegations.
- return {
- aliveDelegations: delegations,
- deceasedDelegations: [],
- fromNameInfo: [],
- }
- }
- }
-
- private handlerGetError(error: null | Error) {
- return error
- }
-
- /**
- * Checks if item is not an instance of Error
- */
- private isNotError(item: T | Error): item is T {
- return item instanceof Error === false
- }
-
/**
* Finds person by nationalId.
*/
private getPersonByNationalId(
- identities: Array,
+ identities: Array | undefined,
nationalId: string,
) {
- return identities.find((identity) => identity?.nationalId === nationalId)
+ return identities?.find((identity) => identity?.nationalId === nationalId)
}
private async findAccessControlList(
diff --git a/libs/auth-api-lib/src/lib/delegations/delegations-incoming-representative.service.ts b/libs/auth-api-lib/src/lib/delegations/delegations-incoming-representative.service.ts
index b4380ad4aef1..42d9ccb0a540 100644
--- a/libs/auth-api-lib/src/lib/delegations/delegations-incoming-representative.service.ts
+++ b/libs/auth-api-lib/src/lib/delegations/delegations-incoming-representative.service.ts
@@ -1,9 +1,6 @@
import { Inject, Logger } from '@nestjs/common'
-import {
- IndividualDto,
- NationalRegistryClientService,
-} from '@island.is/clients/national-registry-v2'
+import { User } from '@island.is/auth-nest-tools'
import { LOGGER_PROVIDER } from '@island.is/logging'
import { AuditService } from '@island.is/nest/audit'
import {
@@ -14,10 +11,11 @@ import { isDefined } from '@island.is/shared/utils'
import { PersonalRepresentativeDTO } from '../personal-representative/dto/personal-representative.dto'
import { PersonalRepresentativeService } from '../personal-representative/services/personalRepresentative.service'
+import { AliveStatusService } from './alive-status.service'
import { UNKNOWN_NAME } from './constants/names'
import { ApiScopeInfo } from './delegations-incoming.service'
import { DelegationDTO } from './dto/delegation.dto'
-import { partitionWithIndex } from './utils/partitionWithIndex'
+import { NationalRegistryV3FeatureService } from './national-registry-v3-feature.service'
type FindAllIncomingOptions = {
nationalId: string
@@ -28,10 +26,11 @@ type FindAllIncomingOptions = {
export class DelegationsIncomingRepresentativeService {
constructor(
private prService: PersonalRepresentativeService,
- private nationalRegistryClient: NationalRegistryClientService,
+ private aliveStatusService: AliveStatusService,
@Inject(LOGGER_PROVIDER)
private logger: Logger,
private auditService: AuditService,
+ private readonly nationalRegistryV3FeatureService: NationalRegistryV3FeatureService,
) {}
async findAllIncoming(
@@ -41,6 +40,7 @@ export class DelegationsIncomingRepresentativeService {
requireApiScopes,
}: FindAllIncomingOptions,
useMaster = false,
+ user?: User,
): Promise {
if (
requireApiScopes &&
@@ -95,42 +95,35 @@ export class DelegationsIncomingRepresentativeService {
}
})
- const personPromises = personalRepresentatives.map(
- ({ nationalIdRepresentedPerson }) =>
- this.nationalRegistryClient
- .getIndividual(nationalIdRepresentedPerson)
- .catch(this.handlerGetIndividualError),
- )
+ const isNationalRegistryV3DeceasedStatusEnabled =
+ await this.nationalRegistryV3FeatureService.getValue(user)
- const persons = await Promise.all(personPromises)
- const personsValues = persons.filter((person) => person !== undefined)
- const personsValuesNoError = personsValues.filter(this.isNotError)
-
- // Divide personal representatives into alive or deceased.
- const [alive, deceased] = partitionWithIndex(
- personalRepresentatives,
- ({ nationalIdRepresentedPerson }, index) =>
- // Pass through although Þjóðskrá API throws an error since it is not required to view the personal representative.
- persons[index] instanceof Error ||
- // Make sure we can match the person to the personal representatives, i.e. not deceased
- (persons[index] as IndividualDto)?.nationalId ===
- nationalIdRepresentedPerson,
- )
+ const { aliveNationalIds, deceasedNationalIds, aliveNameInfo } =
+ await this.aliveStatusService.getStatus(
+ personalRepresentatives.map((d) => ({
+ nationalId: d.nationalIdRepresentedPerson,
+ name: UNKNOWN_NAME,
+ })),
+ isNationalRegistryV3DeceasedStatusEnabled,
+ )
- if (deceased.length > 0) {
+ if (deceasedNationalIds.length > 0) {
+ const deceased = personalRepresentatives.filter((pr) =>
+ deceasedNationalIds.includes(pr.nationalIdRepresentedPerson),
+ )
await this.makePersonalRepresentativesInactive(deceased)
}
- return alive
- .map((pr) => {
- const person = this.getPersonByNationalId(
- personsValuesNoError,
- pr.nationalIdRepresentedPerson,
- )
+ const alive = personalRepresentatives.filter((pr) =>
+ aliveNationalIds.includes(pr.nationalIdRepresentedPerson),
+ )
- return toDelegationDTO(person?.name ?? UNKNOWN_NAME, pr)
- })
- .filter(isDefined)
+ return alive.map((pr) => {
+ const person = aliveNameInfo.find(
+ (n) => n.nationalId === pr.nationalIdRepresentedPerson,
+ )
+ return toDelegationDTO(person?.name ?? UNKNOWN_NAME, pr)
+ })
} catch (error) {
this.logger.error('Error in findAllRepresentedPersons', error)
}
@@ -154,25 +147,4 @@ export class DelegationsIncomingRepresentativeService {
system: true,
})
}
-
- private handlerGetIndividualError(error: null | Error) {
- return error
- }
-
- /**
- * Finds person by nationalId.
- */
- private getPersonByNationalId(
- persons: Array,
- nationalId: string,
- ) {
- return persons.find((person) => person?.nationalId === nationalId)
- }
-
- /**
- * Checks if item is not an instance of Error
- */
- private isNotError(item: T | Error): item is T {
- return item instanceof Error === false
- }
}
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 1792164ddb48..b2e0775d3b48 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
@@ -2,10 +2,6 @@ import { BadRequestException, Injectable } from '@nestjs/common'
import { InjectModel } from '@nestjs/sequelize'
import { User } from '@island.is/auth-nest-tools'
-import {
- IndividualDto,
- NationalRegistryClientService,
-} from '@island.is/clients/national-registry-v2'
import { SyslumennService } from '@island.is/clients/syslumenn'
import { logger } from '@island.is/logging'
import { FeatureFlagService, Features } from '@island.is/nest/feature-flags'
@@ -19,6 +15,7 @@ import { ClientDelegationType } from '../clients/models/client-delegation-type.m
import { Client } from '../clients/models/client.model'
import { ApiScopeDelegationType } from '../resources/models/api-scope-delegation-type.model'
import { ApiScope } from '../resources/models/api-scope.model'
+import { AliveStatusService, NameInfo } from './alive-status.service'
import { UNKNOWN_NAME } from './constants/names'
import { DelegationDTOMapper } from './delegation-dto.mapper'
import { DelegationProviderService } from './delegation-provider.service'
@@ -27,8 +24,10 @@ import { DelegationsIncomingCustomService } from './delegations-incoming-custom.
import { DelegationsIncomingRepresentativeService } from './delegations-incoming-representative.service'
import { DelegationsIncomingWardService } from './delegations-incoming-ward.service'
import { DelegationsIndexService } from './delegations-index.service'
+import { DelegationRecordDTO } from './dto/delegation-index.dto'
import { DelegationDTO } from './dto/delegation.dto'
import { MergedDelegationDTO } from './dto/merged-delegation.dto'
+import { NationalRegistryV3FeatureService } from './national-registry-v3-feature.service'
type ClientDelegationInfo = Pick<
Client,
@@ -66,9 +65,10 @@ export class DelegationsIncomingService {
private delegationsIncomingWardService: DelegationsIncomingWardService,
private delegationsIndexService: DelegationsIndexService,
private delegationProviderService: DelegationProviderService,
- private nationalRegistryClient: NationalRegistryClientService,
+ private aliveStatusService: AliveStatusService,
private readonly featureFlagService: FeatureFlagService,
private readonly syslumennService: SyslumennService,
+ private readonly nationalRegistryV3FeatureService: NationalRegistryV3FeatureService,
) {}
async findAllValid(
@@ -96,22 +96,34 @@ export class DelegationsIncomingService {
)
delegationPromises.push(
- this.delegationsIncomingCustomService.findAllValidIncoming({
- nationalId: user.nationalId,
- domainName,
- }),
+ this.delegationsIncomingCustomService.findAllValidIncoming(
+ {
+ nationalId: user.nationalId,
+ domainName,
+ },
+ false,
+ user,
+ ),
)
delegationPromises.push(
- this.delegationsIncomingCustomService.findAllValidGeneralMandate({
- nationalId: user.nationalId,
- }),
+ this.delegationsIncomingCustomService.findAllValidGeneralMandate(
+ {
+ nationalId: user.nationalId,
+ },
+ false,
+ user,
+ ),
)
delegationPromises.push(
- this.delegationsIncomingRepresentativeService.findAllIncoming({
- nationalId: user.nationalId,
- }),
+ this.delegationsIncomingRepresentativeService.findAllIncoming(
+ {
+ nationalId: user.nationalId,
+ },
+ false,
+ user,
+ ),
)
const delegationSets = await Promise.all(delegationPromises)
@@ -211,11 +223,15 @@ export class DelegationsIncomingService {
) {
delegationPromises.push(
this.delegationsIncomingRepresentativeService
- .findAllIncoming({
- nationalId: user.nationalId,
- clientAllowedApiScopes,
- requireApiScopes: client.requireApiScopes,
- })
+ .findAllIncoming(
+ {
+ nationalId: user.nationalId,
+ clientAllowedApiScopes,
+ requireApiScopes: client.requireApiScopes,
+ },
+ false,
+ user,
+ )
.then((ds) =>
ds.map((d) => DelegationDTOMapper.toMergedDelegationDTO(d)),
),
@@ -335,11 +351,64 @@ export class DelegationsIncomingService {
clientAllowedApiScopes,
requireApiScopes,
)
- const merged = records.map((d) =>
- DelegationDTOMapper.recordToMergedDelegationDTO(d),
- )
- await Promise.all(merged.map((d) => this.updateName(d)))
+ const isNationalRegistryV3DeceasedStatusEnabled =
+ await this.nationalRegistryV3FeatureService.getValue(user)
+
+ const { aliveNationalIds, deceasedNationalIds, aliveNameInfo } =
+ await this.aliveStatusService.getStatus(
+ Array.from(
+ new Set(
+ records.map((d) => ({
+ nationalId: d.fromNationalId,
+ name: UNKNOWN_NAME,
+ })),
+ ),
+ ),
+ isNationalRegistryV3DeceasedStatusEnabled,
+ )
+
+ if (deceasedNationalIds.length > 0) {
+ const deceasedDelegations = records.filter((d) =>
+ deceasedNationalIds.includes(d.fromNationalId),
+ )
+ // Delete all deceased delegations from index
+ const deletePromises = deceasedDelegations.map((delegation) => {
+ this.delegationsIndexService.removeDelegationRecord(
+ {
+ fromNationalId: delegation.fromNationalId,
+ toNationalId: delegation.toNationalId,
+ type: delegation.type,
+ provider: AuthDelegationProvider.DistrictCommissionersRegistry,
+ },
+ user,
+ )
+ })
+
+ const results = await Promise.allSettled(deletePromises)
+ results.forEach((result, index) => {
+ if (result.status === 'rejected') {
+ logger.error('Failed to remove delegation record', {
+ error: result.reason,
+ delegation: deceasedDelegations[index],
+ })
+ }
+ })
+ }
+
+ const aliveNationalIdSet = new Set(aliveNationalIds)
+ const merged = records.reduce(
+ (acc: MergedDelegationDTO[], d: DelegationRecordDTO) => {
+ if (aliveNationalIdSet.has(d.fromNationalId)) {
+ acc.push({
+ ...DelegationDTOMapper.recordToMergedDelegationDTO(d),
+ fromName: this.getNameFromNameInfo(d.fromNationalId, aliveNameInfo),
+ })
+ }
+ return acc
+ },
+ [],
+ )
return merged
}
@@ -381,17 +450,12 @@ export class DelegationsIncomingService {
})
}
- private async updateName(
- mergedDelegation: MergedDelegationDTO,
- ): Promise {
- try {
- const fromIndividual: IndividualDto | null =
- await this.nationalRegistryClient.getIndividual(
- mergedDelegation.fromNationalId,
- )
- mergedDelegation.fromName = fromIndividual?.name ?? UNKNOWN_NAME
- } catch (error) {
- mergedDelegation.fromName = UNKNOWN_NAME
- }
+ private getNameFromNameInfo(
+ nationalId: string,
+ nameInfo: NameInfo[],
+ ): string {
+ return (
+ nameInfo.find((n) => n.nationalId === nationalId)?.name ?? UNKNOWN_NAME
+ )
}
}
diff --git a/libs/auth-api-lib/src/lib/delegations/delegations.module.ts b/libs/auth-api-lib/src/lib/delegations/delegations.module.ts
index 21aae6fe43e4..031423b76886 100644
--- a/libs/auth-api-lib/src/lib/delegations/delegations.module.ts
+++ b/libs/auth-api-lib/src/lib/delegations/delegations.module.ts
@@ -3,10 +3,11 @@ import { SequelizeModule } from '@nestjs/sequelize'
import { RskRelationshipsClientModule } from '@island.is/clients-rsk-relationships'
import { NationalRegistryClientModule } from '@island.is/clients/national-registry-v2'
+import { NationalRegistryV3ClientModule } from '@island.is/clients/national-registry-v3'
import { CompanyRegistryClientModule } from '@island.is/clients/rsk/company-registry'
import { SyslumennClientModule } from '@island.is/clients/syslumenn'
-import { FeatureFlagModule } from '@island.is/nest/feature-flags'
import { ZendeskModule } from '@island.is/clients/zendesk'
+import { FeatureFlagModule } from '@island.is/nest/feature-flags'
import { ClientAllowedScope } from '../clients/models/client-allowed-scope.model'
import { Client } from '../clients/models/client.model'
@@ -19,6 +20,7 @@ import { ResourcesModule } from '../resources/resources.module'
import { UserIdentitiesModule } from '../user-identities/user-identities.module'
import { UserSystemNotificationModule } from '../user-notification'
import { DelegationAdminCustomService } from './admin/delegation-admin-custom.service'
+import { AliveStatusService } from './alive-status.service'
import { DelegationProviderService } from './delegation-provider.service'
import { DelegationScopeService } from './delegation-scope.service'
import { IncomingDelegationsCompanyService } from './delegations-incoming-company.service'
@@ -37,12 +39,14 @@ import { DelegationScope } from './models/delegation-scope.model'
import { DelegationTypeModel } from './models/delegation-type.model'
import { Delegation } from './models/delegation.model'
import { NamesService } from './names.service'
+import { NationalRegistryV3FeatureService } from './national-registry-v3-feature.service'
@Module({
imports: [
ResourcesModule,
PersonalRepresentativeModule,
NationalRegistryClientModule,
+ NationalRegistryV3ClientModule,
RskRelationshipsClientModule,
CompanyRegistryClientModule,
ZendeskModule,
@@ -79,6 +83,8 @@ import { NamesService } from './names.service'
DelegationsIndexService,
DelegationProviderService,
DelegationAdminCustomService,
+ AliveStatusService,
+ NationalRegistryV3FeatureService,
],
exports: [
DelegationsService,
diff --git a/libs/auth-api-lib/src/lib/delegations/national-registry-v3-feature.service.ts b/libs/auth-api-lib/src/lib/delegations/national-registry-v3-feature.service.ts
new file mode 100644
index 000000000000..e750dbc12126
--- /dev/null
+++ b/libs/auth-api-lib/src/lib/delegations/national-registry-v3-feature.service.ts
@@ -0,0 +1,17 @@
+import { Injectable } from '@nestjs/common'
+
+import { User } from '@island.is/auth-nest-tools'
+import { FeatureFlagService, Features } from '@island.is/nest/feature-flags'
+
+@Injectable()
+export class NationalRegistryV3FeatureService {
+ constructor(private readonly featureFlagService: FeatureFlagService) {}
+
+ getValue(user?: User): Promise {
+ return this.featureFlagService.getValue(
+ Features.isNationalRegistryV3DeceasedStatusEnabled,
+ false,
+ user,
+ )
+ }
+}
diff --git a/libs/cms/src/lib/cms.contentful.service.ts b/libs/cms/src/lib/cms.contentful.service.ts
index f54e8a4794c9..4488f25f5201 100644
--- a/libs/cms/src/lib/cms.contentful.service.ts
+++ b/libs/cms/src/lib/cms.contentful.service.ts
@@ -83,10 +83,11 @@ import { LifeEventPage, mapLifeEventPage } from './models/lifeEventPage.model'
import { GetGenericTagBySlugInput } from './dto/getGenericTagBySlug.input'
import { GetGenericTagsInTagGroupsInput } from './dto/getGenericTagsInTagGroups.input'
import { Grant, mapGrant } from './models/grant.model'
-import { GrantList } from './models/grantList.model'
import { mapManual } from './models/manual.model'
import { mapServiceWebPage } from './models/serviceWebPage.model'
import { mapEvent } from './models/event.model'
+import { GetOrganizationParentSubpageInput } from './dto/getOrganizationParentSubpage.input'
+import { mapOrganizationParentSubpage } from './models/organizationParentSubpage.model'
const errorHandler = (name: string) => {
return (error: Error) => {
@@ -1151,4 +1152,27 @@ export class CmsContentfulService {
return (result.items as types.IGenericTag[]).map(mapGenericTag)
}
+
+ async getOrganizationParentSubpage(input: GetOrganizationParentSubpageInput) {
+ const params = {
+ content_type: 'organizationParentSubpage',
+ 'fields.slug': input.slug,
+ 'fields.organizationPage.sys.contentType.sys.id': 'organizationPage',
+ 'fields.organizationPage.fields.slug': input.organizationPageSlug,
+ limit: 1,
+ }
+
+ const response = await this.contentfulRepository
+ .getLocalizedEntries(
+ input.lang,
+ params,
+ )
+ .catch(errorHandler('getOrganizationParentSubpage'))
+
+ return (
+ (response.items as types.IOrganizationParentSubpage[]).map(
+ mapOrganizationParentSubpage,
+ )[0] ?? null
+ )
+ }
}
diff --git a/libs/cms/src/lib/cms.resolver.ts b/libs/cms/src/lib/cms.resolver.ts
index 462b5e4531ec..8c82719d6bdc 100644
--- a/libs/cms/src/lib/cms.resolver.ts
+++ b/libs/cms/src/lib/cms.resolver.ts
@@ -126,6 +126,8 @@ import { Grant } from './models/grant.model'
import { GetGrantsInput } from './dto/getGrants.input'
import { GetSingleGrantInput } from './dto/getSingleGrant.input'
import { GrantList } from './models/grantList.model'
+import { OrganizationParentSubpage } from './models/organizationParentSubpage.model'
+import { GetOrganizationParentSubpageInput } from './dto/getOrganizationParentSubpage.input'
const defaultCache: CacheControlOptions = { maxAge: CACHE_CONTROL_MAX_AGE }
@@ -707,6 +709,14 @@ export class CmsResolver {
): Promise {
return this.cmsElasticsearchService.getTeamMembers(input)
}
+
+ @CacheControl(defaultCache)
+ @Query(() => OrganizationParentSubpage, { nullable: true })
+ getOrganizationParentSubpage(
+ @Args('input') input: GetOrganizationParentSubpageInput,
+ ): Promise {
+ return this.cmsContentfulService.getOrganizationParentSubpage(input)
+ }
}
@Resolver(() => LatestNewsSlice)
diff --git a/libs/cms/src/lib/dto/getOrganizationParentSubpage.input.ts b/libs/cms/src/lib/dto/getOrganizationParentSubpage.input.ts
new file mode 100644
index 000000000000..c45e5ef9fd33
--- /dev/null
+++ b/libs/cms/src/lib/dto/getOrganizationParentSubpage.input.ts
@@ -0,0 +1,18 @@
+import { Field, InputType } from '@nestjs/graphql'
+import { IsString } from 'class-validator'
+import { ElasticsearchIndexLocale } from '@island.is/content-search-index-manager'
+
+@InputType()
+export class GetOrganizationParentSubpageInput {
+ @Field()
+ @IsString()
+ slug!: string
+
+ @Field()
+ @IsString()
+ organizationPageSlug!: string
+
+ @Field(() => String)
+ @IsString()
+ lang: ElasticsearchIndexLocale = 'is'
+}
diff --git a/libs/cms/src/lib/models/organizationParentSubpage.model.ts b/libs/cms/src/lib/models/organizationParentSubpage.model.ts
new file mode 100644
index 000000000000..9c9a4d927059
--- /dev/null
+++ b/libs/cms/src/lib/models/organizationParentSubpage.model.ts
@@ -0,0 +1,48 @@
+import { CacheField } from '@island.is/nest/graphql'
+import { Field, ID, ObjectType } from '@nestjs/graphql'
+import { IOrganizationParentSubpage } from '../generated/contentfulTypes'
+import { getOrganizationPageUrlPrefix } from '@island.is/shared/utils'
+
+@ObjectType()
+class OrganizationSubpageLink {
+ @Field()
+ label!: string
+
+ @Field()
+ href!: string
+}
+
+@ObjectType()
+export class OrganizationParentSubpage {
+ @Field(() => ID)
+ id!: string
+
+ @Field()
+ title!: string
+
+ @CacheField(() => [OrganizationSubpageLink])
+ childLinks!: OrganizationSubpageLink[]
+}
+
+export const mapOrganizationParentSubpage = ({
+ sys,
+ fields,
+}: IOrganizationParentSubpage): OrganizationParentSubpage => {
+ return {
+ id: sys.id,
+ title: fields.title,
+ childLinks:
+ fields.pages
+ ?.filter(
+ (page) =>
+ Boolean(page.fields.organizationPage?.fields?.slug) &&
+ Boolean(page.fields.slug),
+ )
+ .map((page) => ({
+ label: page.fields.title,
+ href: `/${getOrganizationPageUrlPrefix(sys.locale)}/${
+ page.fields.organizationPage.fields.slug
+ }/${fields.slug}/${page.fields.slug}`,
+ })) ?? [],
+ }
+}
diff --git a/libs/feature-flags/src/lib/features.ts b/libs/feature-flags/src/lib/features.ts
index ae839cdd1690..fc77efa64398 100644
--- a/libs/feature-flags/src/lib/features.ts
+++ b/libs/feature-flags/src/lib/features.ts
@@ -106,6 +106,9 @@ export enum Features {
// General mandate delegation type
isGeneralMandateDelegationEnabled = 'isGeneralMandateDelegationEnabled',
+
+ // Should auth api use national registry v3 for checking deceased status
+ isNationalRegistryV3DeceasedStatusEnabled = 'isNationalRegistryV3DeceasedStatusEnabled',
}
export enum ServerSideFeature {
diff --git a/libs/judicial-system/types/src/index.ts b/libs/judicial-system/types/src/index.ts
index 2a7cf9901626..140f6c9bc92a 100644
--- a/libs/judicial-system/types/src/index.ts
+++ b/libs/judicial-system/types/src/index.ts
@@ -102,6 +102,7 @@ export {
export {
getIndictmentVerdictAppealDeadlineStatus,
VERDICT_APPEAL_WINDOW_DAYS,
+ FINE_APPEAL_WINDOW_DAYS,
} from './lib/indictmentCase'
export type {
diff --git a/libs/judicial-system/types/src/lib/indictmentCase.ts b/libs/judicial-system/types/src/lib/indictmentCase.ts
index d751d60616b5..f823acbdf4b4 100644
--- a/libs/judicial-system/types/src/lib/indictmentCase.ts
+++ b/libs/judicial-system/types/src/lib/indictmentCase.ts
@@ -1,6 +1,7 @@
const DAYS_TO_MILLISECONDS = 24 * 60 * 60 * 1000
export const VERDICT_APPEAL_WINDOW_DAYS = 28
-const MILLISECONDS_TO_EXPIRY = VERDICT_APPEAL_WINDOW_DAYS * DAYS_TO_MILLISECONDS
+export const FINE_APPEAL_WINDOW_DAYS = 3
+const getDays = (days: number) => days * DAYS_TO_MILLISECONDS
/*
This function takes an array of verdict info tuples:
@@ -12,6 +13,7 @@ const MILLISECONDS_TO_EXPIRY = VERDICT_APPEAL_WINDOW_DAYS * DAYS_TO_MILLISECONDS
*/
export const getIndictmentVerdictAppealDeadlineStatus = (
verdictInfo?: [boolean, Date | undefined][],
+ isFine?: boolean,
): [boolean, boolean] => {
if (
!verdictInfo ||
@@ -34,5 +36,10 @@ export const getIndictmentVerdictAppealDeadlineStatus = (
new Date(0),
)
- return [true, Date.now() > newestViewDate.getTime() + MILLISECONDS_TO_EXPIRY]
+ return [
+ true,
+ Date.now() >
+ newestViewDate.getTime() +
+ getDays(isFine ? FINE_APPEAL_WINDOW_DAYS : VERDICT_APPEAL_WINDOW_DAYS),
+ ]
}
diff --git a/libs/portals/admin/delegation-admin/src/screens/CreateDelegation/CreateDelegation.tsx b/libs/portals/admin/delegation-admin/src/screens/CreateDelegation/CreateDelegation.tsx
index 489fdf9c84f3..7e25f55962b6 100644
--- a/libs/portals/admin/delegation-admin/src/screens/CreateDelegation/CreateDelegation.tsx
+++ b/libs/portals/admin/delegation-admin/src/screens/CreateDelegation/CreateDelegation.tsx
@@ -1,54 +1,53 @@
-import React, { useEffect, useState } from 'react'
import {
- Box,
- Stack,
AlertMessage,
+ Box,
Checkbox,
+ DatePicker,
GridColumn,
GridRow,
+ Icon,
Input,
Select,
- DatePicker,
+ Stack,
toast,
- Icon,
} from '@island.is/island-ui/core'
-import { BackButton } from '@island.is/portals/admin/core'
import { useLocale } from '@island.is/localization'
+import { BackButton } from '@island.is/portals/admin/core'
+import React, { useEffect, useState } from 'react'
+import { Identity } from '@island.is/api/schema'
import { IntroHeader, m as coreMessages } from '@island.is/portals/core'
-import { m } from '../../lib/messages'
-import { DelegationAdminPaths } from '../../lib/paths'
-import NumberFormat from 'react-number-format'
+import {
+ DelegationsFormFooter,
+ useDynamicShadow,
+} from '@island.is/portals/shared-modules/delegations'
+import { useUserInfo } from '@island.is/react-spa/bff'
+import { replaceParams } from '@island.is/react-spa/shared'
+import { AuthDelegationType } from '@island.is/shared/types'
+import { maskString, unmaskString } from '@island.is/shared/utils'
+import cn from 'classnames'
import startOfDay from 'date-fns/startOfDay'
+import kennitala from 'kennitala'
+import debounce from 'lodash/debounce'
+import NumberFormat from 'react-number-format'
import {
Form,
- redirect,
useActionData,
useNavigate,
useSearchParams,
useSubmit,
} from 'react-router-dom'
+import { CreateDelegationConfirmModal } from '../../components/CreateDelegationConfirmModal'
+import { FORM_ERRORS } from '../../constants/errors'
+import { m } from '../../lib/messages'
+import { DelegationAdminPaths } from '../../lib/paths'
import { CreateDelegationResult } from './CreateDelegation.action'
import * as styles from './CreateDelegation.css'
import { useIdentityLazyQuery } from './CreateDelegation.generated'
-import debounce from 'lodash/debounce'
-import cn from 'classnames'
-import {
- DelegationsFormFooter,
- useDynamicShadow,
-} from '@island.is/portals/shared-modules/delegations'
-import { CreateDelegationConfirmModal } from '../../components/CreateDelegationConfirmModal'
-import { Identity } from '@island.is/api/schema'
-import kennitala from 'kennitala'
-import { maskString, unmaskString } from '@island.is/shared/utils'
-import { useAuth } from '@island.is/auth/react'
-import { replaceParams } from '@island.is/react-spa/shared'
-import { FORM_ERRORS } from '../../constants/errors'
-import { AuthDelegationType } from '@island.is/shared/types'
const CreateDelegationScreen = () => {
const { formatMessage } = useLocale()
- const { userInfo } = useAuth()
+ const userInfo = useUserInfo()
const navigate = useNavigate()
const [searchParams, setSearchParams] = useSearchParams()
diff --git a/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.tsx b/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.tsx
index daea025576c2..fa47e3700ce1 100644
--- a/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.tsx
+++ b/libs/portals/admin/delegation-admin/src/screens/DelegationAdminDetails/DelegationAdmin.tsx
@@ -1,24 +1,23 @@
-import { Button, GridColumn, Box, Stack, Tabs } from '@island.is/island-ui/core'
-import { BackButton } from '@island.is/portals/admin/core'
-import { useLocale } from '@island.is/localization'
-import { useLoaderData, useNavigate } from 'react-router-dom'
-import { DelegationAdminResult } from './DelegationAdmin.loader'
-import { DelegationAdminPaths } from '../../lib/paths'
-import { formatNationalId, IntroHeader } from '@island.is/portals/core'
-import { m } from '../../lib/messages'
-import React from 'react'
-import DelegationList from '../../components/DelegationList'
import { AuthCustomDelegation } from '@island.is/api/schema'
-import { DelegationsEmptyState } from '@island.is/portals/shared-modules/delegations'
-import { useAuth } from '@island.is/auth/react'
import { AdminPortalScope } from '@island.is/auth/scopes'
+import { Box, Button, GridColumn, Stack, Tabs } 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 { DelegationsEmptyState } from '@island.is/portals/shared-modules/delegations'
+import { useUserInfo } from '@island.is/react-spa/bff'
import { maskString } from '@island.is/shared/utils'
+import { useLoaderData, useNavigate } from 'react-router-dom'
+import DelegationList from '../../components/DelegationList'
+import { m } from '../../lib/messages'
+import { DelegationAdminPaths } from '../../lib/paths'
+import { DelegationAdminResult } from './DelegationAdmin.loader'
const DelegationAdminScreen = () => {
const { formatMessage } = useLocale()
const navigate = useNavigate()
const delegationAdmin = useLoaderData() as DelegationAdminResult
- const { userInfo } = useAuth()
+ const userInfo = useUserInfo()
const hasAdminAccess = userInfo?.scopes.includes(
AdminPortalScope.delegationSystemAdmin,
diff --git a/libs/portals/admin/delegation-admin/src/screens/Root.tsx b/libs/portals/admin/delegation-admin/src/screens/Root.tsx
index 3dd3600dd6c8..d7e8866b9f3f 100644
--- a/libs/portals/admin/delegation-admin/src/screens/Root.tsx
+++ b/libs/portals/admin/delegation-admin/src/screens/Root.tsx
@@ -1,20 +1,20 @@
-import { useLocale } from '@island.is/localization'
-import { IntroHeader } from '@island.is/portals/core'
-import { m } from '../lib/messages'
-import { Form, Outlet, useActionData, useNavigate } from 'react-router-dom'
+import { AdminPortalScope } from '@island.is/auth/scopes'
import {
AsyncSearchInput,
+ Box,
Button,
GridColumn,
GridRow,
- Box,
} from '@island.is/island-ui/core'
-import React, { useEffect, useState } from 'react'
+import { useLocale } from '@island.is/localization'
+import { IntroHeader } from '@island.is/portals/core'
+import { useUserInfo } from '@island.is/react-spa/bff'
import { useSubmitting } from '@island.is/react-spa/shared'
-import { GetDelegationForNationalIdResult } from './Root.action'
+import { useEffect, useState } from 'react'
+import { Form, Outlet, useActionData, useNavigate } from 'react-router-dom'
+import { m } from '../lib/messages'
import { DelegationAdminPaths } from '../lib/paths'
-import { useAuth } from '@island.is/auth/react'
-import { AdminPortalScope } from '@island.is/auth/scopes'
+import { GetDelegationForNationalIdResult } from './Root.action'
const Root = () => {
const [focused, setFocused] = useState(false)
@@ -24,7 +24,7 @@ const Root = () => {
const { isSubmitting, isLoading } = useSubmitting()
const [error, setError] = useState({ hasError: false, message: '' })
const navigate = useNavigate()
- const { userInfo } = useAuth()
+ const userInfo = useUserInfo()
const hasAdminAccess = userInfo?.scopes.includes(
AdminPortalScope.delegationSystemAdmin,
diff --git a/libs/portals/admin/regulations-admin/src/components/EditBasics.tsx b/libs/portals/admin/regulations-admin/src/components/EditBasics.tsx
index 304a1e004f20..b2868d63fe0f 100644
--- a/libs/portals/admin/regulations-admin/src/components/EditBasics.tsx
+++ b/libs/portals/admin/regulations-admin/src/components/EditBasics.tsx
@@ -29,6 +29,7 @@ import { ReferenceText } from './impacts/ReferenceText'
import { DraftChangeForm, DraftImpactForm } from '../state/types'
import { makeDraftAppendixForm } from '../state/makeFields'
import { hasAnyChange } from '../utils/formatAmendingUtils'
+import { usePristineRegulations } from '../utils/hooks'
const updateText =
'Ósamræmi er í texta stofnreglugerðar og breytingareglugerðar. Texti breytingareglugerðar þarf að samræmast breytingum sem gerðar hafa verið á stofnreglugerð, eigi breytingarnar að færast inn með réttum hætti.'
@@ -38,12 +39,13 @@ export const EditBasics = () => {
const { draft, actions, ministries } = useDraftingState()
const [editorKey, setEditorKey] = useState('initial')
const [titleError, setTitleError] = useState(undefined)
- const [hasUpdated, setHasUpdated] = useState(false)
const [hasUpdatedAppendix, setHasUpdatedAppendix] = useState(false)
const [references, setReferences] = useState()
const [isModalVisible, setIsModalVisible] = useState(true)
const [hasConfirmed, setHasConfirmed] = useState(false)
const [hasSeenModal, setHasSeenModal] = useState(false)
+ const { removePristineRegulation, isPristineRegulation } =
+ usePristineRegulations()
const { text, appendixes } = draft
const { updateState } = actions
@@ -120,13 +122,6 @@ export const EditBasics = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [draft.impacts])
- useEffect(() => {
- if (!hasUpdatedAppendix && hasUpdated) {
- updateAppendixes()
- setHasUpdatedAppendix(true)
- }
- }, [hasUpdated, hasUpdatedAppendix])
-
const updateAppendixes = () => {
// FORMAT AMENDING REGULATION APPENDIXES
const impactArray = Object.values(draft.impacts).flat()
@@ -167,9 +162,17 @@ export const EditBasics = () => {
const additionString = additions.join('') as HTMLText
updateState('text', additionString)
- setHasUpdated(true)
+ // Update appendixes
+ if (!hasUpdatedAppendix) {
+ updateAppendixes()
+ setHasUpdatedAppendix(true)
+ }
+
+ // Remove from session storage
+ removePristineRegulation(draft.id)
}
+ const shouldShowModal = isPristineRegulation(draft.id)
return (
<>
@@ -328,7 +331,7 @@ export const EditBasics = () => {
)}
- {!hasUpdated ? (
+ {shouldShowModal ? (
{
const chaptersField = draft.lawChapters
const activeChapters = chaptersField.value
- const { data: mentionedList /*, loading , error */ } =
- useRegulationListQuery(draft.mentioned)
+ // const { data: mentionedList /*, loading , error */ } =
+ // useRegulationListQuery(draft.mentioned)
const lawChaptersOptions = useLawChapterOptions(
lawChapters.list,
@@ -47,16 +47,17 @@ export const LawChaptersSelect = () => {
)
// Auto fill lawChapters if there are mentions and no lawchapters present
- useEffect(() => {
- if (mentionedList?.length && !draft.lawChapters.value.length) {
- mentionedList.forEach((mention) => {
- mention.lawChapters?.forEach((ch) => {
- actions.updateLawChapterProp('add', ch.slug)
- })
- })
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [mentionedList])
+ // REMOVE FOR NOW. NOT SHOWING CORRECT RESULTS. MIGHT WANT TO REVISIT
+ // useEffect(() => {
+ // if (mentionedList?.length && !draft.lawChapters.value.length) {
+ // mentionedList.forEach((mention) => {
+ // mention.lawChapters?.forEach((ch) => {
+ // actions.updateLawChapterProp('add', ch.slug)
+ // })
+ // })
+ // }
+ // // eslint-disable-next-line react-hooks/exhaustive-deps
+ // }, [mentionedList])
return (
diff --git a/libs/portals/admin/regulations-admin/src/components/impacts/EditChange.tsx b/libs/portals/admin/regulations-admin/src/components/impacts/EditChange.tsx
index aad98a8ca232..443c1545203b 100644
--- a/libs/portals/admin/regulations-admin/src/components/impacts/EditChange.tsx
+++ b/libs/portals/admin/regulations-admin/src/components/impacts/EditChange.tsx
@@ -7,6 +7,7 @@ import {
GridRow,
GridColumn,
Text,
+ toast,
} from '@island.is/island-ui/core'
import { useEffect, useState, useMemo } from 'react'
import {
@@ -51,7 +52,10 @@ import {
updateFieldValue,
validateImpact,
} from '../../state/validations'
-import { useGetRegulationHistory } from '../../utils/hooks'
+import {
+ useGetRegulationHistory,
+ usePristineRegulations,
+} from '../../utils/hooks'
import { DraftRegulationChange } from '@island.is/regulations/admin'
import { useLocale } from '@island.is/localization'
import { cleanTitle } from '@island.is/regulations-tools/cleanTitle'
@@ -74,6 +78,7 @@ export const EditChange = (props: EditChangeProp) => {
const today = useMemo(() => new Date(), [])
const [minDate, setMinDate] = useState()
const [showChangeForm, setShowChangeForm] = useState(false)
+ const { addPristineRegulation } = usePristineRegulations()
// Target regulation for impact
const [activeRegulation, setActiveRegulation] = useState<
@@ -280,7 +285,12 @@ export const EditChange = (props: EditChangeProp) => {
}
return { success: true, error: undefined }
})
+ .then(() => {
+ addPristineRegulation(draft.id)
+ closeModal(true)
+ })
.catch((error) => {
+ toast.error(t(msg.errorOnSaveReg))
return { success: false, error: error as Error }
})
} else {
@@ -306,12 +316,15 @@ export const EditChange = (props: EditChangeProp) => {
}
return { success: true, error: undefined }
})
+ .then(() => {
+ addPristineRegulation(draft.id)
+ closeModal(true)
+ })
.catch((error) => {
+ toast.error(t(msg.errorOnSaveReg))
return { success: false, error: error as Error }
})
}
-
- closeModal(true)
}
const localActions = {
diff --git a/libs/portals/admin/regulations-admin/src/components/impacts/ImpactAmendingSelection.tsx b/libs/portals/admin/regulations-admin/src/components/impacts/ImpactAmendingSelection.tsx
index ec8ab460d01d..109679e96c38 100644
--- a/libs/portals/admin/regulations-admin/src/components/impacts/ImpactAmendingSelection.tsx
+++ b/libs/portals/admin/regulations-admin/src/components/impacts/ImpactAmendingSelection.tsx
@@ -2,7 +2,11 @@ import { useEffect, useState } from 'react'
import { impactMsgs } from '../../lib/messages'
import { useLocale } from '@island.is/localization'
-import { RegulationOptionList, RegulationType } from '@island.is/regulations'
+import {
+ RegulationOption,
+ RegulationOptionList,
+ RegulationType,
+} from '@island.is/regulations'
import { DraftImpactName } from '@island.is/regulations/admin'
import { AsyncSearch, Option, Text } from '@island.is/island-ui/core'
@@ -39,10 +43,24 @@ export const ImpactAmendingSelection = ({
RegulationSearchListQuery,
{
onCompleted: (data) => {
- setResults(
- (data.getRegulationsOptionSearch as RegulationOptionList) ||
- undefined,
- )
+ // The search results need to be reordered so that the objects with titles containing the input string are first
+ const results = data.getRegulationsOptionSearch
+ const reorderedResults = value
+ ? [
+ // Objects with titles containing the input string
+ ...results.filter((obj: RegulationOption) =>
+ obj.title.toLowerCase().includes(value.toLowerCase()),
+ ),
+ // The rest of the objects
+ ...results.filter(
+ (obj: RegulationOption) =>
+ !obj.title.toLowerCase().includes(value.toLowerCase()),
+ ),
+ ]
+ : results // If value is undefined, keep the original order
+
+ setIsLoading(false)
+ setResults((reorderedResults as RegulationOptionList) || undefined)
},
fetchPolicy: 'no-cache',
},
@@ -64,7 +82,6 @@ export const ImpactAmendingSelection = ({
variables: { input: { q: value, iA: false, iR: false } },
})
}
- setIsLoading(false)
},
600,
[value],
diff --git a/libs/portals/admin/regulations-admin/src/lib/messages.ts b/libs/portals/admin/regulations-admin/src/lib/messages.ts
index e49a79d65275..1483376cd6af 100644
--- a/libs/portals/admin/regulations-admin/src/lib/messages.ts
+++ b/libs/portals/admin/regulations-admin/src/lib/messages.ts
@@ -571,6 +571,11 @@ export const errorMsgs = defineMessages({
id: 'ap.regulations-admin:title-is-too-long',
defaultMessage: 'Titill reglugerðar er of langur.',
},
+ errorOnSaveReg: {
+ id: 'ap.regulations-admin:error-on-save-reg',
+ defaultMessage:
+ 'Villa kom upp við að vista texta. Vinsamlegast afritið texta og reynið aftur síðar.',
+ },
})
export const homeMessages = defineMessages({
diff --git a/libs/portals/admin/regulations-admin/src/utils/formatAmendingRegulation.ts b/libs/portals/admin/regulations-admin/src/utils/formatAmendingRegulation.ts
index d42974eb109f..651408bdf618 100644
--- a/libs/portals/admin/regulations-admin/src/utils/formatAmendingRegulation.ts
+++ b/libs/portals/admin/regulations-admin/src/utils/formatAmendingRegulation.ts
@@ -9,11 +9,15 @@ import flatten from 'lodash/flatten'
import uniq from 'lodash/uniq'
import {
allSameDay,
+ containsAnyTitleClass,
extractArticleTitleDisplay,
formatDate,
+ getArticleTitleType,
+ getArticleTypeText,
getTextWithSpaces,
groupElementsByArticleTitleFromDiv,
hasAnyChange,
+ hasSubtitle,
isGildisTaka,
removeRegPrefix,
updateAppendixWording,
@@ -203,13 +207,21 @@ export const formatAmendingRegBody = (
const testGroup: {
arr: HTMLText[]
original?: HTMLText[]
- title: string
+ titleObject: {
+ text: string
+ hasSubtitle: boolean
+ type: 'article' | 'chapter' | 'subchapter'
+ }
isDeletion?: boolean
isAddition?: boolean
} = {
arr: [],
original: [],
- title: '',
+ titleObject: {
+ text: '',
+ hasSubtitle: false,
+ type: 'article',
+ },
isDeletion: undefined,
isAddition: undefined,
}
@@ -218,16 +230,19 @@ export const formatAmendingRegBody = (
let pushHtml = '' as HTMLText
let isParagraph = false
- let isArticleTitle = false
+ let isSectionTitle = false
let isNumberList = false
let isLetterList = false
- if (element.classList.contains('article__title')) {
+ const containsAnyTitle = containsAnyTitleClass(element)
+ if (containsAnyTitle) {
const clone = element.cloneNode(true)
const textContent = getTextWithSpaces(clone)
articleTitle = extractArticleTitleDisplay(textContent)
- testGroup.title = articleTitle
- isArticleTitle = true
+ testGroup.titleObject.hasSubtitle = hasSubtitle(textContent)
+ testGroup.titleObject.type = getArticleTitleType(element)
+ testGroup.titleObject.text = articleTitle
+ isSectionTitle = true
paragraph = 0 // Reset paragraph count for the new article
} else if (element.nodeName.toLowerCase() === 'p') {
paragraph++
@@ -249,7 +264,7 @@ export const formatAmendingRegBody = (
const elementType =
isLetterList || isNumberList
? 'lidur'
- : isArticleTitle
+ : isSectionTitle
? 'greinTitle'
: undefined
@@ -275,7 +290,7 @@ export const formatAmendingRegBody = (
// Paragraph was deleted
pushHtml =
`${paragraph}. mgr. ${articleTitle} ${regNameDisplay} fellur brott.
` as HTMLText
- } else if (isArticleTitle) {
+ } else if (isSectionTitle) {
// Title was deleted
pushHtml =
`Fyrirsögn ${articleTitle} ${regNameDisplay} fellur brott.
` as HTMLText
@@ -304,7 +319,7 @@ export const formatAmendingRegBody = (
paragraph - 1
}. mgr. ${articleTitle} ${regNameDisplay} kemur ný málsgrein sem orðast svo:
${newText}
` as HTMLText)
: (`Á undan 1. mgr. ${articleTitle} ${regNameDisplay} kemur ný málsgrein svohljóðandi:
${newText}
` as HTMLText)
- } else if (isArticleTitle) {
+ } else if (isSectionTitle) {
// Title was added
testGroup.original?.push(`${newText}
` as HTMLText)
pushHtml =
@@ -337,7 +352,7 @@ export const formatAmendingRegBody = (
// Change detected. Not additon, not deletion.
testGroup.isDeletion = false
testGroup.isAddition = false
- if (isArticleTitle) {
+ if (isSectionTitle) {
// Title was changed
pushHtml =
`Fyrirsögn ${articleTitle} ${regNameDisplay} breytist og orðast svo:
${newText}
` as HTMLText
@@ -365,7 +380,7 @@ export const formatAmendingRegBody = (
}
})
if (testGroup.isDeletion === true) {
- const articleTitleNumber = testGroup.title
+ const articleTitleNumber = testGroup.titleObject.text
additionArray.push([
`${articleTitleNumber} ${regNameDisplay} fellur brott.
` as HTMLText,
@@ -376,7 +391,7 @@ export const formatAmendingRegBody = (
if (prevArticle.length > 0) {
prevArticleTitle = prevArticle[0]?.innerText
}
- const articleTitleNumber = testGroup.title
+ const articleTitleNumber = testGroup.titleObject.text
const originalTextArray = testGroup.original?.length
? flatten(testGroup.original)
: []
@@ -395,8 +410,13 @@ export const formatAmendingRegBody = (
: ''
}
+ const articleTypeText = getArticleTypeText(testGroup.titleObject.type)
additionArray.push([
- `Á eftir ${prevArticleTitleNumber} ${regNameDisplay} kemur ný grein, ${articleTitleNumber}, ásamt fyrirsögn, svohljóðandi:
${articleDisplayText}` as HTMLText,
+ `Á eftir ${prevArticleTitleNumber} ${regNameDisplay} kemur ${articleTypeText}, ${
+ articleTitleNumber ? articleTitleNumber + ',' : ''
+ }${
+ testGroup.titleObject.hasSubtitle ? ' ásamt fyrirsögn,' : ''
+ } svohljóðandi:
${articleDisplayText}` as HTMLText,
])
} else {
additionArray.push(testGroup.arr)
diff --git a/libs/portals/admin/regulations-admin/src/utils/formatAmendingUtils.ts b/libs/portals/admin/regulations-admin/src/utils/formatAmendingUtils.ts
index f67c3b420f81..3108a49dff83 100644
--- a/libs/portals/admin/regulations-admin/src/utils/formatAmendingUtils.ts
+++ b/libs/portals/admin/regulations-admin/src/utils/formatAmendingUtils.ts
@@ -11,10 +11,8 @@ export const groupElementsByArticleTitleFromDiv = (
Array.from(div.children).forEach((child) => {
const element = child as HTMLElement
- if (
- element.classList.contains('article__title') ||
- element.classList.contains('chapter__title')
- ) {
+ const containsAnyTitle = containsAnyTitleClass(element)
+ if (containsAnyTitle) {
if (currentGroup.length > 0) {
result.push(currentGroup)
}
@@ -31,15 +29,53 @@ export const groupElementsByArticleTitleFromDiv = (
return result
}
+const titleArray = ['article__title', 'subchapter__title', 'chapter__title']
+export type ArticleTitleType = 'article' | 'chapter' | 'subchapter'
+
+export const containsAnyTitleClass = (element: HTMLElement): boolean => {
+ return titleArray.some((title) => element.classList.contains(title))
+}
+
+export const getArticleTypeText = (titleType?: ArticleTitleType): string => {
+ switch (titleType) {
+ case 'chapter':
+ return 'nýr kafli'
+ case 'subchapter':
+ return 'nýr undirkafli'
+ default:
+ return 'ný grein'
+ }
+}
+
+export const getArticleTitleType = (element: HTMLElement): ArticleTitleType => {
+ if (element.classList.contains('subchapter__title')) {
+ return 'subchapter'
+ } else if (element.classList.contains('chapter__title')) {
+ return 'chapter'
+ } else {
+ return 'article'
+ }
+}
+
/**
* Extracts article title number (e.g., '1. gr.' or '1. gr. a') from a string, allowing for Icelandic characters.
*/
+const extractRegex =
+ /^\d+\. gr\.(?: [\p{L}]\.)?(?= |$)|^(?:\d+|[IVXLCDM]+)\.?\s*Kafli(?=\b| |$)/iu
+
export const extractArticleTitleDisplay = (title: string): string => {
- const grMatch = title.match(/^\d+\. gr\.(?: [\p{L}])?(?= |$)/u)
+ const grMatch = title.match(extractRegex)
const articleTitleDisplay = grMatch ? grMatch[0] : title
return articleTitleDisplay
}
+export const hasSubtitle = (title: string): boolean => {
+ const grMatch = title.match(extractRegex)
+ const articleTitleDisplay = grMatch ? grMatch[0] : title
+ const hasSubText = title.trim() !== articleTitleDisplay.trim()
+ return hasSubText
+}
+
export const getTextWithSpaces = (element: Node): string => {
let result = ''
diff --git a/libs/portals/admin/regulations-admin/src/utils/hooks.ts b/libs/portals/admin/regulations-admin/src/utils/hooks.ts
index 753a62100bc5..45eed9f313d8 100644
--- a/libs/portals/admin/regulations-admin/src/utils/hooks.ts
+++ b/libs/portals/admin/regulations-admin/src/utils/hooks.ts
@@ -105,3 +105,46 @@ export const useAffectedRegulations = (
mentionedOptions,
}
}
+
+export const usePristineRegulations = () => {
+ const P_REG_KEY = 'PRISTINE_REGULATIONS'
+
+ const [pristineRegulations, setPristineRegulations] = useState(
+ () => {
+ const storedRegulations = sessionStorage.getItem(P_REG_KEY)
+ if (!storedRegulations) return []
+
+ try {
+ return JSON.parse(storedRegulations)
+ } catch (e) {
+ console.error('Failed to parse pristine regulations:', e)
+ return []
+ }
+ },
+ )
+
+ const addPristineRegulation = (regId: string) => {
+ if (!pristineRegulations.includes(regId)) {
+ const updatedRegulations = [...pristineRegulations, regId]
+ setPristineRegulations(updatedRegulations)
+ sessionStorage.setItem(P_REG_KEY, JSON.stringify(updatedRegulations))
+ }
+ }
+
+ const removePristineRegulation = (regId: string) => {
+ const updatedRegulations = pristineRegulations.filter(
+ (id: string) => id !== regId,
+ )
+ setPristineRegulations(updatedRegulations)
+ sessionStorage.setItem(P_REG_KEY, JSON.stringify(updatedRegulations))
+ }
+
+ const isPristineRegulation = (regId: string) =>
+ pristineRegulations.includes(regId)
+
+ return {
+ addPristineRegulation,
+ removePristineRegulation,
+ isPristineRegulation,
+ }
+}
diff --git a/libs/portals/admin/service-desk/src/screens/Companies/Companies.action.ts b/libs/portals/admin/service-desk/src/screens/Companies/Companies.action.ts
index 6110e892e1b6..106e56adce3c 100644
--- a/libs/portals/admin/service-desk/src/screens/Companies/Companies.action.ts
+++ b/libs/portals/admin/service-desk/src/screens/Companies/Companies.action.ts
@@ -68,20 +68,6 @@ export const CompaniesAction: WrappedActionFn =
throw res.error
}
- const companies = res.data?.companyRegistryCompanies?.data
-
- // Redirect to Procurers screen if only one company is found
- if (companies?.length === 1) {
- return redirect(
- replaceParams({
- href: ServiceDeskPaths.Company,
- params: {
- nationalId: companies[0].nationalId,
- },
- }),
- )
- }
-
return {
errors: null,
data: res.data.companyRegistryCompanies,
diff --git a/libs/portals/admin/service-desk/src/screens/Companies/Companies.tsx b/libs/portals/admin/service-desk/src/screens/Companies/Companies.tsx
index 2955edb6e8fe..e15a2c5115f6 100644
--- a/libs/portals/admin/service-desk/src/screens/Companies/Companies.tsx
+++ b/libs/portals/admin/service-desk/src/screens/Companies/Companies.tsx
@@ -79,14 +79,12 @@ const Companies = () => {
buttonProps={{
type: 'submit',
disabled: !searchInput,
- onClick: (e) => {
- e.preventDefault()
+ onClick: () => {
if (searchInput) {
setSearchParams((params) => {
params.set('q', searchInput)
return params
})
- submit(formRef.current)
}
},
}}
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 4d723cad3321..13454e204aaa 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
@@ -78,22 +78,6 @@ export const UsersAction: WrappedActionFn =
throw res.error
}
- const { data: respData, totalCount } = res.data.UserProfileAdminProfiles
-
- if (totalCount === 1) {
- return redirect(
- replaceParams({
- href: ServiceDeskPaths.User,
- params: {
- nationalId: (await maskString(
- respData[0].nationalId,
- userInfo.profile.nationalId,
- )) as string,
- },
- }),
- )
- }
-
return {
data: res.data.UserProfileAdminProfiles,
errors: null,
diff --git a/libs/portals/admin/service-desk/src/screens/Users/Users.tsx b/libs/portals/admin/service-desk/src/screens/Users/Users.tsx
index 694c3357a959..642110a7ad76 100644
--- a/libs/portals/admin/service-desk/src/screens/Users/Users.tsx
+++ b/libs/portals/admin/service-desk/src/screens/Users/Users.tsx
@@ -87,14 +87,12 @@ const Users = () => {
buttonProps={{
type: 'submit',
disabled: searchInput.length === 0,
- onClick: (e) => {
- e.preventDefault()
+ onClick: () => {
if (searchInput) {
setSearchParams((params) => {
params.set('q', searchInput)
return params
})
- submit(formRef.current)
}
},
}}
diff --git a/libs/portals/shared-modules/delegations/src/components/delegations/DelegationViewModal.tsx b/libs/portals/shared-modules/delegations/src/components/delegations/DelegationViewModal.tsx
index 277823485167..aaa02e0972ae 100644
--- a/libs/portals/shared-modules/delegations/src/components/delegations/DelegationViewModal.tsx
+++ b/libs/portals/shared-modules/delegations/src/components/delegations/DelegationViewModal.tsx
@@ -1,6 +1,5 @@
-import { useEffect } from 'react'
-import { useAuth } from '@island.is/auth/react'
import { Box } from '@island.is/island-ui/core'
+import { useEffect } from 'react'
import { useLocale } from '@island.is/localization'
import { formatNationalId } from '@island.is/portals/core'
@@ -9,10 +8,11 @@ import { IdentityCard } from '../IdentityCard/IdentityCard'
import { AccessListContainer } from '../access/AccessList/AccessListContainer/AccessListContainer'
import { useAuthScopeTreeLazyQuery } from '../access/AccessList/AccessListContainer/AccessListContainer.generated'
-import { m } from '../../lib/messages'
-import format from 'date-fns/format'
import { AuthDelegationScope, AuthDelegationType } from '@island.is/api/schema'
+import { useUserInfo } from '@island.is/react-spa/bff'
+import format from 'date-fns/format'
import isValid from 'date-fns/isValid'
+import { m } from '../../lib/messages'
type DelegationViewModalProps = {
delegation?: {
@@ -45,7 +45,7 @@ export const DelegationViewModal = ({
...rest
}: DelegationViewModalProps) => {
const { formatMessage, lang } = useLocale()
- const { userInfo } = useAuth()
+ const userInfo = useUserInfo()
const isOutgoing = direction === 'outgoing'
const [getAuthScopeTree, { data: scopeTreeData, loading: scopeTreeLoading }] =
useAuthScopeTreeLazyQuery()